Welcome!

PowerBuilder Authors: Chris Pollach, Yeshim Deniz, Jayaram Krishnaswamy, Kevin Benedict, Avi Rosenthal

Related Topics: PowerBuilder

PowerBuilder: Article

The PowerBuilder DataWindow as an Image Thumbnail Display Control

Using a metadata technique of storing multiple properties in a single string

While external controls exist for viewing image thumbnails, there can be the need to view images as thumbnails within the context of other data within a multi-row list, treeview or report. This can be easily done with a PowerBuilder DataWindow object. In this case a treeview DataWindow (see Figure 1) was needed to display multiple images as thumbnails that related to the data presented in each row. Required functionalities include making the images selectable, displaying data for each thumbnail, such as the name of the file, and to keep the appearance orderly by having the images appear the same height in a row.

Figure 1: Thumbnail images within a treeview DataWindow

I will present the techniques used to create thumbnail images in DataWindows. The methods presented assume that the images are stored as files in some folder that's accessible by the application. It's also assumed that the image types are any of the standard image types that are recognized by PowerBuilder: .bmp, .gif, .jpg, .wmf, and .png. The image files are not converted in any way but are displayed in a thumbnail size.

Overview
The techniques for displaying thumbnails in a DataWindow are based on my previous work on DataWindows ("Utilizing the Power of the DataWindow Object," ISUG Technical Journal Q1 2006). This technique basically uses a metadata technique of storing multiple properties in a single string, in this case storing the image display properties in different fixed length sections of a string. These property strings are stored in DataWindow columns, so that each row can contain its own set of images along with all the properties to properly display each image. The columns are used to store the values for each image property such as x and y location, width, height, visibility and image file name. These properties are used in a DataWindow by addressing the corresponding section of the string for each graphic object and converting it into a string using the MID(string,start,length) function or number if necessary using the LONG(MID(string,start,length)) function.

Getting Started
The base DataWindow should contain string columns to hold the properties for each image. The strings should be long enough to contain all the data required for the maximum number of images for each DataWindow row. In this case 40 is the maximum number of images that would be displayed per row so each column had to be defined as 40 times the length of the property for each image. Numeric properties such as x, y, width can each have a maximum length of 5 so they must be defined in strings that are 200 characters long. Visibility only requires 1 character, either ‘0' or ‘1,' so it can be defined in a string of length 40. The fully qualified names of image files can vary greatly so the maximum length should be considered when creating the DataWindow. In this case the maximum length is 100 characters, which requires a column that is 4000 characters wide.

Figure 2 shows the contents of the different property columns. The x, width, visibility and file name properties for the first three images are highlighted with different colors corresponding to the border color of the actual images. You can see the relationship between the image and each of its properties.

Figure 2: Screenshot showing the formatting of various display property columns in the DataWindow. The background color of the data ‘cells' correspond to the colors of the frames surrounding the thumbnail image.

These columns are defined in the DataWindow during development, either in an external or select DataWindow. In this case they are defined in the select statement for the DataWindow:

SELECT *** , '' as x, ‘' as w, ‘' as filepath, ‘' as vis, ‘' as y, ‘' as filename, ***
FROM ***
WHERE ***

The lengths of the fields are modified during runtime. The vis column, which contains the parameter for image visibility, is set to FILL(‘0',40) so that initially no images are visible for any row.

At Runtime
The actual image objects are defined dynamically once at runtime during the startup process. Since it's unknown how many images are needed, the maximum number is created even if it's highly unlikely that they will all be used. A text field is also created to display the name of the file. A loop is used to create 40 instances of the compute object that will display the image:

dw_cmd = dw_cmd + ‘ create compute(band=detail alignment="2" expression="bitmap(trim(mid(filepath,'+STRING(ll_x100)+',100)))" border="1" color="0" x="0~tlong(mid(x,'+STRING(ll_x6)+',6))" y="0~tlong(mid(y,'+STRING(ll_x6)+',6))" height="200" width="0~tlong(mid(w,'+STRING(ll_x6)+',6))" format="[GENERAL]" html.valueishtml="0" name=p'+STRING(ll_row)+' visible="0~tlong(mid(vis,'+STRING(ll_x1)+',1))" *** )'

A compute object is used to take advantage of the bitmap() function that converts a string into an image. The name of the object contains the sequence of the properties for that object, for instance p1, p2, p3. The sequence number will be stripped from the name and used to point to the location in the string that contains the properties for that particular object. Please note the items in bold. These are the properties that are held in the string columns that were defined in the DataWindow. There are four variables that control where in the string the property should appear. They are incremented by the same amount for each pass in the loop:

ll_x1 = ll_x1 + 1
ll_x6 = ll_x6 + 6
ll_x50 = ll_x50 + 50
ll_x100 = ll_x100 + 100

The result is 40 identical compute objects that refer to different sections of the property columns. This section of code shows the definition of the second image, p2, that was created at runtime from the code above. The variables have been replaced with values of the location within the string for each property:

' create compute(band=detail alignment="2" expression="bitmap(trim(mid(filepath,101,100)))" border="1" color="0" x="0           long(mid(x,7,6))" y="0           long(mid(y,7,6))" height="200" width="0     long(mid(w,7,6))" format="[GENERAL]" html.valueishtml="0" name=p2 visible="0  long(mid(vis,2,1))" *** ) ‘

For instance for image p2 the property expression="bitmap(trim(mid(filepath,101,100)))" indicates that the function bitmap() should look at the text that exists starting at position 101 for a length of 100 in the column filepath. This will return the fully qualified name for the image file. The same process exists for the other display properties.

When the DataWindow is displayed another datastore retrieves all the images for the DataWindow. A loop is set up to build the property strings for each row in the presentation DataWindow. A filter is used to isolate the image properties for each row in the presentation DataWindow. If images exist for a row, a loop is run to extract the properties for each image that are concatenated together into variables on a fixed length basis.

ls_image_file = lds.object.filepath[ll_row]
ls_image_nsme = lds.object.filensme[ll_row]
//set the qualified file name to a string of a length 100
ls_file = ls_file + LEFT(ls_image_file +FILL(‘ ‘,100),100)
//set the file name to a string of a length 50 to display under the image
ls_filename = ls_filename + LEFT(ls_iamge_name[ll_row]+FILL(‘ ‘,50),50)
//make the image visible
ls_vis = ls_vis+'1'

Once complete, these variables are then loaded into the appropriate columns in the display DataWindow. As you can see in the figures the width of each image varies but the height of each image is constant and is set to 200 units. To keep the proportions of the original image the width must be converted. This is done by moving the image file to an invisible picture object located on the same window as the presentation DataWindow, called p_1, that is also 200 units in height.

//set the name from the DataWindow
ls_image_file = lds.object.filepath[ll_row]
//set the picturename property on the picture object
p_1.picturename = ls_image_file
//set the originasize = TRUE to get the correct dimensions
p_1.originalsize = TRUE
//set height to 200
ll_height = 200
//get the ratio of desired height to the picture height
ld_ratio = ll_height / p_1.height
//set the thumbnail width to the image width * the ratio of the heights
ll_width = p_1.width * ld_ratio
//concatonate the calulcated width to the width property string
ls_width = ls_width + RIGHT(‘   ‘+STRING(ll_width),6)
//concatonate the x value to the x property string
ls_x = ls_x + RIGHT(‘   ‘+STRING(ll_x),6)
//concatonate the y value to the y property string
ls_y = ls_y + RIGHT(‘   ‘+STRING(ll_y),6)
//calculate the x value for the next image by adding the width of the current image plus 200 for extra space between images
ll_x = ll_x + ll_w + 200

If the images need to appear larger, simply change the height constant to a larger number. This can actually be set at runtime before the DataWindow is displayed.

Figure 3: Screenshot showing the values that hold the y location property for the thumbnail images

Another issue that needs to be handled is setting the x location of each image. Theoretically the x value can be incremented up to over 32,000, but this would generate a very wide DataWindow that would require a lot of horizontal scrolling. To make things more user-friendly it would be better to create the images in a format that appears as rows of images. These rows are not actual data rows in a DataWindow but the images are displayed to appear in a row format within the DataWindow detail or trailer band (see Figure 3). This is achieved by changing the y value after the x value is greater than a certain amount. In this case the maximum x value is set to 3000. This can be adjusted at runtime based on the width of the DataWindow. When the x value is greater than the max value, the x value is reset to the starting value of 535 and the y value is increased by 340.

ll_x = ll_x + 200 + ll_w
IF ll_x > 3000 THEN
ll_y = ll_y + 340
ll_x = 535
END IF

Figure 4: Demonstrating selectability by double clicking on a thumbnail to open the full size image in an image viewer

Selectability
The images are selectable with the mouse by using the doubleclicked() event. A user may want to select an image to open a detail window or to view a larger version of the image. In this case the image is selected and passed as a parameter to an external imageviewer control (see Figure 4). In the DataWindow's doubleclicked() event the getobjectatpointer() function is used to get the object under the cursor. The object name contains the sequence of the image in the row. For instance object p2 is image number 2. The sequence is extracted using various PowerBuilder string functions. The location of the file name in the filepath column is extracted using the sequence number.

//get the object
ls_object = this.getobjectatpointer()
//extract the row of the object
row = long(right(ls_object,len(ls_object) - pos(ls_object,'~t')))
//extract the name of the object
ls_object_name = LEFT(ls_object,POS(ls_object,'~t') - 1)
//extract the sequence of the object
IF ISNUMBER(MID(ls_object_name,LEN(ls_object_name) - 1,1)) THEN
ll_seq = LONG(RIGHT(ls_object_name,LEN(ls_object_name) - 2))
ELSE
ll_seq = LONG(RIGHT(ls_object_name,LEN(ls_object_name) - 1))
END IF
//get the string containing the file names of all images on the row.
ls_filepath = this.object.filepath[row]
//extract the file name of the object that was selected
ls_file = MID(ls_filepath,(ll_seq * 100) - 99,100
//call the imageview with the file name
command='rundll32.exe shimgvw.dll,ImageView_Fullscreen ‘+ ls_file
Run(command)

The same function can also be called from the clicked() event and the rbuttondown() event to control a dropdownmenu display.

Conclusion
Using some advanced functionality the DataWindow control can be used as an image thumbnail display tool. The techniques described can also be embedded in reports to display images in a report. The examples and code presented in this article were created using PB 12.5 classic; however, the same techniques have worked since at least PB version 6.5. Applications using the same techniques have also been successfully deployed to Appeon as well as created in Visual Basic using DataWindow.NET. It's assumed that these techniques can be deployed in PB.NET as well.

More Stories By Buck Woolley

Buck Woolley has been involved in programming and software develop- ment since 1987 and has been a Powerbuilder consultant since 1994, starting with version 3.0. Currently he is the main client server developer at Patrix AB in Gothenberg, Sweden. He has made numerous Techwave presentations covering advanced datawindow techniques. Buck and his wife reside in San Diego, California.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.