Files
minigui-docs/programming-guide-zh/MiniGUIProgGuidePart1Chapter05-zh.md
2022-11-21 22:08:34 +00:00

12 KiB
Raw Permalink Blame History

控件高级编程

1 自定义控件

用户也可以通过 RegisterWindowClass 函数注册自己的控件类,并建立该控件类的控件实例。如果程序不再使用某个自定义的控件类,则应该使用 UnRegisterWindowClass 函数注销自定义的控件类。关于上述两个函数的用法,可参阅本指南 3.2.6 节“控件类”。

2 控件的子类化

采用控件类和控件实例的结构不仅可以提高代码的可重用性而且还可以方便地对已有控件类进行扩展。比如在需要建立一个只允许输入数字的编辑框时就可以通过重载已有编辑框控件类而实现而不需要重新编写一个新的控件类。在 MiniGUI 中这种技术称为子类化或者窗口派生。子类化的方法有三种

  • 一种是对已经建立的控件实例进行子类化,子类化的结果只会影响这一个控件实例。
  • 一种是对某个控件类进行子类化,将影响其后创建的所有该控件类的控件实例。
  • 最后一种是在某个控件类的基础上新注册一个子类化的控件类,不会影响原有控件类。在 Windows 中,这种技术又称为超类化。

在 MiniGUI 中控件的子类化实际是通过替换已有的窗口过程实现的。清单 1 中的代码就通过控件类创建了两个子类化的编辑框,一个只能输入数字,而另一个只能输入字母:

清单 1 控件的子类化

#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);
                /* 确定被屏蔽的按键类型 */
                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')))
                /* 收到被屏蔽的按键消息,直接返回 */
                return 0;
        }
        /* 由老的窗口过程处理其余消息 */
        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);
                        /* 用自定义的窗口过程替换编辑框的窗口过程,并保存老的窗口过程。*/
                        old_edit_proc = SetWindowCallbackProc (hWnd1, RestrictedEditBox);
                        SetWindowCallbackProc (hWnd2, RestrictedEditBox);
                        break;
                }
                ......
        }
        return DefaultMainWinProc (hWnd, message, wParam, lParam);
}

3 控件的组合使用

我们可以将两个不同的控件组合在一起使用,以达到某种特殊效果。其实,组合框这种预定义控件类就属于组合使用控件的典型。我们在组合不同控件时,可以将组合后的控件封装并注册为新的控件类,也可以不作封装而直接使用。

为了更好地说明组合使用控件的方法假定我们要完成一个时间编辑器。这个时间编辑器以“08:05:30”的形式显示时间根据用户需求我们还要添加一种灵活编辑时间的方法。为了满足这种需求我们可以将编辑框和旋钮框组合起来使用它们分别实现如下功能

  • 编辑框中以“HH:MM:SS”的形式显示时间。
  • 当输入焦点位于编辑框中时,用户不能直接编辑时间,而必须以光标键和 PageDown 及 PageUp 键来控制光标所在位置的时间单元值。为此,我们必须将该编辑框子类化,以捕获输入其中的按键,并做适当处理。
  • 编辑框旁边安置一个旋钮控件,用户单击旋钮控件即可对光标所在的时间单元进行调整,增加或者减小。为实现这一目的,我们可以利用旋钮控件的功能,将其目标窗口句柄设置为编辑框。

这样,我们的时间编辑器就能正常工作了。该程序的部分代码见 清单 2,完整源代码可见本指南示例程序包 mg-samples 中的 timeeditor.c 文件。图 1 给出了时间编辑器的运行效果。

清单 2 时间编辑器

#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

/* 用于编辑框的字体。为了取得较好效果,本程序使用了 TrueType 字体 */
static PLOGFONT timefont;

/* 保存老的编辑框窗口过程 */
static WNDPROC old_edit_proc;

/* 本函数根据当前插入符的位置,修改相应的时间单元值 */
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 */
                /* 在秒的位置 */
                second += offset;
                if (second < 0)
                second = 59;
                if (second > 59)
                second = 0;
        }
        else if (caretpos > 2) { /* change minute */
                /* 在分的位置 */
                minute += offset;
                if (minute < 0)
                minute = 59;
                if (minute > 59)
                minute = 0;
        }
        else { /* change hour */
                /* 在时的位置 */
                hour += offset;
                if (hour < 0)
                hour = 23;
                if (hour > 23)
                hour = 0;
        }
        
        /* 将修改后的时间字符串置于编辑框 */
        sprintf (time, "%02d:%02d:%02d", hour, minute, second);
        SetWindowText (hwnd, time);
        
        /* 恢复插入符位置 */
        SendMessage (hwnd, EM_SETCARETPOS, 0, caretpos);
}

/* 这是编辑框的子类化窗口过程函数 */
static int TimeEditBox (HWND hwnd, int message, WPARAM wParam, LPARAM lParam)
{
        /* 只处理按键消息。下面这些键按下时,调用 on_down_up 函数修改时间值 */
        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;
                }
        }
        
        /* 忽略下面两个消息,用户只能通过上面的按键进行操作 */
        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:
                {
                        HWND hwnd;
                        HDC hdc;
                        HWND timeedit, spin;
                        SIZE size;
                        
                        /* 建立说明性静态框 */
                        hwnd = CreateWindow (CTRL_STATIC, 
                        "This is a time editor.\n\n"
                        "Pressing <Down-Arrow>, <Up-Arrow>, <PgDn>, and <PgUp> keys"
                        " when the box has input focus will change the time.\n\n"
                        "You can also change the time by clicking the SpinBox.\n",
                        WS_CHILD | WS_VISIBLE | SS_LEFT, 
                        IDC_STATIC, 
                        10, 10, 220, 200, hWnd, 0);
                        
                        /* 创建编辑框使用的逻辑字体 */
                        timefont = CreateLogFont (NULL, "Arial", "ISO8859-1", 
                        FONT_WEIGHT_BOOK, FONT_SLANT_ROMAN, FONT_FLIP_NIL,
                        FONT_OTHER_NIL, FONT_UNDERLINE_NONE, FONT_STRUCKOUT_NONE, 
                        30, 0);
                        
                        /* 计算输出时间所用的大小和宽度 */
                        hdc = GetClientDC (hWnd);
                        SelectFont (hdc, timefont);
                        GetTextExtent (hdc, "00:00:00", -1, &size);
                        ReleaseDC (hdc);
                        
                        /* 按照计算出的值创建编辑框窗口 */
                        timeedit = CreateWindow (CTRL_SLEDIT, 
                        "00:00:00", 
                        WS_CHILD | WS_VISIBLE | ES_BASELINE, 
                        IDC_EDIT, 
                        40, 220, size.cx + 4, size.cy + 4, hWnd, 0);
                        
                        /* 设置编辑框字体 */
                        SetWindowFont (timeedit, timefont);
                        
                        /* 子类化编辑框 */
                        old_edit_proc = SetWindowCallbackProc (timeedit, TimeEditBox);
                        
                        /* 创建旋钮控件 */
                        spin = CreateWindow (CTRL_SPINBOX, 
                        "", 
                        WS_CHILD | WS_VISIBLE, 
                        IDC_SPINBOX, 
                        40 + size.cx + 6, 220 + (size.cy - 14) / 2, 0, 0, hWnd, 0);
                        
                        /* 
                        * 将旋钮控件的目标窗口设置为编辑框,这样,
                        * 当用户单击旋钮时,将模拟 MSG_KEYDOWN 消息
                        * 并发送到编辑框。
                        */
                        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);
}

/* 以下创建主窗口的代码从略 */

时间编辑器的运行效果

图 1 时间编辑器的运行效果