Welcome!

Java Authors: Maureen O'Gara, Bruce Armstrong, Liz McMillan, Walter H. Pinson, III, Yakov Werde

Related Topics: PowerBuilder, Java

PowerBuilder: Article

PBDJ Feature: Take Control of Your GUI

Drawing custom controls

Your second option is to build the controls yourself using nothing more than your knowledge of PowerBuilder and a little extra time. In the past it was pretty much impossible to draw your own controls in PowerBuilder. PowerBuilder, being a 4GL language, lacked the low-level control needed to perform such a task. The result was a UI that flickered. This would be completely unacceptable.

This all changed when a dedicated PowerBuilder developer named Yeyi developed a UI framework called Kodigo (kodigo.sourceforge.net). Yeyi used PBNI to create a custom userobject that could be drawn on without the flicker. This opened the door for anyone to create any control they could imagine.

With Yeyi's consent, I used the PBNI object to create some of my own controls. The first control I created was the navigation control found in Outlook 2003 (see Figure 1). I was amazed by how simple it was to create. To get the bulk of the control to work and look proper took me about one day. It took me another couple of days to get it to function exactly right. The controls I created afterward were done a lot quicker because the learning curve was no longer there.

All the controls I created are available to you with full source. You can get everything you need at www.PowerToTheBuilder.com. The site also has a support forum so you can ask questions or provide feedback and comments. Seeing that the source is pure PowerBuilder, it shouldn't be too intimidating to open up the sample application and take a look. You can even customize the controls to make them work more to your needs.

How to Do It...
Now we have a means to draw our own custom control, great. How do we draw it? The controls found on PowerToTheBuilder.com use the Windows GDI API (gdi32.dll). There are a handful of functions required to get the effect we want. Before creating your control, you should come up with a game plan, that is, you have to plan out what needs to be drawn and when. Let's take the button list bar, for example (see Figure 2). On this control there are four things that need to be drawn: gradient background, round rectangle, some images, and text. If you were to draw the image and text before the gradient background, you would lose the image and text. The gradient background would end up overlapping what was previously drawn.

What we need to do is draw the gradient background, then the round rectangle with a gradient fill, followed by the image and then the text. Now that we have a plan in place, let's look at the functions required to get it done. First, the gradient background. We need to declare two structures (see Listing 1 for definitions):

Next we need to set the colors that are to be used for the gradient fill. The API function that we will use needs the individual Red, Green, and Blue values for the color. If you're using a single integer value to represent the colors, split them (see Listing 2):

colors in the first corner (top left)
of_SplitRGB (al_Color1, ll_Red, ll_Green, ll_Blue)
Corner[1].Red = ll_Red
Corner[1].Green = ll_Green
Corner[1].Blue = ll_Blue

// Set the colors in the third corner (bottom right)
of_SplitRGB (al_Color2, ll_Red, ll_Green, ll_Blue)
Corner[3].Red = ll_Red
Corner[3].Green = ll_Green
Corner[3].Blue = ll_Blue

The next step is to set some coordinates:

l_Gradient[1].UpperLeft = 0 // First corner, top left
l_Gradient[1].LowerRight = 2 // Third corner, bottom right

Last but not least, draw the gradient fill (see Listing 3 for function declarations). The last argument specifies the fill type\direction. In this case it will be a vertical gradient fill (see Listing 4 for some other options).

GradientRectangle (hDC, Corner, 4, l_Gradient, 1, GRADIENT_FILL_RECT_V)

As you can see, with just a little code we're already well on our way to creating a visually appealing custom control.

The next thing we need to do is add buttons to the control. The buttons will consist of three components. The first is a round rectangle to give the overall appearance of a button. The next two will be an image and text. The code required to add and organize the buttons is a little long; it consists of loops, calculating coordinates, figuring out text lengths and heights, and so on. Instead of cluttering this article with all the required code, the full source is available at www.PowerToTheBuilder.com. To note, the source code requires an install of PowerBuilder 10.5 or greater. Instead of listing all the code, I'll show the core functions required to finish the task. Again, please refer to Listing 3 for external function declarations.

To create the round rectangle we need to do the following: create a pen object to draw the rectangle border. This function allows us to set the border style, color, and width:

hRPen = CreatePen(0,0,al_bordercolor)

To use the "pen" we just created, associate it with our device context handle:

SelectObject(hDC, hRPen)

Now we create the actual rectangle. The first function call creates a region. This region will be used for the gradient fill. The next function call creates the actual rectangle:

hRgn = CreateRoundRectRgn( Corner[1].X, Corner[1].Y, Corner[3].X, Corner[3].Y,7,7)
RoundRect( hDC, Corner[1].X - 1, Corner[1].Y - 1, Corner[3].X, Corner[3].Y,10,10)

To fill the new rectangle select the region created in the above step, then call the already familiar GradientRectangle function:

SelectClipRgn(hDC, hRgn)
GradientRectangle (hDC, Corner, 4, l_Gradient, 1, GRADIENT_FILL_RECT_V)

To finish off, de-select the clip region and destroy all pens and regions created earlier:

SelectClipRgn(hDC, 0)
DeleteObject(hRPen)
DeleteObject(hRgn)

To really oversimplify things, to draw an image use the ImageList_DrawEx function and to draw text use the DrawText function. Of course there's a little more to it than that. Both functions require a small amount of prep work before they can be used. The ImageList_DrawEx function requires a handle to an image list and the DrawText function requires a handle to a Font object.

The easiest way to get a handle to an image list is to use a ListView control. Place a new ListView on your controls and make it invisible. You can add images dynamically or through the properties window; it depends on what your needs are. To set an image dynamically and get the ImageList handle, do the following:

lv_imagelist.AddLargePicture ( is_image )

il_imagelist = Send(Handle(lv_imagelist), LVM_GETIMAGELIST, LVSIL_NORMAL, 0)

Creating a pointer to a font object is just as easy as getting a pointer to an image list. All you will need is a structure (see logfont in Listing1) and a function (see Listing 5).

We can put all this together and end up with the image seen in Figure 3. The reality is, once you get comfortable with the functions, you should be able to create a control like this one in a relatively short amount of time. If I recall, this control took about a day to write. Once you get a few of them going, it will only get easier because a lot of the code may be similar, so the chance of having some reuse becomes greater.

Going Forward
As I mentioned at the beginning of the article, you have several options available to make your application look as current as possible. If you're starting an application from scratch and are able to fit a UI framework into your design, I would strongly encourage you to get your hands on Kodigo. If you need a few controls to accommodate an immediate need, please try the GUI controls from PowerToTheBuilder. I just want to sign off by saying good luck and please remember to share. The PowerBuilder community can use all the support they can get their hands on.

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 (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.