跟我一起学Windows界面封装(五) 之 封装第一个控件:按钮

     Button是我们遇到的最多的控件之一,因此以它来作为入手控件还是很合适的。

      通常我们见到的Button都是有窗口控件,当然也有很多优秀的库为了提高效率做成无窗口控件。所有有窗口和无窗口就是是否控件和一个HWND句柄相关联。我们还是以常规的有窗口控件来讲述下吧。

      在CreateWindow函数的参数中,我们注意到还有几个的参数我们没用过,其中一个就是hWndParent。在创建普通窗口时该参数我们都设成了NULL,但现在我们需要用到了。从名字我们就看出hWndParent指的就是父窗口句柄。按钮控件作为一个窗口不能独立存在,它是依附于在别的窗口上的,也就是其父窗口。因此,我们在hWndParent参数需要指定。

     另外,还有dwStyle,其是DWORD类型。查看msdn可以看出有一种类型值WS_CHILD,解释中也是告诉Windows创建一个子窗口。

    注:这里还有一个概念,windows窗口有owned-owner和child-parent两种关系,我们普通的按钮和窗口就是后者,而模态对话框和调用显示其的窗口就是owned-owner的关系。具体二者有什么区别可额外查询下资料。当仅指定hParent时就是owned-owner关系;当同时指定hParent和WS_CHILD时就是child-parent的关系。

     有了这两个参数后创建按钮控件算是完成了第一步。下面就还有一个问题:注册一个什么样的WNDCLASS呢?不然怎么创建呢?

     Windows为用户预定义并注册了一些控件的WndClass,其中包括一些常用的按钮(BUTTON)、ListBox等等。因此我们也就没必要在重新声明注册按钮控件窗口类型了。

     到这里可能你会发现还有一个问题没解决,WNDCLASS都会指定一个窗口过程函数地址,但windows注册的窗口怎么能知道我们所需要的函数地址呢?怎么才能让其执行到我们的控件窗口过程函数中去呢?这里就引入了ATL中的SUPERCLASS。

     ATL会有这么一个宏:DECLARE_WND_SUPERCLASS。看了其定义就发现其有两个参数,一个是我们想注册的控件名称(比如叫“XButton”),另外一个参数就是其所希望继承的窗口类型。SuperClass的意思就是在注册窗口时能够继承一个已经注册过的窗口类型(也就是克隆技术),同时窗口过程函数可以使用我们自定义的。这样我们的问题也就解决了,剩下就是利用windows的api来怎么实现了。

题外话:

需要注意的是,superclass是在注册窗口类时就改变了窗口的行为。WTL中还有subclass(子类化),其是在窗口创建完毕后,通过修改窗口函数的地址等改变一个窗口的行为的。

 

     先看看我们定义的控件基类吧,它和XWindowImpl差不多,区别就是在注册的时候不一样了。

template
class XWindowComponentImpl: public XWindow
{
	XThunk* m_pThunk;
	
public:
	CString m_strTitle;		//多了点成员变量
	RECT m_rect;
	HWND m_hParent;

public:
	XWindowComponentImpl()
	{
		CWindowImpl
	}

	HWND Create(
		LPCTSTR szSuperClass,
		LPCTSTR szTitle, 
		RECT* pRect = nullptr,
		HWND hParent = nullptr,
		DWORD dwStyle = WS_OVERLAPPEDWINDOW,
		DWORD dwStyleEx = 0)
	{
		m_strTitle = CString(szTitle);
		m_rect = *pRect;
		m_hParent = hParent;

		WNDCLASSEX wc;
		if (!::GetClassInfoEx(_XModule.GetInstance(), szSuperClass, &wc))
		{
			ATLASSERT(FALSE);
		}
		wc.lpszClassName = T::GetXWndClassName();        // 创建wndclass也不一样
		wc.lpfnWndProc = &StartXWndProc;
		wc.cbSize = sizeof(wc);
		ATOM ato = ::RegisterClassEx(&wc);

		RECT rc;
		if (pRect == nullptr)
			rc = rcDefault;
		else
			rc = *pRect;
		m_pThunk = (XThunk *)VirtualAlloc(NULL, sizeof(XThunk), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
		_XModule.m_pCurWnd = (void*)this;

		HWND hWnd = ::CreateWindow(T::GetXWndClassName(), szTitle, wc.style | WS_CHILD, 
			rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
			hParent, nullptr, _XModule.GetInstance(), nullptr);

		ATLASSUME(m_hWnd == hWnd);

		return m_hWnd;
	} 

protected:
	static LRESULT CALLBACK StartXWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		ATLASSERT(_XModule.m_pCurWnd != nullptr);

		XWindowComponentImpl* pThis = (XWindowComponentImpl*)_XModule.m_pCurWnd;


		WNDPROC pWndProc = (WNDPROC)pThis->m_pThunk;
		pThis->m_pThunk->Init((DWORD)pThis, (DWORD)&XWindowComponentImpl::RealXWndProc);

		pThis->m_hWnd = hWnd;
		;
		SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pWndProc);
		return pWndProc(hWnd, uMsg, wParam, lParam);
	}

	static LRESULT CALLBACK RealXWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		XWindowComponentImpl* pThis = (XWindowComponentImpl*)hWnd;
		return pThis->ProcessXWndMessage(uMsg, wParam, lParam);
	}
public:
	virtual LRESULT ProcessXWndMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		return ::DefWindowProc(this->m_hWnd, uMsg, wParam, lParam);
	}
};


代码有点多,我们就把核心部分挑出来说明下吧!

首先就是注册控件窗口:

WNDCLASSEX wc;
		if (!::GetClassInfoEx(_XModule.GetInstance(), szSuperClass, &wc))
		{
			ATLASSERT(FALSE);
		}
		wc.lpszClassName = T::GetXWndClassName();
		wc.lpfnWndProc = &StartXWndProc;
		wc.cbSize = sizeof(wc);
		ATOM ato = ::RegisterClassEx(&wc);

这里面用到了函数GetClassInfoEx(),整个过程就是先获取已经注册了的superclass窗口的WNDCLASS信息,然后将名字和过程函数替换成我们的,再注册就ok了。Atl中会有一堆代码封装的比较完善,但流程也就这个意思,理解这个再看ATL会很easy了。

然后再Create的时候Style参数要设成wc.style | WS_CHILD就ok了。意思就是首先保持注册窗口的style再加上子窗口类别。

其他代码就不用再解释了,前面章节已经说得比较详细了。

起航,创建一个按钮控件

有了基类就不怕了!上代码

class XButton : public XWindowComponentImpl
{
public:
	XButton()
	{
	}
	static LPCTSTR GetXWndClassName()
	{
		return _T("XButton");
	}
public:
	LRESULT ProcessXWndMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		HDC hdc;
		PAINTSTRUCT ps;
		switch(uMsg)
		{		
case WM_LBUTTONDOWN:
			::SendMessage(m_hParent, WM_COMMAND, 2013,0);
			break;
		case WM_PAINT:
			hdc = BeginPaint(m_hWnd, &ps);
			Rectangle(hdc,m_rect.left, m_rect.top, m_rect.right,m_rect.bottom);
			TextOut(hdc,20,20,(LPCTSTR)m_strTitle, m_strTitle.GetLength());
			EndPaint(m_hWnd, &ps);
			break;
		case WM_CLOSE:
			DestroyWindow();
			break;
		default:
			return ::DefWindowProc(this->m_hWnd, uMsg, wParam, lParam);
		}
		return 0;
	}
};

很简单吧,也就ProcessXWndMessage中多了一个WM_PAINT消息的处理,他就是绘制我们的按钮,为了简单我们就只画个框和文字了。具体代码就不再解释了。

另外,还有一个消息WM_LBUTTONDOWN需要处理,不然上层的SimpleWindow怎么知道我们点了按钮呢?该消息工作就是通知父窗口并发送一个WM_COMMAND消息。WM_COMMAND消息的两个参数内容可以查看msdn。为了简化,我们就直接把WPARAM的值传入button的ID(整型值)。这个ID应该不陌生了,MFC中都会定义resource.h,里面就是放了这种数值。

好了,下面就把这个按钮放到我们的窗口中试试:

首先定义一个按钮成员XButton m_btn;

然后在哪创建呢?就在WM_CREATE中吧。

最后就是接收按钮的按下消息并响应了,这个就是在WM_COMMAND消息中,并判断下WPARAM的ID。具体代码如下:

	LRESULT ProcessXWndMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		HDC hdc;
		PAINTSTRUCT ps;
		RECT rc = {10,10,125,40};
		switch(uMsg)
		{
		case WM_COMMAND:
			if (wParam == 2013)
			{
				HDC dc = ::GetDC(m_hWnd);
				CString strInfo(L"You have pushed the button");
				TextOut(dc,20,100,strInfo, strInfo.GetLength());
			}
		case WM_CREATE:
			m_btn.Create(_T("BUTTON"),L" I am Button", &rc, m_hWnd );
			m_btn.ShowWindow(SW_SHOW);
			m_btn.UpdateWindow();
			break;
		case WM_CLOSE:
			DestroyWindow();
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		default:
			return ::DefWindowProc(this->m_hWnd, uMsg, wParam, lParam);
		}
		return 0;
	}

最后,编译运行。我们很高兴按钮出来了,点击后会发现下面多出了一行文字。

跟我一起学Windows界面封装(五) 之 封装第一个控件:按钮_第1张图片

最后,还要说一点:

我们使用MFC、ATL中窗口的处理各种消息,包括按钮等时经常会用到宏,也就是我们常说的消息映射,类似如下:

BEGIN_MESSAGE_MAP(class)
	COMMAND_MESSAGE(ID1,FUNC1)
	COMMAND_MESSAGE(ID2,FUNC2)
	WINDOW_MESSAGE(WM_XXXX, FUNC3)
	… …
END_MESSAGE_MAP()

假如我们展开宏就会发现:

BEGIN_MESSAGE_MAP也就是定义窗口成员函数ProcessXWndMessage;

COMMAND_MESSAGE就是处理WM_COMMAND,内部会有一堆的cass,并调用FUNCx;

WINDOW_MESSAGE就是处理其他WM_XXX消息,然后调用指定的FUNCi;

至此,窗口封装就算有所小成了。


你可能感兴趣的:(Windows开发,C/C++)