[原创]好久没用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消息有些特别,要分开来考虑。 关键看消息是怎么在WIN32里面走的……
比如鼠标的消息通常是一组HITTEST来判断哪个窗口收到此消息,并且如果不行会交给父窗口来处理,很奇怪楼主为什么没有提到窗口树或者窗口层次……
使用ID可以说是为了方便,当时偶弄个20个按钮的CODE没有用ID真是累死了……
至于如何封装,一般了解了基本原理就可以自己写了……
其实对UI这种个人不是很感兴趣一直没有去深入过……
偶把FX拉过来…………她肯定知道~ 引用第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);
};
我下面顺便帖上裸体指针的使用方法,该代码可以在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;
}
上面那个觉得不够裸,还是用了模板,这次来个更裸的.没有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;
}
好像,现在新的GUi都是vcl那样的。MFC是出了名的难入门。反正是不懂。 不明白为什么要
btn1.SetMouseDownEvent( &mydlg, function_cast(&CMyDialog::OnBtn1MouseDown) );
因为从概念来说,对话框的功能为什么会给按钮去使用呢?
总之实现应该没什么问题……
PS:另外印象中直接使用对象指针可能会有问题,具体记不清楚了,反正偶通常统一使用static类似的CB,至于对象化都是用结构的参数传递…… WIN32:DefWndProc这个交给父窗口处理- -
通常一般会这么调用,而如果不交给父窗口处理的情况就好比把消息中途拦截掉类似……
具体的实现方法,偶说过方法很多的XD,所以不太在意……
就不知道LINUX下面怎么做,大概也是消息队列-窗口<->传递 这样巴…… 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的做法,加个函数类型参数判断。 使用裸指针除了参数的调用肯定能出问题之外,
我能想到的就是在多继承的情况下对象指针的不确定性:
测试代码如下:
struct CBaseA{ virtual void OnBtn1MouseDown() = 0; };
struct CBaseB{ virtual void OnBtn2MouseDown() = 0; };
class CMyDialog : public CBaseA, public CBaseB
{
public:
virtual void OnBtn1MouseDown ()
{
cout << "Btn1 down" << endl;
}
virtual void OnBtn2MouseDown ()
{
cout << "Btn2 down" << 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