Advanced Programming of Control
User-Defined Control
You can also register your own control class by calling RegisterWindowClass function, and create an instance of your control class. If the program will not use a certain user-defined control class any more, it should use UnregisterWindowClass function to unregister this user-defined control class. Please refer to "control class" of section 3.2.4 of this guide for the usage of the above two functions.
Subclassing of Control
Using the framework of a control class and control instance can not only improve the code reusability, but also extend the existing control class conveniently. For example, when you need to create an edit box which only allows to input digits, you can realize by overriding the existing edit box control class rather than write a new control class. In MiniGUI, this technique is called subclassing or window derived. Methods of subclassing have three types:
- The first one is to perform subclassing on the created control instance, and the result of subclassing only affects this control instance;
- The last one is to register a subclassing control class based on a certain control class, which will not affect the original control class. In Windows, this technique is also called super-subclassing.
In MiniGUI, the subclassing of a control is actually implemented by replacing the existing window procedure. The codes in List 6.1 create two subclassing edit boxes by subclassing: one only allows inputting digits, and the other only allows inputting letters:
List 6.1 Subclassing of control
#define IDC_CTRL1 100
#define IDC_CTRL2 110
#define IDC_CTRL3 120
#define IDC_CTRL4 130
#define MY_ES_DIGIT_ONLY 0x0001
#define MY_ES_ALPHA_ONLY 0x0002
static WNDPROC old_edit_proc;
static int RestrictedEditBox (HWND hwnd, int message, WPARAM wParam, LPARAM lParam)
{
if (message == MSG_CHAR) {
DWORD my_style = GetWindowAdditionalData (hwnd);
/* Determine the key-pressed type being shielded */
if ((my_style & MY_ES_DIGIT_ONLY) && (wParam < '0' || wParam > '9'))
return 0;
else if (my_style & MY_ES_ALPHA_ONLY)
if (!((wParam >= 'A' && wParam <= 'Z') || (wParam >= 'a' && wParam <= 'z')))
/* Receive the key-pressed message being shielded, and returns directly */
return 0;
}
/* Handle the other messages by the old window procedure */
return (*old_edit_proc) (hwnd, message, wParam, lParam);
}
static int ControlTestWinProc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case MSG_CREATE:
{
HWND hWnd1, hWnd2, hWnd3;
CreateWindow (CTRL_STATIC, "Digit-only box:", WS_CHILD|WS_VISIBLE|SS_RIGHT, 0,
10, 10, 180, 24, hWnd, 0);
hWnd1 = CreateWindow (CTRL_EDIT, "", WS_CHILD|WS_VISIBLE|WS_BORDER, IDC_CTRL1,
200, 10, 180, 24, hWnd, MY_ES_DIGIT_ONLY);
CreateWindow (CTRL_STATIC, "Alpha-only box:", WS_CHILD|WS_VISIBLE|SS_RIGHT, 0,
10, 40, 180, 24, hWnd, 0);
hWnd2 = CreateWindow (CTRL_EDIT, "", WS_CHILD|WS_BORDER|WS_VISIBLE, IDC_CTRL2,
200, 40, 180, 24, hWnd, MY_ES_ALPHA_ONLY);
CreateWindow (CTRL_STATIC, "Normal edit box:", WS_CHILD|WS_VISIBLE|SS_RIGHT, 0,
10, 70, 180, 24, hWnd, 0);
hWnd3 = CreateWindow (CTRL_EDIT, "", WS_CHILD|WS_BORDER|WS_VISIBLE, IDC_CTRL2,
200, 70, 180, 24, hWnd, MY_ES_ALPHA_ONLY);
CreateWindow ("button", "Close", WS_CHILD|BS_PUSHBUTTON|WS_VISIBLE, IDC_CTRL4,
100, 100, 60, 24, hWnd, 0);
/* Replace the window procedure of the edit box with the
* user-defined window procedure, and save the old window procedure. */
old_edit_proc = SetWindowCallbackProc (hWnd1, RestrictedEditBox);
SetWindowCallbackProc (hWnd2, RestrictedEditBox);
break;
}
}
return DefaultMainWinProc (hWnd, message, wParam, lParam);
}
Combined Use of Controls
We can also combine two different controls together to achieve a certain special effect. In fact, the predefined control class of the combo box is a typical one of combining controls. When we combine different controls, we can encapsulate and register the combined control to be a new control class, and can also use it directly without encapsulation.
To illustrate the method to combine controls more clearly, we can assume that we want to implement a time editor. This time editor displays the time in form of "08:05:30", and we need further add a method to edit the time neatly according the user¡¯s requirement. To meet this requirement, we combine the edit box and the spin box together which implement the following functions, respectively:
- The edit box displays the time in form of "HH:MM:SS".
- When the input focus is in the edit box, the user can not edit the time directly, but must control the time value where the caret is with the arrow keys and PageDown and PageUp keys. So we must subclass this edit box to catch the key-pressed in it and perform the appropriate handling.
- Place a spin box beside the edit box. The user can adjust the time element where the caret is to increase or decrease by clicking the spin box. To achieve this goal, we can use the function of the spin box, and set the handle of the target window to be the edit box.
Thus, the time editor can work normally. Partial codes of this program are listed in List 6.2, and please refer to timeeditor.c file of the sample program package of this guide for the complete source code. Fig 6.1 shows the running effect of the timeeditor.
List 6.2 Time Editor
/*
** $Id: timeeditor.c,v 1.2 2003/06/13 02:41:54 weiym Exp $
**
** Listing 6.2
**
** timeeditor.c: Sample program for MiniGUI Programming Guide
** A time editor, use SpinBox and Edit controls.
**
** Copyright (C) 2003~2005 Feynman Software.
**
** License: GPL
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/control.h>
#include <minigui/mgext.h>
#define IDC_EDIT 100
#define IDC_SPINBOX 110
/* The font used in the edit box. This program uses TrueType font to get a good effect */
static PLOGFONT timefont;
/* Save the old window procedure of the edit box */
static WNDPROC old_edit_proc;
/* Change the corresonding time value according the current position of the caret */
static void on_down_up (HWND hwnd, int offset)
{
char time [10];
int caretpos;
int hour, minute, second;
GetWindowText (hwnd, time, 8);
caretpos = SendMessage (hwnd, EM_GETCARETPOS, 0, 0);
hour = atoi (time);
minute = atoi (time + 3);
second = atoi (time + 6);
if (caretpos > 5) { /* change second */
/* At the second position */
second += offset;
if (second < 0)
second = 59;
if (second > 59)
second = 0;
}
else if (caretpos > 2) { /* change minute */
/* At the minite position */
minute += offset;
if (minute < 0)
minute = 59;
if (minute > 59)
minute = 0;
}
else { /* change hour */
/* At the hour position */
hour += offset;
if (hour < 0)
hour = 23;
if (hour > 23)
hour = 0;
}
/* Place the changed time string in the edit box */
sprintf (time, "%02d:%02d:%02d", hour, minute, second);
SetWindowText (hwnd, time);
/* Restore the caret position */
SendMessage (hwnd, EM_SETCARETPOS, 0, caretpos);
}
/* This is the subclassing window procedure of the edit box */
static int TimeEditBox (HWND hwnd, int message, WPARAM wParam, LPARAM lParam)
{
/* Handle the key-pressed message only.
* When the following keys are pressed down,
* call on_down_up function to change the time value */
if (message == MSG_KEYDOWN) {
switch (wParam) {
case SCANCODE_CURSORBLOCKUP:
on_down_up (hwnd, 1);
return 0;
case SCANCODE_CURSORBLOCKDOWN:
on_down_up (hwnd, -1);
return 0;
case SCANCODE_PAGEUP:
on_down_up (hwnd, 10);
return 0;
case SCANCODE_PAGEDOWN:
on_down_up (hwnd, -10);
return 0;
case SCANCODE_CURSORBLOCKLEFT:
case SCANCODE_CURSORBLOCKRIGHT:
break;
default:
return 0;
}
}
/* Ignore the following two messages.
* The user can only perform operation by the keys on it */
if (message == MSG_KEYUP || message == MSG_CHAR)
return 0;
return (*old_edit_proc) (hwnd, message, wParam, lParam);
}
static int TimeEditorWinProc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case MSG_CREATE:
{
HDC hdc;
HWND timeedit, spin;
SIZE size;
/* Create an prompt static control */
CreateWindow (CTRL_STATIC,
"This is a time editor.\n\n"
"Pressing , , , and keys"
" when the box has input focus will change the time.\n\n"
"You can also change the tiem by clicking the SpinBox.\n",
WS_CHILD | WS_VISIBLE | SS_LEFT,
IDC_STATIC,
10, 10, 380, 100, hWnd, 0);
/* Create the logical font used by the edit box */
timefont = CreateLogFont (NULL, "Arial", "ISO8859-1",
FONT_WEIGHT_BOOK, FONT_SLANT_ROMAN, FONT_SETWIDTH_NORMAL,
FONT_SPACING_CHARCELL, FONT_UNDERLINE_NONE, FONT_STRUCKOUT_NONE,
30, 0);
/* Calcute the size and width for outputing the time */
hdc = GetClientDC (hWnd);
SelectFont (hdc, timefont);
GetTextExtent (hdc, "00:00:00", -1, &size);
ReleaseDC (hdc);
/* Create the edit box according to the calcuted value */
timeedit = CreateWindow (CTRL_SLEDIT,
"00:00:00",
WS_CHILD | WS_VISIBLE | ES_BASELINE,
IDC_EDIT,
120, 120, size.cx + 4, size.cy + 4, hWnd, 0);
/* Set the font for the edit box */
SetWindowFont (timeedit, timefont);
/* Subclass edit box */
old_edit_proc = SetWindowCallbackProc (timeedit, TimeEditBox);
/* Create a spinbox control */
spin = CreateWindow (CTRL_SPINBOX,
"",
WS_CHILD | WS_VISIBLE,
IDC_SPINBOX,
120 + size.cx + 6, 120 + (size.cy - 14) / 2, 0, 0, hWnd, 0);
/*
* Set the target window of the spinbox control to be the edit box,
* So that when the user clicks the spinbox, it will emulate and send
* MSG_KEYDOWN message to the edit box
*/
SendMessage (spin, SPM_SETTARGET, 0, timeedit);
break;
}
case MSG_DESTROY:
DestroyAllControls (hWnd);
DestroyLogFont (timefont);
return 0;
case MSG_CLOSE:
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc (hWnd, message, wParam, lParam);
}
/* Following codes to create the main window are omitted */
Fig. 6.1 Running effect of time editor