How do I? - Part 18

From EDM2
Jump to: navigation, search
How Do I? / Part
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19

by Eric Slaats

Pop me up (Scottie)

Hi all. Once again I missed a month. As usual the last month before the summer holidays are overloaded with 'last minute' work, so I didn't have the chance to do much on programming stuff. Most of my hours went to management chores (I hate that). The only thing I had the chance to take a look at is another Java development package. I toyed around with Borland's JBuilder. This is really a great package! It really is a pity that such stuff (with the exception of VisualAge) isn't available for OS/2. On the up side OS/2 runs Java stuff really well. It takes a little getting used to, but I think Java applets which can run on a multitude of platforms are the future of software. Luckily for us C people, Java has a lot in common with C, so the learning time shouldn't be that long. On the other hand, Java is still in its infancy and although much has been learned from other programming languages and environments, there is room for improvement. Faster VMs is one of them. If I get a better grip on things, I'll report some of the Java stuff in here.

This month we'll make a start with the use of popup menus and subclassing. Pop-ups are an underestimated part of the GUI as far as I'm concerned. As a principle, a popup menu can be used on every control (window) in an application. So with little effort a context sensitive menu can be created. An example of this can be seen in Smalled. In Smalled the main edit window has a context popup. But also the statusbar has a separate popup. This goes even further, separate parts of the statusbar each have their own distinct popup.

In a popup we can use every trick that can be used in normal menu's. So stuff like bitmaps, check marks, cascade menu's etc. are available. (I paid attention to the possibilities of menus in an earlier column. So for enhancing the pop-ups, read some of my earlier work in the e-Zine!)

So lets define a simple popup menu. This can be done with a simple editor by creating the following entry in the RC file. (Most compilers have a visual editor for creating these components.)

MENU POPUP1
BEGIN
	MENUITEM "Item ~1", 1001
	MENUITEM "Item ~2", 1002
	MENUITEM SEPARATOR
	MENUITEM "Item ~3", 1003
	MENUITEM "Item ~4", 1004
END

As you may notice there is no real difference in the way a popup is defined from a normal menu. The only difference is the way the menu component is used. Now that we've defined a popup, lets check out how to use it.

A popup should appear when the event of a right mouse click happens over a control. There are several messages that can be used to intercept a mouse click. A quick look at the messages reveals the following (we keep it narrowed down to the right button):

WM_BUTTON2CLICK
WM_BUTTON2DBLCLICK
WM_BUTTON2DOWN
WM_BUTTON2UP

In this case the WM_BUTTON2DBLCLICK can't be used since a popup is started on a single click. The other three could be used to initiate the popup. However, there is one problem. People can be right and left handed. OS/2 has always supported left as right handed people. In the mouse settings, this can be changed (left handed people will use the button1 to call a popup) . Besides that, in the mouse properties -> mappings page a user can set the key/button combinations that must produce a context-popup. This can even be mapped to a double-click. So we're back were we started. Of course the mouse settings can be queried from the system. If we know the settings we can check them an create a complex event handler that will check the events against the settings the user has made (in the worst case, left handed, SHIFT-ALT-CTRL double-click button1). This sounds like a lot of work, besides it isn't very elegant. We could be blunt and simply use the BUTTON2CLICK, but that would also be a rather crude solution. In most cases when there are a lot of system settings involved, OS/2 has the solution onboard. So let's check a little further.

Lucky us there also exists a WM_CONTEXTMENU message. This is a message that occurs when the user requests a popup menu. So this message should be turned on in the settings as the user entered them in the mappings page (it's very simple to test the effect of the WM_CONTEXTMENU message. Simply start the sample application you'll find linked to at the bottom of this article, then open the mouse settings notebook in the OS/2 System folder and go to the mappings page. Make a change to the popup section and test the changes in the sample application. The notebook doesn't have to be closed to make the changes effective. After this experiment, you can simply click the undo button and close the notebook and everything will be back to normal.)

Right now we're able to intercept the event that will occur when the user wants a popup. It isn't that simple that this will also invoke the popup. Invoking the popup has to be done in the code that is placed in the event handler for the WM_CONTEXTMENU message. To present a popup the API call WinPopupMenu has to be used. This function looks like this:

rc = WinPopupMenu(hwndParent, hwndOwner, hwndPopup, x, y, idItem, fs);

HWND     hwndParent;  //  Parent-window handle.
HWND     hwndOwner;   //  Owner-window handle.
HWND     hwndPopup;   //  Pop-up menu-window handle.
LONG     x;           //  x-coordinate of the pop-up menu position.
LONG     y;           //  y-coordinate of the pop-up menu position.
LONG     idItem;      //  Item identity.
ULONG    fs;          //  Options.
BOOL     rc;          //  Pop-up menu invoked indicator.

Let's take a look at the required parameters and how we can get hold of valid values for them. The first two parameters are the Parent and Owner window handles (In one of my earlier columns I talked about the difference between parent and owner windows.) In this case the window handle of the dialog will do. So the hwndDlg variable that is defined in the dialog procedure header will be sufficient.

The Popup window handle hwndMenu is a variable that will contain the menu handle of the popup after it is displayed. For this variable we have to supply an empty one, so we simply declare one in the event handler. The x and y coordinates of the popup define the left bottom corner of spot were the popup will occur. These coordinates are defined against the parent window, meaning the lower left corner of the parent window is the "0,0" coordinate. What we want is that the popup displays at the mouse position, so we have to get hold of the x and y coordinates of the mouse relative to the parent window. We could use the WinQueryPointerPos API, but that would give the pointer position on the desktop and we'd still have to recalculate the position in the parent window. This sounds complex, so it's not a good idea. If we reexamine the WM_CONTEXTMENU massage we'll see that the MP1 parameter contains a variable of the POINTS type (contains two short values that make up for an x and a y value). This Points variable contains the x and y value of the mouse pointer relative the left bottom of the window on which the popup event occurred. This is exactly what we need.

The next parameter is the item id in the popup that will appear directly under the mouse pointer, or is selected as a default. This is related to the last parameter the options.

The options dictate among other things how the idItem value has to be interpreted. The following items could be ordered together:

PU_POSITIONONITEM : Position the pop-up menu so that the item identified by the idItem parameter of the top-level menu specified by the hwndMenu parameter will lie directly under the x,y coordinates.

PU_HCONSTRAIN : Constrain the pop-up menu so that its width is wholly visible on the desktop (meaning the coordinates might be altered by OS/2 to make this possible.) PU_VCONSTRAIN does the same for the vertical display of the popup.

PU_MOUSEBUTTON1DOWN : The pop-up menu is initialized with mouse button 1 depressed. The pop-up is displayed as long as the button is depressed. Menu items can be selected by moving the cursor over the menu. PU_MOUSEBUTTON2DOWN, PU_MOUSEBUTTON3DOWN do the same for the other two mouse buttons.

PU_NONE : The pop-up menu is to be presented uninfluenced by the user interaction which caused it to be summoned. This is the default value.

PU_SELECTITEM : The item identified by idItem is to be selected. This is only valid if PU_NONE is also set. The following list shows the input device valid for interaction with the pop-up menu with each option:

PU_KEYBOARD : The keyboard.
PU_MOUSEBUTTON1 : Mouse button 1.
PU_MOUSEBUTTON2 : Mouse button 2.
PU_MOUSEBUTTON3 : Mouse button 3.

I guess now things are starting to dazzle. I'll put together the full API call in a moment. In the meantime take a minute to reflect the possibilities all these options give. The most common method of putting together a popup is first to make sure it is always fully visible. We can make sure this happens by using the horizontal en vertical restraining option PU_HCONSTRAIN and PU_VCONSTRAIN. Besides that we want the popup to be handled by the keyboard and a mouse button. This gives the following options line:

PU_HCONSTRAIN | PU_VCONSTRAIN | PU_MOUSEBUTTON1 | PU_KEYBOARD

Now we can put together the complete call to display the popup, the complete event handler will look like this:

case WM_CONTEXTMENU:
	{
	HWND hwndPopup;                                         // popuphandle
	hwndPopup = WinLoadMenu(hwndDlg, NULLHANDLE, POPUP1);   // load popup
	WinPopupMenu(hwndDlg,                                   // Start popup
			   hwndDlg,
			   hwndPopup,
			   SHORT1FROMMP(mp1),
			   SHORT2FROMMP(mp1),
			   0,
			   PU_HCONSTRAIN | PU_VCONSTRAIN | PU_MOUSEBUTTON1 | PU_KEYBOARD);
	}
return (MRFROMLONG(TRUE));

When one of the menu options is pressed a WM_COMMAND message will be sent to the owner. To prove this is happening, the sample of this month will contain a simple event handler for the WM_COMMAND message. Every item in the popup will create a beep of a certain frequency. So, this creates a firm foundation for what's coming: we now know how to create a popup and how to handle it when it's invoked. But at this moment we are only able to start a popup from the main window. It would be nice if every control in an application could have it's own popup. For this we need another technique called subclassing. This even gives the possibility to create a small help message for every control in an application. We'll take a look at subclassing and adding pop-ups to controls next month. For now, I'm going to enjoy my summer holiday which started yesterday.

If you want to play on your own, download the source and compiled sample for this month's column (21K).