【问题标题】:Handling windows messages from a subclass处理来自子类的 windows 消息
【发布时间】:2013-07-09 03:41:18
【问题描述】:

我将自定义窗口封装在一个带有静态 WndProc 函数的类中,以处理由它生成的消息。现在我有一个子类,它在消息处理过程中实现了与父类不同的一些功能。

例如在下面的代码中,子类中 WM_LBUTTONDOWN 中发生的情况与父类中发生的情况不同。

我考虑过多态性,但我认为它不起作用,因为从父类调用 ::SetWindowLongPtr() 并且传递给的“this”指针属于父类,如果我错了,请纠正我。

如果我错了,多态性在这种情况下会起作用,那么也有一些消息没有被父类处理,应该在子类中处理,并为此在父类中放置一个空的虚函数看起来并不干净,除了很难为窗口产生的每条消息放置一个空的虚函数,只是为了将来是否会被使用。

会有几个这样的子类,每个子类对某些消息的行为不同,但不是全部。

那么,我该怎么做呢。

parent.cpp

parent::parent()
{

    WNDCLASSEX wincl;

    wincl.hInstance         = hInstance;
    wincl.lpszClassName     = "parent";
    wincl.lpfnWndProc       = WndProc;
    wincl.style             = CS_BYTEALIGNWINDOW;
    wincl.cbSize            = sizeof (WNDCLASSEX);
    wincl.hIcon             = 0;
    wincl.hIconSm           = 0;
    wincl.hCursor           = ::LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName      = NULL;
    wincl.cbClsExtra        = 0;
    wincl.cbWndExtra        = 4;
    wincl.hbrBackground     = ::CreateSolidBrush( backgroundColor );

    ::RegisterClassEx ( &wincl );

    hwnd = ::CreateWindowEx ( 0, "parent", txt.c_str(), WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_CHILD, x, y, width, height, parent, 0, hInstance, 0 ) ;

    ::SetWindowLongPtr( hwnd , GWLP_USERDATA , ( LONG ) this ) ;

}



LRESULT CALLBACK parent::WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{


   view::parent* panel = reinterpret_cast < view::parent* > (  ::GetWindowLongPtr ( hwnd , GWLP_USERDATA )  );


   switch (message)
   {    


      case WM_CREATE:

          ::SendMessage ( hwnd, WM_SETFONT, ( WPARAM ) panel->hFont, ( LPARAM ) true );

          break ;



      case WM_COMMAND:


          return panel->command ( message, wParam, lParam );

          break ;



      case WM_LBUTTONDOWN:


          return panel->lButton ( message, wParam, lParam );

          break;



      case WM_RBUTTONDOWN:


          return panel->rButton ( message, wParam, lParam );

          break;



      case WM_ERASEBKGND:


          return 1;

          break;



      case WM_PAINT:


          return panel->paint ( );


          break ;



      default:


         return ::DefWindowProc (hwnd, message, wParam, lParam);


   }

    return 0 ;


};

谢谢。

【问题讨论】:

  • 一种常见的方法是创建虚拟或纯虚拟成员函数,例如virtual LRESULT onButtonDown(...) {}
  • @JoshGreifer 所以你是说多态性在这种情况下会起作用,即使传递给 ::SetWindowLongPtr() 的“this”指针属于父类?
  • 使parent::WndProc 虚拟化。派生类覆盖child::WndProc,处理他们想要覆盖的消息,对于那些他们不想覆盖的消息,将调用转发到parent::WndProcThis is a standard technique.
  • @StudentX:您的 WndProc 需要在使用之前检查 panel 是否为 NULL。 WM_CREATE 不是窗口接收的第一条消息,并且窗口在CreateWindowEx() 退出之前,在您的代码调用SetWindowLongPtr() 之前接收消息。更安全一点的方法是将this 指针传递给CreateWindowEx(),然后在WM_CREATE 处理程序中调用SetWindowLongPtr()。不过,您仍然需要考虑在WM_CREATE 之前收到的消息。
  • 当对某些东西的工作方式有疑问时(在本例中为虚拟调度和this 指针),您应该编写一个简单的测试程序,检查它的执行情况并了解它是如何工作的。不要一直猜测。

标签: c++ winapi


【解决方案1】:

试试这样的:

class parent
{
private:
    // ...
    static LRESULT CALLBACK WndProcCallback(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
protected:
    HWND m_hwnd;
    // ...
    virtual LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);
    virtual LRESULT DefWndProc(UINT message, WPARAM wParam, LPARAM lParam);
    virtual LRESULT command(WPARAM wParam, LPARAM lParam);
    virtual LRESULT lButtonDown(WPARAM wParam, LPARAM lParam);
    virtual LRESULT rButtonDown(WPARAM wParam, LPARAM lParam);
    virtual LRESULT paint();
    // ...
public:
    parent();
    virtual ~parent();
    // ...
};

parent::parent()
{
    WNDCLASSEX wincl = {0};

    wincl.hInstance         = hInstance;
    wincl.lpszClassName     = "parent";
    wincl.lpfnWndProc       = WndProcCallback;
    wincl.style             = CS_BYTEALIGNWINDOW;
    wincl.cbSize            = sizeof(WNDCLASSEX);
    wincl.hIcon             = 0;
    wincl.hIconSm           = 0;
    wincl.hCursor           = ::LoadCursor(NULL, IDC_ARROW);
    wincl.lpszMenuName      = NULL;
    wincl.cbClsExtra        = 0;
    wincl.cbWndExtra        = 4;
    wincl.hbrBackground     = ::CreateSolidBrush(backgroundColor);

    ::RegisterClassEx(&wincl);

    m_hwnd = NULL;
    ::CreateWindowEx(0, "parent", txt.c_str(), WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_CHILD, x, y, width, height, parent, 0, hInstance, this);
}

parent::~parent()
{
    if (m_hwnd)
        DestroyWindow(m_hwnd);
}

LRESULT CALLBACK parent::WndProcCallback(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    parent* panel;

    if (message == WM_CREATE)
    {
        CREATESTRUCT *cs = reinterpret_cast<CREATESTRUCT*>(lParam);
        panel = static_cast<parent*>(cs->lpCreateParams);
        panel->m_hwnd = hwnd;
        ::SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(panel));
    }
    else
       panel = reinterpret_cast<parent*>(::GetWindowLongPtr(hwnd, GWLP_USERDATA));

    if (panel)
        return panel->WndProc(message, wParam, lParam);

    return ::DefWindowProc(hwnd, message, wParam, lParam);
}

LRESULT parent::WndProc(UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
   {    
      case WM_CREATE:
          ::SendMessage(m_hwnd, WM_SETFONT, (WPARAM) hFont, TRUE);
          break ;

      case WM_COMMAND:
          return command(wParam, lParam);
          break ;

      case WM_LBUTTONDOWN:
          return lButtonDown(wParam, lParam);
          break;

      case WM_RBUTTONDOWN:
          return rButtonDown(wParam, lParam);
          break;

      case WM_ERASEBKGND:
          return 1;
          break;

      case WM_PAINT:
          return paint();
          break;
    }

    return DefWndProc(message, wParam, lParam);
}

LRESULT parent::DefWndProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    return ::DefWindowProc(m_hwnd, message, wParam, lParam);
}

LRESULT parent::command(WPARAM wParam, LPARAM lParam)
{
    return DefWndProc(WM_COMMAND, wParam, lParam);
}

LRESULT parent::lButtonDown(WPARAM wParam, LPARAM lParam)
{
    return DefWndProc(WM_LBUTTONDOWN, wParam, lParam);
}

LRESULT parent::rButtonDown(WPARAM wParam, LPARAM lParam)
{
    return DefWndProc(WM_RBUTTONDOWN, wParam, lParam);
}

LRESULT parent::paint()
{
    return 0;
}

这不仅包含多态性和封装,而且使WndProc() 本身为虚拟允许派生类覆盖任何接收到的消息(嗯,在WM_CREATE 之后接收到的任何消息)的行为,尤其是parent 具有的消息没有概念:

class child : public parent
{
protected:
    LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);
};

LRESULT child::WndProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_SOMETHING)
    {
        // ...
        return 0;
    }

    return parent::WndProc(message, wParam, lParam);
}

【讨论】:

  • 非常感谢,我已经弄清楚了,甚至改变了“this”指针传递给回调的方式,就像你做的小修改一样:P。不过我有一个问题,如果我使用 CREATESTRUCT 来检索指向 WM_CREATE 中的对象的指针,就像在你的示例中一样,那么它不起作用,所以我像在 blogs.msdn.com/b/oldnewthing/archive/2005/04/22/410773.aspx 中一样将 WM_CREATE 更改为 WM_NCCREATE 并且它可以工作,甚至 MSDN使用 WM_CREATE 那么为什么它对我不起作用? msdn.microsoft.com/en-us/library/ff381400%28VS.85%29.aspx
  • 准确定义“不起作用”。 WM_CREATEWM_NCCREATE 对我来说都很好,parent* 指针在两条消息的 CREATESTRUCT 结构中都是有效的。但是,我必须做出的一项更改是移动parent::m_hwnd 成员的分配,以便它在parent::WndProc() 内部可用,而CreateWindowEx() 仍在运行。我已经相应地更新了我的答案。
猜你喜欢
  • 1970-01-01
  • 2020-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多