Welcome!

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

Related Topics: PowerBuilder

PowerBuilder: Article

Dockable Windows in PowerBuilder

Another PowerBuilder challenge

If you've read any of my past articles, you know that I love a good challenge, a challenge that really gets me thinking and ultimately ends up teaching me something. One thing I always wanted to do was create dockable windows using pure PowerScript. I held off from doing this for some time because of the almost overwhelming complexity that was inherent with this type of control. Seeing as this would be the perfect test of my skills, I motivated myself to do it.

The first thing I had to do before I started was investigate an existing control so that I could record proper behavior. To do this effectively I would need a really good example to analyze. Those of us using PowerBuilder 7 or higher know that the System Tree window in the IDE is as good an example as any.

Here is what I observed using the System Tree as an example:

  1. An outline appears when the windows title bar is clicked.
  2. The outline moves with the mouse clinging to the edges of the MDI window as a docked window would do.
  3. The outline disappears when the user lets go of the mouse button.
  4. The window moves to where the user last dragged the outline.
  5. Windows have a fixed position on the outer edges of the MDI frame.
  6. The window has a wide variety of resize capabilities.
After a little brainstorming I found that three main components were needed to accomplish our goal of making dockable windows.
  1. A non-visual object to manage all the open docked windows.
  2. An ancestor window from which all other docked windows will be inherited from.
  3. A window drag outline.
Due to the large amount of code produced for this example, it would be best to go to www.sys-con.com/pbdj to download the source provided so that you can follow along without having to sift through pages and pages of code within this article.

Once you have the source take a look at dockwindows.pbl. There are three objects that represent the three main components outlined above:

n_cst_dockwindowmanager
w_dockancestor
w_outline

n_cst_dockwindowmanager
This non-visual object is used to manage all open dockable windows; think of it as a layout manager. As windows are being opened, closed, or repositioned they need to be arranged in such a way that everything appears fixed and organized.

It's very important that this object be declared as a global variable like so: n_cst_dockwindowmanager gnv_dockmanager. The variable name in this case is very important; this is how it'll be referenced in the other objects.

There are a few key functions that you should be aware of that will help you interact with the docked windows. Review the code in each function for a better understanding of what's happening:

  • of_CloseWindow: This function closes a docked window. Pass as a string the name of the window you wish to close.

    gnv_dockmanager.of_CloseWindow("w_dockoutput")

  • of_GetWindow: Returns a reference to an open window. Pass as a string the name of the window you wish to get a reference to.

    w_dockoutput lw_output

    lw_output = gnv_dockmanger.of_GetWindow("w_dockoutput")

  • of_IsWindowOpen: Similar to PowerBuilder's IsValid function. Returns true if the specified window is open and false if otherwise.

    w_dockoutput lw_output

    IF of_IsWindowOpen("w_dockoutput")
         THEN
    lw_output = gnv_dockmanger.of_GetWindow("w_dockoutput")
    END IF

  • of_OpenWindow: This function opens a docked window in the position specified. Valid position values are left, right, top, and bottom. Alternatively, you can use the LEFT, RIGHT, TOP and BOTTOM constants as the second argument.

    gnv_dockmanager.of_OpenWindow("w_dockoutput", "bottom")

  • of_ResizeDockWindows: Positions and resizes all open docked windows and the MDI client area. Called in the resize event of the applications MDI window. An example and a better explanation will be provided closer to the end of the article.
  • of_SetClientArea: Called once in the MDI's open event to create a reference to the MDI client area.

    gnv_dockmanager.of_SetClientArea(THIS.MDI_1)

w_dockancestor
This window will be the ancestor of all your docked windows. Inherit one for every new window you require for your application. Once inherited and saved you will need to determine its position. Its position will dictate how you initially size the window. Windows docked to the left or right should have narrow widths. Windows docked to the top or bottom should have short heights. Take a look at w_dockpblselect and w_dockoutput for an example. W_dockpblselect docks to the left and w_dockoutput docks to the bottom.

Your new window should only have one control placed on it. Resizing and positioning of this control is done automatically. This is done by simply creating a reference to the fifth element in the control array within the open event of the window (i.e., ido_object). If more than one control is required, you must encapsulate everything into a custom visual user object and place that onto the window.

Take note of the windows event list. You will see two new user events: ResizeEnd and Resizing. Both are called from the Other event. Resizing is triggered before the Resize event and lets us know how the user is sizing the window (e.g., left edge, right edge, top edge, etc.). ResizeEnd is triggered once the user is finished sizing the window. The Resizing event is used to restrict the user in how they can size the docked window. If the window is docked to the left, you would only want them to resize by dragging the right edge, not the left, top, or bottom. If they are resizing from the wrong edge, the window stops drawing and is repositioned to its proper location. The ResizeEnd event will allow the window to start drawing if necessary.

While looking at the event list you may have noticed two other user events: PositionChanging and PositionChanged. PositionChanging is called before a window is repositioned by the user. You can prevent the window from being repositioned by returning 1 from this event. PositionChanged is called after the window's position has changed. Arguments in both events are integers that are represented by the LEFT, RIGHT, TOP, and BOTTOM constants found in n_cst_dockwindowmanager. Both user events are triggered from the of_SetPosition function.

The drag outline appears when a user clicks on the window's title bar and starts dragging. Refer to of_DragWindow to see exactly how this is managed. The SetCapture and ReleaseCapture Windows API functions are used to direct all mouse activity to the window title bar. By doing this, the mouse can leave the window and still trigger the MouseMove event of the title bar, which in turn makes a call to of_DragWindow.

One other thing to make note of is the visual appearance of the windows title bar. The title bar comprises of the following images; feel free to change them to suite the overall look of your application:
barhorizontal.bmp
barvertical.bmp
barclosehorizontal.bmp
barclosevertical.bmp

w_outline
Of all the components needed, this is by far the simplest. W_outline is used to display the outline of the docked window as it's being dragged within the MDI frame. The outline is just a rectangle object on a popup window. The fill color of the rectangle is set to Aqua, which is the color that will be converted to transparent.

The open event of the window uses an API call to change the windows style to be layered so that transparency can be added.

SetWindowLong(Handle(THIS), GWL_EXSTYLE, WS_EX_LAYERED)

Another API call is triggered to convert the Aqua color to transparent.

SetLayeredWindowAttributes(Handle
    (THIS), RGB(0,255,255), char(0),
    LWA_COLORKEY)

That's it; we now have an outline (see Figure 1). To change the color of the outline you would just change the line color of the rectangle.

Putting It Together
Now that we have all our logic encapsulated into the above objects, implementing this functionality is very simple. To add docked windows to your application, simply do the following:

//Declare Global Variable
n_cst_dockwindowmanager gnv_dockmanager

//Open Event of Application Object
gnv_dockmanager = CREATE n_cst_dockwindowmanager
//Open Event of your MDI Window
gnv_dockmanager.of_SetClientArea
(THIS.MDI_1)

gnv_dockmanager.of_OpenWindow("w_dockpblselect", "left")

//Resize Event of your MDI Window
gnv_dockmanager.of_ResizeDock
Windows(0, gnv_toolbar.of_
GetToolbarHeight(), THIS.
WorkSpaceWidth(), THIS.WorkSpace Height() - MDI_1.MicroHelpHeight)

Pay attention to the call to of_ResizeDockWindows in the frames resize event. You can see that the size and position is being set based on having your own homemade toolbars implemented (http://pbdj.syscon.com/read/140739.htm). If you're using the built-in PowerBuilder toolbars, then you will need to set the size and position in a different way. The first argument should be 100 x the number of toolbars visible and the last argument should be the work space height - micro-help height + (100 x the number of toolbars visible).

Conclusion
While our example isn't a perfect solution, it is an acceptable one. The next time you see a control in another application that isn't available in PowerBuilder, challenge yourself to create it. Trust me; it's a fun and exciting way to learn the full capabilities of this great development tool (see Figure 2).

More Stories By Brad Wery

Brad Wery is the President of Werysoft Inc. (www.werysoft.com) and the creator of www.PowerToTheBuilder.com, a site dedicated to helping PowerBuilder developers create visually appealing user interfaces. He has been a member of TeamSybase since 2006 and is an active participant in the PowerBuilder Newsgroups.

Comments (1) View Comments

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.


Most Recent Comments
PBDJ News Desk 01/30/06 01:24:53 PM EST

If you've read any of my past articles, you know that I love a good challenge, a challenge that really gets me thinking and ultimately ends up teaching me something. One thing I always wanted to do was create dockable windows using pure PowerScript. I held off from doing this for some time because of the almost overwhelming complexity that was inherent with this type of control. Seeing as this would be the perfect test of my skills, I motivated myself to do it.