MFC自定义控件开发与使用指南

MFC自定义控件开发与使用指南

1. 概述

MFC(Microsoft Foundation Classes)框架提供了丰富的内置控件,但在实际开发中,我们常常需要创建自定义控件来满足特定的界面需求。本文将详细介绍如何在MFC中开发自定义控件,并以CCustomTextControl为例,展示自定义控件的实现和使用方法。

示例代码仓库:https://github.com/wang161113/MFCSplitWindow

2. 自定义控件的基本原理

在MFC中,创建自定义控件通常有以下几种方式:

  1. 继承自现有控件类:如CButtonCEdit等,适合对现有控件进行功能扩展。
  2. 继承自CWnd:完全自定义控件的外观和行为,拥有最大的灵活性。
  3. 使用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_PAINTWM_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函数是自定义控件的核心,它实现了控件的绘制逻辑:

  1. 创建内存DC进行双缓冲绘制,避免闪烁
  2. 绘制背景色
  3. 将控件区域分为上下两部分
  4. 分别在上下区域绘制文本,应用相应的字体和颜色
  5. 将内存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_HREDRAWCS_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.cppOnInitDialog函数中:

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;
}

这段代码展示了如何创建和初始化自定义控件:

  1. 获取占位控件的位置(通常在对话框模板中放置一个静态控件作为占位符)
  2. 调用Create方法创建控件实例
  3. 创建字体对象
  4. 设置控件的文本、字体和颜色属性

4.3 动态更新控件属性

CLeftDlg.cppOnBnClickedButton1函数中:

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());  // 背景也设置为随机颜色
}

这段代码展示了如何动态更新控件属性:

  1. 生成随机字体大小和颜色
  2. 释放旧字体资源并创建新字体
  3. 更新控件的文本、字体和颜色属性

5. 自定义控件开发的最佳实践

5.1 设计原则

  1. 单一职责原则:控件应专注于一个特定功能
  2. 接口清晰:提供简洁明了的公共接口
  3. 资源管理:正确管理GDI资源,避免泄漏
  4. 性能优化:使用双缓冲等技术避免闪烁

5.2 常见问题及解决方案

  1. 控件闪烁:使用双缓冲绘制技术
  2. 资源泄漏:确保GDI对象(字体、画笔等)正确释放
  3. 控件响应性:在属性变更后调用Invalidate()触发重绘
  4. 字体管理:注意字体对象的生命周期管理

6. 总结

通过本文的介绍,我们学习了如何在MFC中创建自定义控件,并通过CCustomTextControl的实例详细了解了自定义控件的实现和使用方法。自定义控件为MFC应用程序提供了更大的灵活性和更丰富的用户界面,是MFC开发中的重要技术。

开发自定义控件的关键步骤包括:

  1. 继承适当的基类(通常是CWnd
  2. 实现消息映射和处理函数
  3. 提供清晰的公共接口
  4. 在对话框或视图中创建和使用控件

通过掌握这些技术,您可以根据应用程序的需求创建各种自定义控件,提升用户体验。

THE END
分享
二维码
打赏
海报
MFC自定义控件开发与使用指南
MFC自定义控件开发与使用指南 1. 概述 MFC(Microsoft Foundation Classes)框架提供了丰富的内置控件,但在实际开发中,我们常常需要创建自定义控件来满足特……
<<上一篇
下一篇>>