【VC++深入详解笔记】Note 5:文本编程

本文介绍了MFC编程中的关键技巧,包括插入符的创建与显示、窗口重绘的处理方法、路径与裁剪区域的应用,以及字符输入的实现过程。

1. 插入符

1.1 文本插入符

在视图窗口中创建一个文本插入符的j基本流程:

  • 在OnCreate函数中利用CreateSolidCaret函数创建一个插入符
  • 利用ShowCaret函数将插入符显示在视图窗口中
    在创建文本插入符的时候,需要手动的设置插入符的大小信息。为了使创建的插入符适合当前字体的大小,我们需要根据字体信息来设置插入符的大小。程序中的字体信息可以通过CDC类的GetTextMetrics成员函数来获得。该函数能够将当前的字体信息储存在一个TEXTMETRIC结构中。TEXTMETRIC类型包含了多个数据成员,这些成员包含了字体的上升高度、下降高度和平均宽度等,根据这些数据成员就可以设置与当前字体相匹配的插入符号
int Cchap12View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO:  在此添加您专用的创建代码

    // 创建设备描述表
    CClientDC dc(this);
    // 定义文本信息结构体变量
    TEXTMETRIC tm;
    // 获得设备描述表中的文本信息
    dc.GetTextMetrics(&tm);

    // 根据字体大小,创建合适的插入符
    // 将插入符宽度设置为字体平均宽度的1/8并不是一定的,只是这样比较好看而已
    CreateSolidCaret(tm.tmAveCharWidth / 8, tm.tmHeight);
    // 显示插入符
    ShowCaret();

    return 0;
}

注意

  • 创建插入符是在OnCreate函数中实现的,这个函数是WM_CREATE的消息响应函数

1.2 图形插入符

创建一个图形插入符的基本流程

  • 创建一个CBitmap对象,用这个对象来加载一个bitmap资源
  • 用该CBitmap对象作为实参调用CreateCaret函数创建一个插入符
  • 调用ShowCaret函数将插入符显示出来
int Cchap12View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO:  在此添加您专用的创建代码

    // 创建设备描述表
    CClientDC dc(this);
    // 定义文本信息结构体变量
    TEXTMETRIC tm;
    // 获得设备描述表中的文本信息
    dc.GetTextMetrics(&tm);

    // 加载位图资源
    bitmap.LoadBitmap(IDB_CLASS_VIEW);

    // 利用位图对象创建一个插入符
    CreateCaret(&bitmap);
    ShowCaret();

    return 0;
}

注意

  • 在这里用来创建插入符的函数跟创建文本插入符的函数并不是一样的,这里是CreateCaret,文本插入符是CreateSolidCaret,并不是重载了接收CBitmap对象的重载函数
  • 如果将CBitmap在OnCreate函数中创建,那么这个局部变量在OnCreate函数执行完之后就会被销毁,绑定到这个对象的位图资源也会被释放,这样在视图窗口中并不会看到任何的插入符。因此需要把这个CBitmap对象设置为一个私有的成员变量,这样即使执行完OnCreate函数之后,对象也不会被销毁,从能在视图窗口中看到一个图片插入符
  • 如果利用新建的位图资源来绑定到CBitmap对象时,有时会报错该位图资源的资源ID是未定义的标识符。这个问题其实可能是因为VS暂时没能识别新建的资源ID号造成的,直接运行或者重启vs即可。

2. 窗口重绘

2.1 OnDraw函数

OnDraw是一个类似消息WM_PAINT响应函数的函数,都是当每次窗口发生重绘的时候,程序框架就会调用该函数。窗口重绘一般发生在窗口初次出现或者改变窗口大小时,而窗口重绘会将原来在视图窗口显示的东西擦除,所以那些希望长久保存的东西应该在OnDraw函数中处理。
OnDraw函数接收一个CDC类对象作为形参,因此可以通过这个对象来实现各种绘图功能,如在视图窗口中显示一个字符串。

void Cchap12View::OnDraw(CDC* pDC)
{
    Cchap12Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: 在此处为本机数据添加绘制代码
    CString str("vc++ 深入编程");
    pDC->TextOut(50, 50, str);
}

注意

  • 在创建一个新的MFC之后,程序视图类中已经实现了OnDraw函数,不需要再通过类向导再手动添加一次了。
  • OnDraw函数是类似响应函数的一个函数,实际上并不是一个响应函数,而是一个CWnd类中的纯虚函数,而view类视CWnd类的子类,所以也必须在其类实现中实现OnDraw函数
  • OnDraw函数和OnPaint函数的区别
    • OnDraw函数是一个类似响应函数的函数,在CWnd类中被定义为一个纯虚函数,所以CWnd类的子类必须重载该函数;OnPaint函数则是一个纯正的响应函数,对WM_PAINT做出响应
    • 实际上OnPaint函数调用了OnDraw函数,并把将设备DC传递给OnDraw函数
    • 如果在OnDraw函数中实现了绘制操作,那么OnPaint必须显示调用OnDraw函数以完成这些绘制操作,所以实际上一般实现了OnDraw函数即可,而不必重写OnPaint函数
  • OnDraw函数和OnCreate函数的区别
    • OnCreate函数是一个响应函数,对消息WM_CREATE做出响应;OnDraw是类似响应函数的函数,接收到WM_PAINT函数之后就会被调用,但实际上是一个CWnd类中定义的纯虚函数
    • 调用时机不同,OnCreate函数是在每次程序框架创建完窗口,准备显示窗口时接收到WM_CREATE消息时被调用;OnDraw函数是在每次窗口重绘,接收到WM_PAINT消息之后被调用

2.2 添加字符串资源

添加字符串资源大致上跟添加位图资源一样,都是首先通过VS的资源视图添加一个资源,然后再利用一个CString对象读取这个字符串资源。但不同之处在于字符串资源都是添加到一个资源表中的(String Table),而一个程序只能有一个String Table。打开String Table,双击表最后的空白行,输入资源ID(会默认生成一个,也可以手动修改)和资源内容,输入完之后回车保存即可
CString读取字符串资源,并在视图窗口中输出

void Cchap12View::OnDraw(CDC* pDC)
{
    Cchap12Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: 在此处为本机数据添加绘制代码
    CString str;
    // 利用CString的成员函数加载一个字符串资源
    str.LoadString(IDS_STRING101);
    // 在视图窗口显示该字符串
    pDC->TextOut(50, 50, str);
}

注意 虽然字符串资源看起来比使用字符串常量麻烦,但实际上使用字符串资源在某些场景下是比使用字符串常量更好。例如当同一个字符串在多处地方使用时,如果用的是字符串常量,那么当要修改该字符串的时候就需要进行大量的修改;而如果使用的是字符串资源那么只要修改字符串资源表中的值则其他地方的字符串值就自动修改了。

3. 路径

这里主要是有两个概念——路径层和裁剪区域

  • 路径层 就类似于图层的东西,可以在路径层中绘制不同于当前绘图区域的对象。一个程序可以打开多个路径层。路径层的初始大小为0,当在路径层中绘制了新的对象之后路径层的大小会变成与绘制的对象一样。
  • 裁剪区域 相当于当前的绘图区域,在裁剪区域中绘制的东西会直接显示。裁剪区域的大小是可以调整的。
  • 路径层与裁剪区域之间的交互 可以利用CDC类的成员函数SelectClipPath当前设置的路径层和设备描述表中已有的裁剪区域按照一种指定的模式进行交互

相关实验代码及效果:

  • 打开一个路径层,在里面绘制一个矩形,视图窗口中并不会显示绘制的矩形
void Cchap12View::OnDraw(CDC* pDC)
{
    Cchap12Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: 在此处为本机数据添加绘制代码
    CString str;
    // 利用CString的成员函数加载一个字符串资源
    str.LoadString(IDS_STRING101);
    // 在视图窗口显示该字符串
    pDC->TextOut(50, 50, str);

    // 获取视图窗口显示的字符串区域大小
    CSize sz = pDC->GetTextExtent(str);

    // 在路径层显示一个刚好覆盖这个字符串的矩形
    pDC->BeginPath();
    pDC->Rectangle(50, 50, 50 + sz.cx, 50 + sz.cy);
    pDC->EndPath();

    // 在视图窗口输出一些网格以帮助显示
    for (int i = 0; i < 300; i += 10)
    {
        pDC->MoveTo(0, i);
        pDC->LineTo(300, i);
        pDC->MoveTo(i, 0);
        pDC->LineTo(i, 300);
    }
}

视图窗口中并没有把在路径层中绘制的矩形显示出来
这里写图片描述
- 利用SelectClipPath来使路径层和裁剪区域进行交互,并将模式设定为RGN_DIFF或RGN_AND

void Cchap12View::OnDraw(CDC* pDC)
{
    Cchap12Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: 在此处为本机数据添加绘制代码
    CString str;
    // 利用CString的成员函数加载一个字符串资源
    str.LoadString(IDS_STRING101);
    // 在视图窗口显示该字符串
    pDC->TextOut(50, 50, str);

    // 获取视图窗口显示的字符串区域大小
    CSize sz = pDC->GetTextExtent(str);

    // 在路径层显示一个刚好覆盖这个字符串的矩形
    pDC->BeginPath();
    pDC->Rectangle(50, 50, 50 + sz.cx, 50 + sz.cy);
    pDC->EndPath();

    // pDC->SelectClipPath(RGN_DIFF);
    pDC->SelectClipPath(RGN_AND);

    // 在视图窗口输出一些网格以帮助显示
    for (int i = 0; i < 300; i += 10)
    {
        pDC->MoveTo(0, i);
        pDC->LineTo(300, i);
        pDC->MoveTo(i, 0);
        pDC->LineTo(i, 300);
    }
}

当交互模式设置为RGN_DIFF时,新的裁剪区域等于原来的裁剪区域除去路径层部分
这里写图片描述
当交互模式设置为RGN_AND时,新的裁剪区域等于原来的裁剪区域与路径层的交集
这里写图片描述

注意 即使设置了路径层与裁剪区域的交互模式,路径层之中绘制的矩形是依然不会显示出来的,设置路径层只不过是能够与裁剪区域产生交互从而达到一些特殊效果

  • 打开两个路径层,分别在里面绘制一个矩形,利用RGN_DIFF来设置路径层与裁剪区域的交互模式
void Cchap12View::OnDraw(CDC* pDC)
{
    Cchap12Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;

    // TODO: 在此处为本机数据添加绘制代码
    CString str;
    // 利用CString的成员函数加载一个字符串资源
    str.LoadString(IDS_STRING101);
    // 在视图窗口显示该字符串
    pDC->TextOut(50, 50, str);

    // 获取视图窗口显示的字符串区域大小
    CSize sz = pDC->GetTextExtent(str);

    // 在路径层显示一个刚好覆盖这个字符串的矩形
    pDC->BeginPath();
    pDC->Rectangle(50, 50, 50 + sz.cx, 50 + sz.cy);
    pDC->EndPath();

    // 再打开一个新的路径层,并在这个新的路径层里面
    pDC->BeginPath();
    pDC->Rectangle(50, 200, 50 + sz.cx, 200 + sz.cy);
    pDC->EndPath();

    pDC->SelectClipPath(RGN_DIFF);
    // pDC->SelectClipPath(RGN_AND);

    // 在视图窗口输出一些网格以帮助显示
    for (int i = 0; i < 300; i += 10)
    {
        pDC->MoveTo(0, i);
        pDC->LineTo(300, i);
        pDC->MoveTo(i, 0);
        pDC->LineTo(i, 300);
    }
}

运行之后可以发现当前的裁剪区域只是原来的裁剪区域除去第二个路径层的部分,并没有除去第一个路径层的部分
这里写图片描述
修改一下代码,在关闭第一个路径层的语句下面一句设置路径层与当前裁剪区域的交互

// 在路径层显示一个刚好覆盖这个字符串的矩形
pDC->BeginPath();
pDC->Rectangle(50, 50, 50 + sz.cx, 50 + sz.cy);
pDC->EndPath();
pDC->SelectClipPath(RGN_DIFF);

再次运行后可以发现第一个路径层的作用也被显示出来了
这里写图片描述
所以每个路径层都需要使用一个SelectClipPath函数来设置它与当前裁剪区域之间的关系

4. 字符输入

4.1 字符输入实现

字符输入其实是对前面的所学的一个综合,主要需要考虑几个问题

  • 接收并储存字符的输入
  • 对特殊字符的考虑,比如接收到换行符之后就需要新起一行来显示输入的字符,接收到退格符之后就要删除一个已输出的字符
  • 插入符的位置显示,当用鼠标点检任意一个位置之后插入符就需要在鼠标点击的地方出现
  • 插入符位置的更新。当输入新的字符之后,插入符需要不断向后移动;当删除已有的字符之后,插入符需要向前移动
  • 当发生换行之后,插入符需要移动到准确的位置

4.2 字符字体

  • 利用一个CFont对象来设置当前字体
  • 用CDC类成员函数SelectObject来将CFont对象设置为当前字体

4.3 字符变色功能实现

5. 遇到问题及解决办法

  • 新建资源的资源标识符无法被VS报错,未定义的标识符
    忽略VS警告直接运行即可或者重启一下VS,这个问题只不过是VS的编辑器的数据没有更新而已
  • 添加新的字符串资源时显示该资源只能有一个实例
    字符串资源只能通过在字符串表中插入一个新的资源来实现,并不能新建字符串表

6. 参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值