Tech Articles


A Simple Methodology for Complex Resizing Scenarios Using the PFC


The Resize service included in the PowerBuilder Foundation Class (PFC) framework is a powerful, yet easy-to-use tool in the developer’s toolbox. Due to several factors, however, many PB developers struggle to make the Resize service perform as they would like, particularly when the layout of window controls and/or the desired resizing behavior steps become non-trivial… and often the developer ends up getting frustrated. As a result, the desired functionality is frequently omitted or severely curtailed.

The issue is not that the Resize service is unable to perform the desired task(s) – I believe it can, in many (perhaps not all) cases. The real issue is learning how to effectively utilize the functionality that already exists. There admittedly is not a lot of instructional material available on how to use the PFC in general and the Resize service in particular, so I’ve created an easy-to-follow methodology you can use to make the Resize service do what you want instead of the Resize service making you to do what it wants.

As you’ll soon see, the steps I describe in this tutorial are simple, yet they can be quickly applied to complex resizing scenarios. I’m also going to give you some free code that implements a couple of handy resize-related features you may want to add to your projects.

Disclaimers

This is not a tutorial on how to use the PFC or the PFC Resize service.

This article assumes you are familiar with the PFC and its coding conventions and terminology. It also assumes you have previously used the PFC Resize service. I encourage you to download the example application that accompanies this article. It can be found in the PowerBuilder category of the CodeXchange section of the Appeon Community website:

https://community.appeon.com/index.php/codeexchange/powerbuilder

The PFC Resizing Example application is a single-window, non-MDI (Multiple-Document Interface) PFC application constructed and tested using PB 2017 R2. It uses a plain-vanilla, untouched copy of the 2017 version of the standard set of ten PFC libraries.

The application contains eight variations of a non-updateable grid DataWindow that contains pre-loaded data, so no database or database connection is needed or used. These DataWindows vary the order and sorting specification of the column objects in each presentation to provide a variety of data presentations throughout the application. Each DataWindow Object displays a large, red, single-digit number in the foreground layer only to clearly identify and differentiate the DataWindow controls contained in the various tab pages.

What If You Don't Use the PFC?

The approach I use to analyze window control layouts is independent of the PFC ... the PFC's Resize service is only the mechanism used in the tutorial to translate the analysis into code. Once you understand how to deconstruct a layout for resizing analysis, you should be able to implement by utilizing any reasonably robust framework that provides functionality similar to the PFC's Resize service.

If You Use Out-of-Date PFC Libraries

It’s not uncommon for an enterprise that uses the PFC to use an older version of the PFC libraries. After all, it’s much easier to migrate/regenerate all of the objects in the PFC libraries when you upgrade PowerBuilder versions that it is to download, analyze, retrofit any extension-layer changes to the PFC and finally, test a newer set of PFC libraries. This is especially true if your organization has developed a "corporate" extension layer to the PFC object framework and libraries. It's not a trivial task.

Although there has not been an abundance of enhancements and bug fixes made to the PFC over the years, there have been some, and you forego these improvements when you choose to not periodically upgrade your PFC libraries.

In the case of the Resize service, there was an enhancement made to the most commonly-used format of the of_Register function around the time of PB version 12.5 that added several new resize “methods”:

  • FixedToCenter

  • FixedToCenterTop

  • FixedToCenterBottom

  • FixedToCenterLeft

  • FixedToCenterRight

A few bug fixes have also been applied to the Resize service on or subsequent to that enhancement, so if you are using an older version of the PFC libraries, perhaps you should consider a refresh/upgrade.

The Specialized and the Generalized Formats of the of_Register Function

Are you aware there are two formats of the Resize service’s of_Register function? There’s the format you are likely familiar with that accepts two arguments: The window control and a resize method name. For example:

of_Register(cb_close,’FixedToRight&Bottom’)

In this article, I’ll be referring to this format as the “specialized” format because there exists only a small number of predefined resize methods and each one works in a specific way.

The other format you may not be familiar with is what I will be referring to as the “generalized” format because of the greater degree of flexibility and control that is available. Here is the description of the generalized format:

of_Register(controlname, movex, movey, scalewidth, scaleheight)

controlname  (WindowObject) The window control to be registered.

movex        (Integer) The percentage to move the control along the X-axis (left/right).

movey        (Integer) The percentage to move the control along the Y-axis (up/down).

scalewidth   (Integer) The percentage to resize the control along the X-axis (width).

scaleheight  (Integer) The percentage to resize the control along the Y-axis (height).

The descriptions of the four integer, scaling argument parameters begin with “The percentage…”. You may ask: “A percentage of what?” That’s a reasonable question. It is a percentage of the amount of change in the size of the control’s “container object”. A container object must be one of the following:

  • A custom visual user object that contains the window control, or

  • A tab page (which is actually a custom visual user object) that contains the window control, or

  • A tab control (this can only contain custom visual user objects/tab pages), or

  • The parent window that contains the window control.

Because of the flexibility it affords, the discussion and examples in this tutorial will focus almost exclusively on the generalized format.

The "PFC Resizing" Example Application

When started, the example application appears as shown below:

The "Resize Test" sample application

The window's title includes the window’s current width and height as percentages of its original width and height and the window updates this information as the window is resized.

The client area of the window contains six controls:

  • A static text control positioned near the upper-left corner of the window.

  • A DataWindow control positioned near the upper-right corner of the window (more about it later!)

  • A tab control. This tab control contains four tab pages, but only the first tab page is initially visible. The other tab pages can be shown/hidden via command buttons located at the bottom of the first tab page. The only reason I’ve done this is to demonstrate the Resize service does indeed work correctly on hidden and/or disabled tab pages. "Create on demand" tab controls are another matter entirely and will be discussed in the next section.

  • A static text control positioned near the lower-left corner of the window.

  • A command button positioned near the lower-right corner of the window.

  • A “resize grip” at the lower-right corner of the window (more about this later, also!)

The registering of the window controls and many of the controls contained within the four tab pages of the tab control is performed in the window’s ue_ConfigureResize custom user event, which gets invoked from the window’s pfc_PreOpen event script. Shown below is some of the code in the ue_ConfigureResize event; the code shown enables the Resize service and registers all but one of the window’s controls as well as the controls on the “Single DW” tab page:

This.of_SetResize(True)

// Window controls.
This.inv_resize.of_Register(st_1,      'ScaleToRight')
This.inv_resize.of_Register(dw_percent,'FixedToRight')
This.inv_resize.of_Register(tab_1,     'ScaleToRight&Bottom')
This.inv_resize.of_Register(st_2,      'FixedToBottom&ScaleToRight')
This.inv_resize.of_Register(cb_close,  'FixedToRight&Bottom')

// Tab page 1 ("Single DW") contents.
This.inv_resize.of_Register(tab_1.tp_1.dw_1_tp1, 'ScaleToRight&Bottom')
This.inv_resize.of_Register(tab_1.tp_1.cb_double,'FixedToRight&Bottom')
This.inv_resize.of_Register(tab_1.tp_1.cb_triple,'FixedToRight&Bottom')
This.inv_resize.of_Register(tab_1.tp_1.cb_nested,'FixedToRight&Bottom')

This is very typical code and is therefore of little interest in this discussion, but I’ve included it here for completeness and to allow you to see how I’ve named the controls in the window (“tp_1” = tab page 1, “dw_1_tp1” = DataWindow 1 in tab page 1, for example). Note that the “resize grip” control is not registered at the same time as the other window controls. This is because it is a dynamically-added user object, created in the window’s pfc_PreOpen event after the ue_ConfigureResize event script has been executed. Here is nearly all of the code in the window's pfc_PreOpen event:

// Register window controls with the Resize service.
This.EVENT ue_ConfigureResize()

// Should a resize grip be added to the window?
If This.ib_use_resize_grip Then
   // Add the resize grip user object to the window.
   // Add it to the upper-left corner of the window (it is initially
   // invisible). Once it exists, move it to the lower-right corner
   // and make it visible.
   This.OpenUserObject(This.iuo_grip,0,0)
   
   // Determine the initial position of the resize grip in the window,
   // then move it.

   // Note: The "grip" is drawn using a character from the Marlett font
            in a static text control. PB pads below the text a few pixels,
            so adjust for this when positioning the grip.
   If IsValid(This.iuo_grip) Then
      This.ii_grip_x = This.WorkSpaceWidth()  - This.iuo_grip.Width
      This.ii_grip_y = This.WorkSpaceHeight() - This.iuo_grip.Height + &
         PixelsToUnits(3,YPixelsToUnits!)
      
      This.iuo_grip.Move(This.ii_grip_x,This.ii_grip_y)
      This.iuo_grip.Visible = True
      
      // Register the grip with the Resize service and allow the 
      // service to move it around as needed from here on out.
      This.inv_resize.of_Register(This.iuo_grip,'FixedToRight&Bottom')
   End If
End If

I’ve included a discussion of the “resize grip” user object in a separate section at the end of the article for anyone that is interested.

The Problem With "Create on Demand" Tab Controls

The PowerBuilder tab control supports a feature/property named “CreateOnDemand”. When enabled, tab pages and the controls they contain are instantiated, but the Constructor event of the controls on hidden tab pages is not triggered until the user views the tab page by either 1) clicking on the tab page or 2) by calling the SelectTab function of the tab control. This reduces the time it takes to open a window because the creation of a graphical representation of hidden tab pages is deferred until it is needed.

All well and good. However, one side effect is that scripts cannot reference a control on a tab page until its Constructor event has run and the graphical representation of the control has been created. This poses a problem for the Resize service because the controls have to be registered (i.e., they are referenced) before their Constructor event is triggered..

Another problem is that resizing of the controls get “out of sync”. For example, if a user resizes a window, then selects a “create on demand” tab that contains a DataWindow that needs to be managed by the Resize service, the DataWindow cannot be sized correctly. It cannot be registered until the tab is selected, but the window size may be different (either larger or smaller) from what the developer designed. If you register it normally as part of the window’s pre-open processing, the DataWindow control’s Constructor event has not been triggered and it therefore cannot be successfully referenced.

As you can see, the Resize service needs to be enhanced; it needs to retroactively resize the DataWindow control based on the window's original size compared to the window's current size, once the control has been registered and when it resides in a “create on demand” tab page after the window has opened. For this reason, the Resize service and tab controls using the “create on demand” feature are currently not compatible. That would be a nice enhancement.

The "Double DW" Tab Page - Now Things Get Interesting

The second tab page is made visible when you click on the “Show Double DW” button.

I made it possible to show/hide several tab pages (not using a "create on demand" tab control) because some recent posts in the Appeon Community Q&A Forum indicated this might be a factor that adversely affects the operation of the Resize service. Let’s be clear: It does not affect the Resize service, as long as you register controls that are to be managed by the Resize service on these hidden tab pages.

Here is the layout of the “Double DW” tab page:

The "Double DataWindow" tab page

As the name of the tab page suggests, there are two DataWindow controls. We would like these DataWindows to continue to fill the tab page as the window (and the tab_1 tab control) are resized. The static text I’ve placed above each DataWindow control summarizes what needs to happen:

  • DW 1 should scale left/right (horizontally) at 50% and scale to the bottom (vertically) at 100%.

  • DW 2 needs to move horizontally at 50% because DW 1 is getting wider/narrower, but it also needs to scale left/right at 50% and vertically at 100%.

How do we tell the Resize service to do this?

A technique I strongly recommend you adopt is to make a simple sketch of the layout and note both the desired movement and size scaling percentages for every control. You may not see the value in making a sketch, particularly for this relatively simple example, but the examples are going to get increasingly complex, so by the time we’re finished with this tutorial, I think you will see the benefits of making and using a sketch.

Here’s my “sketch”. I created it here using the Windows Paint utility, but normally I simply use a piece of scrap paper.

The "Double DataWindow" sketch

Please note in the sketch how I have labeled the movement and size scaling percentages: X (X-axis movement scaling), Y (Y-axis movement scaling), W (X-axis size, or width scaling), and H (Y-axis size, or height scaling). These match up with the “movex”, “movey”, “scalewidth” and “scaleheight” arguments of the generalized format of the of_Regster function, which makes the translation of the information in the sketch into of_Register function calls a trivial task. I’ve also included the DataWindow number in the name (X1, W2, etc.) given to every movement and size scaling percentage to eliminate any confusion in the discussion.

Based on the sketch and the syntax of the generalized of_Register function, it should be fairly evident that conceptually, the function calls will appear as follows:

of_Register(dw_1, x1, y1, w1, h1)
of_Register(dw_2, x2, y2, w2, h2)

Here’s the code snippet from the window’s ue_ConfigureResize event that registers the two DataWindow controls in the “Double DW” tab page:

// Tab page 2 ("Double DW") contents.
This.inv_resize.of_Register(tab_1.tp_2.dw_1_tp2,  0, 0, 50, 100)
This.inv_resize.of_Register(tab_1.tp_2.dw_2_tp2, 50, 0, 50, 100)

Please note I’ve omitted here the of_Register function call in the code in the window that registers the static text control above DW 2 because these static text controls exist only to communicate to you how resizing is configured. Looking at these two of_Register function calls, you can clearly see how the values for the movement scaling percentages and the size scaling percentages from the sketch plug right in as anticipated. It could hardly be any easier!

Let’s examine at a slightly more complex example, then we’ll see if some general rules can be inferred from what we’ve learned so far.

The "Triple DW" Tab Page - Kicking It Up A Notch

The "Triple DW" tab page contains three DataWindow controls arranged in a vertical stack:

The "Triple DataWindow" tab page

Although the three DataWindows are nearly identical in size initially, you can see from the description above each DataWindow how they will resize vertically at different rates. The top one at 10%, the middle one at 30% and the bottom one at 60%.

Let’s start with a sketch:

The "Triple DataWindow" sketch

Using what we learned in the “Double DW” example, the of_Register function calls nearly write themselves! Here is the code from the window:

// Tab page 3 ("Triple DW") contents.
This.inv_resize.of_Register(tab_1.tp_3.dw_1_tp3, 0,  0, 100, 10)
This.inv_resize.of_Register(tab_1.tp_3.dw_2_tp3, 0, 10, 100, 30)
This.inv_resize.of_Register(tab_1.tp_3.dw_3_tp3, 0, 40, 100, 60)

How Were the “Y” Scaling Percentage Values Derived?

The width percentage values in this example are all 100% because each DataWindow spans the entire width of the available space. The height percentage values were given to us, but note how H1, H2 & H3 sum to 100%. Since the left edge of all three DataWindows remain fixed relative to the left side of the available area, the horizontal movement percentage values (X1, X2 & X3) are all zero. The challenge in this example, therefore, is in determining the three vertical movement percentage values, Y1, Y2 & Y3.

Beginning at the top (DW 1), it should be easy to see that the top edge of this DataWindow is fixed, relative to the top of the container object, so its vertical movement scaling percentage (Y1) is zero. We know the height of DW 1 will change at 10% of the amount of the change in height of the container (the tab page). It is key to recognize that in order to maintain the same spacing between DW 1 and DW 2, the top of DW 2 will need to move at the same rate of 10% of the overall vertical change. Thus, Y2 = H1 = 10%.

Aha! So, this means Y3 = H2 = 30%, correct?

Sorry, no, It doesn’t work that way. Here's why: The top of DW 2 moves vertically at 10%, but the height of DW 2 will simultaneously change at 30%. Therefore, the bottom edge of DW 2 will move vertically at 10% + 30% = 40%. We want the top edge of DW 3 to remain the same distance from the bottom edge of DW 2, so it will also have to move vertically at a rate of 40%. In other words, Y3 = (H1 + H2).

Pop Quiz: If there were four DataWindows arranged in a vertical stack, how would you express/determine the value for Y4?

If you said: Y4 = H1 + H2 + H3, you're right! It really isn't any more difficult than that.

If the three DataWindow controls were arranged side-by-side instead of stacked, we would see the same pattern of relationships between the X-axis movement scaling percentages and the width scaling percentages.

This example illustrates why the process of creating and labeling a sketch helps you determine the proper scaling percentage values to use in the of_Register function calls.

I think we’re now ready to take on a significant challenge.

Cranking Up the Volume Control To 11: Eight DataWindows In A Staggered-Tile Layout

The “Nested Tab Control” tab page contains a tab control that has three tabs.

The first tab (labeled “Present”) contains two DataWindows in a side-by-side layout similar to the “Double DW” tab page, except the width scaling percentages are split 33/67 instead of 50/50. Take a minute to come up with the of_Register function calls for these two DataWindows and check the results against the code in the window.

The second tab (labeled “Past”) contains a single DataWindow that uses one of the specialized format resize methods that was added to the Resize service around PB version 12.5. I included it so you could see firsthand how “FixedToCenter” resizing works, in case you have not previously seen it.

The third tab in this “nested” tab control (labeled “Future”) contains eight DataWindows arranged in a staggered-tile layout as shown below:

The "Future" tab page in the nested tab control

I included two red line controls in this tab page (above and between DataWindows 5 and 6) to illustrate how the Resize service successfully handles unusual types of window controls. They will be ignored in the following discussion.

Below is my sketch of this tab page showing only the size scaling percentages for the eight DataWindows:

The eight DataWindows sketch

The X and Y movement scaling percentages have been omitted on purpose as an exercise for the reader. Using what you learned in the “Triple DW” example, you should be able to derive these values with little difficulty. Here’s a table you can print and use to jot down the missing values:

The eight DataWindows scaling percentages table

After you fill in the missing values, examine the code in the window’s ue_ConfigureResize event script to see how well you did.

How Can You Check If the Size Scaling Percentages Work?

You can run the application, of course... but there’s an easy way to check if the size scaling percentages are correct: Take a horizontal (for width) or vertical (for height) cross-section through your sketch that does not occupy a parallel “aisle” … which is what I refer to as the empty area between adjacent controls. For full horizontal cross-sections, the width scaling percentages should sum to 100%. Remember the "Triple DW" example? For full vertical cross-sections, the height scaling percentages should also sum to 100%.

For example, the full horizontal cross-section through DataWindows 5, 6, 7 and 4 (70% + 0% + 20% + 10% = 100%) checks out, as does the full vertical cross-section through DataWindows 1, 5 and 8 (35% + 0% + 65% = 100%).

Examples of full cross-sections

Partial cross-sections from any side to a common “aisle” shared by multiple window controls should sum to the same percentage. Intuitively, this makes sense. Consider the aisle immediately below DataWindows 1 and 2; in order to preserve the height of this aisle, the rate at which both of these DataWindows change height must be the same. If the height scaling percentages were different, one DataWindow would grow taller/shorter than the other. Visually, it would appear as if one of the DataWindow controls "collides" with the control(s) below it as the window height increases. We'll examine the topic of control "collisions"  in a little more detail soon.

Now, generalize this concept; The summed size scaling percentage for all partial cross-sections from an edge of the sketch to an aisle that is perpendicular to the cross-section must be equal.  For example, the partial horizontal cross-section through DataWindows 1 and 2 (40% + 30% = 70%) equals the partial horizontal cross-section through DataWindows 5 and 6. Similarly, the partial vertical cross-section through DataWindows 1 and 5 (35% + 0% = 35%) equals the partial vertical cross-section through DataWindows 2 and 6, as illustrated below.

Examples of partial cross-sections

Producing and using a sketch is immensely helpful in checking the full and partial cross-section size-related scaling percentages.

Should You Mix Usage of the of_Register Function Formats?

The examples in this tutorial have focused on using an alternative, generalized format of the Resize service’s of_Register function where the X, Y, W & H scaling percentages are individually specified. As I mentioned earlier, you may not have previously used this format. Typically, many developers using the PFC Resize service use the so-called “standard” (I prefer to call it the specialized) format of the of_Register function

The Resize service translates the resize method name in the specialized format into equivalent X, Y, W and H scaling percentage values, then invokes the generalized of_Register function. The following table lists the predefined resize method names and their scaling percentage values:

The scaling percentage values for the specialized format method names

To illustrate what can happen when you attempt to mix usage of the specialized and generalized formats of the of_Register function, let’s try it as a "thought experiment". Here, once again, is the code that registers the two DataWindows from the “Double DW” example we first looked at:

// Tab page 2 ("Double DW") contents.
This.inv_resize.of_Register(tab_1.tp_2.dw_1_tp2,  0, 0, 50, 100)
This.inv_resize.of_Register(tab_1.tp_2.dw_2_tp2, 50, 0, 50, 100)

Scan the table above in search of a suitable replacement for either or both of the of_Register function calls.

Any luck?

No? Hmmm. As you can imagine, if you replace either one of these generalized of_Register function calls with one of the specialized ones, the Resize behavior will change. As an exercise, pick the specialized of_Register function call arguments you think might work the best, and substitute the X, Y, W and H values into the sketch you made for the “Double DW” example (you did make a sketch, didn’t you? Good!) Now take some imaginary full cross-sections (there are only three possible full cross-sections; one horizontally through both DataWindows and two vertically through each DataWindow) and see if the width or height scaling percentage values all sum to 100%. Go ahead…

Did you detect any problems? If you didn’t, recheck your work, because there is going to be a problem with at least one of the three cross-section “sums”. This means the modified set of of_Register function calls will not produce a workable implementation and there’s going to be an issue regarding how resizing performs.

The Big Takeaway

To be fair, it’s entirely possible there are scenarios where you can safely and correctly mix these so-called “specialized” and “generalized” formats of the Resize service’s of_Register function, but you’ll have a hard time convincing me that doing so is a good idea. Once you use the generalized format, I think it best to use it consistently for all of the controls in the entire “container” (tab page/user object or window). The example application window uses both formats, but this was done to include both formats for illustration purposes.

Window Control "Collisions"

Whenever the controls in a window (or tab page/user object) can resize and/or move dynamically, the specter of “collisions” or “overlap” between controls becomes a possibility.

Just as adherence to the rules for safe driving help to keep collisions between vehicles from occurring, following the methodology I’ve described in this tutorial will help to ensure “safe resizing” … preventing window control collisions during resizing.

If overlap occurs in a layout, go back to the sketch to find the problem. Here is a checklist of the most likely places where an error can be introduced:

  • If any control’s width and/or height is fixed and cannot change, does the movement and size scaling percentages for every adjacent control take this into account? This is likely to be the cause of any collision issue.

  • Are the four scaling percentage values (X, Y, W and H) for every control in each container object correct?

  • Did these values get translated into of_Register function calls correctly? Typo’s happen!

  • Do all of the width/height scaling percentage values in full cross-sections sum to 100%?

  • Are the width/height scaling percentage value sums in partial cross-sections from a side edge to a shared, common aisle all equal?

When A Collision Can’t Be Avoided – Just Say No (No to Limitless Resizing)

It’s extremely difficult to foresee every possible potential window control layout problem, so there may be a case where it’s not possible to avoid a collision between window controls during resizing. One technique for addressing the issue is to set a minimum size for the window. The PFC Resize service provides the of_SetMinSize function that can be used for this purpose, but it can be a little awkward to use because the minimum width and height argument values are specified in PowerBuilder Units (PBU’s), which can be difficult to estimate. A simpler approach might be to just specify the minimum allowable percentage of the original window size. It’s unfortunate there is no corresponding of_SetMaxSize function in the Resize service, so this capability does not exist today.

Limiting Window Min/Max Size - Free Code!

It makes little sense to me to allow a window to be greatly reduced in size, say, to 25% or less of its original/design size, particular when the option to fully minimize a main or sheet window is available. However, I can foresee the usefulness of limiting the maximum size of a window when needed, even though the PFC Resize service does not currently support this feature. I’ve incorporated the means to accomplish both in the example application, without making any changes to the Resize service.

The upper-right corner of the window contains mechanisms you can use to experiment with this feature:

The Min/Max window size limit feature

Note:    This is a free code example that has been provided solely to illustrate the means for setting and enforcing min/max limits on a window’s size. I do not suggest or recommend you mimic this implementation in your production applications… it exists only to show you what this functionality can do and how it can be accomplished.

The window defines a handful of clearly-labeled instance variables and instance constants to manage the min/max size functionality. The window also contains two object structures (os_point and os_minmaxinfo; representations of the POINT and MINMAXINFO Windows API structures, respectively) and two external function declarations (named GetMinMaxInfo and SetMinMaxInfo, each aliased to the RtlMoveMemory Windows API function).

How Window Resize Limits Work

Via the ItemChange event, changes to any of the settings in the upper-right corner of the window affects the instance variables in the window. All values are specified in pixels instead of PBU’s because a Windows API function is involved.

A user event named ue_LimitMinMax has been added to the window that is mapped to the pbm_getminmaxinfo PowerBuilder event. PowerBuilder triggers this event in response to the Windows O/S (Operating System) sending a WM_GETMINMAXINFO message whenever the window is about to be resized. The Windows O/S supplies the memory address of a Windows MINMAXINFO structure, and PowerBuilder passes this memory address along to the application window. The MINMAXINFO structure contains five POINT structures (these are os_point structures in PB). Each os_point structure contains two members of type Long; An X-coordinate and a Y-coordinate. In our case, the X-value is going to be the width of a window and the Y-value is going to be the height of a window, both expressed in pixels.

In this user event script, the GetMinMaxInfo external function is called to copy the contents of the MINMAXINFO structure from Windows-controlled memory into a PowerBuilder structure of type os_minmaxinfo so that the structure members can be accessed in PowerScript. If the user has elected to enforce a minimum window size, one of the nested os_point structures in the os_minmaxinfo structure is used to define the minimum allowable size of the window. Another of the os_point structures can be used to specify the maximum allowable size of the window if the user has requested a maximum size be enforced.

If either the minimum or maximum allowable window sizes are set, the SetMinMaxInfo external function is called to copy the contents of the PowerBuilder os_minmaxinfo structure back into Windows-controlled memory. Windows compares the proposed new window width and height against the minimum and/or maximum allowable values that have been set, if any, and decides whether or not to send the actual WM_RESIZE message to the window so that PowerBuilder will trigger the pbm_resize event and resize the window.

Examine the code in the window’s ue_LimitMinMax user event for more details.

The idea for using the pbm_getminmaxinfo event comes from the ResizeReponse free PowerBuilder code example application provided by Roland Smith at the TopWizProgramming web site:

https://www.topwizprogrammings.com/freecode_resize_response.html

All of the DataWindows in the Resize example application which are pre-loaded with “customer” data originated in the “DWGripXP” free code example at TopWizProgramming.com.

The Window Resize "Grip" Control - More Free Code!

If you have run the PFC Resizing example application, you may have noticed the window contains a working "grip" control in the lower-right corner

The resize "grip' control

The grip control is fully functional. When the mouse is over the grip control the pointer changes to the diagonal “double-arrow” resize pointer. A click-drag performed on the grip control will resize the window, and the grip control disappears when the window is maximized. This is accomplished without making any changes to the PFC.

Note:    This is a free code example that has been provided solely to illustrate how to implement a resize grip control. it exists only to show you what this functionality can do and how it can be accomplished.

The window defines a handful of clearly-labeled instance variables and instance constants to manage the resize grip control functionality. The window contains an object structure named os_rect which is a PB representation of the Windows RECT (rectangle) API structure. and four external function declarations (named GetWindowRect, GetCapture, ReleaseCapture and SetCapture) that call Windows API functions of the same names. The example application’s PowerBuilder Library (pbl) contains the grip control user object, u_resize_grip, which is actually an inherited static text control with several predefined properties.

How the Grip Control Works

The code to implement the resize grip control resides in one window object function and six window event scripts:

  • The of_CaptureWindowOriginalSize object function.

  • The pfc_PreOpen event (you saw the majority of this event script earlier).

  • The MouseDown event.

  • The MouseMove event.

  • The MouseUp event.

  • The Resize event.

  • The Close event.

Many of the scripts are very short. The longest is approximately 60 lines of code, including comments.

The code calls some Windows API functions to capture and release the mouse. When a window “captures” the mouse, it receives mouse-related messages while a mouse button is pressed and held, even if the mouse is moved outside of the boundary of the window … which is what is needed to make a resize grip control work properly. The window “releases” the mouse when the mouse button is released.

The code is pretty straightforward and includes many comments. Since you are a PowerBuilder developer that uses the PFC, you can read the code in these scripts and figure out what it does (it’s not rocket science), so I’m not going to discuss it in any more detail here.

The Critical Setting That Allows The Grip Control To Work

The resize grip control depends on one critical setting in order for it to work properly, so I’m going to mention it. The grip control user object (u_resize_grip) is a standard static text control that displays the “grip” symbol (the symbol is a lowercase “O” (oh, not a zero) in the Marlett font). This static text control must be disabled. If it is enabled, PowerBuilder will assign input focus to this static text control when you click on it. Disabling it prevents it from receiving input focus, so the window then receives the MouseDown, MouseMove and MouseUp events, which are essential to allowing the grip control to work.

Conclusion

Many a PFC developer has wrestled with the Resize service; cajoling and coercing it in order to accomplish the desired resizing behavior, especially in cases when the layout of the window controls has grown beyond simple scenarios. The PFC Resize service isn’t bad, it’s just misunderstood. Actually, it has suffered from a lack of a published and easy-to-follow methodology on how to analyze resizing behavior and translate requirements into code. I hope this tutorial helps you make better use of the Resize service. Your feedback is welcome and appreciated.

Comments (1)
Friday, Oct 09 2020

Thanks! A lot of people will be helped by this.
Myself I've used the register format with percentages and x, y positions several times, but to be honest, this is where I think that the pfc resize service can be improved. It makes things pretty complicated and once you move things around, the registrations have to be changed.
Let's see if Appeon comes up with something more advanced and modern

1

Find Articles by Tag

Database Profile Database Connection PowerBuilder (Appeon) Jenkins TFS Text HTTPClient Filter Automated Testing .NET Std Framework Open Source SnapDevelop JSONGenerator Configuration NativePDF Mobile Database Table Data iOS PostgreSQL ODBC driver Git Windows OS Application JSON RibbonBar MessageBox Migration DevOps Oracle Debugging Expression PowerScript (PS) Installation Debug Script Database Table Schema Syntax Service Array Menu Authentication License GhostScript DataWindow Encryption CoderObject API Graph 32-bit Android DataWindow JSON Debugger RichTextEdit Control Resize SQL Model Export Database SnapObjects Elevate Conference Database Table Data Interface Azure Class Encoding IDE PDFlib TLS/SSL BLOB OrcaScript File DLL PBDOM Outlook UI Themes .NET Assembly Event Database Object PowerBuilder DataType Design Bug WinAPI Stored Procedure PFC SqlModelMapper Web Service Proxy Testing PowerServer Web Messagging SOAP Transaction Import Window C# UI Modernization OAuth 2.0 PowerBuilder Compiler Trial TreeView OLE ActiveX Event Handler Error Import JSON Excel SqlExecutor TortoiseGit PDF XML Source Control WebBrowser External Functions Database Painter Source Code 64-bit Branch & Merge OAuth Repository RibbonBar Builder InfoMaker Platform Performance Deployment Icon SQL Server DragDrop PowerServer Mobile CrypterObject Variable JSONParser Event Handling PostgreSQL UI Validation SDK COM Charts Web API SVN REST RESTClient Export JSON ODBC .NET DataStore Linux OS Authorization Windows 10 Icons CI/CD Sort