创建自定义控件






4.84/5 (73投票s)
MFC 中创建自定义控件入门
引言
在上一篇文章中,我演示了如何通过子类化 Windows 通用控件来修改其行为或扩展其功能。有时,你对 Windows 通用控件的修改只能达到一定程度。我遇到的一个例子是,需要一个网格控件来显示和编辑表格数据。我子类化了一个 CListCtrl
并对其进行了扩展,使其能够进行子项编辑、多行单元格、单击排序的标题以及许多其他功能。然而,从根本上说,它仍然是一个列表控件,到了某个点,我似乎写了更多代码来阻止控件执行某些操作,而不是实际让它做点什么。
我需要从头开始,基于一个只提供我所需功能的基类,而不是任何我不需要的功能(或负担)。自定义控件由此而生。
创建自定义控件类
编写自定义控件与子类化 Windows 通用控件非常相似。你从现有类派生一个新类,并重写基类的功能,使其按照你的意愿工作。
在本例中,我们将从 CWnd
派生一个类,因为该类提供了我们所需的最低限度功能,而没有过多的开销。
创建自定义控件的第一步是从你选择的基类(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; };
在你的 CBitmapViewer
实现文件中,为你 的 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
函数注册一个 Windows 类名。注册类允许我们指定背景颜色、光标和样式。有关更多信息,请参阅文档中的 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
}
DDX_Control
通过调用 SubclassWindow
将成员变量 m_Viewer 与 ID 为 IDC_CUSTOM1
的控件关联起来。使用类名“MFCBitmapViewerCtrl”在你的对话框资源中创建自定义控件,会创建一个行为如 CBitmapViewer::RegisterWindowClass
所指定的窗口,然后 DDX_Control
调用将你的 CBitmapViewer
对象与这个预先准备好的窗口关联起来。
编译你的项目,运行应用程序,然后惊叹吧。你刚刚创建了一个自定义控件。