当前位置:C++技术网 > 精选软件 > 窗口子类化如何实现切换多个子窗口控件的焦点

窗口子类化如何实现切换多个子窗口控件的焦点

更新时间:2016-02-05 00:24:51浏览次数:1+次

    在对话框程序中,我们知道,我们按下Tab键,就可以将焦点在各个控件之间切换。如果我们要输入用户名和密码,我们就需要输完用户名后按Tab键快速将焦点切换到密码框。而这个焦点的切换,在对话框中,都是为我们提供好的。
    然而,如果你不能使用对话框的时候,也要切换焦点,怎么办呢?如果不能使用对话框,那么自己动手嘛。
    我们先创建三个按钮,即调用三次CreateWindow函数实现。我们使用button系统窗口类来创建,得到标准的按钮控件。为了后面方便使用,我们将三个按钮控件的窗口句柄存储在静态的窗口句柄变量中,当然,我们也需要指定三个不同的窗口ID。虽然这个ID我们这里不用,但是三个ID必须不能一样哦。
    然后我们就要在窗口的键盘按下消息中进行响应。当按下Tab键时,就来切换焦点。我们用一个静态变量i,然后每一次Tab键按下后,递增i的值。然后对i值用4取模,这样就可以得到4中情况的轮流。我们分别调用SetFocus来将焦点设置到对应的按钮或者父窗口上。这里要用到按钮的窗口句柄,所以我们在WM_CREATE消息中创建按钮控件时就将按钮控件的窗口句柄存储起来了。
    然后在窗口的WM_COMMAND消息中,可以在按钮单击后,将焦点设置到按钮上。因为WPARAM参数就是按钮控件的窗口句柄,所以直接就可以设置了。
    这些做完后,你或许会满心满意的运行了程序,然后按下Tab键,竟然发现当焦点切换到按钮之后,焦点就无法再切换了。问题出在哪呢??
    有焦点的窗口才具有键盘输入焦点。当焦点切换到按钮之后,那么键盘输入都流入了按钮控件了。默认情况下,按钮只接受空格按键的输入,就如同单击了按钮一样。其他键盘按键都忽略了。这样就导致了Tab键对于按钮来讲是没有用的,所以就无法切换焦点了。因为默认的按钮窗口过程根本就不鸟Tab键。那怎么办呢?
    如果在按钮接受到Tab键之后,能够将这个消息告诉父窗口,那不就解决了。然而,按钮的窗口过程是系统提供的,如何来修改呢?系统不会让我们来修改按钮窗口过程的。不过,系统提供了另一种处理控件的消息的方式,即窗口子类化。
    窗口子类化也就是提供一个窗口过程,然后就可以在这个窗口过程里处理你想要处理的消息,不处理的消息,再发回给控件的默认的窗口过程处理即可。
    窗口子类化实际上就是修改窗口的窗口过程。我们使用SetWindowLong,传入GWL_WNDPROC,然后传入新的窗口过程的函数名即可。而这个新的窗口过程和WinMain函数的的窗口过程是一样的。所以,你也不用太紧张。将窗口过程函数声明复制一下,然后改一个名称即可。
    对每一个窗口都要修改窗口过程,这样可以让每一个按钮控件都可以将Tab键按下消息发送给父窗口。在按钮控件的子类化窗口过程(我们提供的)中,我们处理消息和正常的窗口过程是一样一样的。所以,你也可以放心了,不用担心有什么规定,不用担心不会写代码了。
    我们在子类化窗口过程中,处理WM_KEYDOWN消息,这个消息就是按钮有焦点时键盘按下了Tab键得到的。我们按钮控件的任务就是将这个消息再转发给父窗口,即可完成任务。焦点的切换还是在父窗口的窗口过程完成。因为父窗口拥有所有子窗口的句柄,可以方便操作。处理完后,按钮控件就完成了任务。
    按钮的其他消息,就不用我们处理了。但是我们要记住,我们不处理的消息,一定要交给原先的按钮窗口过程处理。不然谁来维持按钮的形状和各种动作的默认响应呢?如果其他消息不转交给按钮窗口过程处理,那么你看到的按钮就是一片白,和默认的客户区的白色混为一体了。而默认的按钮控件窗口过程,就是要绘制一个立体的按钮,单击时有按下效果等等。而调用原先的窗口过程,使用CallWindowProc函数。我们这里不能使用默认的窗口过程,因为这个过程就不会给我们绘制按钮。
    CallWindowProc函数的第一个参数就是窗口过程函数名。我们这里就要按钮控件的默认窗口过程函数名称。我们如何得到呢?在修改窗口过程的时候,SetWindowLong会返回之前的窗口过程的函数地址。函数名不就是函数地址嘛。那我们事先将按钮的窗口过程函数地址存储起来,在子类化的窗口过程中,就传给CallWindowProc函数了。

    那么整个过程,就讲解完毕了。下面是实现的效果图:

用Tab键在控件之间以及窗口之间切换焦点

【用Tab键在控件之间以及窗口之间切换焦点】
    下面是完整的代码:
#include "windows.h"
#include <tchar.h>
TCHAR txt[]=_T("C++技术网:修改窗口背景颜色");
// - 项目是Unicode字符集
HWND hwndPa;//父窗口句柄,便于子窗口发送消息,设置为全局变量
WNDPROC wndProc;//窗口过程类型变量,用于存储窗口之前的窗口过程
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK BtnProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    //按钮控件子类化的窗口过程,对按钮的一些事件进行处理
    switch(message)
    {
    case WM_KEYDOWN:
        if (wParam==VK_TAB)//子窗口将Tab键返回给父窗口
        {
            SendMessage(hwndPa,message,wParam,lParam);//向父窗口发送Tab键单击消息
            return 0;//只将Tab键的单击消息发送给父窗口,转换为父窗口的Tab键单击
        }
    default:
        break;//跳出到默认处理
    }
    return CallWindowProc(wndProc,hwnd, message, wParam, lParam);//调用按钮控件的窗口过程,这样就支持按钮的各种特性
}
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
    static HWND hChild1,hChild2,hChild3;
    static int i=-1;
    switch (message)
    {
        case WM_CREATE:
            hChild1 = CreateWindow(_T("button"),_T("1"),WS_CHILD|WS_VISIBLE,0,0,100,50,hwnd,(HMENU)1,(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),0);
            hChild2 = CreateWindow(_T("button"),_T("2"),WS_CHILD|WS_VISIBLE,0,50,100,50,hwnd,(HMENU)2,(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),0);
            hChild3 = CreateWindow(_T("button"),_T("3"),WS_CHILD|WS_VISIBLE,0,100,100,50,hwnd,(HMENU)3,(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),0);
            wndProc = (WNDPROC)SetWindowLong(hChild1,GWL_WNDPROC,(LONG)BtnProc);//子类化按钮1,得到按钮的原先系统的按钮窗口过程
            SetWindowLong(hChild2,GWL_WNDPROC,(LONG)BtnProc);//子类化按钮2,与按钮1的的按钮窗口过程一样的,所以不需要再获取了
            SetWindowLong(hChild3,GWL_WNDPROC,(LONG)BtnProc);//子类化按钮3,与按钮1的的按钮窗口过程一样的,所以不需要再获取了
            break;
        case WM_KEYDOWN://父窗口统一处理Tab键,不管焦点在不在父窗口,父窗口都可以处理了。
            if (wParam==VK_TAB)
            {
                if(i%4==0)//父窗口得到焦点
                {
                    SetFocus(hwnd);
                }
                else if(i%4==1)//按钮1得到焦点
                {
                    SetFocus(hChild1);
                }
                else if(i%4==2)//按钮2得到焦点
                {
                    SetFocus(hChild2);
                }
                else if(i%4==3)//按钮3得到焦点
                {
                    SetFocus(hChild3);
                }
                i++;
            }
            break;
        case WM_COMMAND:
            {
                //单击按钮将焦点设置到按钮上
                 SetFocus((HWND)lParam);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
        break;//跳出到默认处理
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow)
{
    TCHAR ClassName[] = _T("MyClass");
    TCHAR title1[] = _T("C++技术网http://www.cjjjs.com");
    WNDCLASS wndClass;
    wndClass.cbClsExtra=0;
    wndClass.cbWndExtra=0;
    wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
    wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wndClass.hInstance = hInstance;
    wndClass.lpfnWndProc = WinProc;
    wndClass.lpszClassName = ClassName;
    wndClass.lpszMenuName=NULL;
    wndClass.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;

    if(!RegisterClass(&wndClass))return 0;
    hwndPa = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,10,100,600,400,NULL,NULL,hInstance,NULL);
    ShowWindow(hwndPa,SW_SHOWNORMAL);

    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}