A tutorial useful for those wanting to make intelligent GUI elements like dynamic buttons using the Windows Win32 API with child window and control ID processing.
This article is a follow on from the Dynamic User Interface Trick article which covers a general approach to maintaining a dynamic user interface. If the reader is not familiar with creating dynamic child controls, they should read that article first, as this borrows heavily on some of the techniques described in it.
The buttons need to be created with a call to CreateWindow, specifying "button" as the class, WS_CHILD and probably WS_PUSHBUTTON in the style parameter, with the wizard window (or dialog box) as the parent. The ID needs to be set dynamically, so that the window procedure associated with the wizard can process the WM_COMMAND notification messages correctly.
The initial window text (such as Previous, Next, Finish, Cancel etc.) can be set in the call to CreateWindow, but it might also need to be changed depending on the interaction with the user. There are 2 methods to get or set window text for child window controls. The first method requires a window handle to the button:
The last parameter of the GetWindowText function is the maximum number of characters we can cope with in the LPSTR. If we have not stored the result of the initial CreateWindow call, then we have to use GetDlgItem to obtain the window handle. An easier method is just to use the API calls:
In these, the second parameter is the child ID, as we would have used in the GetDlgItem call. Again, the last parameter of GetDlgItemText is the number of characters we can cope with. We can use these to create, for example, a Start/Stop button:
The ideal place to put this would be as part of the notification message processing in the wizard window procedure.
The notification that the user has clicked the button comes through a WM_COMMAND message sent to the parent, as if it were a menu item that had been selected. We can intercept this as in the Dynamic User Interface Trick article.
During the processing, we can alter the text, or send messages to other screen elements. In a wizard we might just need to move to the next screen, which may also entail enabling or disabling some of the elements.
There might be times when we want the button to be disabled but still visible. For example, on the first screen of a wizard, the Previous button is usually grayed out rather than not present, and there is also usually a Finish button in a similar state. This is also useful if, for example, we want to allow the user to click Finish before the end of the process - rather like the Excel Data Import Wizard does.
This is achieved by using the EnableWindow function:
The second parameter is either TRUE or FALSE, depending on whether we want the window to be enabled or not. For buttons, this causes the item to be grayed. If the button is oner-drawn, then processing the WM_ENABLE message that is sent to the button might be required.
To use this function to enable or disable a child item, where we have not stored the window handle, we could, of course, use the following:
Where the hWnd is the parent and nItem is the child ID. Note that, despite the name of the function, the parent does not have to be a dialog box, just a containing window where the child ID refers to an item that has been created with the style WS_CHILD, and the hParent parameter in CreateWindow set to hWnd.