How do I? - Part 13

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

Hello. I had a number of reactions on the Win NT 4.0 server adventures I told last month. In the mean time I solved a number of these problems. A number of them due to reactions of you, the readers. I know, I know, this is about OS/2, but since a lot of choices in my environment are made for NT, it pays to have your favorite client connect in a satisfactory way. I haven't solved all the problems, especially the browsing of the NT machine keeps getting updated badly, but most of it is working. Here are two pointers that solved a lot for me.

1) NT allows upper and lower case characters in the login ID and the password. Warp uses basically UPPERCASE. To login, in NT the case isn't that important. However, to Browse it must match exactly. For this I created an ID in UPPERCASE with and UPPERCASE password. I experimented with this by logging in with lowercase. I could enter NT, but could not browse it!

2) You must allow OS/2 clients to browse explicitly. Use the Network settings→services→Server→select the Make Browser Broadcasts to LAN Manager 2.x clients at the bottom of the dialog page.

After this it still wouldn't work, but after the following questionable action the browser saw it all. I took a directory with a small name and content (DOS) created a share on it in NT and Warp saw it. After deleting this share, the C-DRIVE share became visible.

Luckily, this month I hadn't too much to do with NT and had some time to spend on OS/2 programming. I'm in the process of doing the Smalled 2.0 beta and again running into all kinds of interesting trouble. For example: I wanted to create a possibility to add a timestamp into a file or print one in the header. I found that simply using the country info didn't get the wanted results. Why, well you can make changes in the country notebook that are different from the default country settings. OS/2 will honour your settings! Of course I also wanted that. Nobody seemed to know how this worked. So I did a little hunting and discovered a wealth of information in the OS2.INI. I knew it was there, but didn't realize its full potential until now. So it will be very likely that I'll do a 'How do I' on this in the nearby future.

So what are we going to look at this month? Well, I think it's time to take a look at controls and how we can handle them. In the last year only one control, the button, was used. I guess the time is right for some deeper stuff. This might mean you have to read some of the older columns in order to understand the stuff I'm talking about this month. Anyway we'll take a global look at some controls and we'll revitalize our knowledge about some of the more global messages.

What is a control? The answer to this is essentially simple; it's a window. So all the stuff you usually see in a dialog like buttons, spin buttons, sliders, entry fields etc. are windows. This might come as a surprise, but when you think about it, it will have some logic. We know about a window that it's perfectly fitted for the event paradigm (see the first "How Do I Part 1" of Oct. 96). It will have a window procedure in which it can handle messages sent to it. So when clicking on a button, some messages are sent to this button's window procedure which will make it behave as a button. It will click down as you hold the mouse button and up if you release it. Besides that, we can make it generate other messages such as WM_COMMAND and send them to the main window procedure so that the button acts as a menu item. You might have guessed that a control will have its own set of messages that it can handle in its window procedure. We will see in a "How do I" in the very nearby future how we can modify the window procedures of controls and change them to fit personal taste (this technique is called subclassing).

For now we will not dive deep into control specific messages, but take a look at one that has to do with all controls; the WM_CONTROL message. This is a signaling message. A control will send a WM_CONTROL message to its owner that will notify the owner of a particular event. Hmm, what can we do with this? Well, we could modify the control or other controls based on that message. Some examples:

  • Generate a message in a status bar based on a WM_CONTROL change message from the MLE control. For example the line and column numbers.
  • Disable some controls when a particular check box is set
  • Autosave some settings based on a change message
  • Perform some checks. (does item exist etc.)
  • Handle an error message

The nice thing about it is that we can do this in the window procedure of the owner. This means we don't have to break into the control's own window procedure to do this! We'll look at some code in a minute, but let us first dissect the WM_CONTROL message.

When a control sends a message to its owner, the two message parameters mp1 and mp2 will contain the following information:

mp1
   USHORT id            // Control-window identity.
   USHORT usnotifycode  // Notify code.  specific for the control

mp2
   ULONG  ulcontrolspec // Control-specific information defined per control

The id. We saw when we were building menus that every menu item has its own id. This is the same for every control in a dialog or a window. Even every part of a window like the title bar, the max-min-close buttons etc. have ids. This is simply a short number by which the control is identified. It's advisable to give every control in a window its own id so that it's identifiable. With every dialog editor you can easily attach an id to a control. We can also hard code controls in a window with the WinCreateWindow function. In that case it's the id provided for this function.

The usnotifycode: This is a short that contains the notification a control wants to send. Every control has a set of shorts predefined which specify the notification. For example, when a change occurs in an MLE this code will contain the MLM_CHANGE value. When a spin button up arrow is clicked, this short will contain the SPBN_UPARROW value.

The ulcontrolspec: This value contains some specific information in relation to the usnotifycode. For example, what is the slider position.

The values attached to constant names as MLM_CHANGE and SPBN_UPARROW are predefined in the header files that come with your compiler or with the OS/2 toolkit.

One question may have popped up now. How the hell can I put two values in one message parameter? A message parameter is a long, so it will claim 4 bytes or 32 bits of memory space. This means we can store up to 32 1-bit pieces of information. Along this line, we should also be able to store 2 16-bit information pieces. A short is 16 bits, so we can store 2 shorts in one long. There are some handy macros defined to store information like this in a message parameter, or to get it from a parameter. To get a short from a long we can use the SHORT1FROMMP or SHORT2FROMMP macros. This will return the first or the second short from a message parameter. For example:

SHORT2FROMMP(mp1) will return the second short from mp1.

To fill message parameters we can use: MPFROM2SHORT. This will have the form MPFROM2SHORT(short1,short2).

Armed with this basic knowledge we can create a very simple dialog to see when WM_CONTROL messages are sent. To do this, we'll use the program of last month and modify it. I changed the menu so that it only contains an exit option. This will enable you to leave the application. Besides that, 3 controls are placed on the client area; two entry fields and an MLE control. The entry fields are grouped so that you can change between them by using the arrow buttons.

The idea behind this is to capture the WM_CONTROL messages that are generated by the entry fields and display some information about this in the MLE control. This way we might get some insight into what's happening. The choice fell for the entry field control because this one doesn't need a setup and has a relatively simple WM_CONTROL structure.

How do we handle this? Well, first we've got to intercept the WM_CONTROL message in the dialog's window procedure. This is easy enough, simply add a case statement for the WM_CONTROL message. Inside the handler for WM_CONTROL we like to build a text string that holds the wanted information to display in the MLE. For the handling of the string we'll use a standard C function; sprintf. To enable your compiler to use this function you must include the <stdio.h> file (see the header of the sample program).

First we'll capture which entry field sends the WM_CONTROL. This information is held in the first short of the WM_CONTROL message parameter1. With the following piece of code we can fill the string achControl with the text "Entry1" of "Entry2".

//------------------------------------------------------------------
// Determine which entry field send the control message  (short 1)
//------------------------------------------------------------------
switch (SHORT1FROMMP(mp1))
	{
	case ENTRYFIELD1: sprintf(achControl,"Entry1 "); break;
	case ENTRYFIELD2: sprintf(achControl,"Entry2 "); break;
	}

next we want to find out which notification message was sent. This can be done by looking at the second short from mp1. The following piece of code takes care of business.

//------------------------------------------------------------------
// Determine which notification was given  (short 2)
//------------------------------------------------------------------
switch (SHORT2FROMMP(mp1))
	{
	case EN_CHANGE:    sprintf(achNotify, "EN_CHANGE \n");    break;
	case EN_KILLFOCUS: sprintf(achNotify, "EN_KILLFOCUS \n"); break;
	case EN_MEMERROR:  sprintf(achNotify, "EN_MEMERROR \n");  break;
	case EN_OVERFLOW:  sprintf(achNotify, "EN_OVERFLOW \n");  break;
	case EN_SCROLL:    sprintf(achNotify, "EN_SCROLL \n");    break;
	case EN_SETFOCUS:  sprintf(achNotify, "EN_SETFOCUS \n");  break;
	//-------------------------------------------------------------
	// For undocumented stuff like EN_INSERTMODETOGGLE, EN_UPDATE etc.
	//-------------------------------------------------------------
	default: sprintf(achNotify, "%x \n", SHORT2FROMMP(mp1));  break;
	}

The first piece of code was easy enough. This one however, needs some explanation. This piece of code prints the notification message into a string called achNotify with a carriage return. I took all six documented notification messages for the entry field. These are:

EN_CHANGE: The content of the entry field control has changed, and the change is already showing.

EN_KILLFOCUS: The entry field control is losing the focus. This happens when you switch to the other entry field.

EN_MEMERROR: The entry field control cannot allocate the storage necessary to accommodate window text. Normally this message shouldn't occur. The size of the memory needed is, in our case, the default of 16 bytes. We'll look at a method to enlarge this space in the future.

EN_OVERFLOW: The entry field control cannot insert more text than the current text limit. In the case of the sample program this is 16 chars.

EN_SCROLL: The entry field control is about to scroll horizontally. You can try this by completely filling the entry field in the sample and walking through it with the arrow-keys.

EN_SETFOCUS: The entry field control is receiving the focus.

When I had implemented this, I found that sometimes there are notification messages that weren't part of the six I defined. So I added the last default line in the case statement. This line will be executed if the SHORT2FROMMP(mp1) isn't in the earlier defined 6 cases. We can conclude here that the Entry field is using some notification that isn't documented! So I started a search through the header files and indeed found some of the non-documented EN_ notification messages for the Entry field in PMWIN.H and some other header files. Among those are EN_INSERTMODETOGGLE and EN_UPDATE. For EN_UPDATE I can guess what it does. It's a sort of EN_CHANGE, but before the change is made on the screen. I haven't got the slightest clue what EN_INSERTMODETOGGLE does. Maybe someone can enlighten me?

Well, what's next? We've got to place the text in the MLE. As I mentioned before, It's possible to send a message to a control and to let it perform an action. We can use the WinSendMsg call for this, but then we've got to know the windowhandle of the control we're targeting. This is a problem, we don't know the handle of the MLE, only its id. There are several ways to get a grip on this. I prefer a macro that handles it. We can get the handle of a window if we know its id and the Parent. This is the WinWindowFromID function. This function is used by the WinSendDlgItemMsg macro, so I prefer that one. The following calls are equal:

  1. WinSendMsg(WinWindowFromID(hwndParent, id), ulMsg, mp1, mp2);
  2. WinSendDlgItemMsg(hwndDlg, id, ulMsg, mp1, mp2);

What message can we use to display text in an MLE? There are again several alternatives, but I like to use the MLM_INSERT in this case. For the MLM_INSERT, the mp2 is empty and the mp1 contains a null terminated string. So the text obtained earlier can be displayed in the MLE by the following code:

WinSendDlgItemMsg(hwndDlg, MLE1, MLM_INSERT, achControl, 0l);
WinSendDlgItemMsg(hwndDlg, MLE1, MLM_INSERT, achNotify, 0l);

One thing has to be taken care of. We don't want to display this text if it's triggered by an MLE WM_CONTROL message. (This would lead to a endless loop). So we check if it isn't generated by the MLE by checking mp1.

if (SHORT1FROMMP(mp1) != MLE1)

Well, it was somewhat difficult this month, but we need to know about WM_CONTROL before we can do some serious stuff. Feel free to grab the complete sample program and code (ZIP, 19.2k) from this month's column. Next month we'll dive into one control specifically to work our way to a first small application. I've got some ideas already, but anyone having an idea for a small easy applet is encouraged to mail me. I might change my original idea :-) For now, have a fruitful month.