窗口子类化二

尽管Windows系统提供了丰富的通用控件(如按钮,编辑框,滑动条等),但仍不可能满足我们实际应用中千差万别的需求,笔者在某项目的开发工作中就遇见了这样的问题。项目需要一个供用户输入表格数据的接口界面,要求只接收用户输入的数据信息,并可以利用键盘上的光标键移动输入位置以避免用户在键盘和鼠标之间的频繁切换。简单地使用Windows的编辑框控件不仅不能对输入字符进行有效过滤(如果给编辑框控件加上ES_NUMBER风格则只能接收0~9之间的数字而不能接收小数点正负号等需要的字符),而且无法移动控件。如果重起炉灶自己编程来实现,其工作量是相当可观的。为此,笔者经多次尝试,终于通过采用窗口子类化方法,很好地解决了上述问题。

窗口处理原理:

  应用程序为了登记一个窗口类,首先要填写好一个WNDCLASS结构,其中的结构参数lpfnWndProc就是该类窗口函数的地址,接着调用RegisterClass()函数向Windows系统申请登记这个窗口类。这时Windows会为其分配一块内存来存放该类的全部信息,这个内存块称为窗口类内存块。

  当应用程序要创建一个属于某一已登记窗口类的窗口时,Windows便为这个窗口分配一块内存,即窗口内存块,用来存放与该窗口有关的专用信息。这些信息一部分来自传递给窗口创建函数CreateWindow()或CreateWindowEx()的参数信息,另一部分则来自所属窗口类的窗口类内存块,其中参数lpfnWndProc便被Windows从窗口类内存块复制到为新创建窗口分配的窗口内存块中。当有消息被发送到这个窗口时,Windows检查该窗口内存块中的窗口函数地址(lpfnWndProc),并调用该地址上的函数来处理这些消息。

子类化原理:

  所谓窗口子类化,实际上就是改变窗口内存块中的有关参数。由于这种修改只涉及到一个窗口的窗口内存块,因此它不会影响到属于同一窗口类的其它窗口的功能和表现。窗口子类化中最常见的是修改窗口内存块中的窗口函数地址(lpfnWndProc),使其指向一个新的窗口函数,从而改变原窗口函数的处理方法,改进其功能。

子类化方法:

方法一:

  (1)编写子类化窗口函数。该函数必须为标准的窗口函数格式即:

  LRESULT CALLBACK SubClassWndProc ( HWND , UINT , WPARAM , LPARAM ) ;

  在这个函数中对感兴趣的消息进行处理,而把未处理或者需要原窗口函数进一步处理的消息传送给原窗口函数;

  (2)利用待子类化窗口的句柄hWnd,调用GetWindowLong ( hWnd , GWL_WNDPROC ) 函数获得原窗口函数的地址并保存起来;

     (3)调用SetWIndowLong ( hWnd , GWL_WNDPROC , SubClassWndProc ) 把窗口函数设置成子类化窗口函数,完成窗口子类化。

方法二:

      子类化的方法还有另外一种:

       使用 SubclassWindow()或是SubclassDlgItem(),方法很简单,直接MSDN。

方法三:

         但是通常我们都不用以上的两种方法,如果是用MFC + vc的话,我们可以直接在ClassWizard里创建一个类,这个类的子类是MFC的控件类。然后对需要特殊照顾的控件(例如为按钮贴图, 改变标签控件的标签头,背景等)关联一个控件变量,变量的类型是我们所创建的类就可以了。这时候就不能再用Subclasswindow()了。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/thinkingmyl/archive/2009/10/29/4744791.aspx

Windows是一个基于消息的系统,消息在Windows的对象之间进行着传递。子类化和Windows的钩子机制存在于消息系统之中,我们可以利用这些机制来操纵、修改甚至丢弃那些在操作系统或是进程中传递的消息,以求改变系统的一些行为。子类化技术用来截取窗口或控件之间的消息,当然是消息在到达目的窗口之前完成的操作。这些被截获的消息既可以保留也可以修改它们的状态,之后就继续发送到目的地。子类化技术实现了一些正常情况下无法实现的功能,试想鼠标右键单击TextBox,系统默认弹出Undo、Cut、Copy、Paste等菜单,我们就可以利用子类化技术来改变这个系统菜单。

    简单的说,子类化就是创建一个新的窗口消息处理过程,并将其插入到原先的默认窗口消息处理过程之前。子类化分为三类:实例子类化(instance subclassing)—从窗口或控件的单一实例截获消息,这种子类化技术最普遍;全局子类化(global subclassing)—能够截获从相同的窗口类创建出来的多个窗口或控件的消息;超类化(superclassing)—和全局子类化很类似,区别在于可以应用在新的窗口类上面。
  
首先,我们看看这个C++程序:
#include
using namespace std;
class Parent
{
public:
   void func { cout << "Parent" << endl; }
};
class Child : public Parent
{
public:
   void func { cout << "Child" << endl; }
};
void main()
{
   Parent p;
   Child c;
   p.func();
   c.func();
}
  现在我来解说一下。这段代码中我定义了两个C++类:父类和子类,并且子类是继承自父类的;它们有一个具有相同名称的成员函数func。在main函数中,我分别构造了父类和子类的对象,并调用了它们各自的成员函数func。结果如下:
Parent
Child
  简单说来,这段代码就是子类根据自己的需要改写了func成员函数。而Win32的子类化的原理也与此类似,只不过子类化实际上并没有像C++一样重载哪个函数,而是靠拦截Windows系统中的某些消息来自己进行处理罢了。举例来说,请大家看以下这段简单的窗口回调过程:
LRESULT CALLBACK ProcMain(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
  switch (Msg)
  {
   case WM_CLOSE:
       EndDialog(hDlg, 0);
       break;
   case WM_DESTROY:
       PostQuitMessage(0);
       break;
  }
   return 0;
}
  在这个回调之中,我手动处理了两个消息:在单击了“关闭”按钮(WM_CLOSE)的时候,我将对话框关闭(EndDialog);在对话框销毁(WM_DESTROY)的时候,我向系统消息队列中发送了退出的消息来完成结束工作(PostQuitMessage)。也就是说,如果把 WM_CLOSE的响应代码改成:
  case WM_CLOSE:
    ShowWindow(hDlg, SW_MINIMIZE);
    break;
  这样一来,这个对话框就会和MSN一样,在单击了“关闭”之后,就会完成最小化的工作了。那么,对于窗口过程已定义好的系统控件,将如何手动响应它的消息呢?
  我们可以用函数指针的办法,将我们感兴趣的消息拦截下来,处理完之后再让预定义的窗口过程处理。这个过程大致如下:
  WNDPROC OldProc;
  OldProc = (WNDPROC)SetWindowsLong(hWnd, GWL_WNDPROC, (LONG)NewProc);
  当然,这里的新窗口过程NewProc是预先由你实现好的。上述代码执行以后,系统在处理hWnd的窗口消息时,就会先进入你实现的NewProc回调过程,然后在处理过你感兴趣的消息之后,通过CallWindowProc函数和你预先保存的OldProc再次回到原来的回调过程中完成剩余的工作。
  以上就是窗口子类化的原理分析,下面我通过一个实例来实际解说如何对窗口进行子类化。当我们需要一个特殊的Edit来限制浮点数的输入,但是现有的Edit却并不能完成这项工作——因为它只能够单纯的限制大小写或者纯数字。就可以采用“子类化”。

  下面我开始按步骤完成对这两个窗口的子类化:
  第一步,在主窗口对话框初始化的时候,保存原有的窗口过程,并设置新的窗口过程。代码如下:
 case WM_INITDIALOG:
     EditProc = (WNDPROC)SetWindowLong(GetDlgItem(hDlg, IDC_EDIT), GWL_WNDPROC, (LONG)ProcFloat);
     StaticProc = (WNDPROC)SetWindowLong(GetDlgItem(hDlg, IDC_ST_HOMEPAGE), GWL_WNDPROC, (LONG)ProcLink);
     break;
  第二步,实现浮点编辑框的窗口过程:
LRESULT CALLBACK ProcFloat(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
   if (Msg == WM_CHAR && wParam != '.' && (wParam <= '0' || wParam >= '9') && wParam != VK_BACK)
   {
      MessageBeep(MB_OK);
      return 0;
  }
  else
      return CallWindowProc(EditProc, hWnd, Msg, wParam, lParam);
}
  这里需要解释的是,由于控件本身的需求,所以只需要拦截一个消息,就是接收字符的WM_CHAR。当用户输入的字符不是小数点、0~9以及退格键(注意不要少了退格键,否则你将会发现你的编辑框无法删除输入错误的数字)的时候,就发出一声声音以提示输入错误。至于其它的消息,则调用原有的回调函数进行处理。
       子类化的限制:因子类化是对已存在的某一窗口产生作用,所以其作用范围只有这一窗,又由于可能不清楚该类怎样使用额外的类和窗口字节,所以不能保证正确使用这些空间存储信息,最后,因窗口已存在,所以新的窗口过程永远不会接收到第一个WM_CREATE消息或其他以前的消息。子类化只适用于改变极少数窗口行为和属性时使用。

/

 

窗口子类化的作用

窗口子类化技术最大的特点就是能够截取Windows的消息。一旦用户自定义的窗口函数截取了传向原窗口函数的消息,就可以对被截取的消息进行如下处理:

将其传给原来的窗口函数。这是对大多数消息应该采取的措施,因为子类通常只对原来的窗口特性作少量的改动

截取该消息,阻止其向原窗口函数发送。

修改该消息,修改完毕以后再向原窗口函数发送。

Windows SDK提供了一些设计好的窗口类,如EDIT、LISTBOX、TREEVIEW等。通过截取这些通用窗口类的消息,用户程序可以为它们添加新的特性,改善其外观,扩充其功能。

子类化的优点主要体现在以下两个方面:首先,它不需要创建新的窗口类,不需要了解一个窗口的窗口过程。这在原来的窗口函数是由别人编写,而且创建过程不可见的情况下非常有用;其次,子类化比较容易实现,因为所有要做的工作仅仅就是写一个窗口函数

 

在VC中实现窗口子类化

上面介绍的子类化是从Windows本身的窗口函数概念来讲的,实际上属于SDK(Software Development Kit)编程的范畴,在MFC中情况有所不同。下面将分别描述在这两种情况下窗口子类化实现的方法。

 

VC中基于SDK编程的窗口子类化

VC中基于SDK编程的窗口子类化的基本步骤如下:

(1)       正常创建原始窗口,得到窗口的句柄。

(2)       调用GetWindowLong得到原来的窗口函数OldWndProc。

(3)       调用SetWindowLong设置新的窗口函数NewWndProc。

新的窗口函数的代码如下所示:

LRESULT NewWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

{

       if(message==WM_IcareIt)

       {

              //截取自己感兴趣的消息,作一些处理,达到改变特性的目的

   }

       //必要时可以调用原来的窗口函数,使被子类化的窗口仍具有原来的很多特性

   return CallWndowProc(OldWndProc,hWnd,message,wParam,lParam);

}

值得注意的是,在调用旧的窗口函数时,不能直接用OldWndProc(…),而必须用函数CallWndProc进行调用,否则会出现堆栈错误。

MFC编程中的窗口子类化

MFC窗口实际上已经是被子类化的窗口。所有的MFC窗口共享同一个窗口函数,由这个窗口函数根据窗口句柄,查找这个窗口对应的CWnd派生类实例,再通过消息映射这个窗口类的消息处理函数。鉴于以上原因,在MFC中要子类化一个窗口就比较容易了,因为你的任务只是编写一个新的MFC窗口类而不需要写一个窗口函数。

假如我们现在有一个对话框,里面有一个编辑控件,我们只希望在该控件中接受非数字字符输入,我们可以拦截WM_CHAR消息,在它的处理函数中忽略任何数字的输入。MFC编程中窗口子类化的具体实现步骤在下一节笔者将用一个简单的实例来加以说明。

 

VC中窗口子类化的应用举例

MFC为广大编程者提供了很多功能丰富的窗口类,如果能在这些通用窗口类的基础上进行子类化的话,将会给编程者带来很多便利。下面举一个例子来说明MFC编程中的子类化是多么的简单易行。该例完成上面提到的在编辑控件只接受非数字字符输入的功能。实现这个子类化的基本步骤和相关代码如下:

(1)利用AppWziard创建一个基于对话框的程序SubClassing。

(2)对MFC提供的标准的对话框中的控件进行修改,删除MFC提供的静态文本控件,添加自己的一个编辑控件,设置新控件的ID为IDC_EDIT。合理布置对话框上各控件的位置,使程序界面布局合理、美观。

(3)用ClassWizard从CEdit类派生一个新的窗口类,新窗口的窗口类叫CNoNumEdit。截取CNoNumEdit类的WM_CHAR消息,在OnChar函中完成忽略任何数字的输入的处理。实现代码如下:

void CNoNumEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

         TCHAR ch=nChar;

         if(ch>=_T('0')&&ch<=_T('9'))

        {

              AfxMessageBox(("请不要输入数字!"),MB_OK);

              //当输入数字字符时将被忽略,并显示警告信息

              return;

        }

        CEdit::OnChar(nChar, nRepCnt, nFlags);//输入为非数字字符时调用原处理函数

}

(4)在对话框窗口类CSubClassingDlg的定义中添加变量CNoNumEdit ed。在CSubClassingDlg::OnInitDialog()函数中调用CWnd类的成员函数SubClassWindow进行子类化。

ed.SubclassWindow(GetDlgItem(IDC_EDIT)->m_hWnd);

(5)  在对话框窗口类CsubClassing的OnDestroy中调用ed.UnSubClassWindow()执行窗口类的反子类化。

现在可以编译执行这个程序了,当用户输入数字字符时将会忽略该输入,并显示警告信息。

 

在Windows编程中,适当使用窗口子类化技术,可以很方便地达到改变一个窗口的特性的目的。当然子类化也存在其局限性。实际上,子类化的概念是针对一个已经创建的窗口来谈的,所以修改窗口函数是在窗口创建之后进行的,在窗口创建期间的消息无法捕获,也就无法处理。另外有些窗口的特性与窗口类本身的属性有关。比如如果一个窗口类没有CS_DBLCLKS属性的话,那么要想通过子类化这些窗口达到处理WM_LBUTTONDBLCLK消息的目的是无法实现的。对于子类化的以上局限性,可以通过超类化(SuperClassing)技术消除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值