65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (9投票s)

2002年2月21日

6分钟阅读

viewsIcon

105250

downloadIcon

3242

一篇关于实现吸附窗口框架的文章。

Sample Image

请参阅本文档下方演示如何构建

引言

我想分享我第一次尝试添加类似于 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

当浮动时,snapPinnedsnapMinibar 标志无效。

结构 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

要解决的更棘手的问题之一是在吸附位置之间以及吸附状态和浮动状态之间拖动窗口。经过多次失败的尝试,我现在自己管理窗口的拖动。在此过程中,我存储了光标的起始点以及到窗口上参考点的偏移量。诀窍是移动参考点并根据窗口吸附到的侧面更新偏移量。

例如,当吸附到右下角时,窗口的右下角是参考点。将窗口拖动到浮动位置时,重要的是右下角保持在相同的位置,而不受窗口状态的影响。如果窗口现在被拖动并吸附到左上角位置,同样重要的是保持窗口的左上角位置在相同的位置,而不受状态的影响。

我决定保持内部窗口的大小不变,也就是视图的大小。我认为这在视图基于对话框的情况下会有所帮助。然而,这个决定并没有使移动变得更容易,因为窗口的外部大小现在会根据吸附上下文或窗口是否浮动而改变。因此,需要精心设计的参考点与偏移量来管理拖动。

CSnapWindowInfoCSnapTrackInfo 用于在窗口拖动模式下跟踪鼠标移动事件时管理状态。模板类 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
};

就这样,各位!

© . All rights reserved.