MFC自定义控件开发与使用指南
MFC自定义控件开发与使用指南
1. 概述
MFC(Microsoft Foundation Classes)框架提供了丰富的内置控件,但在实际开发中,我们常常需要创建自定义控件来满足特定的界面需求。本文将详细介绍如何在MFC中开发自定义控件,并以CCustomTextControl为例,展示自定义控件的实现和使用方法。
示例代码仓库:https://github.com/wang161113/MFCSplitWindow
2. 自定义控件的基本原理
在MFC中,创建自定义控件通常有以下几种方式:
- 继承自现有控件类:如
CButton、CEdit等,适合对现有控件进行功能扩展。 - 继承自
CWnd类:完全自定义控件的外观和行为,拥有最大的灵活性。 - 使用
CStatic控件的owner-draw特性:通过重写绘制函数来自定义静态控件的外观。 
本文将重点介绍第二种方式,即通过继承CWnd类来创建完全自定义的控件。
3. CCustomTextControl实现分析
3.1 类定义
首先,我们来看CCustomTextControl的类定义:
class CCustomTextControl : public CWnd {
    DECLARE_DYNAMIC(CCustomTextControl)
public:
    CCustomTextControl();
    virtual ~CCustomTextControl();
    // 接口方法
    void SetUpperText(LPCTSTR text);
    void SetLowerText(LPCTSTR text);
    void SetUpperFont(CFont* pFont);
    void SetLowerFont(CFont* pFont);
    void SetUpperColor(COLORREF color);
    void SetLowerColor(COLORREF color);
    void SetBkColor(COLORREF color);
protected:
    DECLARE_MESSAGE_MAP()
    afx_msg void OnPaint();
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
private:
    CString m_upperText;
    CString m_lowerText;
    CFont* m_pUpperFont;
    CFont* m_pLowerFont;
    COLORREF m_upperColor;
    COLORREF m_lowerColor;
    COLORREF m_bkColor;
    CBitmap m_memBitmap;
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
};
这个类继承自CWnd,包含以下关键部分:
- 成员变量:存储上下文本、字体、颜色等属性
 - 公共接口:提供设置文本、字体和颜色的方法
 - 消息处理:处理绘制和背景擦除消息
 - 窗口创建:通过
PreCreateWindow自定义窗口类样式 
3.2 类实现
3.2.1 构造函数和析构函数
CCustomTextControl::CCustomTextControl()
{
    m_upperColor = RGB(0, 0, 0);
    m_lowerColor = RGB(0, 0, 0);
    m_bkColor = RGB(255, 255, 255);
    m_pUpperFont = nullptr;
    m_pLowerFont = nullptr;
}
CCustomTextControl::~CCustomTextControl()
{
}
构造函数初始化成员变量,设置默认颜色和字体指针。
3.2.2 消息映射
BEGIN_MESSAGE_MAP(CCustomTextControl, CWnd)
    ON_WM_PAINT()
    ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
消息映射将WM_PAINT和WM_ERASEBKGND消息映射到对应的处理函数。
3.2.3 绘制函数
void CCustomTextControl::OnPaint()
{
    CPaintDC dc(this);
    CRect rect;
    GetClientRect(&rect);
    // 双缓冲绘制
    CDC memDC;
    memDC.CreateCompatibleDC(&dc);
    m_memBitmap.DeleteObject();
    m_memBitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
    CBitmap* pOldBitmap = memDC.SelectObject(&m_memBitmap);
    // 绘制背景
    memDC.FillSolidRect(rect, m_bkColor);
    // 预定义分割区域
    CRect upperRect(rect);
    upperRect.left = 15;  //左边距,DT_CENTER时可设置为0
    upperRect.bottom = upperRect.top + rect.Height() / 2;
    CRect lowerRect(rect);
    lowerRect.left = 15;  //左边距,DT_CENTER时可设置为0
    lowerRect.top = upperRect.bottom;
    // 绘制上层文本
    if (!m_upperText.IsEmpty())
    {
        memDC.SetTextColor(m_upperColor);
        if (m_pUpperFont)
            memDC.SelectObject(m_pUpperFont);
        //DT_VCENTER垂直居中;DT_LEFT左对齐,换成DT_CENTER可变成水平居中
        memDC.DrawText(m_upperText, upperRect, DT_VCENTER | DT_LEFT | DT_SINGLELINE);
    }
    // 绘制下层文本
    if (!m_lowerText.IsEmpty())
    {
        memDC.SetTextColor(m_lowerColor);
        if (m_pLowerFont)
            memDC.SelectObject(m_pLowerFont);
        memDC.DrawText(m_lowerText, lowerRect, DT_VCENTER | DT_LEFT | DT_SINGLELINE);
    }
    // 拷贝到屏幕DC
    dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
    memDC.SelectObject(pOldBitmap);
}
OnPaint函数是自定义控件的核心,它实现了控件的绘制逻辑:
- 创建内存DC进行双缓冲绘制,避免闪烁
 - 绘制背景色
 - 将控件区域分为上下两部分
 - 分别在上下区域绘制文本,应用相应的字体和颜色
 - 将内存DC的内容复制到屏幕DC
 
3.2.4 属性设置方法
void CCustomTextControl::SetUpperText(LPCTSTR text)
{
    m_upperText = text;
    if(GetSafeHwnd()) Invalidate();
}
// 其他属性设置方法类似...
每个属性设置方法都会在修改属性后调用Invalidate()触发重绘,确保界面及时更新。
3.2.5 窗口创建前的准备
BOOL CCustomTextControl::PreCreateWindow(CREATESTRUCT& cs)
{
    cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW);
    return CWnd::PreCreateWindow(cs);
}
PreCreateWindow函数注册了一个具有CS_HREDRAW和CS_VREDRAW样式的窗口类,这确保了控件在大小改变时会完全重绘。
4. 在对话框中使用CCustomTextControl
4.1 在对话框类中声明控件变量
在CLeftDlg.h中:
// CLeftDlg 对话框
class CLeftDlg : public CDialogEx
{
    // ...
protected:
    CCustomTextControl m_customTextControl;
    // ...
public:
    CFont m_upperFont;
    CFont m_lowerFont;
    // ...
};
4.2 创建和初始化控件
在CLeftDlg.cpp的OnInitDialog函数中:
BOOL CLeftDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    // 创建自定义文本控件
    CRect rcPlaceholder;
    GetDlgItem(IDC_CUSTOM_TEXT)->GetWindowRect(&rcPlaceholder);
    ScreenToClient(&rcPlaceholder);
    if (!m_customTextControl.Create(_T("CCustomTextControl"), L"", WS_CHILD | WS_VISIBLE, rcPlaceholder, this, IDC_CUSTOM_TEXT))
        TRACE("Failed to create custom text control!\n");
    //初始化 设置文本、字体、颜色
    m_upperFont.CreatePointFont(120, _T("Arial"));
    m_lowerFont.CreatePointFont(90, _T("Arial"));
    m_customTextControl.SetUpperText(_T("Upper Text"));
    m_customTextControl.SetLowerText(_T("Lower Text"));
    m_customTextControl.SetBkColor(RGB(0, 0, 0));
    m_customTextControl.SetUpperFont(&m_upperFont);
    m_customTextControl.SetLowerFont(&m_lowerFont);
    m_customTextControl.SetUpperColor(RGB(0, 0, 255));
    m_customTextControl.SetLowerColor(RGB(128, 0, 0));
    return TRUE;
}
这段代码展示了如何创建和初始化自定义控件:
- 获取占位控件的位置(通常在对话框模板中放置一个静态控件作为占位符)
 - 调用
Create方法创建控件实例 - 创建字体对象
 - 设置控件的文本、字体和颜色属性
 
4.3 动态更新控件属性
在CLeftDlg.cpp的OnBnClickedButton1函数中:
void CLeftDlg::OnBnClickedButton1()
{
    // 创建随机数生成器
    srand(static_cast<unsigned int>(time(nullptr)));
    // 生成随机颜色的辅助函数
    auto GetRandomColor = []() {
        return RGB(rand() % 256, rand() % 256, rand() % 256);
    };
    int upperFontSize = 80 + (rand() % 121);  // 80-200之间的随机数
    int lowerFontSize = 80 + (rand() % 121);  // 80-200之间的随机数
    // 释放旧字体
    if (m_upperFont.GetSafeHandle())
        m_upperFont.DeleteObject();
    if (m_lowerFont.GetSafeHandle())
        m_lowerFont.DeleteObject();
    m_upperFont.CreatePointFont(upperFontSize, _T("Arial"));
    m_lowerFont.CreatePointFont(lowerFontSize, _T("Arial"));
    m_customTextControl.SetUpperFont(&m_upperFont);
    m_customTextControl.SetLowerFont(&m_lowerFont);
    static int i = 1;
    m_customTextControl.SetUpperText(_T("Upper Text ") + CString(std::to_string(i++).c_str()));
    m_customTextControl.SetUpperColor(GetRandomColor());  // 为上文本设置随机颜色
    m_customTextControl.SetLowerText(_T("Lower Text ") + CString(std::to_string(rand()).c_str()));
    m_customTextControl.SetLowerColor(GetRandomColor());  // 为下文本设置随机颜色
    m_customTextControl.SetBkColor(GetRandomColor());  // 背景也设置为随机颜色
}
这段代码展示了如何动态更新控件属性:
- 生成随机字体大小和颜色
 - 释放旧字体资源并创建新字体
 - 更新控件的文本、字体和颜色属性
 
5. 自定义控件开发的最佳实践
5.1 设计原则
- 单一职责原则:控件应专注于一个特定功能
 - 接口清晰:提供简洁明了的公共接口
 - 资源管理:正确管理GDI资源,避免泄漏
 - 性能优化:使用双缓冲等技术避免闪烁
 
5.2 常见问题及解决方案
- 控件闪烁:使用双缓冲绘制技术
 - 资源泄漏:确保GDI对象(字体、画笔等)正确释放
 - 控件响应性:在属性变更后调用
Invalidate()触发重绘 - 字体管理:注意字体对象的生命周期管理
 
6. 总结
通过本文的介绍,我们学习了如何在MFC中创建自定义控件,并通过CCustomTextControl的实例详细了解了自定义控件的实现和使用方法。自定义控件为MFC应用程序提供了更大的灵活性和更丰富的用户界面,是MFC开发中的重要技术。
开发自定义控件的关键步骤包括:
- 继承适当的基类(通常是
CWnd) - 实现消息映射和处理函数
 - 提供清晰的公共接口
 - 在对话框或视图中创建和使用控件
 
通过掌握这些技术,您可以根据应用程序的需求创建各种自定义控件,提升用户体验。