1 duilib窗口类
duilib自己封装了Window类
class UILIB_API Window : public virtual nbase::SupportWeakCallback
{
public:
Window();
~Window();
/**
* @brief 获取窗口所属的 Windows 句柄
* @return 返回窗口关联的 Windows 窗口句柄
*/
HWND GetHWND() const;
/**
* @brief 注册窗口类
* @return 返回 true 表示成功,否则表示失败
*/
bool RegisterWindowClass();
/**
* @brief 注册控件窗口类(与窗口的过程函数不同)
* @return 返回 true 表示成功,否则表示失败
*/
bool RegisterSuperClass();
/**
* @brief 获取窗口类名称
* @return 返回窗口类名称
*/
virtual std::wstring GetWindowClassName() const;
/**
* @brief 获取控件窗口类
* @return 返回控件窗口类
*/
virtual std::wstring GetSuperClassName() const;
/**
* @brief 获取窗口类的样式,该方法由实例化的子类实现,https://docs.microsoft.com/en-us/windows/desktop/winmsg/window-class-styles
* @return 返回窗口类的样式
*/
virtual UINT GetClassStyle() const;
/**
* @brief 子类化窗口(更改窗口过程函数)
* @param[in] 窗口句柄
* @return 新的窗口句柄
*/
HWND Subclass(HWND hWnd);
/**
* @brief 取消子类化窗口(恢复原来的窗口过程函数)
* @return 无
*/
void Unsubclass();
/**
* @brief 创建窗口
* @param[in] hwndParent 父窗口句柄
* @param[in] pstrName 窗口名称
* @param[in] dwStyle 窗口样式
* @param[in] dwExStyle 窗口拓展样式
* @param[in] isLayeredWindow 是否带有层窗口属性,默认为 true
* @param[in] rc 窗口大小
* @return 返回窗口句柄
*/
virtual HWND Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, bool isLayeredWindow = true, const UiRect& rc = UiRect(0, 0, 0, 0));
/**
* @brief 关闭窗口
* @param[in] 关闭消息
* @return 无
*/
virtual void Close(UINT nRet = IDOK);
其中,创建窗口使用
* @brief 创建窗口
* @param[in] hwndParent 父窗口句柄
* @param[in] pstrName 窗口名称
* @param[in] dwStyle 窗口样式
* @param[in] dwExStyle 窗口拓展样式
* @param[in] isLayeredWindow 是否带有层窗口属性,默认为 true
* @param[in] rc 窗口大小
* @return 返回窗口句柄
*/
virtual HWND Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, bool isLayeredWindow = true, const UiRect& rc = UiRect(0, 0, 0, 0));
2 窗口管理类,对窗口统一管理
//map<窗口类名,map<窗口id,窗口指针>>, 如果同一类只有一个窗口,使用类名作为id
typedef std::map> WindowsMap;
typedef std::list WindowList;
class WindowsManager
{
public:
SINGLETON_DEFINE(WindowsManager);
WindowsManager();
virtual ~WindowsManager();
//根据窗口类名和id注册窗口
bool RegisterWindow(const std::wstring wnd_class_name, const std::wstring wnd_id, WindowEx *wnd);
//根据窗口类名和id注销窗口
void UnRegisterWindow(const std::wstring &wnd_class_name, const std::wstring &wnd_id, WindowEx *wnd);
//根据窗口类名和id获取窗口
WindowEx* GetWindow(const std::wstring &wnd_class_name, const std::wstring &wnd_id);
//获取所有窗口
WindowList GetAllWindows();
//获取指定class对应的所有窗口
WindowList GetWindowsByClassName(LPCTSTR classname);
//关闭所有窗口
void DestroyAllWindows();
//设置禁止窗口创建
void SetStopRegister(bool stop=true){stop_register_ = stop;}
//是否禁止窗口创建
bool IsStopRegister(){return stop_register_;}
template
static WindowType* SingletonShow(const std::wstring& window_id,int nParam=-1,bool bSHow=true,HWND hParent=NULL)
{
if (nParam > 0)
kClass_Param_map[WindowType::kClassName] = nParam;
WindowType *window = (WindowType*)(WindowsManager::GetInstance()->GetWindow(WindowType::kClassName, window_id));
if (!window)
{
window = new WindowType;
window->Create(hParent, WindowType::kClassName, WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 0);
window->CenterWindow();
if (bSHow)
{
window->ShowWindow();
}
}
else
{
window->ActiveWindow();
}
return window;
}
private:
WindowsMap windows_map_; //所有窗口
std::string user_id_;
bool stop_register_; //禁止窗口创建
public:
static std::map kClass_Param_map; // 窗口创建前,对应kClassName窗口中的参数,用此参数可以进行不同的初始化处理
};
其中,单例模板中,负责窗口创建
调用dulib窗口类创建函数, 创建默认duilib窗口
virtual HWND Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, bool isLayeredWindow = true, const UiRect& rc = UiRect(0, 0, 0, 0));
window = new WindowType;
window->Create(hParent, WindowType::kClassName, WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 0);
3 窗口创建流程
以登录窗口创建为例,看看窗口创建流程
nim_comp::WindowsManager::SingletonShow
在窗口创建Create中,RegisterWindowClass 注册窗口处理函数
bool Window::RegisterWindowClass()
{
WNDCLASS wc = { 0 };
wc.style = GetClassStyle();
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hIcon = NULL;
wc.lpfnWndProc = Window::__WndProc;
wc.hInstance = ::GetModuleHandle(NULL);
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
std::wstring className = GetWindowClassName();
wc.lpszClassName = className.c_str();
ATOM ret = ::RegisterClass(&wc);
ASSERT(ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS);
return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
}
窗口处理函数
LRESULT CALLBACK Window::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
Window* pThis = NULL;
if( uMsg == WM_NCCREATE ) {
LPCREATESTRUCT lpcs = reinterpret_cast(lParam);
pThis = static_cast(lpcs->lpCreateParams);
pThis->m_hWnd = hWnd;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(pThis));
}
else {
pThis = reinterpret_cast(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
if( pThis->m_bSubclassed ) pThis->Unsubclass();
pThis->OnFinalMessage(hWnd);
return lRes;
}
}
if (pThis != NULL) {
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
其中,通过GWLP_USERDATA 记录窗口指针
typedef struct tagCREATESTRUCT
{
LPVOID lpCreateParams; //指向将被用于创建窗口的数据的指针。
HANDLE hInstance; //标识了拥有新窗口的模块的模块实例的句柄。
HMENU hMenu; //标识了要被用于新窗口的菜单。如果是子窗口,则包含整数ID。
HWND hwndParent; //标识了拥有新窗口的窗口。如果新窗口是一个顶层窗口,这个参数可以为NULL。
int cy; //指定了新窗口的高。
int cx; //指定了新窗口的宽。
int y; //指定了新窗口的左上角的y轴坐标。
int x; //指定了新窗口的左上角的x轴坐标。
LONG style; //指定了新窗口的风格。
LPCSTR lpszName; //指向一个以null结尾的字符串,指定了新窗口的名字。
LPCSTR lpszClass; //指向一个以null结尾的字符串,指定了新窗口的Windows类名
DWORD dwExStyle; //指定了新窗口的扩展风格。
} CREATESTRUCT;
WM_GETMINMAXINFO 0x0024 36
WM_NCCREATE 非客户区创建消息 0x0081 129
WM_NCCALCSIZE 0x0083 131
WM_CREATE 0x0001
WM_STYLECHANGING 7C 124
WM_STYLECHANGED 7D 125
WM_WINDOWPOSCHANGING 0X46 70
WM_WINDOWPOSCHANGED 0X 46 71
窗口创建时 消息顺序
4 窗口信息
在调用 nim_comp::WindowsManager::SingletonShow
window = new WindowType; 生成LoginForm对象,
然后调用Create(hParent, WindowType::kClassName, WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 0); 创建窗口
最终,在基类 Window中 使用 API CreateWindowEx 创建窗口,并传入this指针,以便在窗口处理函数中使用
win32窗口程序中如果需要给窗口过程函数传递自定义参数,可以通过LPCREATESTRUCT结构体中的lpCreateParams进行传递。
HWND CreateWindowEx(
DWORD DdwExStyle, //窗口的扩展风格
LPCTSTR lpClassName, //指向注册类名的指针
LPCTSTR lpWindowName, //指向窗口名称的指针
DWORD dwStyle, //窗口风格
int x, //窗口的水平位置
int y, //窗口的垂直位置
int nWidth, //窗口的宽度
int nHeight, //窗口的高度
HWND hWndParent, //父窗口的句柄
HMENU hMenu, //菜单的句柄或是子窗口的标识符
HINSTANCE hInstance, //应用程序实例的句柄
LPVOID lpParam //指向窗口的创建数据
);
在窗口处理函数中,窗口刚开始创建时,也就是收到WM_NCCREATE 时,设置窗口m_hWnd句柄
if( uMsg == WM_NCCREATE ) {
LPCREATESTRUCT lpcs = reinterpret_cast
pThis = static_cast
pThis->m_hWnd = hWnd;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast
}
窗口处理函数,在收到WM_CREATE 消息时,通过THIS指针调用 pThis->HandleMessage(uMsg, wParam, lParam);
这个this,就是之前创建window = new WindowType; 生成LoginForm对象时的this,
只不过,这里用的LoginForm类的基类指针Window* pThis ,
这里通过base类的this指针,调用的派生类的虚函数HandleMessage
相关知识:虚函数
这里涉及到虚函数的知识
普通函数也就是非虚函数,取地址,取得是函数地址,取到后可以直接调用即可。
虚函数,取得是在虚表中的偏移, 同一个虚函数在基类虚表和派生类虚表中的偏移都是相同的,
所以调用时,取决的不是基类指针获得的虚表的偏移还是派生类指针获得的虚表偏移,因为两者一样,而是取决于使用哪个虚表, 并不是基类指针调用虚函数就使用基类虚表,这里只是获得了在虚表中的偏移,实际调用哪个虚表取决于实际对象的类型。
此处,this的实际对象是window = new WindowType; 生成LoginForm对象时的this,
因此调用pThis->HandleMessage(uMsg, wParam, lParam); 调用的是派生类LoginForm的函数
LRESULT LoginForm::HandleMessage( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
return __super::HandleMessage(uMsg, wParam, lParam);
}
然后逐渐的向基类传递
LRESULT WindowEx::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(uMsg == WM_CLOSE)
{
if(!::IsWindowEnabled(m_hWnd))
{
::SetForegroundWindow(m_hWnd);
return FALSE;
}
}
else if(uMsg == WM_KILLFOCUS)
{
KillFocus();
}
else if(uMsg == WM_KEYDOWN)
{
if(wParam == VK_ESCAPE)
{
BOOL bHandled = FALSE;
OnEsc(bHandled);
if( !bHandled )
this->Close();
}
}
return __super::HandleMessage(uMsg,wParam,lParam);
}
LRESULT WindowImplBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
BOOL bHandled = TRUE;
switch (uMsg)
{
case WM_CREATE: lRes = OnCreate(uMsg, wParam, lParam, bHandled); break;
case WM_CLOSE: lRes = OnClose(uMsg, wParam, lParam, bHandled); break;
case WM_DESTROY: lRes = OnDestroy(uMsg, wParam, lParam, bHandled); break;
case WM_NCACTIVATE: lRes = OnNcActivate(uMsg, wParam, lParam, bHandled); break;
case WM_NCCALCSIZE: lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled); break;
case WM_WINDOWPOSCHANGING: lRes = OnWindowPosChanging(uMsg, wParam, lParam, bHandled); break;
case WM_NCPAINT: lRes = OnNcPaint(uMsg, wParam, lParam, bHandled); break;
case WM_NCLBUTTONDBLCLK:lRes = OnNcLButtonDbClick(uMsg, wParam, lParam, bHandled); break;
case WM_NCHITTEST: lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break;
case WM_GETMINMAXINFO: lRes = OnGetMinMaxInfo(uMsg, wParam, lParam, bHandled); break;
case WM_MOUSEWHEEL: lRes = OnMouseWheel(uMsg, wParam, lParam, bHandled); break;
case WM_SIZE: lRes = OnSize(uMsg, wParam, lParam, bHandled); break;
case WM_CHAR: lRes = OnChar(uMsg, wParam, lParam, bHandled); break;
case WM_SYSCOMMAND: lRes = OnSysCommand(uMsg, wParam, lParam, bHandled); break;
case WM_KEYDOWN: lRes = OnKeyDown(uMsg, wParam, lParam, bHandled); break;
case WM_KILLFOCUS: lRes = OnKillFocus(uMsg, wParam, lParam, bHandled); break;
case WM_SETFOCUS: lRes = OnSetFocus(uMsg, wParam, lParam, bHandled); break;
case WM_LBUTTONUP: lRes = OnLButtonUp(uMsg, wParam, lParam, bHandled); break;
case WM_LBUTTONDOWN: lRes = OnLButtonDown(uMsg, wParam, lParam, bHandled); break;
case WM_MOUSEMOVE: lRes = OnMouseMove(uMsg, wParam, lParam, bHandled); break;
case WM_MOUSEHOVER: lRes = OnMouseHover(uMsg, wParam, lParam, bHandled); break;
case WM_DPICHANGED: lRes = OnDpiChanged(uMsg, wParam, lParam, bHandled); break;
default: bHandled = FALSE; break;
}
if (bHandled) return lRes;
return Window::HandleMessage(uMsg, wParam, lParam);
}
LRESULT Window::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
bool handled = false;
LRESULT ret = DoHandlMessage(uMsg, wParam, lParam, handled);
if (handled) {
return ret;
}
else {
return CallWindowProc(uMsg, wParam, lParam);
}
}
在窗口基类函数中,定义了基本的消息处理方式
LRESULT Window::DoHandlMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& handled)
{
handled = false;
// Cycle through listeners
for (auto it = m_aMessageFilters.begin(); it != m_aMessageFilters.end(); it++)
{
BOOL bHandled = false;
LRESULT lResult = (*it)->MessageHandler(uMsg, wParam, lParam, bHandled);
if (bHandled && uMsg != WM_MOUSEMOVE) {
handled = true;
return lResult;
}
}
// Custom handling of events
switch (uMsg) {
case WM_APP + 1:
{
for (auto it = m_aDelayedCleanup.begin(); it != m_aDelayedCleanup.end(); it++) delete *it;
m_aDelayedCleanup.clear();
}
break;
case WM_CLOSE:
{
// Make sure all matching "closing" events are sent
if (m_pEventHover != NULL) {
m_pEventHover->HandleMessageTemplate(kEventMouseLeave);
}
if (m_pEventClick != NULL) {
m_pEventClick->HandleMessageTemplate(kEventMouseButtonUp);
}
if (m_pEventTouch != NULL) {
m_pEventTouch->HandleMessageTemplate(kEventMouseButtonUp);
}
if (m_pEventPointer != NULL) {
m_pEventPointer->HandleMessageTemplate(kEventMouseButtonUp);
}
SetFocus(NULL);
// Hmmph, the usual Windows tricks to avoid
// focus loss...
HWND hwndParent = GetWindowOwner(m_hWnd);
if (hwndParent != NULL) ::SetFocus(hwndParent);
}
break;
case WM_ERASEBKGND:
{
handled = true;
return 1;
}
case WM_PAINT:
{
Paint();
handled = true;
return 0;
}
// If any of the painting requested a resize again, we'll need
// to invalidate the entire window once more.
if (m_bIsArranged) {
::InvalidateRect(m_hWnd, NULL, FALSE);
}
handled = true;
return 0;
case WM_SIZE:
{
if (m_pFocus != NULL) {
m_pFocus->HandleMessageTemplate(kEventWindowSize);
}
if (m_pRoot != NULL) m_pRoot->Arrange();
if (wParam == SIZE_MAXIMIZED) {
m_shadow.MaximizedOrRestored(true);
}
else if (wParam == SIZE_RESTORED) {
m_shadow.MaximizedOrRestored(false);
}
}
handled = true;
return 0;
case WM_MOUSEHOVER:
{
m_bMouseTracking = false;
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
Control* pHover = FindControl(pt);
if (pHover == NULL) break;
// Generate mouse hover event
if (m_pEventHover != NULL) {
m_pEventHover->HandleMessageTemplate(kEventMouseHover, 0, 0, 0, pt);
}
// Create tooltip information
std::wstring sToolTip = pHover->GetToolTipText();
//if( sToolTip.empty() ) {
// handled = true;
// return 0;
//}
::ZeroMemory(&m_ToolTip, sizeof(TOOLINFO));
m_ToolTip.cbSize = sizeof(TOOLINFO);
m_ToolTip.uFlags = TTF_IDISHWND;
m_ToolTip.hwnd = m_hWnd;
m_ToolTip.uId = (UINT_PTR)m_hWnd;
m_ToolTip.hinst = ::GetModuleHandle(NULL);
m_ToolTip.lpszText = const_cast((LPCTSTR)sToolTip.c_str());
m_ToolTip.rect = pHover->GetPos();
if (m_hwndTooltip == NULL) {
m_hwndTooltip = ::CreateWindowEx(0, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hWnd, NULL, ::GetModuleHandle(NULL), NULL);
::SendMessage(m_hwndTooltip, TTM_ADDTOOL, 0, (LPARAM)&m_ToolTip);
}
::SendMessage(m_hwndTooltip, TTM_SETMAXTIPWIDTH, 0, pHover->GetToolTipWidth());
::SendMessage(m_hwndTooltip, TTM_SETTOOLINFO, 0, (LPARAM)&m_ToolTip);
::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&m_ToolTip);
}
handled = true;
return 0;
case WM_MOUSELEAVE:
{
if (m_hwndTooltip != NULL) ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM)&m_ToolTip);
if (m_bMouseTracking) ::SendMessage(m_hWnd, WM_MOUSEMOVE, 0, (LPARAM)-1);
m_bMouseTracking = false;
}
break;
case WM_MOUSEMOVE:
{
if (m_pEventTouch != NULL || m_pEventPointer != NULL)
break;
// Start tracking this entire window again...
if (!m_bMouseTracking) {
TRACKMOUSEEVENT tme = { 0 };
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.hwndTrack = m_hWnd;
tme.dwHoverTime = m_hwndTooltip == NULL ? 400UL : (DWORD) ::SendMessage(m_hwndTooltip, TTM_GETDELAYTIME, TTDT_INITIAL, 0L);
_TrackMouseEvent(&tme);
m_bMouseTracking = true;
}
// Generate the appropriate mouse messages
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
if (!HandleMouseEnterLeave(pt, wParam, lParam)) break;
if (m_pEventClick != NULL) {
m_pEventClick->HandleMessageTemplate(kEventMouseMove, wParam, lParam, 0, pt);
}
else if (m_pNewHover != NULL) {
m_pNewHover->HandleMessageTemplate(kEventMouseMove, wParam, lParam, 0, pt);
}
}
break;
case WM_LBUTTONDOWN:
{
if (m_pEventTouch != NULL || m_pEventPointer != NULL)
break;
// We alway set focus back to our app (this helps
// when Win32 child windows are placed on the dialog
// and we need to remove them on focus change).
::SetFocus(m_hWnd);
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
Control* pControl = FindControl(pt);
if (pControl == NULL) break;
if (pControl->GetWindow() != this) break;
m_pEventClick = pControl;
pControl->SetFocus();
SetCapture();
pControl->HandleMessageTemplate(kEventMouseButtonDown, wParam, lParam, 0, pt);
}
break;
case WM_RBUTTONDOWN:
{
if (m_pEventTouch != NULL || m_pEventPointer != NULL)
break;
::SetFocus(m_hWnd);
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
Control* pControl = FindControl(pt);
if (pControl == NULL) break;
if (pControl->GetWindow() != this) break;
m_pEventClick = pControl;
pControl->SetFocus();
SetCapture();
pControl->HandleMessageTemplate(kEventMouseRightButtonDown, wParam, lParam, 0, pt);
}
break;
case WM_LBUTTONDBLCLK:
{
if (m_pEventTouch != NULL || m_pEventPointer != NULL)
break;
::SetFocus(m_hWnd);
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
Control* pControl = FindControl(pt);
if (pControl == NULL) break;
if (pControl->GetWindow() != this) break;
m_pEventClick = pControl;
SetCapture();
pControl->HandleMessageTemplate(kEventInternalDoubleClick, wParam, lParam, 0, pt);
}
break;
case WM_LBUTTONUP:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
ReleaseCapture();
if (m_pEventClick == NULL) break;
m_pEventClick->HandleMessageTemplate(kEventMouseButtonUp, wParam, lParam, 0, pt);
m_pEventClick = NULL;
}
break;
case WM_RBUTTONUP:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
ReleaseCapture();
if (m_pEventClick == NULL) break;
m_pEventClick->HandleMessageTemplate(kEventMouseRightButtonUp, wParam, lParam, 0, pt);
//m_pEventClick = NULL;
}
break;
case WM_IME_STARTCOMPOSITION:
{
if (m_pFocus == NULL) break;
m_pFocus->HandleMessageTemplate(kEventImeStartComposition, wParam, lParam, wParam);
}
break;
case WM_IME_ENDCOMPOSITION:
{
if (m_pFocus == NULL) break;
m_pFocus->HandleMessageTemplate(kEventImeEndComposition, wParam, lParam, wParam);
}
break;
case WM_MOUSEWHEEL:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
::ScreenToClient(m_hWnd, &pt);
m_ptLastMousePos = pt;
Control* pControl = FindControl(pt);
if (pControl == NULL) break;
if (pControl->GetWindow() != this) break;
int zDelta = (int)(short)HIWORD(wParam);
pControl->HandleMessageTemplate(kEventMouseScrollWheel, zDelta, lParam);
}
break;
case WM_TOUCH:
{
unsigned int nNumInputs = (int)wParam;
TOUCHINPUT* pInputs = new TOUCHINPUT[nNumInputs];
// 只关心第一个触摸位置
if (nNumInputs >= 1 && GetTouchInputInfoWrapper((HTOUCHINPUT)lParam, nNumInputs, pInputs, sizeof(TOUCHINPUT)))
{
if (pInputs[0].dwID != 0)
{
POINT pt;
pt.x = TOUCH_COORD_TO_PIXEL(pInputs[0].x);
pt.y = TOUCH_COORD_TO_PIXEL(pInputs[0].y);
ScreenToClient(m_hWnd, &pt);
if (pInputs[0].dwFlags & TOUCHEVENTF_DOWN)
{
if (m_pEventClick != NULL || m_pEventPointer != NULL)
break;
::SetFocus(m_hWnd);
m_ptLastTouchPos = pt;
Control *pControl = FindControl(pt);
if (pControl == NULL) break;
if (pControl->GetWindow() != this) break;
m_pEventTouch = pControl;
pControl->SetFocus();
SetCapture();
pControl->HandleMessageTemplate(kEventTouchDown, 0, lParam, 0, pt);
}
else if (pInputs[0].dwFlags & TOUCHEVENTF_MOVE)
{
if (m_pEventClick != NULL || m_pEventPointer != NULL)
break;
if (m_ptLastTouchPos.x == pt.x && m_ptLastTouchPos.y == pt.y)
break;
m_ptLastTouchPos = pt;
if (m_pEventTouch == NULL) break;
if (!HandleMouseEnterLeave(pt, wParam, lParam)) break;
m_pEventTouch->HandleMessageTemplate(kEventTouchMove, 0, 0, 0, pt);
}
else if (pInputs[0].dwFlags & TOUCHEVENTF_UP)
{
m_ptLastTouchPos = pt;
ReleaseCapture();
if (m_pEventTouch == NULL) break;
m_pEventTouch->HandleMessageTemplate(kEventTouchUp, 0, lParam, 0, pt);
m_pEventTouch = NULL;
}
}
}
CloseTouchInputHandleWrapper((HTOUCHINPUT)lParam);
delete[] pInputs;
}
break;
case WM_POINTERDOWN:
case WM_POINTERUP:
case WM_POINTERUPDATE:
case WM_POINTERLEAVE:
case WM_POINTERCAPTURECHANGED:
{
if (!m_bHandlePointer) {
break;
}
// 只关心第一个触摸点
if (!IS_POINTER_PRIMARY_WPARAM(wParam)) {
handled = true;
break;
}
UINT32 pointerId = GET_POINTERID_WPARAM(wParam);
POINTER_INPUT_TYPE type;
if (!GetPointerTypeWrapper(pointerId, &type)) {
return 0;
}
POINT pt = { 0 };
FLOAT pressure = 0.0f;
switch (type)
{
case PT_TOUCH:
POINTER_TOUCH_INFO touchInfo;
if (!GetPointerTouchInfoWrapper(pointerId, &touchInfo)) {
return 0;
}
pt = touchInfo.pointerInfo.ptPixelLocationRaw;
pressure = (float)touchInfo.pressure / 1024;
break;
case PT_PEN:
POINTER_PEN_INFO penInfo;
if (!GetPointerPenInfoWrapper(pointerId, &penInfo)) {
return 0;
}
pt = penInfo.pointerInfo.ptPixelLocationRaw;
pressure = (float)penInfo.pressure / 1024;
break;
default:
return 0;
break;
}
ScreenToClient(m_hWnd, &pt);
switch (uMsg)
{
case WM_POINTERDOWN:
{
if (m_pEventClick != NULL || m_pEventTouch != NULL) {
handled = true;
break;
}
::SetFocus(m_hWnd);
m_ptLastMousePos = pt;
Control *pControl = FindControl(pt);
if (pControl == NULL) break;
if (pControl->GetWindow() != this) break;
m_pEventPointer = pControl;
pControl->SetFocus();
SetCapture();
pControl->HandleMessageTemplate(kEventPointDown, 0, lParam, 0, pt, pressure);
// 如果控件不支持处理WM_POINTERUPDATE消息,则不设置handled,程序会进入WM_BUTTON处理流程
if (m_pEventPointer && m_pEventPointer->IsReceivePointerMsg()) {
handled = true;
}
}
break;
case WM_POINTERUPDATE:
if (m_pEventClick != NULL || m_pEventTouch != NULL) {
handled = true;
break;
}
m_ptLastMousePos = pt;
// 如果没有按下,则不设置handled,程序会转换为WM_BUTTON类消息
if (m_pEventPointer == NULL) break;
if (!HandleMouseEnterLeave(pt, wParam, lParam)) break;
m_pEventPointer->HandleMessageTemplate(kEventPointMove, 0, 0, 0, pt, pressure);
// 如果控件不支持处理WM_POINTERUPDATE消息,则不设置handled,程序会进入WM_MOUSEMOVE处理流程
if (m_pEventPointer && m_pEventPointer->IsReceivePointerMsg()) {
handled = true;
}
break;
case WM_POINTERUP:
case WM_POINTERLEAVE:
case WM_POINTERCAPTURECHANGED:
m_ptLastMousePos = pt;
// 如果没有按下,则不设置handled,程序会转换为WM_BUTTON类消息
ReleaseCapture();
if (m_pEventPointer == NULL) break;
m_pEventPointer->HandleMessageTemplate(kEventPointUp, 0, lParam, 0, pt, pressure);
// 如果控件不支持处理WM_POINTERUPDATE消息,则不设置handled,程序会进入WM_BUTTON处理流程
if (m_pEventPointer && m_pEventPointer->IsReceivePointerMsg()) {
handled = true;
}
m_pEventPointer = NULL;
break;
default:
break;
}
return 0;
}
break;
case WM_SETFOCUS:
{
}
break;
case WM_KILLFOCUS:
{
Control *pControl = m_pEventClick ? m_pEventClick : NULL;
pControl = m_pEventTouch ? m_pEventTouch : pControl;
pControl = m_pEventPointer ? m_pEventPointer : pControl;
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
if (pControl == NULL || !pControl->IsNeedButtonUpWhenKillFocus()) break;
ReleaseCapture();
pControl->HandleMessageTemplate(kEventMouseButtonUp, wParam, lParam, 0, pt);
m_pEventClick = NULL;
m_pEventTouch = NULL;
m_pEventPointer = NULL;
}
break;
case WM_CONTEXTMENU:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
::ScreenToClient(m_hWnd, &pt);
m_ptLastMousePos = pt;
if (m_pEventClick == NULL) break;
ReleaseCapture();
m_pEventClick->HandleMessageTemplate(kEventInternalMenu, wParam, (LPARAM)m_pEventClick, 0, pt);
m_pEventClick = NULL;
}
break;
case WM_CHAR:
{
if (m_pFocus == NULL) break;
m_pFocus->HandleMessageTemplate(kEventChar, wParam, lParam, wParam);
}
break;
case WM_KEYDOWN:
{
if (m_pFocus == NULL) break;
m_pEventKey = m_pFocus;
m_pFocus->HandleMessageTemplate(kEventKeyDown, wParam, lParam, wParam);
}
break;
case WM_KEYUP:
{
if (m_pEventKey == NULL) break;
m_pEventKey->HandleMessageTemplate(kEventKeyUp, wParam, lParam, wParam);
m_pEventKey = NULL;
}
break;
case WM_SETCURSOR:
{
if (LOWORD(lParam) != HTCLIENT) break;
if (m_pEventClick != NULL || m_pEventTouch != NULL || m_pEventPointer != NULL) {
handled = true;
return 0;
}
POINT pt = { 0 };
::GetCursorPos(&pt);
::ScreenToClient(m_hWnd, &pt);
m_ptLastMousePos = pt;
Control* pControl = FindControl(pt);
if (pControl == NULL) break;
//if( (pControl->GetControlFlags() & UIFLAG_SETCURSOR) == 0 ) break;
pControl->HandleMessageTemplate(kEventSetCursor, wParam, lParam, 0, pt);
}
handled = true;
return 0;
case WM_NOTIFY:
{
LPNMHDR lpNMHDR = (LPNMHDR)lParam;
if (lpNMHDR != NULL) return ::SendMessage(lpNMHDR->hwndFrom, OCM__BASE + uMsg, wParam, lParam);
handled = true;
return 0;
}
break;
case WM_COMMAND:
{
if (lParam == 0) break;
HWND hWndChild = (HWND)lParam;
handled = true;
return ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);
}
break;
case WM_CTLCOLOREDIT:
case WM_CTLCOLORSTATIC:
{
// Refer To: http://msdn.microsoft.com/en-us/library/bb761691(v=vs.85).aspx
// Read-only or disabled edit controls do not send the WM_CTLCOLOREDIT message; instead, they send the WM_CTLCOLORSTATIC message.
if (lParam == 0) break;
HWND hWndChild = (HWND)lParam;
handled = true;
return ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam);
}
break;
default:
break;
}
return 0;
}
5 WM_CREATE 窗口创建消息时,duilib的处理
LRESULT WindowImplBase::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
::SetWindowLong(this->GetHWND(), GWL_STYLE, GetStyle());
Init(m_hWnd);
AddPreMessageFilter(this);
SetWindowResourcePath(GetSkinFolder());
WindowBuilder builder;
std::wstring strSkinFile = GetWindowResourcePath() + GetSkinFile();
auto callback = nbase::Bind(&WindowImplBase::CreateControl, this, std::placeholders::_1);
auto pRoot = (Box*)builder.Create(strSkinFile.c_str(), callback, this);
ASSERT(pRoot && L"Faield to load xml file.");
if (pRoot == NULL) {
TCHAR szErrMsg[MAX_PATH] = { 0 };
_stprintf_s(szErrMsg, L"Failed to load xml file %s", strSkinFile.c_str());
MessageBox(NULL, szErrMsg, _T("Duilib"), MB_OK | MB_ICONERROR);
return -1;
}
pRoot = m_shadow.AttachShadow(pRoot);
AttachDialog(pRoot);
InitWindow();
if (pRoot->GetFixedWidth() == DUI_LENGTH_AUTO || pRoot->GetFixedHeight() == DUI_LENGTH_AUTO) {
CSize maxSize(99999, 99999);
CSize needSize = pRoot->EstimateSize(maxSize);
if( needSize.cx < pRoot->GetMinWidth() ) needSize.cx = pRoot->GetMinWidth();
if( pRoot->GetMaxWidth() >= 0 && needSize.cx > pRoot->GetMaxWidth() ) needSize.cx = pRoot->GetMaxWidth();
if( needSize.cy < pRoot->GetMinHeight() ) needSize.cy = pRoot->GetMinHeight();
if( needSize.cy > pRoot->GetMaxHeight() ) needSize.cy = pRoot->GetMaxHeight();
::MoveWindow(m_hWnd, 0, 0, needSize.cx, needSize.cy, FALSE);
}
Control *pControl = (Control*)FindControl(L"closebtn");
if (pControl) {
Button *pCloseBtn = dynamic_cast
在OnCreate中,duilib做了这样几件工作:
1)设置窗口样式,取消窗口默认标题栏
::SetWindowLong(this->GetHWND(), GWL_STYLE, GetStyle());
LONG WindowImplBase::GetStyle()
{
LONG styleValue = ::GetWindowLong(GetHWND(), GWL_STYLE);
styleValue &= ~WS_CAPTION;
return styleValue;
}
2)窗口句柄相关的初始化
void Window::Init(HWND hWnd)
{
m_hWnd = hWnd;
ASSERT(::IsWindow(hWnd));
// Remember the window context we came from
m_hDcPaint = ::GetDC(hWnd);
// We'll want to filter messages globally too
GlobalManager::AddPreMessage(this);
m_renderContext = GlobalManager::CreateRenderContext();
RegisterTouchWindowWrapper(hWnd, 0);
}
3)解析XML样式文件
WindowBuilder builder;
std::wstring strSkinFile = GetWindowResourcePath() + GetSkinFile();
auto callback = nbase::Bind(&WindowImplBase::CreateControl, this, std::placeholders::_1);
auto pRoot = (Box*)builder.Create(strSkinFile.c_str(), callback, this);
ASSERT(pRoot && L"Faield to load xml file.");
if (pRoot == NULL) {
TCHAR szErrMsg[MAX_PATH] = { 0 };
_stprintf_s(szErrMsg, L"Failed to load xml file %s", strSkinFile.c_str());
MessageBox(NULL, szErrMsg, _T("Duilib"), MB_OK | MB_ICONERROR);
return -1;
}
其中,pRoot为XML样式中的根Box
duilib使用WindowBuilder类对XML进行解析
class UILIB_API WindowBuilder
{
public:
WindowBuilder();
Box* Create(STRINGorID xml, CreateControlCallback pCallback = CreateControlCallback(),
Window* pManager = nullptr, Box* pParent = nullptr, Box* pUserDefinedBox = nullptr);
Box* Create(CreateControlCallback pCallback = CreateControlCallback(), Window* pManager = nullptr,
Box* pParent = nullptr, Box* pUserDefinedBox = nullptr);
CMarkup* GetMarkup();
void GetLastErrorMessage(LPTSTR pstrMessage, SIZE_T cchMax) const;
void GetLastErrorLocation(LPTSTR pstrSource, SIZE_T cchMax) const;
private:
Control* _Parse(CMarkupNode* parent, Control* pParent = NULL, Window* pManager = NULL);
Control* CreateControlByClass(const std::wstring& strControlClass);
void AttachXmlEvent(bool bBubbled, CMarkupNode& node, Control* pParent);
private:
CMarkup m_xml;
CreateControlCallback m_createControlCallback;
};
4) 阴影BOX
pRoot = m_shadow.AttachShadow(pRoot);
Box*Shadow::AttachShadow(Box* pRoot)
{
if (!m_bShadowAttached)
return pRoot;
m_pRoot = new ShadowBox();
m_pRoot->GetLayout()->SetPadding(m_rcCurShadowCorner, false);
int rootWidth = pRoot->GetFixedWidth();
if (rootWidth > 0) {
rootWidth += m_rcCurShadowCorner.left + m_rcCurShadowCorner.right;
}
m_pRoot->SetFixedWidth(rootWidth, true, false);
int rootHeight = pRoot->GetFixedHeight();
if (rootHeight > 0) {
rootHeight += m_rcCurShadowCorner.top + m_rcCurShadowCorner.bottom;
}
m_pRoot->SetFixedHeight(rootHeight, false);
if (m_bUseDefaultImage)
{
CSize size(3, 3);
pRoot->SetBorderRound(size);
}
m_pRoot->Add(pRoot);
m_pRoot->SetBkImage(m_strImage);
return m_pRoot;
}
如果不添加阴影时,直接返回
if (!m_bShadowAttached)
return pRoot;
否则,生成一个BOX m_pRoot = new ShadowBox();作为根BOX
m_pRoot->Add(pRoot);
将从XML样式中获得的原根BOX作为阴影BOX的子容器。
也就是说,有阴影属性时,duilib内部暗自添了个顶层根BOX
5) 窗口初始化
pRoot = m_shadow.AttachShadow(pRoot);
AttachDialog(pRoot);
InitWindow();
virtual void InitWindow(){}
这是虚函数,直接跳转到派生类的初始化函数中
最后是最大最小还原按钮的默认处理方式