MFC封装窗口

来源:互联网 发布:浏览器代理软件 编辑:程序博客网 时间:2024/06/06 02:12

这是从http://topic.csdn.net/u/20120817/16/56ee2da8-9f93-4ba9-8b22-53ecb93aa9b4.html?seed=1057171120&r=79434947#r_79434947刚刚领悟的,谢谢几位老师的指点,我把我的理解整理了一下,和大家分享,有错大家指出啦!

子类化与超类化心得

首先看Win32的窗口实现过程:

    #include <windows.h>

    LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam);

    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)

    {

            TCHAR lpszClassName[] = TEXT("MainWindow"); //窗口类名

            WNDCLASS wndclass;

            wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC//窗口类型为缺省类型ClassStyle

             wndclass.lpfnWndProc =WndProc//窗口处理函数为WndProc

            wndclass.cbClsExtra = 0; //窗口类无扩展

             wndclass.cbWndExtra = 0; //窗口实例无扩展

             wndclass.hInstance = hInstance//当前实例句柄

             wndclass.hIcon = (HICON)LoadImage(NULL,TEXT("..\\Win32-       

            Window\\res\\luckstone.ico"),IMAGE_ICON,0,0,LR_LOADFROMFILE);//使用图标

             wndclass.hCursor =LoadCursor(NULL, IDC_ARROW);  //窗口采用箭头光标

            wndclass.hbrBackground = NULL//窗口背景为白色

            wndclass.lpszMenuName = NULL;  //窗口中无菜单

            wndclass.lpszClassName = lpszClassName//窗口类名为'窗口实例'

            if (!RegisterClass(&wndclass)) //注册窗口,若失败,则发出声音

             {

                 MessageBeep(0);

                 return FALSE;

             }

             //创建窗口操作

             HWND hwnd;

             int nWidth=GetSystemMetrics(SM_CXSCREEN)-500;

            int nHeightGetSystemMetrics(SM_CYSCREEN)-500;

            TCHAR lpszTitle[] = TEXT("Win32窗口示例"); //窗口标题名

             hwnd = CreateWindow(lpszClassName,//窗口类名

                                                 lpszTitle//窗口实例的标题名

                                                 WS_SYSMENU|WS_CLIPCHILDREN | WS_CLIPSIBLINGS, //窗口的风格

                                                CW_USEDEFAULT,//窗口左上角x坐标缺省值(CW_USEDEFAULT)

                                                CW_USEDEFAULT,//窗口左上角y坐标缺省值(CW_USEDEFAULT)

                                                nWidth,//宽度缺省值(CW_USEDEFAULT)

                                                 nHeight,//高度缺省值(CW_USEDEFAULT)

                                                 NULL//无父窗口

                                                 NULL//主菜单

                                                 hInstance,//应用程序当前句柄

                                                 NULL); //不使用该值

             ShowWindow(hwndnCmdShow); //显示窗口

             UpdateWindow(hwnd); //绘制用户区

             MSG msg//消息结构

             while (GetMessage(&msgNULL, 0, 0)) //消息循环

             {

                  TranslateMessage(&msg);

                   DispatchMessage(&msg);

             }

             return msg.wParam//程序终止时,将信息返回操作系统

    }

    LRESULT CALLBACK WndProc(HWND hwnd,//接收消息的窗口句柄

                                                         UINT message//消息名(可以是自定义消息名)

                                                         WPARAM wParam,//消息参数(32)

                                                         LPARAM lParam//消息参数(16)

     ){//消息的组成:消息名称(UINT)+两个参数(WPARAMLPARAM)。参数用于确定消息的详细信息

             switch (message)

             {

                 case WM_CREATE:

                 break

                 case WM_PAINT://不要用空的WM_PAINT消息处理

                      ValidateRect(hwndNULL);

                 break;

                 case  WM_COMMAND:

                 break;

                 case WM_LBUTTONDOWN:

                 break;

                 case WM_DESTROY:

                      PostQuitMessage(0);//调用该函数发出WM_QUIT消息

                 break;

                 default//缺省消息处理函数

                 return DefWindowProc(hwndmessagewParamlParam);

             }

             return 0;

    }

     --------------------------------------------------------------------------------------

    windows系统中创建窗口的过程为:

    1.注册窗口类:创建描述窗口类型的WNDCLASS结构并向系统注册。

    2.CreateWindowEx以注册成功的窗口类创建窗口。

    3.开启消息循环。

     

    WNDCLASS

    WNDCLASS是一个由系统支持的结构,用来储存某一类窗口的信息。即窗口类型,简称窗口类(注意和MFC与C++中的类不是一个概念)。WNDCLASS中含有窗口类样式CS(即ClassStyle,是该类窗口的系统基础样式和CreateWindowEx中的特定窗口的附加样式不是同一概念)。

    WNDCLASS中含有窗口类(该类窗口)的类名,因为每次CreateWindowEx创建窗口都要用窗口类名做参数,所以注册窗口类是为了一次注册多次使用,否则每次创建都要传WNDCLASS参数给windows系统,windows系统也要为每一个窗体保存 一个WNDCLASS信息,开销太大。

     

    由此引出MFC的封装方式:DDX(Dialog Data Exchange 对话框数据交换)、消息映射、子类化、超类化。

    MFC的控件类都向系统注册一个窗口类(WNDCLASS 注意和MFC与C++中的类不是一个概念),无论通过资源管理器添加控件还是自己派生类,然后调用Create来创建控件,都是使用的MFC控件类注册的类(WNDCLASS 和前一个“类”不同,前者指代码层面的类,后者指WNDCLASS类型),即子类化。

    子类化 子类化(subclass 直译为 sub:改变,class:类型)。

         概念

         子类化和超类化都是对Windows控件来说的,先别跟代码级别所说的类扯上关系。
    子类化就是针对某一个已经存在的控件,然后你去通过修改这个控件的窗口函数指针(lpfnWndProc)来达到重新定义这个控件的行为。

         窗口类是窗口的模板(这里不是指C++中的类),窗口是窗口类的实例。窗口类和每个窗口实例都有自己的内部数据结构,窗口类的结构即WNDCLASS,窗口实例的数据结构未托管。Windows虽然没有公开窗口实例的数据结构,但提供了读写这些数据的API。例如:用GetClassLong和SetClassLong函数可以读写窗口类的数据;用GetWindowLong和SetWindowLong可以读写指定窗口实例的数据。使用这些接口,可以在运行时读取或修改窗口类或窗口实例的窗口过程地址。这些接口是子类化的实现基础。
         子类化的目的是在不修改现有代码的前提下,扩展现有窗口的功能。它的思路很简单,就是将窗口过程地址修改为一个新函数地址,新的窗口过程函数处理自己感兴趣的消息,将其它消息传递给原窗口过程。通过子类化,我们不需要现有窗口的源代码,就可以定制窗口功能。

           系统的类和消息映射

           首先你要明白:Window的控件的设计也是遵循了面向对象的思想的,但是这种思想跟现在的我们从代码级别的类和对象的关系是不同的。
    windows的内置的每一种控件都有一个所谓的“类”,数据结构类型就是WNDCLASS,而我们每次使用控件的时候真正通过CreateWindow返回的句柄就是所谓的“控件对象”。例如一个窗口上面有5个Button,这个5个Button的类都是相同的,即WDNCLASS.lpszClassName="BUTTON"的一个结构,而这5个Button就是这个BUTTON类的对象。

           因为WNDCLASS规定了窗口过程函数( wndclass.lpfnWndProc,即消息处理)所以属于同一个类(WNDCLASS)的实例都使用同一个窗口过程函数,但是如何使不同实例都有自己的消息处理呢,那么就要让每个控件都能修改lpfnWndProc,但是又不能够直接修改lpfnWndpro(因为在向系统注册WDNCLASS时,lpfnWndpro的框架已经固定了),因此要在lpfnWndpro中留出接口,这个接口就称为消息映射机制(MessageMap)。消息映射就是建立一个消息和函数的对应表,当收到消息时查找表,如果表中有相应的消息,就将消息交给相应的函数处理。消息映射类似C++的虚函数,消息会先检索到子类然后向父类检索,因此子类可以屏蔽一些消息,但是消息映射并不是虚函数表的实现,因为虚函数的开销和性能的限制:先说虚函数的运行时开销,一次整形加法(指针偏移)和一次指针引用,每个类型一个虚表,典型情况下小于32字节;每个对象若干个(大部分情况下是一个)虚表指针,典型情况下小于8字节 。虚函数是C++运行时多态特性中开销最小的,可在对性能有苛刻要求的场合,或者需要频繁调用,对性能影响较大的地方(比如每秒钟要调用上百次或更多的事件处理函数)就要考虑一下了!再说MFC消息映射通过宏定义实现,代码被翻译后实质是面向过程的switch—case语句,来判断识别消息,消除了对虚拟函数表的需要,减小了开销,也更快些。

           引入C++类

    为了从技术便于实现,采用了C++的类。

    1.用C++的类托管WNDCLASS和每个控件实例:

          如MFC类库手册提供的每个控件基类都托管一个独立的WNDCLASS(如CWnd/CButton都托管不同的类),即在这些控件基类中构建并注册WNDCLASS,因此确定了WDNCLASS.lpszClassName和wndclass.lpfnWndProc等。用于实现子类化(修改lpfnWndProc)的成员包括两部分:消息映射函数和消息相关虚函数。

         a.消息映射成员函数

    首先在类中要用DECLARE_MESSAGE_MAP()宏声明该类拥有消息映射表格,然后在类应用程序文件(.CPP)实现这一表格

    BEGIN_MESSAGE_MAP(CInheritClass,CBaseClass) //第一个宏是BEGIN_MESSAGE_MAP有两个参数,分别是拥有消息表格的类,及其父类。

        //{{AFX_MSG_MAP(CInheritClass)

            ON_COMMAND(ID_EDIT_COPY,OnEditCopy)  //第二个宏是ON_COMMAND,指定命令消息的处理函数名称。

            ………

        //}}AFX_MSG_MAP

    END_MESSAGE_MAP()    //第三个宏是END_MESSAGE_MAP()作为结尾符号。

    其中所有的系统消息都有规定的名称和格式(onCreate,onXXX...),自定义消息也应遵循特定规则。

           b.消息相关虚函数:

    除了消息映射函数,MFC还封装了一些消息处理相关的虚函数,有些比消息映射函数更底层,比如可以拦截源消息(preTranslateMessage prexxx等)。此外还有其他功能性函数(如create,getxxx,setxxx等)。

         总结
    1.window系统的类是WNDCLASS和窗口实例的信息结构和C++中的类不是同一概念。

    2.为了更好管理和实现,用C++的面向对象的思想托管控件类的结构(WNDCLASS)和控件实例的结构,由此引入C++的类。

    3.MFC提供的控件基类都托管一个控件类结构(WNDCLASS)和更细化的实例结构,之后无论用这个类创建实例,还是以此类继承再创建实例,都是用这一个WNDCLASS,而继承类所改变的只是之后创建的实例的结构,而不是WNDCLASS,因此叫做子类化(subclass 直译应为"修改类型"),所以说子类化子类化本质是修改窗口实例的结构,主要是lpfnWndProc,而不要追究是先设定结构再创建实例,还是先创建实例再修改结构,因为本质都是作用于实例而不是WNDCLASS。

    4.超类化(superclass)。超类化就是不仅修改MFC控件类的实例结构,而且修改了WNDCLASS。如果我们要创建一个自己的控件,那么就要构造WNDCLASS结构并向系统注册,然后构造更细化的实例结构,但这是相当复杂的,而超类化则是从现有MFC控件类继承属性,然后修改其WNDCLASS的WDNCLASS.lpszClassName为新的类名,以此注册窗口类,那么这个窗口类便是独立的窗口类,这个C++类就是一个控件基类了,因为它向系统注册了独有的结构。 比如CButton继承自CWnd,它之所以称为一个独立的控件类,是因为它修改了CWnd的WDNCLASS.lpszClassName并以此注册,即superclass。而一般我们从CWnd继承只是修改要创建的窗口的布局和过程即修改实例结构,而并未修改WDNCLASS和重新注册,所以是subclass。

    子类化举例:

    子类化可以分为实例子类化和全局子类化。实例子类化就是修改窗口实例的窗口过程地址,全局子类化就是修改窗口类(指继承的MFC类,而不是WNDCLASS,实质和实例子类化相同)的窗口过程地址。实例子类化只影响被修改的窗口。全局子类化会影响在修改之后,按照该窗口类创建的所有窗口。显然,全局子类化不会影响修改前已经创建的窗口。

    1. 实例子类化 ——默认创建

    a.在MFC窗口中从资源管理器里拖放一个按钮IDC_BUTTON1.选中该按钮实例,可在属性框中查看其属性,或者从.rc文件中看到类似如下代码:

    IDD_CDEMO_DIALOG DIALOGEX 0, 0, 320, 200
    STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
    EXSTYLE WS_EX_OVERLAPPEDWINDOW | WS_EX_APPWINDOW
    CAPTION "MFC控件Demo"
    FONT 9, "MS Shell Dlg", 0, 0, 0x1
    BEGIN
        DEFPUSHBUTTON   "确定",IDOK,209,179,50,14
        PUSHBUTTON      "取消",IDCANCEL,263,179,50,14
        PUSHBUTTON      "Button1",IDC_BUTTON1,18,17,50,14
    END

    .rc是可视化编程关联的资源文件,从属性框中修改的属性会在该文件记录,编译时用于默认的创建等操作的参数。

    b.从CButton继承创建一个类。

    源码中之所以在PreSubclassWindow()中设置属性,而不在onCreate中初始化,从函数名中Subclass也能看出。因为控件是从资源管理器里拖放的,所以不会执行子类化的类的create函数,因为其在子类化时已经创建好了。而子类化是通过SubclassWindow接口实现的所以无论从资源管理器里拖放还是调用类的create创建,SubclassWindow都会执行,因此PreSubclassWindow()是必然执行的。

    //RoundBtn.h头文件

    #pragma once

    class CRoundBtn : public CButton{

    DECLARE_DYNAMIC(CRoundBtn)

    public:

    CRoundBtn();

    virtual ~CRoundBtn();

    protected:

    DECLARE_MESSAGE_MAP()

    public:

    BOOL m_press;

    BOOL m_in;

    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

    afx_msg void OnLButtonDown(UINT nFlagsCPoint point);

    afx_msg void OnLButtonUp(UINT nFlagsCPoint point);

    afx_msg BOOL OnEraseBkgnd(CDCpDC);

    afx_msg void OnTimer(UINT_PTR nIDEvent);

    protected:

    virtual void PreSubclassWindow();

    };

    // RoundBtn.cpp : 实现文件

    #include "stdafx.h"

    #include "RoundBtn.h"

    #include "mmsystem.h"

    #pragma comment(lib"WINMM.LIB")

    IMPLEMENT_DYNAMIC(CRoundBtnCButton)

    CRoundBtn::CRoundBtn(){

    m_press=false;

    m_in=false;

    }

    CRoundBtn::~CRoundBtn(){}

    BEGIN_MESSAGE_MAP(CRoundBtnCButton)

    ON_WM_LBUTTONDOWN()

    ON_WM_LBUTTONUP()

    ON_WM_ERASEBKGND()

    ON_WM_TIMER()

    END_MESSAGE_MAP()

    void CRoundBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

    {

    // TODO:  添加您的代码以绘制指定项

    CRect rect;

    GetClientRect(rect);

    UINT state=lpDrawItemStruct->itemState;

    CRect focusrect(rect);

    focusrect.DeflateRect(2,2,2,2);

    //UINT state=lpDrawItemStruct->itemState;

    if(state&ODS_SELECTED){

    //AfxMessageBox("YES!");

    }else{

    if(m_in){

    CDC dcc;

    dcc.Attach(lpDrawItemStruct->hDC);

    dcc.SetBkMode(TRANSPARENT);

    CBrush brush;

    brush.CreateSolidBrush(RGB(0,100,100));

    dcc.SelectObject(brush);

    CPen pen(PS_SOLID,2,RGB(220,220,220));

    dcc.SelectObject(pen);

    HRGN rgn=CreateRoundRectRgn(0,0,rect.right,rect.bottom,rect.right/2,rect.bottom);

    SetWindowRgn(rgn,true);

    dcc.RoundRect(0,0,rect.right,rect.bottom,rect.right/2,rect.bottom);

    CString wtext;

    GetWindowText(wtext);

    dcc.DrawText(wtext,CRect(0,0,rect.right,rect.bottom),DT_CENTER|DT_VCENTER|DT_SINGLELINE);

    dcc.DeleteDC();

    //PlaySound((LPCWSTR)IDR_WAVE1,NULL,SND_ASYNC |SND_RESOURCE ) ;

    //AfxMessageBox("ok!!!!!!");

    }

    }if(m_press)

    {

    CDC dc;

    dc.Attach(lpDrawItemStruct->hDC);

    dc.SetBkMode(TRANSPARENT);

    CBrush brush;

    brush.CreateSolidBrush(RGB(0,200,200));

    dc.SelectObject(brush);

    CPen pen(PS_SOLID,2,RGB(255,100,0));

    dc.SelectObject(pen);

    HRGN rgn=CreateRoundRectRgn(0,0,rect.right,rect.bottom,rect.right/2,rect.bottom);

    SetWindowRgn(rgn,true);

    dc.RoundRect(0,0,rect.right,rect.bottom,rect.right/2,rect.bottom);

    dc.SetTextColor(RGB(255,100,0));

    CString text;

    GetWindowText(text);

    dc.DrawText(text,CRect(0,0,rect.right,rect.bottom),DT_CENTER|DT_VCENTER|DT_SINGLELINE);

    //PlaySound((LPCWSTR)IDR_WAVE2,NULL,SND_ASYNC |SND_RESOURCE ) ;

    dc.DeleteDC();

    }else{

    CDC dc;

    dc.Attach(lpDrawItemStruct->hDC);

    dc.SetBkMode(TRANSPARENT);

    CBrush brush;

    brush.CreateStockObject(NULL_BRUSH);

    dc.SelectObject(brush);

    CPen pen(PS_SOLID,2,RGB(220,220,220));

    dc.SelectObject(pen);

    HRGN rgn=CreateRoundRectRgn(0,0,rect.right,rect.bottom,rect.right/2,rect.bottom);

    SetWindowRgn(rgn,true);

    dc.RoundRect(0,0,rect.right,rect.bottom,rect.right/2,rect.bottom);

    dc.SetTextColor(RGB(0,0,0));

    CString text;

    GetWindowText(text);

    dc.DrawText(text,CRect(0,0,rect.right,rect.bottom),DT_CENTER|DT_VCENTER|DT_SINGLELINE);

    dc.DeleteDC();

    }

    }

    void CRoundBtn::OnLButtonDown(UINT nFlagsCPoint point)

    {

    // TODO: 在此添加消息处理程序代码和/或调用默认值

    m_press=true;

    CButton::OnLButtonDown(nFlagspoint);

    }

    void CRoundBtn::OnLButtonUp(UINT nFlagsCPoint point){

    // TODO: 在此添加消息处理程序代码和/或调用默认值

    m_press=false;

    CButton::OnLButtonUp(nFlagspoint);

    }

    BOOL CRoundBtn::OnEraseBkgnd(CDCpDC){

    // TODO: 在此添加消息处理程序代码和/或调用默认值

    return CButton::OnEraseBkgnd(pDC);

    }

    void CRoundBtn::PreSubclassWindow(){

    // TODO: 在此添加专用代码和/或调用基类

    ModifyStyle(0,BS_OWNERDRAW);

    SetTimer(0,10,NULL);

    CButton::PreSubclassWindow();

    }

    void CRoundBtn::OnTimer(UINT_PTR nIDEvent)

    {

    // TODO: 在此添加消息处理程序代码和/或调用默认值

    CPoint point;

    GetCursorPos(&point);

    CRect rect;

    GetWindowRect(rect);

    if(rect.PtInRect(point)) //判断鼠标是否在按钮上

    {

    if(m_in == TRUE//判断鼠标是移动到按钮上还是一直在按钮上

    goto END;

    else{

    m_in = TRUE;

    Invalidate(); //重绘按钮

    }

    }

    else{

    if(m_in == FALSE//判断鼠标是移动到按钮外还是一直在按钮外

    goto END;

    else{

    Invalidate(); //重绘按钮

    m_in = FALSE;

    }

    }

    END:CButton::OnTimer(nIDEvent);

    }

    c.创建类的变量与拖放的按钮关联

    在主框架类中声明类的变量CRoundBtn myButton; 在DoDataExchange 函数里添加 DDX_Control(pDX,IDC_BUTTON1,myButton);由此得到子类化的控件实例。

    用类声明的变量并不能称为控件实例,因为一般的控件构造时并未调用create或createwindow创建可视的窗口,所以有两种方法使其成为一个可视的实例:

    <1>将声明的变量与资源里拖放的控件(即由系统create的)关联。 <2>调用变量的create相关函数手动创建。

    d.运行程序可以看到该按钮包含系统提供的属性和子类化添加的属性。

    2.全局子类化 ——动态创建

    如上所述关联已拖放的控件实例指针对一个实例,所以叫实例子类化。而全局子类化当然是手动调用成员函数创建实例了,则用该类创建的实例都被子类化。

    但是拖放的控件系统提供了默认的创建属性,而手动创建则要手动添加属性。

    如同样用构建的类,在主框架中添加变量: CRoundBtn myButton2; 

    在需要的地方创建并显示:

     myButton2.Create(TEXT("动态创建"),WS_CHILD|WS_VISIBLE,CRect(CPoint(50,50),CSize(150,80)),this,1003);
     myButton2.ShowWindow(SW_SHOW);

    至于不知道的风格和参数可在.rc文件里查。

    子类化的位置:
    两种方法都会执行SubClassWindow,其差别是:主动调用Create函数是由MFC类来创建控件,MFC可以在控件的创建过程中完成子类化操作,从而实现响应WM_CREATE等消息;对话框资源上放置的控件是由对话框创建的,MFC对控件的子类化操作是在响应WM_INITDIALOG消息时进行的,此时所有控件都已创建完毕。