创建自定义控件
来源: http://www.codeproject.com/KB/miscctrl/CustomControl.aspx
代码下载: http://www.codeproject.com/KB/miscctrl/CustomControl/CustomControl.zip
介绍
在前一篇文章中(http://www.codeproject.com/miscctrl/subclassdemo.asp)中,我将一个公共窗口控件进行了之类划分,目的是为了修饰其行为或者说是扩展其功能。有时候,你只能将公共窗口控件扩展到这样的底部。我遇到的一个例子是一个比较普遍的话题即需要一个格网控件并且编辑tabular数据。我对ClistCtrl进行了之类划分,并扩展使其能够进行子项编辑、多行元素、按首字母排序以及很多其他的功能。但是,深究起来,它仍然是一个列表控件,重点是看起来我需要写更多的代码去停止控件的表现而不是如我实际所做的为其加上功能。
我需要从乱写开始,从基类开始开始工作,从而提供我需要的功能别且不需要任何我不需要的特征和不利条件。所以从自定义控件开始吧。
创建自定义控件类
创建一个自定义控件同对普通窗口控件进行之类划分非常类似。你需要从已存在的类中继承一个新类并且重载基类的函数以达到你想要的功能。
本例中我们将从CWnd类中继承一个类,因为该类提供了我们需要的最少功能,没有多余的无用函数。
第一步是从选择的generic CWnd基类中继承一个新类。本例中我们将创建一个显示位图的自定义控件,并命名为CbitmapViewer。显然,已经有一个Cstatic类型的类可用于显示位图,但是本例的目的是将可能多的方法展示给“不守本分”的程序员。如下图:
在你的类中,你应该添加WM_PAINT和WM_ERASEBKGND消息的句柄。为了能够初始化已经创建的窗口我已经添加了一个PreSubclassWindow的重载函数。请去上一篇文章中查看PreSubclassWindow函数。如图:
为了在控件中显示位图,添加了一个名为SetBitmap的函数。我们不仅仅是天才的美国程序员同时富有想象力(雷人)。
对于本文来说,内部的实现代码并不重要但是为了完整性也包括在下面了:
在类中添加一个Cbitmap的实例以及函数SetBitmap声明。
class CBitmapViewer : public CWnd
{
// Construction
public:
CBitmapViewer();
// Attributes
public:
BOOL SetBitmap(UINT nIDResource);
protected:
CBitmap m_Bitmap;
};
在实现文件中添加SetBitmap的实现函数,并且添加WM_PAINT和WM_ERASEBKGND消息句柄:
void CBitmapViewer::OnPaint()
{
// Draw the bitmap - if we have one.
if (m_Bitmap.GetSafeHandle() != NULL)
{
CPaintDC dc(this); // device context for painting
// Create memory DC
CDC MemDC;
if (!MemDC.CreateCompatibleDC(&dc))
return;
// Get Size of Display area
CRect rect;
GetClientRect(rect);
// Get size of bitmap
BITMAP bm;
m_Bitmap.GetBitmap(&bm);
// Draw the bitmap
CBitmap* pOldBitmap = (CBitmap*) MemDC.SelectObject(&m_Bitmap);
dc.StretchBlt(0, 0, rect.Width(), rect.Height(),
&MemDC,
0, 0, bm.bmWidth, bm.bmHeight,
SRCCOPY);
MemDC.SelectObject(pOldBitmap);
}
// Do not call CWnd::OnPaint() for painting messages
}
BOOL CBitmapViewer::OnEraseBkgnd(CDC* pDC)
{
// If we have an image then don't perform any erasing, since the OnPaint
// function will simply draw over the background
if (m_Bitmap.GetSafeHandle() != NULL)
return TRUE;
// Obviously we don't have a bitmap - let the base class deal with it.
return CWnd::OnEraseBkgnd(pDC);
}
BOOL CBitmapViewer::SetBitmap(UINT nIDResource)
{
return m_Bitmap.LoadBitmap(nIDResource);
}
制作自定义控件
现在已有一个类允许我们载入并显示一个位图,但是实际上我们还没有办法使用这个类。在创建控件时有两种选择:动态方式条用Create函数或者用Visual Studio的资源编辑器创建对话框模板。
由于我们的类是继承自CWnd类所以我们可以使用CWnd::Create来动态的创建控件。例如,在对话框的OnInitDialog函数中添加一下的代码:
// CBitmapViewer m_Viewer; - declared in dialog class header
m_Viewer.Create(NULL, _T(""), WS_VISIBLE, CRect(0,0,100,100), this, 1);
m_Viewer.SetBitmap(IDB_BITMAP1);
其中,m_Viewer是CbitmapViewer类的实例对象,定义在对话框的头文件中。IDB_BITMAP1是位图资源的ID号。这两句代码的作用是创建控件并显示位图。
但是,如果想要把控件放在一个用Visual Studio资源编辑器创建的对话框模板中又该如何做呢?对于这种情况,我们需要使用AfxRegisterClass函数注册一个窗口类。注册一个类的好处是我们能够指定背景颜色,光标类型和类类型。具体请查阅AfxRegisterWndClass。
在本例中,我们将注册一个简单的类“MFCBitmapViewerCtrl”。我们只需要注册该类一次,为了简便我们只需在类的构造函数中写这几行代码即可:
#define BITMAPVIEWER_CLASSNAME _T("MFCBitmapViewerCtrl") // Window class name
CBitmapViewer::CBitmapViewer()
{
RegisterWindowClass();
}
BOOL CBitmapViewer::RegisterWindowClass()
{
WNDCLASS wndcls;
HINSTANCE hInst = AfxGetInstanceHandle();
if (!(::GetClassInfo(hInst, BITMAPVIEWER_CLASSNAME, &wndcls)))
{
// otherwise we need to register a new class
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = ::DefWindowProc;
wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
wndcls.hInstance = hInst;
wndcls.hIcon = NULL;
wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
wndcls.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1);
wndcls.lpszMenuName = NULL;
wndcls.lpszClassName = BITMAPVIEWER_CLASSNAME;
if (!AfxRegisterClass(&wndcls))
{
AfxThrowResourceException();
return FALSE;
}
}
return TRUE;
}
在我们的例子中,动态创建有所不同:
m_Viewer.Create(_T("MFCBitmapViewerCtrl"), _T(""), WS_VISIBLE, CRect(0,0,100,100), this, 1);
这样将能确保正确的窗口风格、光标和颜色。当然为了自定义控件写一个新的Create函数也是值得的这样用户将不必要记住窗口类的名称。例如:
BOOL CBitmapViewer::Create(CWnd* pParentWnd, const RECT& rect, UINT nID, DWORD dwStyle /*=WS_VISIBLE*/)
{
return CWnd::Create(BITMAPVIEWER_CLASSNAME, _T(""), dwStyle, rect, pParentWnd, nID);
}
为了在对话框资源中定制控件,只需要在对话框中拉出一个自定义控件即可。如下图:
接着在控件属性对话框中,将类名称改为“MFCBitmapViewerCtrl”。
最后一步就是将一个变量同控件连接起来。在对话框类中简单定义一个CbitmapViewer类型的变量(如,m_Viewer)并且在DoDataExchange函数中加入一下代码:
void CCustomControlDemoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CCustomControlDemoDlg)
DDX_Control(pDX, IDC_CUSTOM1, m_Viewer);
//}}AFX_DATA_MAP
}
通过调用SubclassWindow函数,DDX_Control连接类变量m_Viewer与控件IDIDC_CUSTOM1。在对话框“MFCBitmapViewerCtrl”资源中通过自定义控件能够创建由CbitmapViewer::RegisterWindowClass所展示的功能。DDX_Control函数能将CbitmapViewer和预先的窗口连接在一起。
编译并执行,你会看到你已经创建了一个自定义控件。
许可
本文及说附带的代码与文件,执行CPOL许可。
关于作者
Chris Maunder是CodeProject的合作创办者,管理者及首席编辑。从1988年来就开始编程,他的角色多种多样。同时他还是微软VC的全球及加拿大MVP。
他的编程语言包括C/C++、C#、SQL、MFC、ASP、ASP.NET,更多的是FORTRAN。
Chris出生在澳大利亚。
职位:创业者
公司:The Code Project
出生地:加拿大