Utilizing the Power of the Datawindow Object By Buck Woolley Making use of advanced graphical datawindows he PowerBuilder datawindow object is one of the main reasons for the success that PowerBuilder has achieved. However, since the beginning, the datawindow has been used primarily to display and manipulate data as a variety of input and maintenance forms. Today, users are demanding a more dynamic user interface, in which intuitive display and manipulation of graphical representation of data replaces the simple method of displaying and editing text data. These interfaces present data in a graphical format and allow the user to modify that data with the mouse or other pointing device. The PowerBuilder datawindow object is ideal for developing a wide variety of advanced graphically rich dynamic user interfaces, displayed on a variety of platforms such as .net, PDA devices, and web browsers. T Characteristics of Advanced Graphical Datawindows There are several characteristics of the datawindow that make it an ideal tool for developing graphical-based interfaces. First, the datawindow, of course, is a presentation object. Although primarily used to present data in a text format, many of the capabilities can be used to present graphical interfaces as well. Buck Woolley is the owner of dwGraphical datawindows are external and extreme.com and is a PowerBuilder dynamic, and their complexity requires consultant at Patrix AB. He can be reached at bwoolley@dw-extreme.com. that they be developed dynamically at 2 ISUG TECHNICAL JOURNAL runtime. These datawindows are only used for presentation purposes and are normally sourced from other datastores that retrieve the various visual properties from tables. During development, they normally exist as external datawindows, only containing the columns to hold visual properties and without any visual objects. The visual objects are generated at runtime to meet the needs of the user request. Graphical datawindows also take advantage of several powerful native functions, specifically, those that appear in expressions such as string and group functions. This is due to the tight coupling of data to the visual properties of objects on a datawindow. Through the use of the abundant available functions to modify user input, users can manipulate graphical properties simply by changing a column value. These include mouse events such as pbm_lbuttonup, pbm_lbuttondown and pbm_mousemove in PowerBuilder. Datawindows of all types generally use the same methods and techniques in PowerBuilder, PocketBuilder, or Visual Basic through the use of dw.net. The functionalities are the same across platforms. Also, with very few exceptions, these methods and techniques can be deployed to the web using Appeon for PowerBuilder. Secondly, the datawindow is inherently row-based. For certain graphical situations, this is quite beneficial. For example, you can create an object on a datawindow and P O W E R make it appear multiple times, simply by switching its visibility property for each row. This makes rendering and manipulating graphical objects quite efficient in most situations. You can also create and mix graphical objects such as rectangles, ellipses, lines, text, and images with data objects such as columns, expressions, and drop-down datawindows to create components that are not only strictly graphical but have a data component as well. Advanced datawindows are ideal for a variety of purposes. They are especially useful in situations where there is a need to visualize data with a start and end date/time component, or to visualize physical configurations. This could apply to any solution that involves scheduling or recording an asset to a period of time or to a location. Examples include physical items such as people, rooms, autos, equipment, or virtual items such as programs, documents, project requirements, and work events. Other applicable solutions include visualizing parent-child relationships, such as in an organizational chart, or representing physical models such as a warehouse or store shelves. The Metadata Datawindow Each of the solutions we just described uses a method of presentation called the metadata datawindow. The metadata datawindow relies on string columns to store visual properties of datawindow objects. This technique combines the rowbased architecture of the datawindow with the access to column data from visual properties, via property expressions within datawindow objects. For example, visual properties such as x, y, width, height, color, visibility can all be stored in datawindow columns. The metadata aspect comes from using STRING columns to store the visual properties of many objects, rather than one, and using STRING functions in the expressions of the object visual properties to access the values for each object. An example of this technique is the planner datawindow component, which is used to display and manipulate entities represented as rectangles on a time-based grid (see Figure 1). Each row on the grid is a row in the datawindow, allowing the planner to make use of the efficiencies of the row-based nature of the datawindow. In Figure 1, we see seven task rectangles. However, actually only two rectangles have been created. These exist on every row, but are made invisible by setting the visible property of each rectangle on each row. The visible property exists as a metadata column within the datawindow. The rectangles have a variety of other display properties such as colors, patterns, length, and width, stored in datawindow columns as metadata. O F T H E D A T A W I N D O W O B J E C T Figure 1 For a simple illustration of why metadata is the best method to control the various display properties and how it is used, let’s consider the following example. The visibility of the rectangle (r_1) can be set by entering a value in the visible property expression of the object (see Figure 2). Figure 2 This method is limiting, however, as it only allows control of the visibility on one instance of r_1 on one row. A better method is to set the visible expression to the value in the visible column and setting the value in the visible column to 1: Figure 3 This lets the user control the visibility of r_1 on multiple rows within the datawindow. However, it limits us to r_1, and should you create a rectangle (r_2), you’d need to create a new column (visible_2) in the datawindow to control its visibility: FIRST QUARTER 2006 3 P O W E R O F T H E D A T A W I N D O W O B J E C T Figure 4 Using these approaches to control the properties of graphic objects are not sufficient, as in most situations you will not know the number of rectangles required until runtime. The best solution is to be able to store all the visible properties of all possible rectangles, on all possible rows, in a single column. This is done by using a string of sufficient length to store the property of all rectangles and to use the MID() string function within the visible expression to access the portion of the string that applies to it. The visibility properties for rectangles r_1 and r_2 are stored in a single column visible_string. In this example, the column has a value of “10.” Rectangle r_1 takes the value of the first character in visible_string by using a couple of built-in functions with its visible expression. layers of objects have their visibility controlled by metadata. The top object is the marker, which is seen as an inverted “v.” Beneath this are the blue tiles that hides the mines. The numbers indicate the number of mines adjacent to it, then the mines themselves are visible as red-filled circles. Each row of objects is a row in the datawindow. Each time a tile is clicked that has no mine or number beneath it, an algorithm searches for the boundary of contiguous mineless space. During this process, the visible property embedded in the metadata of all the rectangles selected in this algorithm is set to 0, which reveals the blank space and numbers underneath. Figure 6 The source code for the minesweeper game is available from the author. The number of objects that can be switched on and off is only limited to the defined length of the string column containing the data that controls the visible property. Other display properties, such as x, y, width, height, colors, patterns, line types and widths, bitmap filenames, and even text of objects within datawindows can be controlled the same way. Figure 7 Figure 5 The expression contains LONG(MID(visible_string,1,1)), which takes the character in position 1 for length 1 in the column visible_ string and converts it into a long value, which is then applied to the property. Of course, rectangle r_2 would have a value of LONG(MID(visible_string,2,1)) to resolve the value of the second character in the visible_string column. This technique of visual property control by metadata is most easily seen in the datawindow-based version of a minesweeper game that uses metadata to control the visibility of various row-based objects on a datawindow (Figure 6). Four 4 ISUG TECHNICAL JOURNAL This example, from the planner component, shows the columns used to contain the data for three display properties for the three rectangles: pattern, color, and background color. The color and background color are stored in string segments that have a length of 8. In this case, the values are stored in fixed length segments within the string. Each column is defined as a string of sufficient length to contain all the data for the maximum number of rectangles that could exist on one row. This usually isn’t a problem, as a string column capable of holding a string of 8,000 can contain the colors for 1,000 rectangles. P O W E R If you were to create these rectangles at runtime, you would use the create statement and define the values of these three display properties as follows: ls_syntax = ‘create rectangle(... brush.hatch="0~tlong(mid(patternbtype,'+key1+',1))“ brush.color="0~tlong(mid(color,'+key8+',8))“ background.color="0~tlong(mid(background_bcolor,'+key8+',8))"...)’ dw_1.modify(ls_syntax) O F T H E D A T A W I N D O W O B J E C T value in key20 defines the starting position of each of the segments that contain a file name. Other properties required to display the images include visible, x, y, height, and width. Each of these properties is also stored as a metadata column. Other Metadata Datawindows Examples Another example of the metadata datawindow includes the stock chart that displays stock price data in various charting styles. Each chart, such as the one in Figure 10, is contained in a single row within a datawindow. Where key1 is the starting location of values that have a length of 1, such as brush.hatch, key8 is the starting location of values that have a length of 8, such as colors. This technique can also be used to display images: Figure 10 Figure 8 In this example, there are three datawindow rows displayed. Each row contains a number of images of different items. Again, the advantage of the row-based nature of the datawindow is evident. As you can see, there are 32 images displayed. However, only eleven bitmap objects were actually created and are repeated on every row. The file names of the images are stored in a metadata column that is parsed out in similar fashion, as in the previous example. Figure 11 displays three rows from the stock chart datawindow. Once you have generated objects to display the chart for one chart, you may display an infinite number of charts simply by adding additional rows of price data. This technique certainly isn’t limited to stock data and can be applied to a number of situations. Figure 9 The image file names are stored in a string metadata column named file and are accessed using built-in functions within the create statement of the bitmap: Figure 11 'create bitmap(...filename="A~tTRIM(trim(mid(file,'+key20+',20)))"...)’ Filenames are stored in string segments of 20 characters. The Figure 12 demonstrates a railyard datawindow, showing the location of railcars. It allows the user to select a view of FIRST QUARTER 2006 5 P O W E R O F T H E D A T A W I N D O W O B J E C T information about a railcar as well as to reorganize the railcars within the railyard simply by selecting and moving them with the mouse. Figure 12 The timeline datawindow (Figure 13) is similar to the planner; however, it is configured to display all the data in a footprint that doesn’t require horizontal scrolling. This is used in situations where printing is a primary focus. Figure 13 The appointment datawindow (Figure 14) displays appointments with their times on the vertical axis rather than on the horizontal. This datawindow uses the metadata technique to render activities on the appointment page. Figure 14 6 ISUG TECHNICAL JOURNAL Using DragDrop to Move Objects In order to make your datawindow truly interactive, we need to be able to manipulate objects on a datawindow, between multiple datawindows, or between other PowerBuilder objects using the mouse. For instance, in many applications you may need to drag an image from a palette of images to a canvas. In this case, you will copy from one datawindow, containing a set of standard images, to another datawindow in which you are building a network, shelf, chart or workflow diagram. In order to create a seamless movement between the two datawindows, you must use a third independent object that performs the actual move between the objects. In this case, a descendant of the PowerBuilder statictext object will be used. You can create instance variables within the statictext object to store offset values determined by the location of the pointer when you selected the object to move. You also must set the statictext object to “not visible” because you are using it as a drag object only. The action is initiated by the oe_lbd event on an object in the palette datawindow. In this event, you will extract the row of the selected image and the file name of the image. You also want to position the picture object precisely over the datawindow image object, and have it assume its width and height so that it appears as if you are dragging the actual image object. This is made more difficult if you have “autosize” set on your detail band. In the palette datawindow, you must create a computed field that contains the cummulative “y” value of every row. This is done with the following computed expression called row_height: P O W E R cumulativeSum( rowheight() for all) You must also take into account that the user may have scrolled down and the first row on the page is not Row 1. This is resolved by using FirstRowOnPage to determine which row is the first visible row: string ls_object long ll_row, ll_page_row, ll_first_row ls_object = this.getobjectatpointer() IF LEFT(ls_object,4) = 'file' THEN ll_row = LONG(MID(ls_object,POS(ls_object,'~t') + 1, LEN(ls_object) - POS(ls_object,'~t'))) IF ll_row > 0 THEN ll_page_row = LONG(this.Object.DataWindow.FirstRowOnPage) IF ll_row > 1 THEN //This area places the drag object over the selected //image and sets its width and height ll_first_row = LONG(this.object.datawindow.firstrowonpage) IF ll_page_row = 1 THEN iuo_drag_object.y = this.object.cumheight[ll_row - 1] + 28 ELSE iuo_drag_object.y = (this.object.cumheight[ll_row - 1] this.object.cumheight[ll_first_row - 1]) + 28 END IF iuo_drag_object.x = this.object.c_x[ll_row] + 16 iuo_drag_object.height = this.object.height[ll_row] iuo_drag_object.width = this.object.width[ll_row] //Set some instance variables that will be used later iuo_drag_object.il_key = this.object.key[ll_row] iuo_drag_object.is_desc = this.object.desc[ll_row] iuo_drag_object.il_x = iuo_drag_object.pointerx() iuo_drag_object.il_y = iuo_drag_object.pointery() iuo_drag_object.is_file = this.object.file[ll_row] iuo_drag_object.drag(begin!) return -1 END IF END IF END IF At this point, you should see a box perfectly outlining the boundary of the datawindow image object. You should also be able to move the box around the window. If you drop the object on the canvas datawindow, you will process the dragged object in the dragdrop event of the canvas datawindow. The il_x and il_y hold offset values so that the object will be created with the same reference to the mouse pointer as it had when it was selected. Other properties in the dragged object are used to set the size and filename of the new image. O F T H E D A T A W I N D O W O B J E C T iuo_dw_planner.setredraw(FALSE) do_x = iuo_dw_planner.pointerx() luo_drag_object = source IF source.classname() = 'drag_object' THEN i_task = luo_drag_object.il_key i_task_desc = luo_drag_object.is_desc is_file = luo_drag_object.is_file il_width = luo_drag_object.width il_height = luo_drag_object.height ll_y = 324 - il_height ll_x = do_x - luo_drag_object.il_x this.event oe_make_bar(TRUE,ll_y,ll_x,row)uo_pic lp long ll_x, ll_y string ls_id lp = source ll_x = idw.pointerx() + lp.il_x ll_y = idw.pointery() + lp.il_y dw_canvas.modify('create bitmap(band=foreground filename= "'+ lp.picturename +'" border="0" x="' +STRING(ll_x)+'" y="'+STRING(ll_y)+'" height="'+STRING(lp.height)+'" width="'+STRING(lp.width)+'" name='+ls_id+' )') This will create an image on the canvas datawindow at the location of the mouse cursor with the same bitmap and properties as the selected image on the palatte datawindow. To illustrate this, select an image from a palette datawindow of the shelf demo, in this case, a fine bottle of scotch. You notice the rectangle around the selected object which is the picture object with its visibility set to 0. Since the drag (begin!) function is invoked, the outline of the picture object does appear around the bottle. Figure 15 Drag the object from the palette datawindow to the target datawindow, in this case, the shelf. Figure 16 FIRST QUARTER 2006 7 P O W E R O F T H E D A T A W I N D O W O B J E C T Drop the object onto the target datawindow and build the datawindow bitmap object, based on the properties stored in the dragged object and the results of the pointerX() and pointerY() functions. is_function = 'newnode' //Create new node END IF This sequence can be seen in the following figures. Figure 17 Object Manipulation From a Treeview to a Datawindow Many times the source or palette may be a treeview rather than a datawindow. This makes things a bit easier, since it does not require a third object to perform the dragdrop, as the treeview itself acts as the drag object. To initiate the process, place the following code in the Selectionchanged event of the treeview object. Check to see if the treeviewitem selected is one that can be dragged. This can either be a value stored in the treeviewitem data property or the picturename of the treeviewitem, as in this example. IF string(this.picturename[ltvi_item.pictureindex]) = 'arrow2.bmp' THEN this.dragauto = TRUE ELSE this.dragauto = FALSE END IF Figure 18 The desired treeviewitem is selected and drag has to be set to begin!. The treeview item can be dragged since it has the picture indicated, in this case, arrow2.bmp. Figure 19 The item is dragged over the canvas: This automatically enables dragging of the treeview. Drag the treeview icon onto the datawindow and release it at the desired location. The process is similar to the datawindow to datawindow processing. In the dragdrop event of the target datawindow, check for the source of the dragdrop, and if it is a treeview: Dragdrop event IF source.typeof() = treeview! THEN lt_tree = source ll_handle = lt_tree.finditem(currenttreeitem!,0) lt_tree.getitem(ll_handle,lt_item) ll_parent = lt_tree.FindItem(ParentTreeItem! , ll_handle) lt_tree.getitem(ll_parent,lt_parent) //get the bitmap for the node ls_bitmap = string(lt_tree.picturename[lt_parent.pictureindex]) ls_item = lt_item.label is_object_type = 'node' 8 ISUG TECHNICAL JOURNAL Figure 20 The item is dropped and the image is rendered on the canvas, at the location of the pointer, in the dragdrop event of the canvas datawindow. Multiple Platforms One of the great advantages of the datawindow object is that it can be migrated with minimal changes to a variety of platforms. With the development of PocketBuilder, you can take P O W E R the functionality of the datawindow directly to your Windowbased PDA device. This is especially useful for advanced graphical datawindows, since PDA applications tend to be primarily or exclusively pointer-based. Using techniques that allow users to enter data by “drawing” can result in more sophisticated applications. Also, since data is displayed graphically, you can use position, color, sizes, and patterns to make data more meaningful to the user. Figure 21 shows two PocketBuilder views of advanced datawindows used for data input using the PDA pointer. The calendar datawindow allows users to select the current date, a treeview datawindow displaying a list of tasks, and the planner data window in which the task is rendered. The pointer is also used to move, change, and delete tasks from the planner. Datawindow manipulation in PocketBuilder uses the same events and techniques as PowerBuilder, such as metadata and object manipulation, to achieve this functionality. O F T H E D A T A W I N D O W O B J E C T Be aware that there is a learning curve associated with using vb.net due to differences in coding methods between Visual Basic and Powerscript. One major difference is that you must code the event handlers. Let’s consider the commonly used clicked() event in Powerscript. To do the same in Visual Basic, you must add the event handler for the click event, as well as other definitions. There are resources that go in-depth on this topic, but as just one example, consider the clicked() event in Powerscipt where the row number and dwobject are passed automatically as parameters to the clicked event. The following code accomplishes the same in Visual Basic: ‘Define the datawindow variable and the object at pointer variable Dim oDW As Sybase.DataWindow.DataWindowControl Dim oClicked As Sybase.DataWindow.ObjectAtPointer ‘Define the click event handler for datawindow dw_1 Private Sub dw_1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles dw_1.Click ‘Cast the sending object into the datawindow variable oDW = CType(sender, Sybase.DataWindow.DataWindowControl) Figure 21 Visual Studio With the release of datawindow.net for the Microsoft Visual Studio platform, the same advanced datawindows can be created using vb.net or C#. At this time, they have only been created for winforms. Again, the methods used are the same as for the PowerBuilder datawindows. The meta data is built into large strings, then stored in datawindow columns. Objects are built dynamically in the datawindow, which access values within the datawindow columns. ‘Get the object at pointer, its name and the row number that was clicked oClicked = oDW.ObjectUnderMouse oClicked.Gob.Name oClicked.RowNumber There are also differences between the two platforms in the syntax of built-in functions. Some of the more common functions are summarized in the following table. PowerBuilder Visual Basic.net Use (‘) or (“) to enclose string variables Always use (“) Time("6:00:00") TimeValue("6:00:00") Date("01/01/1900") DateValue("01/01/1900") Round(9.334,0) Math.Round(9.334,0) String(‘123’) Str(“123”) RelativeDate(start_dt, 5) DateAdd(DateInterval.Day, 5, start_dt) DayNumber(startdate) Weekday(startdate) RegistryGet("HKEY_CURRENT_USER\ Control Panel\International", "sShortDate",RegString!, local_setting ) Figure 22 Microsoft.Win32.Registry.Current User.OpenSubKey("control panel\\international", False) local_setting = regKey.GetValue("sShortDate") There are resources that go in depth into datawindow.net coding techniques in Visual Studio. FIRST QUARTER 2006 9 P O W E R O F T H E D A T A W I N D O W O B J E C T Browser Advanced datawindows have been migrated to the browser using Appeon for PowerBuilder version 3.0. This integration into the Powerbuilder IDE makes deploying to the web an easy process. Appeon supports much of the dynamic datawindow creation required to display advanced datawindows, as well as dragdrop and other mouse-based functionality. Although Appeon for PowerBuilder does not support the setdetailheight() datawindow function, this can be overcome by using the autosizeheight property on the detail band, which allows the continued use of the detail area for each row. Even the use of dynamically created datawindow columns is supported for storing details regarding each task. As you can see, a very complete and easy deployment of advanced datawindow technology is achieved using Appeon 3.0 for PowerBuilder and PowerBuilder 9. Summary As the datawindow has matured and spread into new technologies and markets, the full capability of this outstanding tool must be harnessed to create applications that are robust and visually appealing to the user. Although it is the premier product for the functionality it has traditionally been used for, the capabilities of the datawindow can be extended to create rich, interactive user experiences in a variety of platforms. My goal is for developers to think outside the box when using the datawindow control in their applications. By using innovative techniques such as metadata to control visual properties and by implementing mouse based object manipulation, you can generate a graphical user interface that creates a more dynamic and interesting user experience. For a more complete explanation of advanced graphical datawindows please refer to Powerbuilder 9 Advanced Client/ Server Development published by Sams (2003). Chapter 7 covers in detail the methods and techniques used to develop the datawindow examples shown in this article. ■ Figure 23 Got a great tip for DBAs or developers? Some great advice, a new method, or excellent syntax? Consider writing an article for the ISUG Technical Journal! Becoming an author for the Journal helps build your resume, establish your reputation, and share your great concepts with fellow ISUG members. For more information, check out the ISUG Technical Journal Online to see sample articles and guidelines for authors. Then contact Managing Editor Mary Freeman at freemancomm@ yahoo.com to discuss your article concept. www.isug.com 10 ISUG TECHNICAL JOURNAL