原创 VisualFC
本文假设读者熟悉WTL,并且已经了解和使用过WTL的分隔窗口。WTL的分隔窗口简单易用,并且可以用在对话框窗口程序中,但我们也发现使用WTL分隔窗口的一些问题,即WTL分隔窗口中的面板必须为其子窗口,这样就无法接受主对话框窗口的TAB控制,在对话框应用程序中必须将对话框控件的父窗口设置为WTL分隔窗口,然后后再添加到到WTL分隔窗口中,代码如下:
CWindow ed1
=
GetDlgItem(IDC_EDIT1);
CWindow ed2
=
GetDlgItem(IDC_EDIT2);
ed1.SetParent(m_wndHorSplit);
ed2.SetParent(m_wndHorSplit);
m_wndHorSplit.SetSplitterPanes( ed1,ed2 );
可以看到,必须将控件的父窗口设置为WTL分隔窗口,我们可以通过修改WTL的分隔窗口源代码来修正这一点,修改后,控件的父窗口仍然是主对话框窗口,如此编程就简单了。代码如下:
m_wndHorSplit.SetSplitterPanes( GetDlgItem(IDC_EDIT1), GetDlgItem(IDC_EDIT2) );
我们来修改WTL分隔窗口源文件atlsplit.h,修改位置在类CSplitterImpl的void UpdateSplitterLayout()函数中,修改如下:
将下列的原始代码
if
(m_hWndPane[nPane]
!=
NULL)
...
{
::SetWindowPos(m_hWndPane[nPane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
}
更改为下列修改后的代码
if
(m_hWndPane[nPane]
!=
NULL)
...
{
//fix: 2007-12-06
HWND hWndParent = ::GetParent(m_hWndPane[nPane]);
if (hWndParent != pT->m_hWnd)
...{
pT->ClientToScreen(&rect);
CWindow(hWndParent).ScreenToClient(&rect);
}
::SetWindowPos(m_hWndPane[nPane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
}
同理将下面的原始代码
if
(m_hWndPane[m_nSinglePane]
!=
NULL)
...
{
::SetWindowPos(m_hWndPane[m_nSinglePane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
}
更改为下面修改后的代码
if
(m_hWndPane[m_nSinglePane]
!=
NULL)
...
{
//fix: 2007-12-06
HWND hWndParent = ::GetParent(m_hWndPane[m_nSinglePane]);
if (hWndParent != pT->m_hWnd)
...{
pT->ClientToScreen(&rect);
CWindow(hWndParent).ScreenToClient(&rect);
}
::SetWindowPos(m_hWndPane[m_nSinglePane], NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
}
最后我们还得处理SW_SHOWWINDOW消息以在WTL分隔窗口隐藏时能够将面板窗口也隐藏起来,为了不对atlsplit作更多的改动,以及方便在对话框应用程序中使用,我们从修正后的WTL分隔窗口派生一个新的分隔窗口类来实现,代码如下:
namespace
WTL
...
{
template <bool t_bVertical = true>
class CSplitterWindowExT : public CSplitterWindowImpl<CSplitterWindowT<t_bVertical>, t_bVertical>
...{
public:
DECLARE_WND_CLASS_EX(_T("WTL_SplitterWindowEx"), CS_DBLCLKS, COLOR_WINDOW)
typedef CSplitterWindowImpl<CSplitterWindowT<t_bVertical>, t_bVertical> _baseClass;
HWND CreateFromID(HWND hWndParent, UINT nDlgItemID)
...{
return CreateFromWindow(::GetDlgItem(hWndParent,nDlgItemID));
}
HWND CreateFromWindow(HWND hWnd)
...{
CWindow wnd = hWnd;
CWindow parent = wnd.GetParent();
UINT nID = wnd.GetDlgCtrlID();
RECT rc;
wnd.GetWindowRect(&rc);
DWORD dwStyle = wnd.GetStyle();
dwStyle |= (WS_CHILD | WS_VISIBLE);
DWORD dwExStyle = wnd.GetExStyle();
parent.ScreenToClient(&rc);
HWND hThisWnd = Create(parent,rc,NULL,dwStyle,dwExStyle,nID,NULL);
if (hThisWnd)
...{
wnd.DestroyWindow();
}
return hThisWnd;
}
public:
BEGIN_MSG_MAP(CSplitterWindowExT)
MESSAGE_HANDLER(WM_SHOWWINDOW, OnShowWindow)
CHAIN_MSG_MAP(_baseClass)
END_MSG_MAP()
LRESULT OnShowWindow(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
...{
if (m_nSinglePane == SPLIT_PANE_NONE)
...{
if ( m_hWndPane[SPLIT_PANE_LEFT] )
::ShowWindow( m_hWndPane[SPLIT_PANE_LEFT], (BOOL)wParam);
if ( m_hWndPane[SPLIT_PANE_RIGHT] )
::ShowWindow( m_hWndPane[SPLIT_PANE_RIGHT], (BOOL)wParam);
}
else
...{
if ( m_hWndPane[m_nSinglePane] )
::ShowWindow( m_hWndPane[m_nSinglePane], (BOOL)wParam);
}
bHandled = FALSE;
return 0;
}
};
typedef CSplitterWindowExT<true> CSplitterWindowEx;
typedef CSplitterWindowExT<false> CHorSplitterWindowEx;
}
;
//
namespace WTL
我们看到CSplitterWindowEx包含一个新的函数CreateFromID和CreateFromWindow,这两个函数的目的是可以从对话框窗口的一个占位用静态窗口来生成扩展的分隔窗口对象,并从其中继承窗口属性。这样我们可以VS60资源设计器来进行分隔窗口的位置设计及窗口属性设计。
资源编辑器:
我们使用IDC_PANE2_STATIC和IDC_PANE_STATIC这两个Picture控件来实现占位操作。目的有三,一是方便从这两个占位控件生成分隔窗口控制,二是通过资源编辑器更改Picure控件的窗口属性可以控制生成的分隔窗口的窗口属性。三是可以使用CDialogResize直接控制分隔窗口的缩放布局。代码如下:
CSplitterWindowEx m_wndSplit;
CHorSplitterWindowEx m_wndHorSplit;
初始化分隔窗口控制的代码放在OnInitDialog函数中。
m_wndHorSplit.CreateFromWindow(GetDlgItem(IDC_PANEL2_STATIC));
m_wndHorSplit.SetSplitterPanes( GetDlgItem(IDC_EDIT1), GetDlgItem(IDC_EDIT2) );
m_wndHorSplit.SetSplitterPos();
m_wndSplit.CreateFromID(m_hWnd,IDC_PANEL_STATIC);
m_wndSplit.SetSplitterPanes(GetDlgItem(IDC_LIST1), m_wndHorSplit);
m_wndSplit.SetSplitterPos(
100
);
怎么样,比修改前的WTL分隔窗口用起来简单多了吧。试一下运行效果,支持TAB顺序切换。与上图中VS60资源编辑器设计相比,是不是所见即所得。:)
运行效果图
对于CDialogResize布局控制我们可以通过VisualFC的WTL类向导来自动生成,生成后的代码如下:
BEGIN_DLGRESIZE_MAP(CMainDlg)
DLGRESIZE_CONTROL(IDC_BUTTON1, DLSZ_MOVE_X)
DLGRESIZE_CONTROL(ID_APP_ABOUT, DLSZ_MOVE_X
|
DLSZ_MOVE_Y)
DLGRESIZE_CONTROL(IDOK, DLSZ_MOVE_X)
DLGRESIZE_CONTROL(IDCANCEL, DLSZ_MOVE_X)
DLGRESIZE_CONTROL(IDC_PANEL_STATIC, DLSZ_SIZE_X
|
DLSZ_SIZE_Y)
END_DLGRESIZE_MAP()
需要注意一点是不要将IDC_EDIT1、IDC_EDIT2和IDC_LIST1以及IDC_PANEL2_STATIC添加到DLGRESIZE中,因为这些控件虽然仍属于主窗口,但已经为WTL分隔窗口所管理了。
最后回顾一下,我们对WTL的atlsplit.h文件进行修改,完善了WTL的分隔窗口类,允许对话框程序中控件的父窗口不更改也可添加到WTL的分隔窗口中,我们通过一个派生的分隔窗口类atlsplitex.h实现了对话框分隔窗口的直接控制,包括使用VS60资源编辑器所见即所得的设计以及对缩放布局的支持。
本文代码下载:wtlsplitex.zip
代码中包括修改后的atlsplit.h和atlsplitex.h文件,你可以将atlsplit.h文件替换WTL80中的atlsplit.h文件,它的表现与原来的atlsplit.h文件完全一致。