微尘--KeepMoving

为了忘却的记忆
posts - 3, comments - 2, trackbacks - 0, articles - 13
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

摘要:本文主要介绍了如何通过窗口子类化技术来实现对编辑控件的限制输入。

  关键词:窗口,子类化,句柄, 消息。

  1.问题的提出

  尽管Windows系统提供了许多通用的控件如:Edit、ComboBox 、ListBox……等,有些情况下这些标准控件也无能为力。比如:在对学生成绩考评过程中需要一个供教
师输入评测等级的编辑控件,要求在编辑框内只能输入A、B、C、D四个等级,这样在编辑框内就禁止对其他字母或数字的操作。简单地使用Windows的编辑框控件是不能对输入字符进行有效过滤的,对于这类问题的解决,我们可以采用子类化的方法来实现编辑控件的限制输入。

  2.实现方法

  每个应用程序为了登记一个窗口类,首先要填写好一个WNDCLASS,其中的结构参数lpfnWndProc就是该类窗口函数的地址,接着调RegisterClass()函数向Windows系统申请登记这个窗口类。这时Windows会为其分配一块内存来存放该类的全部信息,这个内存块称为窗口类内存块。当应用程序要创建一个属于某一已登记窗口类的窗口时,Windows便为这个窗口分配一块内存,即窗口内存块,用来存放与该窗口有关的专用信息。这些信息一部分来自传递给窗口创建函数CreateWindow() 或CreateWindowEx()的参数信息,另一部分则来自所属窗口类的窗口类内存块,其中参数lpfnWndProc便被Windows从窗口类内存块复制到为新创建窗口分配的窗口内存块中。当有消息被发送到这个窗口时,Windows检查该窗口内存块中的窗口函数地址(lpfnWndProc),并调用该地址上的函数来处理这些消息。

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

  3. 实现过程

   首先利用MFC建立一个基于对话框的应用程序NewDialg; 在对话框中添加一个ID为IDC_DEIT1的编辑控件资源。

  并派生一个自己的类CNewEdit (它的基类为CEdit)。通过这个类实现对该编辑控件的限制输入。

  (1) 处理CNewEdit的消息函数OnChar:

  void CNewEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {

   TCHAR ch[20];

   GetWindowText(ch,20);

  //处理只能输入A,B,C,D的情况

  if (strlen(ch) == 1 && (nChar <= ‘D‘ && nChar >= ‘A‘)) return;

  if (nChar != ‘A‘ && nChar != ‘B‘ && nChar != ‘C‘ && nChar!= ‘D‘ ) return;

  CEdit::OnChar(nChar, nRepCnt, nFlags);

  }

  (2) 为CNewDialogDlg类中添加一个数据成员CNewEdit m_edit,并在CtestDlg::OnInitDialog( )中加入下面代码:

  m_edit.SubclassDlgItem(IDC_EDIT1,this);

   m_edit.SetWindowText("<请输入A、B、C、D>"); //提示可以输入的内容

  (3)处理ID为IDC_EDIT1的控件向对话框发送的通知消息:EN_SETFOCUS:

  void CNewDialogDlg::OnSetfocusEdit1( ) {

  // TODO: Add your control notification handler code here

   m_edit.SetWindowText("");

   m_edit.SetFocus( );}

 

  4 过程分析

  下面我们看一下m_edit如何控制程序中资源编号为:IDC_EDIT1的控件的。

  大家都知道,控制Windows窗口、控件、资源……都是通过它们的句柄来实现,如HHANDLE、HWND、HDC都是句柄,它表现为一个32位长整形数据,存放于Windows中的特定区域,我们可以把它理解为指向我们想控制的窗口、控件、资源的索引,有了它,我们就可以控制我们想要控制的对象。

  那么CNewEdit的数据成员m_edit要想控制IDC_EDIT1,也要通过它的句柄,这就要通过SubclassDlgItem函数来实现。

  BOOL CWnd::SubclassDlgItem(UINT nID, CWnd* pParent){

  ASSERT(pParent != NULL);

  ASSERT(::IsWindow(pParent->m_hWnd));

  // check for normal dialog control first

  HWND hWndControl = ::GetDlgItem(pParent->m_hWnd, nID);

  if (hWndControl != NULL)

   return SubclassWindow(hWndControl);

  ……

  }

  SubclassDlgItem函数是CWnd的一个成员函数,它开始时对传入的父窗口做些检查,然后就是先用hWndControl得到我们IDC_EDIT1控件的句柄,再调用SubclassWindow函数,这个函数是实现的关键。

  BOOL CWnd::SubclassWindow(HWND hWnd){

  if (!Attach(hWnd))

   return FALSE;

  ……

  WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxGetAfxWndProc());

  ……

  }

  在这个函数中,首先调用了Attach函数,将hwand这个窗口句柄与一个窗口对象连接,在Attach函数中,有重要的一句: pMap->SetPermanent(m_hWnd = hWndNew, this); 显然只要把窗口的句柄保存下来,就可以在系统中唯一地指定一个窗口,然后对该窗口进行操作。在Attach 函数中把IDC_EDIT1 的句柄保存在了CnewEdit的成员变量m_hWnd 中,那么在m_edit.SetWindowText("<请输入A、B、C>")中,正是通过这个数据成员m_hWnd实现对IDC_EDIT1控制的:

  void CWnd::SetWindowText(LPCTSTR lpszString){

  ASSERT(::IsWindow(m_hWnd));

  if (m_pCtrlSite == NULL)  

  ::SetWindowText(m_hWnd, lpszString);

  else    

  m_pCtrlSite->SetWindowText(lpszString);

  }

  虽然通过句柄实现了m_edit对IDC_EDIT1的控制,对CNewEdit的WM_CHAR的处理是还要通过SubclassWindow函数来实现,在SubclassWindow中有这样一句:

  WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxGetAfxWndProc());

  其中AfxGetAfxWndProc()是我们自己的窗口处理函数,在其中处理过我们感兴趣的消息后,通过返回的原窗口处理函数指针oldWndProc来把其它消息按标准方法处理掉,这样当程序收到发给Edit的WM_CHAR时,本应调用EDIT标准窗口处理函数,现在被改为调用LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)了,然后WM_CHAR消息进行一系列的过程,最终成功到达我们的处理函数CNewEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags), 实现对编辑控件的限制输入。

 

  参考文献:

  [1] aaaaaaaaaawww.msdn.com

  [2] 杨晓鹏 《Visual C++ 7.0 实用编程技术》 北京 中国水利水电出版社。


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理