silekey 发表于 2007-10-3 12:21:42

[原创]好久没用MFC了,就重新复习了一下,突然在发现Windows

现在发现Windows消息有点奇怪喔,以前总觉得用VC怪怪的,你看:
使用Button的时候,同样是发给子控件的消息.
WM_MOUSEMOVE消息传过来的时候,用的是接收该消息的窗口句柄找到该控件来处理事件.
而把按钮铵下去却用的是主窗口的句柄,外加WPARAM参数表示的控件ID号。
在程序中控件ID号包括了所有的窗口句柄。为什么不全都用ID号呢?或者全都用窗口句柄呢?

不过没多久就发现,尽管在主窗口中能从COMMAND消息中得到子窗口的BUTTON DOWN的事件.
但是,并不影响子窗口本身获得自已的Button Down事件。原来啊,WINDOWS发了两条消息过来喔。

现在,困扰我多年的问题终于解决.一开始我认为Windows所有的窗口都具有独立性.直到
在使用VC中碰到控件ID号,我开始困惑窗口之间的关系。然而现在,把它们分开来考虑,原来
的窗口之前的关系又清晰了(每个控件就是一个窗口,窗口有的都应该有)。

其实Button Down的这个事件完全可以由子窗口来获取,而之所以使用COMMAND又插了一脚,应该是为了方便吧。
但是,你要知道,对于一个窗口来说按下的鼠标的事件之类的消息是一类东西,处理起来应该是一样的。
而VC硬是把它们分开处理了,初学者会晕的!

最后,要说说VC与Delphi在做WINDOWS程序中.在这里就可以看出他们的分别了,
VC更接近Windows SDK,要是Windows SDK有些什么缺陷/特性,那么很容易在MFC中出现。
因此,学MFC必学SDK吧。Delphi封装得更好,更接近人类的直觉习惯,同类型的问题应该使用同类型的手段来处理。

经过比较,
最后,我们就在改动尽量少的情况下,试试让VC中的MouseMove接近于Delphi吧。
其实实现方式有很多种啦,使用裸体函数指针,又或使用事件/信号槽,最终还是决定
用老方法,使用接口的方式比较合理:
struct IButtonEvent
{
   virtual void OnButtonDown () = 0;
   virtual void OnButtonMove () = 0;
};

class CMyButton : public CButton
{
public:
   void SetEvent ( IButtonEvent *pEvent );
};

class CMyDialog : public CDialog, pulbic IButtonEvent
{
public:
   virtual void OnButtonDown ();
   virtual void OnButtonMove ();
};

结论:
学MFC要学SDK。
看WINDOWS消息中,COMMAND消息有些特别,要分开来考虑。

lw 发表于 2007-10-4 04:19:54

关键看消息是怎么在WIN32里面走的……
比如鼠标的消息通常是一组HITTEST来判断哪个窗口收到此消息,并且如果不行会交给父窗口来处理,很奇怪楼主为什么没有提到窗口树或者窗口层次……

使用ID可以说是为了方便,当时偶弄个20个按钮的CODE没有用ID真是累死了……
至于如何封装,一般了解了基本原理就可以自己写了……
其实对UI这种个人不是很感兴趣一直没有去深入过……
偶把FX拉过来…………她肯定知道~

silekey 发表于 2007-10-4 10:03:40

引用第1楼lw于2007-10-04 04:19发表的:
关键看消息是怎么在WIN32里面走的……
比如鼠标的消息通常是一组HITTEST来判断哪个窗口收到此消息,并且如果不行会交给父窗口来处理,很奇怪楼主为什么没有提到窗口树或者窗口层次……

使用ID可以说是为了方便,当时偶弄个20个按钮的CODE没有用ID真是累死了……
至于如何封装,一般了解了基本原理就可以自己写了……
.......

1>我认为不会交给父窗口处理,只会交给父类,除非你用代码指定交给父窗口,比如刚才的WM_MOUSEMOVE消息就不会交给父窗口,而且就算是MouseDown也不会交给父窗口。
父窗口收到的那个消息本来就是发给父窗口的COMMAND。

2>唉,上面的封装是有些问题,我忘了。象上面这样的话还是需要使用ID号来区分不同的控件。
    最方便的做法看来还是使用 事件/信号槽。
   //伪代码如下
class CMyButton
{
public:
   CSigV1<CPoint&> m_OnMouseMove; //只有一个参数的信号
};

class CMyDialog : public Dialog
{
   CMyButton m_btn1;
   CMyButton m_btn2;
   CSlots    m_slots; //槽
public:
   CMyDialog ()
   {
       //关连事件
       m_slots.connect(m_btn1.m_OnMouseMove, this, &OnBtn1MouseMove);
       m_slots.connect(m_btn2.m_OnMouseMove, this, &OnBtn2MouseMove);
   }

   void OnBtn1MouseMove(CPoint &point);
   void OnBtn2MouseMove(CPoint &point);
};

silekey 发表于 2007-10-4 10:26:43

我下面顺便帖上裸体指针的使用方法,该代码可以在VC6.0下执行:
#include <iostream>
using namespace std;
//在没有 事件/信号槽机制 的地方可以使用原始的裸体函数指针
//裸体函数指针,因此不是类型安全的,在多继承的情况下要小心使用
class CMyButton
{
public:
    typedef void (CMyButton::*EVENT_BTN_DOWN)(void);
    template <class T>
    void SetMouseDownEvent ( T* pThis, void (T::*pFun)(void) )
    {
      m_event.pThis = reinterpret_cast<CMyButton*>   (pThis);   //这里就相当于裸体了,不过用MyButton是为了方便下面调用而已
      m_event.pfn   = reinterpret_cast<EVENT_BTN_DOWN> (pFun);    //这里函数指针也退化了
    }

    void CallEvent ()
    {
      (m_event.pThis->*m_event.pfn)();
    }
private:
    struct CALL_BACK
    {
      CMyButton       *pThis;
      EVENT_BTN_DOWN   pfn;   
    };
    CALL_BACK m_event;
};

class CMyDialog
{
public:
    void OnBtn1MouseDown ()
    {
      cout << "Btn1 down" << endl;
    }
    void OnBtn2MouseDown ()
    {
      cout << "Btn2 down" << endl;
    }
};
int main(int argc, char* argv[])
{
    CMyDialog mydlg;
    CMyButton btn1;
    CMyButton btn2;

    btn1.SetMouseDownEvent( &mydlg, &CMyDialog::OnBtn1MouseDown );
    btn2.SetMouseDownEvent( &mydlg, &CMyDialog::OnBtn2MouseDown );

    btn1.CallEvent();
    btn2.CallEvent();
    return 0;
}

silekey 发表于 2007-10-4 11:13:00

上面那个觉得不够裸,还是用了模板,这次来个更裸的.没有template的话BUTTON就可以写在dll中了

#include <iostream>
using namespace std;
//在没有 事件/信号槽机制 的地方可以使用原始的裸体函数指针
//裸体函数指针,因此不是类型安全的,在多继承的情况下要小心使用
template <typename T>
void* function_cast( T pfn )
{
    return *reinterpret_cast<void **>((&pfn));
}

class CMyButton
{
public:
    typedef void (CMyButton::*EVENT_BTN_DOWN)(void);

    void SetMouseDownEvent ( void* pThis, void *pfun )
    {
      m_event.pThis =   reinterpret_cast<CMyButton*>   (pThis);    //这里就相当于裸体了,不过用MyButton是为了方便下面调用而已
      m_event.pfn   =*reinterpret_cast<EVENT_BTN_DOWN*>(&pfun);    //这里函数指针也退化了
    }

    void CallEvent ()
    {
      (m_event.pThis->*m_event.pfn)();
    }
private:
    struct CALL_BACK
    {
      CMyButton      *pThis;
      EVENT_BTN_DOWN   pfn;   
    };
    CALL_BACK m_event;
};

class CMyDialog
{
public:
    void OnBtn1MouseDown ()
    {
      cout << "Btn1 down" << endl;
    }
    void OnBtn2MouseDown ()
    {
      cout << "Btn2 down" << endl;
    }
};

int main(int argc, char* argv[])
{
    CMyDialog mydlg;
    CMyButton btn1;
    CMyButton btn2;

    btn1.SetMouseDownEvent( &mydlg, function_cast(&CMyDialog::OnBtn1MouseDown) );
    btn2.SetMouseDownEvent( &mydlg, function_cast(&CMyDialog::OnBtn2MouseDown) );

    btn1.CallEvent();
    btn2.CallEvent();

    return 0;
}

shawind 发表于 2007-10-4 12:04:19

好像,现在新的GUi都是vcl那样的。MFC是出了名的难入门。反正是不懂。

lw 发表于 2007-10-4 16:17:12

不明白为什么要
btn1.SetMouseDownEvent( &mydlg, function_cast(&CMyDialog::OnBtn1MouseDown) );
因为从概念来说,对话框的功能为什么会给按钮去使用呢?
总之实现应该没什么问题……

PS:另外印象中直接使用对象指针可能会有问题,具体记不清楚了,反正偶通常统一使用static类似的CB,至于对象化都是用结构的参数传递……

lw 发表于 2007-10-4 16:28:27

WIN32:DefWndProc这个交给父窗口处理- -
通常一般会这么调用,而如果不交给父窗口处理的情况就好比把消息中途拦截掉类似……
具体的实现方法,偶说过方法很多的XD,所以不太在意……

就不知道LINUX下面怎么做,大概也是消息队列-窗口&lt;-&gt;传递 这样巴……

silekey 发表于 2007-10-4 18:14:14

to lw:

Q1:WIN32:DefWndProc这个交给父窗口处理
A1:没听说过DefWindowProc是会把消息传给父窗口的?原来我有跟踪源代码,最近MFC有点问题,
   跟踪的MFC源代码与执行的MFC DLL不匹配,所以就没有试成。


Q2:不明白为什么要
   btn1.SetMouseDownEvent( &mydlg, function_cast(&CMyDialog::OnBtn1MouseDown) );
   对话框的功能为什么会给按钮去使用呢?
A2: 因为对话框要响应 按钮窗口的事件,所以按钮窗口要回调给对话框响应.
    Delphi,VB不都是这样做的吗?
   
Q3: 反正偶通常统一使用static类似的CB,至于对象化都是用结构的参数传递……
A3: 是不是用static不是关键,你所说的方法最终应该与我的使用接口的方法类似。   
    都是       1个事件调用者调用1个或多个事件接收者.
    而现在是   多个同类的事件调用者调用1个或多个事件接收者. 就好比我有30个Button,就有相应的30 MouseDown事件函数.
   
    现在是不知道接收者的对象类型与函数指针类型,但是知道其调用方法.
    不知道是不是我对你所说的理解有偏差,如果是的话就指出来吧。


Q4: 另外印象中直接使用对象指针可能会有问题.
A4: 对象如果使用多继承的话就有能有问题,但是那种用法实在太BT了。(不是说多继承)
    只要保证对象与相应的对象函数配套就OK了。
    还有,使用裸指针肯定是很容易有问题的啦,比如参数不匹配之类的.很容易就内存越介.
    但是,如果要做成DLL的话只能这样了,因为没办法得到使用者的信息。
    如果不放心的话,可以参考MFC的做法,加个函数类型参数判断。

silekey 发表于 2007-10-4 18:26:09

使用裸指针除了参数的调用肯定能出问题之外,
我能想到的就是在多继承的情况下对象指针的不确定性:
测试代码如下:

struct CBaseA{   virtual void OnBtn1MouseDown() = 0; };
struct CBaseB{   virtual void OnBtn2MouseDown() = 0; };

class CMyDialog : public CBaseA, public CBaseB
{
public:
    virtual void OnBtn1MouseDown ()
    {
      cout &lt;&lt; &quot;Btn1 down&quot; &lt;&lt; endl;
    }
    virtual void OnBtn2MouseDown ()
    {
      cout &lt;&lt; &quot;Btn2 down&quot; &lt;&lt; endl;
    }
};

int main(int argc, char* argv[])
{
    CMyDialog t1;
    CMyButton btn1;
   
    CBaseB *pB = &t1; //pB与t1不是同一个指针了
    //接口与函数指针不匹配,太BT了
    btn1.SetMouseDownEvent( pB, function_cast(&CMyDialog::OnBtn1MouseDown) );

    //结果就调用了 OnBtn2MouseDown 去了。
    btn1.CallEvent();


    return 0;
}
页: [1] 2
查看完整版本: [原创]好久没用MFC了,就重新复习了一下,突然在发现Windows