vc层叠式窗体控件指南[组图](5)
以上处理了控件的排列和显示。
让我们现在添加一个调用到PreSubclassWindow以去除图片控件周围的黑框。在资源编辑器工作时这是有效的,在应用程序运行时它是不必的且难看。
void CStackedWndCtrl::PreSubclassWindow()
{
// 移除黑框并夹住子控件以避免闪烁
ModifyStyle( SS_BLACKFRAME, WS_CLIPCHILDREN );
CStatic::PreSubclassWindow();
}我们已经获得机会添加WS_CLIPCHILDREN 标志以在重新改变控件尺寸时减少闪烁,这提醒我…
…确保该控件能在需要时改变自己的尺寸大小总是一个好主意。在此情况中,该功能是相当容易实现的。调出Classwizard,为WM_SIZE添加一个消息句柄,并做一个调用到RearrangeStack。
void CStackedWndCtrl::OnSize(UINT nType, int cx, int cy)
{
CStatic::OnSize(nType, cx, cy);
RearrangeStack();
}
我们几乎已经做好了。如果你添加一些测试面板,编译并运行;这个层叠式控件将显示所有标题窗体和最后的面板的内容窗体。
当然,这个控件不会对用户点击标题窗体做出反应。我们还没有为其写响应代码啊。它是我们任务清单上的下一个也是最后一个任务了。
标题窗体的惟一需求
至于我们的控件,标题和内容窗体可以是任何一种窗体。照字面意思,可以是对话框、static控件、列表框/控件、树控件、日历控件、编辑框/ richedit控件、generic窗体、甚至自定义控件。如果我们可以获得一个指向它的CWnd指针,CStackedWndCtrl类会如预期一样工作。这里惟一的限制是常识,而不是一个技术问题。举个例子,一个组合框可能被设置为标题或内容窗体,但是其适宜性相当值得怀疑。
然而,这里有一个必要条件,同时它被应用于标题窗体。当它被点击,它必须通知其父(一个CStackedWndCtrl 对象)以使得相关内容窗体可以被显示。我们将通过发送一个消息完成这个任务。
为了简化,我将用按钮作为标题窗体。它们毕竟是绝大多数可能的选择。我们将从CButton继承一个类,并且添加这个有点特别的功能。
那么,我们现在创建一个继承于CButton的名为CTelltaleButton的类。添加下面的消息定义到它的头文件,和一个BN_CLICKED(反射消息)的消息处理程序。
// In TelltaleButton.h
#define WM_RUBRIC_WND_CLICKED_ON ( WM_APP + 04100 )
// In TelltaleButton.cpp
void CTelltaleButton::OnClicked()
{
GetParent()->SendMessage( WM_BUTTON_CLICKED, (WPARAM)this->m_hWnd );
}标题窗体将发送一个包含其自己句柄的消息,如wParam,有了这个信息,它的父控件将可以了解到哪一个标题窗体已经被点击了。
现在,我们通过手工添加一个方法到其消息映射在CStackedWndCtrl中处理这个消息如下:
// In StackedWndCtrl.h
#define WM_RUBRIC_WND_CLICKED_ON ( WM_APP + 04100 )
...
...
// 生成消息映射函数
protected:
//{{AFX_MSG(CStackedWndCtrl)
afx_msg void OnSize(UINT nType, int cx, int cy);
//}}AFX_MSG
afx_msg LRESULT OnRubricWndClicked(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
// In StackedWndCtrl.cpp
...
...
BEGIN_MESSAGE_MAP(CStackedWndCtrl, CStatic)
//{{AFX_MSG_MAP(CStackedWndCtrl)
ON_WM_SIZE()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_RUBRIC_WND_CLICKED_ON, OnRubricWndClicked)
END_MESSAGE_MAP()
...
...
LRESULT CStackedWndCtrl::OnRubricWndClicked(WPARAM wParam, LPARAM /*lParam*/)
{
HWND hwndRubric = (HWND)wParam;
BOOL bRearrange = FALSE;
for( int i = 0; i < m_arrPanes.GetSize(); i++ )
if( m_arrPanes[ i ]->m_pwndRubric->m_hWnd == hwndRubric )
{
// 只有除了属于当前已打开面板的一个标题窗体被点击时才重新排列控件
if( m_arrPanes[ i ]->m_bOpen == FALSE )
{
m_arrPanes[ i ]->m_bOpen = TRUE;
bRearrange = TRUE;
}
}
else
m_arrPanes[ i ]->m_bOpen = FALSE;
if( bRearrange )
RearrangeStack();
// 如果已发送消息的标题窗体希望知道是否控件已被重新排列,返回标志
return bRearrange;
}
它完全归结为遍历面板以寻找已被点击的标题窗体。如果它不同于当前打开的面板的那个(标题窗体),就重新排列控件。
一些Eye Candy
因为对于它的标题和内容窗体所可以被使用的控件来说CStackedWndCtrl是非常灵活,这就很容易使其样式活泼起来。为了演示如何做到这样,我已经在演示工程中包含了一个"普通"控件和一个Davide Calabro的阴影按钮及Everaldo Coelho的图标的控件。正如你能看到的,通过检查演示工程中的代码,没有一行在CStackedWndCtrl中的代码需要被修改。正如其所应该的那样。
我们的短暂的旅程就要结束了,我的朋友;我们从此会各走各路了。我希望你已经用我已向你展示的东西来播下你想像力的种子,而且我们的quiet dealings会对你有益。
反馈
我的意图是提供一个编码清楚的指南,它理解和学习起来务求尽可能的简单。我确信会有比我这里这个功能的实现更好的解决方案。任何关于改进、简单化或更好解释代码的建议我都欢迎。