Dashboard – Anvil of Time https://anvil-of-time.com Nature Forges Everything on the Anvil of Time Thu, 11 Mar 2021 17:32:43 +0000 en-US hourly 1 PowerBuilder ‘Gotcha’ – Grid Datawindows with Picture Controls https://anvil-of-time.com/powerbuilder/powerbuilder-gotcha-grid-datawindows-with-picture-controls/ Wed, 27 Jun 2012 23:14:22 +0000 http://anvil-of-time.com/wordpress/?p=1409 So I’m working on a ‘dashboard’ style datawindow in an application which shows a grid of data along with some graphics so the user can easily see changes/important stuff. There are a number of reasons to use a grid datawindow for this, especially since they can easily be changed by the user to suit their particular tastes and preferences. Since the individual columns can be resized by the user, I can’t use the ‘display as bitmap’ option on a column to display my graphics since they can be resized which screws up the image on the datawindow. Fine, I’ll use picture objects placed on the individual columns and then use expressions to control the positioning of them automatically. Gee, isn’t PowerBuilder great?

Now in my particular situation I have a series of picture objects placed on a column with the value of the column itself determining which of them is visible. This is standard stuff achieved via an expression on the visual property of the picture objects . The next thing to do is to set the X position on the picture objects based on the column it is placed upon; I’d like the position of the picture to change dynamically as the column itself changes due to the user dragging the column to a different position or if they make it larger/smaller in width. So far, so good – I’m starting to count my chickens…

I fire up the app and ‘wha, wha, wha, wha, whaaaa…‘.

Here is the application when I launch it.

Looks fine, my visible expressions are correct.

I resize the Mood column to make it larger and…

Something’s not working as expected.

Okay, lets try re-arranging the columns by dragging Name after Mood.

Well this sucks, grrrr….

What happens if I make the Mood column smaller?

Sigh.
Okay boss, when is the deadline for this?

First let’s tackle the positioning issue. To make the expressions on the pictures work correctly, each has to be unique. So these expressions will not work:

long(describe("custmood.X")) + 100
long(describe("custmood.X")) + 100
long(describe("custmood.X")) + 100

These are the X position expression for my three picture objects on the datawindow column. To make them unique I change them to:

long(describe("custmood.X")) + 100 // p_1.X
long(describe("custmood.X")) + 100 + 0 // p_2.X
long(describe("custmood.X")) + 100 * 1 // p_3.X

Note also that ‘+ 100’ is different from ‘+100’ as far as this goes

Now when I run the app and I change the order of the columns in the grid I get:

Moving Name to after Mood.


Cool

One thing of note. If you are going to use a Modify to change the X position expressions at runtime rather than within the datawindow definition, each of the ‘New’ expressions must be different from those in the original. Thanks to my manager Robert Sisk for this tidbit.

The second issue is a bit trickier and is one I’ve encountered at various times in my PB career. You can look and look and look online for answers to this and find either nothing or many suggestions which don’t work too well. Often you are faced with preventing the user from resizing the column which eliminates one of the main reasons you choose the grid datawindow in the first place.

Since we are dealing with controls which we have created in the datawindow painter to be the size we want them to be when they appear to the user, all we have to do is capture their widths and then re-apply them once the user has resized the columns on the datawindow. However, we have to create a user event on the datawindow to do this since there is no standard event which is triggered when the user resizes a grid column.

First define an instance variable to hold the modify information on the picture objects for the datawindow.
Next capture the initial values of the widths of the picture objects once the window opens; a ‘post open’ style event is good for this. If your application dynamically changes these for some reason during run time, you will have to reset this after the change so the property is set to the correct value.
Define a user event on the datawindow in question which will perform the modify on the picture objects.
Define a user event mapped to pbm_dwnlbuttonup which will post the first user event. This event is triggered when a user releases the left mouse button on the datawindow (which happens when the are finished resizing the column).

//instance var and postopen event
type variables
string is_picture_widths
end variables

event we_postopen();// triggered from open of window
string ls_objects, ls_object, ls_type
long ll_col_pos, ll_col_start, ll_len
// load picture settings to instance variable
ls_objects = dw_1.Object.DataWindow.Objects
// go through tab separated list
ll_col_pos = pos(ls_objects, '~t')
ll_col_start = 1
ll_len = len(ls_objects)
DO WHILE ll_col_start < ll_len
	ls_object = mid(ls_objects, ll_col_start, ll_col_pos - ll_col_start)
	ls_type = dw_1.describe(ls_object + '.type')
	IF  ls_type = 'bitmap' then //only concerned about bitmaps (picture objects)
		is_picture_widths += ls_object + '.width = ' + dw_1.describe(ls_object + '.width') + '~r'
	END IF					
	ll_col_start = ll_col_pos + 1
	ll_col_pos = pos(ls_objects, '~t', ll_col_start)
	IF ll_col_pos = 0 then
		ll_col_pos = ll_len + 1
	END IF
LOOP
end event

event ue_lbuttonup;// call event to resize pictures if needed, mapped to PBM_DWNLBUTTONUP on datawindow
this.setredraw(FALSE)
this.event post ue_checkmodified()
end event

event ue_checkmodified();// posted from ue_lbuttonup
string ls_mess

IF this.object.datawindow.syntax.modified = "yes" THEN
	ls_mess = modify(is_picture_widths) // re apply the picture widths
	IF Len(ls_mess) > 0 THEN
		// error condition
	END IF
END IF
setredraw(true) //was set to false in calling event
end event

So now when run the application we get this.

Resize the column with the picture controls

Now make it smaller.

Ahhhh, all is good again.

I’ve attached a sample application (PB12.5.1) demonstrating how the resizing does not work and how it can be corrected. Exports of the objects are included as well if you need to create your own version. When running the example, first resize/reposition the columns after the window opens. This will show the problems with the picture objects. When you click on the ‘Modify Bitmap X Expressions’ button the changes in the X position expressions for the picture objects will be displayed in the multi line edit control.

The ‘Reset DWO’ button resets the datawindow object if you need to.

]]>
PowerBuilder – Grid datawindow object with variable number of columns https://anvil-of-time.com/powerbuilder/powerbuilder-grid-datawindow-object-with-variable-number-of-columns/ Thu, 18 Aug 2011 01:20:56 +0000 http://anvil-of-time.com/wordpress/?p=848 Here is a way to build a grid datawindow which contains columns corresponding to an unknown number of data elements. You could use this approach in creating a project schedule, inventory location system, baseball box score, or any number of other examples. My example assumes the minimum number of columns to be four.

This create grid event returns a string.

long	ll_columns, ll_count, ll_pos
string ls_errors, ls_sql, ls_dw_presentation, ls_dw_syntax, ls_find, ls_name, ls_mod_string
// base presentation information
ls_dw_presentation =    "style( type=Grid                          &
            Horizontal_spread = 25 )            &
     datawindow( units=1                       &
             Color= 16777215)              &
     column( Font.Face='Arial'         &
         Font.Height=-9                    &
         Font.Weight=400)                  &
         text(   Font.Face='Arial' &
         Font.Height=-9                    &
         Font.Weight=400                   &
             Border=6)"

// set initial grid to 4 columns (plus time_of_day and last_col columns)
ls_sql = "SELECT space(10) as time_of_day, space(40) as col1, space(40) as col2, space(40) as col3, space(40) as col4,"

ll_count = 5 // next column to add would be 5

// this is the variable number of data elements
ll_columns = ids_trains.rowcount()

// add any additional columns past 4
DO WHILE ll_count <= ll_columns
	ls_sql += " space(40) as col" + string(ll_count) + ","
	ll_count++
LOOP

ls_sql += " '' as last_col"
ls_sql += " FROM dbo.TRAINS WHERE 1 = 2"
// build the datawindow syntax from the presentation and sql statement
ls_dw_syntax = SQLCA.Syntaxfromsql( ls_sql, ls_dw_presentation, ls_errors)
IF Len(ls_errors) = 0 THEN
	// widen the header band a bit
	ls_dw_syntax = Replace(ls_dw_syntax, Pos(ls_dw_syntax,"header(height=19)"), Len("header(height=19)"), "header(height=23)")
	// create dwo and assign to grid
	dw_grid.create(ls_dw_syntax,ls_errors)
END IF
// appearance modifies
dw_grid.modify("last_col.visible=FALSE")

FOR ll_count = 1 TO Long(dw_grid.Object.DataWindow.Column.Count)
	IF dw_grid.Describe("#" + String(ll_count) + ".Visible") = "1" Then
		ls_name = dw_grid.Describe("#" + String(ll_count) + ".Name") 
		ls_mod_string = ls_name + "_t.background.mode=0" // must set to opaque or color does not show
		ls_errors = dw_grid.modify(ls_mod_string)
		// set the column heading color
		ls_mod_string = ls_name + "_t.background.color=12632256"
		ls_errors = dw_grid.modify(ls_mod_string)
		IF POS(ls_name ,'time_of_day') = 0 THEN
			ls_mod_string = ls_name + "_t.Alignment=0" //left
			ls_errors = dw_grid.modify(ls_mod_string)
		END IF
		ls_mod_string = ls_name + "_t.Height=18"
		ls_errors = dw_grid.modify(ls_mod_string)
		ls_mod_string = ls_name + "_t.tooltip.enabled=1"
		ls_errors = dw_grid.modify(ls_mod_string)
	END IF
NEXT
RETURN ls_errors

Now I know you can do several modifies with a single statement but it is much easier to debug if you do a single attribute at a time.

Once you have the grid set up you can call subsequent methods to populate it (and make columns invisible if needed), resize the width of the columns, etc.


Example grid for a time schedule with fifteen minute increments.

]]>
PowerBuilder – Get Color Value of Pixel under Pointer https://anvil-of-time.com/powerbuilder/powerbuilder-get-color-value-of-pixel-under-pointer/ Sat, 18 Dec 2010 01:20:21 +0000 http://anvil-of-time.com/wordpress/?p=635 Here is a sample application which will give you the color value of the pixel the user clicked on on a picture control (this can be used for any object inherited from a dragobject). To see the list of draggable controls, open the Browser. All the objects in the hierarchy below dragobject are draggable. This functionality can be used to ‘prettyfy’ a report selection window by providing an irregular bitmap (sales region map, delivery routes, etc.) within which the user clicks. Depending upon the color they choose, a separate report is produced.

Anyway, the functionality is encapsulated in an nvo and involves three API external functions.

FUNCTION ulong GetPixel(ulong hwnd, long xpos, long ypos) LIBRARY "Gdi32.dll"
// get device context - required for graphic stuff
FUNCTION ulong GetDC(long hwnd) LIBRARY "user32.dll"
FUNCTION long ReleaseDC (long hwnd, ulong hDC) LIBRARY "user32.dll"

The function is called of_get_color. It accepts a dragobject by reference.

long ll_xpos, ll_ypos
ulong ul_rtn
ulong ul_handle, ul_device
// get handle
ul_handle = handle(a_object)
ul_device = GetDC(ul_handle)
// get position of pointer
ll_xpos = UnitsToPixels(a_object.PointerX(), XUnitsToPixels!)
ll_ypos = UnitsToPixels(a_object.PointerY(), YUnitsToPixels!)

ul_rtn = GetPixel(ul_device, ll_xpos, ll_ypos)
ReleaseDC( ul_handle, ul_device)

RETURN ul_rtn

On the window there is an event which is triggered when the user clicks on an appropriate object.

int li_r, li_b, li_g
ulong lul_color
n_pixelcolor lnv_color
lul_color =  lnv_color.of_get_color(a_object)
//max value of unsigned long indicates error
IF lul_color = 4294967295 THEN
	r_1.fillcolor = 0
	st_ul.text = 'Invalid Color'
	st_rgb.text = ''
	RETURN
END IF
// get RGB values
li_r = mod(lul_color,256)
li_g = mod((lul_color / 256), 256)
li_b = mod ((lul_color / 65536), 256)

st_ul.text = 'U Long: ' + string(lul_color)
st_rgb.text = 'RGB( ' + string(li_r) + ', ' + string(li_g) + ', ' + string(li_b) + ')'
st_rgb.textcolor = RGB(li_r,li_g,li_b) //just to prove the values are correct
r_1.fillcolor = lul_color

The window displays the color value as well as its derived RGB values.

Pixel Color App

Color clicked on.

The code is in PB11.5.1. A zip file with the .pbl and appropriate exports can be found here.

Special thanks to Roland Smith of TeamSybase.

]]>