65.9K
CodeProject 正在变化。 阅读更多。
Home

创建自定义控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (73投票s)

2000年5月12日

CPOL
viewsIcon

579366

downloadIcon

7824

MFC 中创建自定义控件入门

  • 下载源代码文件 - 21 Kb
  • Sample Image - CustomControl.jpg

    引言

    上一篇文章中,我演示了如何通过子类化 Windows 通用控件来修改其行为或扩展其功能。有时,你对 Windows 通用控件的修改只能达到一定程度。我遇到的一个例子是,需要一个网格控件来显示和编辑表格数据。我子类化了一个 CListCtrl 并对其进行了扩展,使其能够进行子项编辑、多行单元格、单击排序的标题以及许多其他功能。然而,从根本上说,它仍然是一个列表控件,到了某个点,我似乎写了更多代码来阻止控件执行某些操作,而不是实际它做点什么。

    我需要从头开始,基于一个只提供我所需功能的基类,而不是任何我不需要的功能(或负担)。自定义控件由此而生。

    创建自定义控件类

    编写自定义控件与子类化 Windows 通用控件非常相似。你从现有类派生一个新类,并重写基类的功能,使其按照你的意愿工作。

    在本例中,我们将从 CWnd 派生一个类,因为该类提供了我们所需的最低限度功能,而没有过多的开销。

    创建自定义控件的第一步是从你选择的基类(CWnd)派生你的类。在这个例子中,我们将创建一个用于显示位图的自定义控件,并将其命名为 CBitmapViewer。显然,已经有了 CStatic 类可以显示位图,但这个例子只是为了展示有冒险精神的程序员可用的可能性。

    在你的类中,你应该添加 WM_PAINTWM_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_PAINTWM_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 对象与这个预先准备好的窗口关联起来。

    编译你的项目,运行应用程序,然后惊叹吧。你刚刚创建了一个自定义控件。

    创建自定义控件 - CodeProject - 代码之家
    © . All rights reserved.