CKCSideBannerWnd:一款MFC横幅控件,可为大多数窗口增添专业外观...






4.94/5 (92投票s)
一个CWnd派生控件,可以附加到任何窗口,而无需程序员为此进行任何准备

引言
编写软件已成为一门艺术。我一直觉得,编写针对“窗口化”环境的软件的程序员有两种选择:
- 编写软件;给它一个可用的UI,然后就结束。
- 编写软件;花费时间和精力使其看起来不错,并满足客户的审美需求。
根据我的经验,我认为如果软件看起来不错、令人兴奋或对最终用户总体上具有吸引力,那么在用户的生命周期中采用该软件的过程就会不那么痛苦。那些为用户从未看到的“漂亮”用户界面而自豪的开发人员,可以认为他们本身就是艺术家。
为了让我的软件更加“漂亮”,我创建了CKCSideBannerWnd
类。该类实现了横幅的概念,可用于告知用户他们正在查看的当前窗口的用途。
背景
每次我使用软件时,我都会记录下我喜欢UI的哪些方面,不喜欢哪些方面,哪些是好主意,哪些不是,以及更重要的是,为什么。我一直喜欢的一个概念是横幅(就像InstallShield/Wise安装向导、CPropertyEx
派生向导等中的那样)。
就我个人而言,我认为那些“放置得当”的横幅能让软件UI看起来更具吸引力、更专业。所以,我着手编写了一个类,它几乎可以做上面提到的横幅能做到的事情,而且功能更多(比如将横幅附加到窗口的任何边缘)。
我还觉得程序员不应该担心为横幅预留空间,并且将横幅附加到任何窗口都应该是一项快速的工作。我必须承认,我的两个软件包已经“升级”了横幅控件:)
Using the Code
首先,要使用CKCSideBannerWnd
类,您需要在项目中包含以下文件:
- KCSideBannerWnd.h
- KCSideBannerWnd.cpp
您还需要将WndUtil.h文件复制到与KCSideBannerWnd.*文件相同的目录中。一旦您拥有了这个类,使用它就非常简单。要将横幅附加到对话框,请像这样在您的对话框类中添加一个成员:
// YourDialog.h file
#include "KCSideBannerWnd.h"
class CYourDialog : public Dialog
{
public:
CYourDialog();
// ...
// other declarations..
protected:
CKCSideBannerWnd m_banner;
};
现在,在您的OnInitDialog()
函数中添加以下加粗代码:
BOOL CYourDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// usual MFC wizard code...
// add the banner
m_banner.Attach(this);
}
如果您的对话框有调整大小的边框,请重写OnSize()
函数并添加以下加粗代码:
void CYourDialog::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if ( m_banner.m_hWnd )
m_banner.UpdateSize();
}
CKCSideBannerWnd
也可以附加到任何其他窗口。只需按照上述步骤将横幅附加到视图等即可。
工作原理
该控件的基本工作原理如下:
- 调用
Attach()
时,获取父窗口(作为第一个参数传入)。 - 通过在适当的方向上扩展父窗口来为横幅腾出空间。
- 枚举父窗口的所有子窗口(不包括横幅),并相应地偏移它们。
- 创建横幅控件并附加它。
这种技术的优点在于,程序员不必为他们已经定义的任何对话框或窗口中的横幅专门预留空间。只需简单地附加,控件就会为自己腾出空间。
关注点
这篇文章本可以早点发布,如果不是我遇到了一个与Windows及其组合框处理相关的有趣细节的话。我一直在测试这个控件在许多不同的对话框上的表现,一切都很顺利,直到我把它用在一个包含2个组合框控件的对话框上。
当横幅被附加后,组合框控件“失去了”显示选定文本或用户在键盘上输入文本的能力。事实上,组合框的编辑控件似乎停止工作了。(列表框控件工作得很好:))
我发布到论坛上,但似乎没有人能给我一个关于为什么会发生这种情况的答案。所以我开始了一场(??) bug追踪,看看是我做错了什么导致组合框控件部分停止工作。
我最终发现,当调用EnumChildWindows()
并且它调用用户提供的处理程序处理每个子HWND
时,它找到的子窗口之一实际上是组合框的编辑控件。问题出在我移动了实际的编辑控件以及组合框控件。
这个问题的解决方案是在枚举函数中添加一个检查,该检查调用GetParent()
来检查所谓的子HWND
,并确认其父窗口确实是我已知的那个父窗口(对话框或拥有窗口)。组合框控件的编辑控件确实将其父窗口返回为组合框的HWND
,并且由于我不再同时移动组合框及其包含的编辑控件,一切都工作得很顺利。
类文档
-
BOOL Attach(CWnd* pWnd, unsigned int uFlags = KCSB_ATTACH_LEFT, unsigned int uiID = 0xFFF0)
调用此函数将横幅附加到父窗口。
uFlags
可以是以下之一(互斥标志):KCSB_ATTACH_LEFT
:将横幅附加到窗口的左侧 KCSB_ATTACH_TOP
:将横幅附加到窗口的顶部 KCSB_ATTACH_RIGHT
:将横幅附加到窗口的右侧 KCSB_ATTACH_BOTTOM
:将横幅附加到窗口的底部 uiID
是父窗口用于引用此控件的ID。默认值为0xFFF0。 -
void SetSize(int nSize)
调整横幅的大小。这取决于横幅的位置,是在调整横幅的高度还是宽度。换句话说,如果横幅在左侧或右侧,则会调整宽度。如果横幅在顶部或底部,则会调整高度。
-
int GetSize()
返回横幅的当前大小。
-
void UpdateSize()
当父窗口的大小发生变化时调用此函数。
-
void SetPosFlag(unsigned int uFlags)
调用此函数以重新定位横幅。与
Attach()
中讨论的标志相同。 -
unsigned int GetPosFlag()
返回当前位置标志。
-
void SetFillFlag(unsigned int uFlag)
调用此函数来设置横幅的填充类型。可能的值包括:
KCSB_FILL_FLAT
:用 SetColBkg()
设置的颜色填充横幅。KCSB_FILL_GRADIENT
:用从 SetColBkg()
设置的颜色开始并过渡到SetColBkg2()
设置的颜色的渐变填充横幅。KCSB_FILL_TEXTURE
:用可以通过 SetTexture()
设置的位图纹理填充横幅的背景。 -
unsigned int GetFillFlag()
返回当前填充标志。
-
void SetTitle(const char* lpszTitle)
设置横幅的标题(主字符串)。
-
CString GetTitle()
返回横幅的当前标题。
-
void SetCaption(const char* lpszTitle)
设置横幅的副标题(次字符串)。
-
CString GetCaption()
返回横幅的当前副标题。
-
void SetColBkg(COLORREF col)
设置主背景颜色。当渐变填充处于活动状态时,这将是渐变开始的颜色。
-
COLORREF GetColBkg()
返回主背景颜色。
-
void SetColBkg2(COLORREF col)
设置次背景颜色。当渐变填充处于活动状态时,这将是渐变过渡到的颜色。在平面填充模式下,此颜色无效。
-
COLORREF GetColBkg2()
返回次背景颜色。
-
void SetColEdge(COLORREF col)
设置边框颜色。
-
COLORREF GetColEdge()
返回边框颜色。
-
COLORREF SetColTxtTitle(COLORREF col)
设置标题文本颜色。
-
COLORREF GetColTxtTitle()
返回标题文本颜色。
-
COLORREF SetColTxtCaption(COLORREF col)
设置副标题文本颜色。
-
COLORREF SetColTxtCaption()
返回副标题文本颜色。
-
void SetEdgeOffset(CSize szOffset)
设置标题文本距离边缘的XY偏移量。
-
CSize GetEdgeOffset()
返回标题文本距离边缘的XY偏移量。
-
void SetCaptionOffset(CSize szOffset)
设置副标题相对于**标题开始位置**的XY偏移量。换句话说,如果边距偏移量是(5, 5),副标题偏移量也是(5, 5),那么副标题将距离标题5个像素。
-
CSize GetCaptionOffset()
返回副标题文本相对于标题的XY偏移量。
-
void SetTitleFont(CFont* pFont)
设置用于绘制标题文本的字体。
-
void GetTitleFont(LOGFONT* pFont)
在传递给函数的
LOGFONT
结构中返回用于绘制标题文本的字体。 -
void SetCaptionFont(CFont* pFont)
设置用于绘制副标题文本的字体。
-
void GetCaptionFont(LOGFONT* pFont)
在传递给函数的
LOGFONT
结构中返回用于绘制副标题文本的字体。 -
bool SetIcon(HICON hIcon, UINT uiIconPos = KCSB_ICON_RIGHT | KCSB_ICON_VCENTER, bool bSelfDelete = true)
设置将在横幅中绘制的图标。
bSelfDelete
指示控件是否会删除HICON
资源(bSelfDelete = false
),或者控件在不再需要时是否可以删除它(bSelfDelete = true
)。uiIconPos
可以是一个或多个以下值:KCSB_ICON_LEFT
:在横幅的左侧绘制图标。如果横幅附加到窗口的左侧,则图标将绘制在底部。如果横幅附加到窗口的右侧,则图标将绘制在顶部。
注意:此标志不能与
KCSB_ICON_RIGHT
标志组合使用。KCSB_ICON_RIGHT
:在横幅的右侧绘制图标。如果横幅附加到窗口的左侧,则图标将绘制在顶部。如果横幅附加到窗口的右侧,则图标将绘制在底部。
注意:此标志不能与
KCSB_ICON_LEFT
标志组合使用。KCSB_ICON_TOP
:在横幅的顶部绘制图标。如果横幅附加到窗口的左侧,则图标将绘制在左侧。如果横幅附加到窗口的右侧,则图标将绘制在右侧。
注意:此标志不能与
KCSB_ICON_VCENTER
或KCSB_ICON_BOTTOM
标志组合使用。KCSB_ICON_VCENTER
:在横幅中垂直居中绘制图标。
注意:此标志不能与
KCSB_ICON_TOP
或KCSB_ICON_BOTTOM
标志组合使用。KCSB_ICON_BOTTOM
:在横幅的底部绘制图标。如果横幅附加到窗口的左侧,则图标将绘制在右侧。如果横幅附加到窗口的右侧,则图标将绘制在左侧。
注意:此标志不能与
KCSB_ICON_VCENTER
或KCSB_ICON_TOP
标志组合使用。 -
void SetIconPos(UINT uiIconPos)
设置图标在横幅中的位置。使用
SetIcon()
中描述的标志。 -
UINT GetIconPos()
返回图标位置标志(参见
SetIcon()
)。 -
void SetTexture(HBITMAP hBitmap, bool bSelfDelete = true)
设置当控件的填充标志设置为
KCSB_FILL_TEXTURE
时用于绘制背景的位图(参见SetFillFlag()
)。bSelfDelete
标志指示您是删除HBITMAP
资源(bSelfDelete = false
),还是控件在不再需要时可以删除它(bSelfDelete = true
)。 -
HBITMAP GetTexture()
返回用于绘制纹理背景的纹理
HBITMAP
。
我有更多时间时想尝试的事情...
我个人从未编写过一个“为自己腾出空间”的控件,这个概念在一定程度上吸引了我的想象力。我一直在考虑开发一个后台应用程序/服务,该服务会监视所有HWND
的创建,并为所有类型为DIALOG的窗口附加一个横幅。这仅仅是为了好玩和教育价值……但仍然是一个很酷的主意,我认为。
历史
- 2003-10-22
- 首次公开发布。
- 2003-10-23
- 在KCSIDEBANNERWND.H文件中添加了
#pragma comment(lib, "MSIMG32.LIB")
,以消除更改项目设置的必要性 - 感谢Warren Stevens。 - 在*.zip文件下载中添加了项目的一个发布版本,并删除了所有不必要的文件(例如:*.NCB文件) - 再次感谢Warren :)。
- 修复了西里尔文(以及可能受此影响的其他字体)的bug。
- 在KCSIDEBANNERWND.H文件中添加了
- 2003-10-24
- 添加了
GetColTxtTitle()
、SetColTxtTitle()
、GetColTxtCaption()
和SetColTxtCaption()
。
- 添加了
- 2003-10-27
- 增加了提供位图作为背景的功能(参见
SetTexture()
)。 - 增加了指示控件是否可以清理
HICON
和HBITMAP
资源,或者控制程序是否会自行清理的功能。
- 增加了提供位图作为背景的功能(参见
- 2003-10-29
- 移除了对MSIMG32.LIB的完全依赖。该控件现在会检查是否可以动态加载MSIMG32.DLL,如果不行,它将使用自己的
Gradient
函数(感谢John A. Johnson)。这个想法是Dominik Reichl给我的,他从Irek Zielinski的CStaticGradient控件那里获得了这个想法。
- 移除了对MSIMG32.LIB的完全依赖。该控件现在会检查是否可以动态加载MSIMG32.DLL,如果不行,它将使用自己的