可吸附并自动隐藏的工具窗口





5.00/5 (9投票s)
2002年2月21日
6分钟阅读

105250

3242
一篇关于实现吸附窗口框架的文章。
请参阅本文档下方演示如何构建。
引言
我想分享我第一次尝试添加类似于 MS Visio 等应用程序中找到的工具窗口的经验。因此,这里实现了可吸附工具窗口(吸附到视图窗口的侧边),该窗口还提供自动隐藏和固定功能,参见上图。
感谢 Bjarke Viksoe 的停靠框架。尽管这段代码是从头开始编写的,但他的框架让我开始了。 请参阅 atldock.h。
参考
CSnappingWindow
是用于管理可吸附视图的主窗口。它负责在框架调整大小时更新位置等,并提供用于添加和定位可吸附视图的 API。
CSnappingWindow
成员
SetClient
设置要吸附边缘的客户端窗口。 AddSnappableWindow
添加一个可吸附视图。 FloatWindow
将视图浮动到指定位置。 SnapWindow
将视图吸附到指定边缘。 HideWindow
无论视图是吸附还是浮动,都将其隐藏。
CSnappingWindow
成员详细信息
void SetClient(HWND hWndClient)
HWND hWndClient
是视图吸附到的客户端视图边缘SNAPCONTEXT* AddSnappableWindow(HWND hWndView)
HWND hWndView
是要吸附的视图(任何窗口)SNAPCONTEXT*
是指向吸附上下文结构的指针,如果失败则为NULL
备注: 视图最初处于隐藏状态。 void FloatWindow(HWND hWndView, const POINT& ptPos, DWORD dwFlags)
HWND hWndView
是使用AddSnappableWindow
添加到框架的视图const POINT& ptPos
窗口将放置的左上角坐标DWORD dwFlags
附加标志,默认为零备注: 在调用此方法之前,视图需要处于隐藏状态。 void SnapWindow(HWND hWndView, SnapPosition spPos, int cxy,
DWORD dwFlags)HWND hWndView
是使用AddSnappableWindow
添加到框架的视图SnapPosition spPos
吸附位置int cxy
距离顶部或左边缘的偏移量,取决于spPos
,默认为零DWORD dwFlags
附加标志,默认为snapMinibar
备注: 在调用此方法之前,视图需要处于隐藏状态。 void HideWindow(HWND hWndView)
HWND hWndView
是使用AddSnappableWindow
添加到框架的视图
SnapPosition
枚举强制执行允许的位置标志组合。
snapFloat
0x00000000 snapLeft
0x00000001 snapTop
0x00000002 snapRight
0x00000004 snapBottom
0x00000008 snapTopLeft
0x00000003 snapTopRight
0x00000006 snapBottomLeft
0x00000009 snapBottomRight
0x0000000C snapHidden
0x00000010
用于管理 dwFlags
属性的附加标志和掩码
状态标志 snapPinned
0x00000100 snapMinibar
0x00000200 掩码 snapPosition
0x000000FF snapState
0x00FFFF00 snapReserved
0xFF000000 当浮动时,
snapPinned
和snapMinibar
标志无效。
结构 SNAPCONTEXT
是可吸附窗口的上下文。
HWND hWndSnapped
吸附窗口句柄 HWND hWndFloated
浮动窗口句柄 HWND hWndView
视图窗口句柄 HWND hWndView
吸附窗口管理器 DWORD dwFlags
位置和状态标志 在使用此框架时,了解此结构并不重要,但对于扩展它则很重要。
实现细节
为框架定义了以下自定义 Windows 消息
#ifndef SNAP_MSGBASE #define SNAP_MSGBASE WM_USER+860 #endif #define WM_SNAP_FLOAT SNAP_MSGBASE #define WM_SNAP_SNAP SNAP_MSGBASE + 1 #define WM_SNAP_HIDE SNAP_MSGBASE + 2 #define WM_SNAP_QUERYRECT SNAP_MSGBASE + 3 #define WM_SNAP_MOVEDONE SNAP_MSGBASE + 4 #define WM_SNAP_REPOSITION SNAP_MSGBASE + 5 #define WM_SNAP_UPDATELAYOUT SNAP_MSGBASE + 6 #define WM_SNAP_QUERYSIZE SNAP_MSGBASE + 7
要解决的更棘手的问题之一是在吸附位置之间以及吸附状态和浮动状态之间拖动窗口。经过多次失败的尝试,我现在自己管理窗口的拖动。在此过程中,我存储了光标的起始点以及到窗口上参考点的偏移量。诀窍是移动参考点并根据窗口吸附到的侧面更新偏移量。
例如,当吸附到右下角时,窗口的右下角是参考点。将窗口拖动到浮动位置时,重要的是右下角保持在相同的位置,而不受窗口状态的影响。如果窗口现在被拖动并吸附到左上角位置,同样重要的是保持窗口的左上角位置在相同的位置,而不受状态的影响。
我决定保持内部窗口的大小不变,也就是视图的大小。我认为这在视图基于对话框的情况下会有所帮助。然而,这个决定并没有使移动变得更容易,因为窗口的外部大小现在会根据吸附上下文或窗口是否浮动而改变。因此,需要精心设计的参考点与偏移量来管理拖动。
类 CSnapWindowInfo
和 CSnapTrackInfo
用于在窗口拖动模式下跟踪鼠标移动事件时管理状态。模板类 CSnapWindowMover
用于浮动和吸附窗口实现中,并包含根据位置移动窗口和改变其状态的逻辑。
模板类 CSnapFloatingWindowImpl
实现了与浮动窗口相关的所有事件和逻辑,并派生自 CSnapWindowMover
。
// CSnapFloatingWindow and CSnapFloatingWindowImpl // typedef CWinTraits<WS_POPUPWINDOW|WS_CLIPSIBLINGS| WS_OVERLAPPED|WS_THICKFRAME|WS_DLGFRAME, WS_EX_TOOLWINDOW|WS_EX_WINDOWEDGE> CSnapFloatWinTraits; template<class T, class TBase=CWindow, class TWinTraits=CSnapFloatWinTraits> class ATL_NO_VTABLE CSnapFloatingWindowImpl : public CWindowImpl< T, TBase, TWinTraits >, public CSnapWindowMover<T> class CSnapFloatingWindow : public CSnapFloatingWindowImpl<CSnapFloatingWindow>
模板类 CSnapAutoHideWindowImpl
实现了与吸附窗口相关的所有事件和逻辑。此窗口使用计时器来跟踪鼠标是否在扩展边界之外,如果是,它将“自动隐藏”到迷你栏状态。计时器仅为未固定的展开窗口创建,并在返回迷你栏状态时销毁。CSnapAutoHideWindowImpl
派生自 CSnapWindowMover
。
// CSnapAutoHideWindow and CSnapAutoHideWindowImpl // typedef CWinTraits<WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_THICKFRAME, WS_EX_WINDOWEDGE> CSnapAutoHideWinTraits; template<class T, class TBase=CWindow, class TWinTraits=CSnapAutoHideWinTraits> class ATL_NO_VTABLE CSnapAutoHideWindowImpl : public CWindowImpl< T, TBase, TWinTraits >, public CSnapWindowMover<T> // Timer id and interval used for auto hide enum { IDT_AUTOHIDE = 1234, IDT_INTERVAL = 500 }; class CSnapAutoHideWindow : public CSnapAutoHideWindowImpl<CSnapAutoHideWindow>
模板类 CSnappingWindowImpl
实现了与吸附窗口管理器相关的所有事件和逻辑。正如您在下面的代码片段中看到的,吸附和浮动窗口实现可以用您自己的扩展替换。
// CSnappingWindow and CSnappingWindowImpl // template<class T, class TSnappedWindow = CSnapAutoHideWindow, class TFloatingWindow = CSnapFloatingWindow, class TBase = CWindow, class TWinTraits = CControlWinTraits< class ATL_NO_VTABLE CSnappingWindowImpl : public CWindowImpl<T, TBase, TWinTraits> class CSnappingWindow : public CSnappingWindowImpl<CSnappingWindow<
当使用 AddSnappableWindow
成员函数将视图添加到 CSnappingWindow
实例时,会创建浮动和吸附窗口类。
SNAPCONTEXT* AddSnappableWindow(HWND hWndView) { ATLASSERT( ::IsWindow(hWndView) ); if (!::IsWindow(hWndView)) return NULL; // Initialize context SNAPCONTEXT* pCtx = new SNAPCONTEXT; ::ZeroMemory(pCtx, sizeof(SNAPCONTEXT)); pCtx->hWndView = hWndView; pCtx->hWndRoot = m_hWnd; pCtx->dwFlags = snapHidden; // Is in hidden state // Create snapping window TSnappedWindow* wndSnapped = new TSnappedWindow(pCtx); ATLASSERT(wndSnapped); wndSnapped->Create(m_hWnd, rcDefault, NULL); ATLASSERT(::IsWindow(wndSnapped->m_hWnd)); pCtx->hWndSnapped = *wndSnapped; // Create floating window TFloatingWindow* wndFloating = new TFloatingWindow(pCtx); ATLASSERT(wndFloating); TCHAR szCaption[128]; // max text length is 127 for floating caption ::GetWindowText(hWndView, szCaption, sizeof(szCaption)/sizeof(TCHAR)); wndFloating->Create(m_hWnd, rcDefault, szCaption); ATLASSERT(::IsWindow(wndFloating->m_hWnd)); pCtx->hWndFloated = *wndFloating; // Store context pointer in the container // (used for lookup and for memory mgmnt) m_snappableWindows.Add(pCtx); return pCtx; }
默认布局是根据客户端窗口矩形计算的,并考虑了滚动条。请参阅文章顶部的图像。
void QueryRect(RECT& rect) { // Typically override this method to calculate your client layout T* pT = static_cast<T*>(this); HWND hWndClient = pT->GetClient(); ::GetWindowRect(hWndClient ,&rect); LONG style = ::GetWindowLong(hWndClient,GWL_STYLE); if (style & WS_VSCROLL) { rect.right -= ::GetSystemMetrics(SM_CXVSCROLL); } if (style & WS_HSCROLL) { rect.bottom -= ::GetSystemMetrics(SM_CYHSCROLL); } // Compensate for 3D edge of client window LONG styleEx = ::GetWindowLong(hWndClient,GWL_EXSTYLE); if (styleEx & WS_EX_CLIENTEDGE) ::InflateRect(&rect, -2, -2); }
待办
不分先后
- 持久化类,用于帮助在会话之间存储和检索位置。
- 同步工具窗口和框架窗口之间的活动状态。这内置在 MFC 中,但未内置在 WTL 中。
- 可能作为上述项目的一部分,当父框架不再活动时隐藏浮动工具窗口。当使用浮动工具窗口并运行多个应用程序实例时,屏幕会变得拥挤。
演示如何构建
运行 WTL 应用程序向导生成一个带有 Edit 视图的 SDI 应用程序,从而创建了演示应用程序。之后,我添加了 wtlsnappable 头文件和一个要吸附的视图窗口。下面介绍的 CSnapView
只是一个带有绿色背景的虚拟视图窗口,用于展示吸附框架的功能。请注意,这里不需要为吸附框架添加额外的代码。
// //A dummy view // class CSnapView : public CWindowImpl<CSnapView> { public: DECLARE_WND_CLASS(NULL) BOOLPreTranslateMessage(MSG* pMsg) { pMsg; return FALSE; } BEGIN_MSG_MAP(CSnapView) MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd) END_MSG_MAP() LRESULT OnEraseBkgnd(UINT, WPARAM wParam, LPARAM, BOOL&) { HDC dc = (HDC)wParam; HBRUSH hBrush = ::CreateSolidBrush(RGB(0,128,0)); RECT rc; GetClientRect(&rc); ::FillRect(dc,&rc, hBrush); ::DeleteObject(hBrush); return 1; } };
CSnappingWindow
作为成员添加到 CMainFrame
,以及一些 CSnapView
成员。视图在 WM_CREATE
消息处理程序中添加到吸附窗口中,请参阅下面的代码片段
// // MainFrm.h // // Include the snappable framework header file #include "wtlsnappable.h" class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler { public: // Window for the snappable window framework CSnappingWindow m_snapWindow; // Snappable views (normal WTL views) CSnapView m_view1,m_view2,m_view3,m_view4,m_view5; // ... WTL Wizard code omitted LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) { // Initiate snapping framework m_hWndClient = m_snapWindow.Create(m_hWnd, rcDefault, NULL, WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_VISIBLE); // ... Menu and toolbar setup omitted (see demo project) HWND hWndView = m_view.Create(m_hWndClient , rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_NOHIDESEL, WS_EX_CLIENTEDGE); m_snapWindow.SetClient(hWndView); // ... More wizard code omitted // Create and add the five views // Snapping window to a side will auto-hide them by default RECT rcView1 = {0,0,200,300}; RECT rcView2 = {0,0,200,200}; POINT ptFloat = {100,100}; m_view1.Create(m_hWnd, rcView1, _T("View 1"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view1); m_snapWindow.FloatWindow(m_view1,ptFloat); m_view2.Create(m_hWnd, rcView2, _T("View 2"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view2); m_snapWindow.SnapWindow(m_view2, snapTopLeft); m_view3.Create(m_hWnd, rcView1, _T("View 3"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view3); m_snapWindow.SnapWindow(m_view3, snapTop, 100); m_view4.Create(m_hWnd, rcView2, _T("View 4"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view4); // Forced view to stay open (will be pinned) m_snapWindow.SnapWindow(m_view4,snapBottomRight,0,snapPinned); m_view5.Create(m_hWnd, rcView2, _T("View 5"), SNAP_DEFAULT_VIEW_STYLE, WS_EX_CLIENTEDGE); m_snapWindow.AddSnappableWindow(m_view5); m_snapWindow.SnapWindow(m_view5, snapTop, 350); return 0; } // ... WTL Wizard code omitted };
就这样,各位!