WIN32界面开发之三:DUI雏形开发(二)

前言:上篇《WIN32界面开发之三:DUI雏形开发(一)》讲解了界面加载框架的创建,但我们的这些控件并没有起到控件的作用,现在还无法响应我们的点击事件和其它事件,所以我们先给我们的框架添加上EVENT事件机制,然后我们再讲解,为什么我们还要加上NOTIFY通知机制,以及如何添加NOTIFY机制。

一、添加EVENT事件机制

基本思想:以通知某个按钮LButtonDown为例,我们首先在HandleMessage()中,截获WM_LBUTTONDOWN消息,然后根据点击位置,找到某个控件,然后发送EVENT消息通知它。在这个控件收到EVNET通知后,然后根据事件类型,重新绘图。这里涉及到几个问题,1:我们如何根据点击位置找到点击的是哪个控件呢?这个问题的解决在FFindCtrlFromPT实现。2:我们发送EVENT消息给控件时,这个消息结构体应该包含哪些内容?,这个问题的解决在下面的EVENT结构体定义。3:在收到EVNET消息以后,我们的控件怎么判断出消息类型呢,这些消息类型就是我们下面要讲的事件类型枚举。

(一)、事件相关定义

1、事件类型枚举

在添加事件之前,我们要先对我们要先枚举出我们所要添加的事件,这些事件也就是我们感兴趣的事件,比如MOUSEENTER、MOUSELEAVE、BUTTONDOWN、DBLCLICK……,记住这些不是给用户看的,只是为了让我们的控件识别用的。只是为了告诉我们的控件现在用户怎么样我们的控件了,我们的控件在绘图上应该怎么样改变!!!比如当MOUSEIN的时候换成另一个背景图,MOUSELEAVE的时候还原原来的背景图。

typedef enum EVENTTYPE_UI
{
	UIEVENT__FIRST = 0,
	UIEVENT_MOUSEMOVE,
	UIEVENT_MOUSELEAVE,
	UIEVENT_MOUSEENTER,
	UIEVENT_MOUSEHOVER,
	UIEVENT_BUTTONDOWN,
	UIEVENT_BUTTONUP,
	UIEVENT_DBLCLICK,
};
2、消息结构体定义

typedef struct tagTEventUI
{
	int Type;  //消息类型
	CControlUI* pSender; //发送消息者
	DWORD dwTimestamp;//时间戳
	POINT ptMouse;//如果是单双击事件,则传递点击的鼠标位置
	TCHAR chKey;//如果是按键消息,则传递按下的是哪个键
	WORD wKeyState;//键状态
	WPARAM wParam;//WPARAM
	LPARAM lParam;//LPARAM
} TEventUI;
3、FindCtrlFromPT()原理及实现
由于CDialogUI布局范围是整个窗体,所以我们先在CDialogUI中调用FindCtrlFromPT(POINT pt),那FindCtrlFromPT该怎么实现呢,看代码:
CControlUI* CContainerUI::FindCtrlFromPT(POINT pt)
{
	if (!::PtInRect(&m_RectItem,pt)) return NULL;///如果不在当前容器内,则直接返回NULL;
	for( int it = 0; it != m_items.GetSize(); it++ ) {
		CControlUI* pControl = static_cast<CControlUI*>(m_items[it])->FindCtrlFromPT(pt);
		if( pControl != NULL ) return pControl;
	}
	return CControlUI::FindCtrlFromPT(pt);

}
大家可以看到,我们是在CContainerUI中实现的(声明为虚函数),并没有在CDialogUI实现,这是因为CContainerUI也是容器类型,他们两个查找子控件的算法是一样的,所以我们只要把FindCtrlFromPT()声明为CContainerUI的虚函数,让CDialogUI去继承就好了,当然了,CDialogUI也可以重写,不过在这里没这个必要。
另外FindCtrlFromPT()的返回值,是看是不是在控件的范围内,所以给CControlUI也应该加上这个函数的定义。

CControlUI* CControlUI::FindCtrlFromPT(POINT pt)
{
	if (::PtInRect(&m_RectItem,pt))
	{
		return this;
	}else
	{
		return NULL;
	}
}

(二)拦截消息,转发给控件

1、我们先拦截一个吧(WM_LBUTTONDOWN),看代码:
case WM_LBUTTONDOWN:
	{
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

		CControlUI* pControl = m_root->FindCtrlFromPT(pt);//找到点击的控件
		if( pControl == NULL ) break;

		m_pEventClick=pControl;

		TEventUI event = { 0 };
		event.Type = UIEVENT_BUTTONDOWN;
		event.wParam = wParam;
		event.lParam = lParam;
		event.ptMouse = pt;
		event.wKeyState = wParam;
		event.dwTimestamp = ::GetTickCount();
		pControl->SetHwnd(GetHWND());///将HWND传递给CControl,用于SendMessage

		pControl->Event(event); //发送消息
		// We always capture the mouse
		::SetCapture(GetHWND());注意,只有在点在控件内的时候才要SetCapture,否则会造成托动窗口时,会无效,
		//因为上面用FindCtrlFromPT找到了控件,这时应该让这个控件获得焦点
	}
	break;

讲解:
1、这里的顺序就是:先根据点击的位置,获取点击的控件,然后向控件发送消息,最后让该控件获得焦点
2、这里注意一下,多了一句pControl->SetHwnd(),上节我们讲了控件为什么也要HWND,因为SendMessage(),还记得么,SendMessage的第一个参数,就是我们窗体的HWND。
3、多了个变量,CControlUI *m_pEventClick;这个变量是为了记录当前获取事件的控件,以便WM_LBUTTONUP中发送事件消息。

2、拦截第二个消息(WM_LBUTTONUP)

case WM_LBUTTONUP:
	{
		if (m_pEventClick==NULL) break;

		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
		::ReleaseCapture();
		TEventUI event = { 0 };
		event.Type = UIEVENT_BUTTONUP;
		event.wParam = wParam;
		event.lParam = lParam;
		event.ptMouse = pt;
		event.wKeyState = wParam;
		event.dwTimestamp = ::GetTickCount();

		m_pEventClick->Event(event);

		m_pEventClick = NULL;
	}
	break;

(三)控件处理

1、声明变量,标识当前控件状态

UINT m_uButtonState;//按钮状态
记得初始化为0;

2、添加EVNET()函数

其实是,我们将EVENT()函数,在CControlUI中声明为虚函数,并不具体实现,只留一个接口,现在是在CButtonUI中的具体实现,看代码吧:

void CButtonUI::Event(TEventUI& event)
{
	if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK )
	{
		if( ::PtInRect(&m_RectItem, event.ptMouse)) {
			m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED;

			Invalidate();
		}
	}
	if( event.Type == UIEVENT_MOUSEMOVE )
	{
		if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {

			Invalidate();
		}
	}
	if( event.Type == UIEVENT_BUTTONUP )
	{
		if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
			m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
			Invalidate();
		}
	}
	if( event.Type == UIEVENT_MOUSEENTER)
	{
		m_uButtonState |= UISTATE_HOT;
		Invalidate();
	}
	if( event.Type == UIEVENT_MOUSELEAVE)
	{
		m_uButtonState &= ~UISTATE_HOT;
		Invalidate();
	}
}
这部分也没什么难的,就是根据当前不同的状态,给m_uButtonState添加上不同的值,供绘图时判断当前的控件状态。
3、绘图函数(DoPaint)

	assert(hDC);
	Graphics graph(hDC);
	if( (m_uButtonState & UISTATE_DISABLED) != 0 ) {
		graph.FillRectangle(&SolidBrush(Color::Gray),m_RectItem.left,m_RectItem.top,m_RectItem.right-m_RectItem.left,m_RectItem.bottom-m_RectItem.top);
	}
	else if( (m_uButtonState & UISTATE_PUSHED) != 0 ) {
		graph.FillRectangle(&SolidBrush(Color::Red),m_RectItem.left,m_RectItem.top,m_RectItem.right-m_RectItem.left,m_RectItem.bottom-m_RectItem.top);
	}
	else{
		graph.FillRectangle(&SolidBrush(Color::Green),m_RectItem.left,m_RectItem.top,m_RectItem.right-m_RectItem.left,m_RectItem.bottom-m_RectItem.top);
	}

	graph.ReleaseHDC(hDC);
讲解:不难理解,就是根据当前不同的控件状态,给按钮绘制不同的颜色。当点击时,是红色,放开时,是绿色。

添加EVENT后,点击任一个按钮,界面如图

二、添加NOTIFY机制

问题提出及解决:试想一下,上面的EVENT机制能够通知我们的控件,当前控件应该处于哪种状态,并完成哪种绘制,但,当用户想点击控件的时候托动窗体,或者在点击控件的时候弹出另一个窗体该怎么办呢????这,就是NOTIFY通知机制,比如当前控件处理BUTTON—DWON的状态时,我们就向用户发送“click”通知消息,又比如,当我们的控件处于"BUTTON-DOWN"同时又具有"MOUSE-MOVE" 状态时,我们就向用户发送“drag”(拖动)通知消息

(一)、抽象与派生

1、通知消息结构体定义

与EVENT相似,要发送通知消息,总是以结构体为单位的,这样能包含的信息多一些,而这里的消息通知的结构体定义围绕着我们感兴趣的几个变量,如消息发送者(控件)、消息类型、时间戳、鼠标信息等,具体定义如下:

typedef struct tagTNotifyUI 
{
	CStdString sType;  //消息类型
	CControlUI* pSender;  //发送者
	DWORD dwTimestamp;//时间戳
	POINT ptMouse;//鼠标位置
	WPARAM wParam;//WPARAM
	LPARAM lParam;//LPARAM
} TNotifyUI;
2、接口抽象(INotifyUI)
由于我们的NOTIFY机制是一个功能独立的模块,所以我们把它单独抽象为一个类,定义如下:
class INotifyUI
{
public:
	virtual void Notify(TNotifyUI& msg) = 0;
};
如定义可知,这个类只包含一个纯虚函数,Notify(),参数为一个TNotifyUI的结构体
3、派生(CStartPage)

将我们的窗体类CStartPage派生自INotifyUI,即在声明中添加:

class CStartPage: public CWindowWnd, public INotifyUI
{
virtual void Notify(TNotifyUI& msg);
 };

记得实现Notify()函数。这个我们就写说个声明,最后我们会写出实现代码。

(二)、发送与接收

我们到底想实现怎么样的功能呢,我就是想只在CStartPage中的Notify中获取当前发送NOTIFY的控件及消息内容,然后处理,所以我们应该在CControlUI中定义一个INotifyUI变量,用来保存CStartPage的this指针,然后在需要发送NOTIFY的地方,直接调用this->Notify(msg)即可,这就是第二部分的实现内容:
一、CContolUI中封装CStartPage的this指针

1、CControlUI中变量定义(INotifyUI *m_pNotifyer;)

因为我们要用的是CStartPage中的Notify函数,所以我们将其声明为它的基类类型,当我们调用m_pNotifyer->Notify(msg)时,根据虚函数的派生关系,就会调用到CStartPage中的Notify()函数了。
我们一般将此类变量封装成parivate类型,所以我们要加一个设置函数,如下:

virtual void SetNotifyer(INotifyUI * pCtrl){m_pNotifyer=pCtrl;}
2、使用SetNotifyer()
在CStartPage的HandleMessage中,添加SetNotifier(this);,其它代码都是EVENT的

	case WM_LBUTTONDOWN:
		{
			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

			CControlUI* pControl = m_root->FindCtrlFromPT(pt);
			if( pControl == NULL ) break;

			m_pEventClick=pControl;

			TEventUI event = { 0 };
			event.Type = UIEVENT_BUTTONDOWN;
			event.wParam = wParam;
			event.lParam = lParam;
			event.ptMouse = pt;
			event.wKeyState = wParam;
			event.dwTimestamp = ::GetTickCount();
			pControl->SetHwnd(GetHWND());///将HWND传递给CControl,用于SendMessage

			pControl->SetNotifyer(this);
			pControl->Event(event);

			::SetCapture(GetHWND());注意,只有在点在控件内的时候才要SetCapture,否则会造成托动窗口时,会无效,
			//因为上面用FindCtrlFromPT找到了控件,这时应该让这个控件获得焦点
		}
		break;
	case WM_LBUTTONUP:
		{
			if (m_pEventClick==NULL) break;

			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			::ReleaseCapture();
			TEventUI event = { 0 };
			event.Type = UIEVENT_BUTTONUP;
			event.wParam = wParam;
			event.lParam = lParam;
			event.ptMouse = pt;
			event.wKeyState = wParam;
			event.dwTimestamp = ::GetTickCount();

			m_pEventClick->SetNotifyer(this);
			m_pEventClick->Event(event);

			m_pEventClick = NULL;
		}
		break;
我们再添加一个对消息的响应,大家试想一下,如果我在点击的按钮,然后移动鼠标的话,那应该发送给用户什么消息呢?对,“Drag”消息!!!!所以我们要在CStartPage中拦截WM_MOUSEMOVE消息,代码如下:
case WM_MOUSEMOVE:
	{
		POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

		CControlUI* pNewHover = m_root->FindCtrlFromPT(pt);
		TEventUI event = { 0 };
		event.ptMouse = pt;
		event.dwTimestamp = ::GetTickCount();

		if( m_pEventClick != NULL ) {
			event.Type = UIEVENT_MOUSEMOVE;
			event.pSender = NULL;
			::ReleaseCapture();///增加这个
			m_pEventClick->Event(event);
		}
		else if( pNewHover != NULL ) {
			event.Type = UIEVENT_MOUSEMOVE;
			event.pSender = NULL;
			pNewHover->Event(event);
		}
	}
	break;
二、控件中的发送消息
借助于事件消息,在鼠标按下的时候,发送“click”消息,当同时具有鼠标按下和鼠标移动的状态时,发送“drag”消息
void CButtonUI::Event(TEventUI& event)
{
	if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK )
	{
		if( ::PtInRect(&m_RectItem, event.ptMouse)) {
			m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED;

			TNotifyUI Msg;
			POINT pt;pt.x=0;pt.y=0;
			Msg.pSender = this;
			Msg.sType =_T("click");
			Msg.wParam = 0;
			Msg.lParam = 0;
			Msg.ptMouse =pt;
			Msg.dwTimestamp = ::GetTickCount();
			// Allow sender control to react
			INotifyUI* pNotifier=static_cast<INotifyUI*>(m_pNotifyer);
			pNotifier->Notify(Msg);

			Invalidate();
		}
	}
	if( event.Type == UIEVENT_MOUSEMOVE )
	{
		if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {

			TNotifyUI Msg;
			POINT pt;pt.x=0;pt.y=0;
			Msg.pSender = this;
			Msg.sType =_T("drag");
			Msg.wParam = 0;
			Msg.lParam = 0;
			Msg.ptMouse =pt;
			Msg.dwTimestamp = ::GetTickCount();
			// Allow sender control to react
			INotifyUI* pNotifier=static_cast<INotifyUI*>(m_pNotifyer);
			pNotifier->Notify(Msg);

			Invalidate();
		}
	}
	if( event.Type == UIEVENT_BUTTONUP )
	{
		if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
			m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
			Invalidate();
		}
	}
	if( event.Type == UIEVENT_MOUSEENTER)
	{
		m_uButtonState |= UISTATE_HOT;
		Invalidate();
	}
	if( event.Type == UIEVENT_MOUSELEAVE)
	{
		m_uButtonState &= ~UISTATE_HOT;
		Invalidate();
	}
}
三、CStartPage中接收Notify消息
这就是对CStartPage的Notify消息的具体实现了,代码如下:
void CStartPage::Notify(TNotifyUI& msg)
{
	if (msg.sType==L"click")
	{
		//SendMessage(m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, NULL);
		//MessageBox(NULL,L"1122",L"cation",MB_OK);
	}
	if (msg.sType==L"drag")
	{
		SendMessage(GetHWND(), WM_NCLBUTTONDOWN, HTCAPTION, NULL);
		SendMessage(GetHWND(), WM_LBUTTONUP, NULL, NULL);
	}

}
实现的功能,当drag的时候,拖动窗体,当然大家还可以判断发送方是不是Button控件,做进一步识别。
现在的界面如下,在点击按钮的时候可以托动。


本文由HARVIC完成,如若转载,请标明出处,请大家尊重初创者的版权,谢谢!!

源文地址:http://blog.csdn.net/harvic880925/article/details/9612075

源码地址:http://download.csdn.net/detail/harvic880925/5837779

声明:感谢金山影音漂亮的界面图片,该图片来自网络。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值