文章目录
- 虚函数和纯虚函数的区别?
- MFC中什么是UPDATE_COMMAND_UI 消息
- 如何查看控件对应的成员变量
- 模态对话框的理解
- HGDIOBJ" 类型的值不能用于初始化 "CBrush *" 类型的实体错误
- MFC编程中CDC类型和HDC类型有什么区别?
- 关于WIDING和ALTERNATE填充方式的问题
这是MFC中CdcdrawView函数中的消息映射宏,我的问题是如何找到ID_SET对应的是什么菜单项资源
最后的解决方案比较简单,只需要使用 Ctrl + F
,然后输入 ID_SET
来搜索,注意到要调整搜索范围为当前项目,就可以找到对应的文件了!
注意要点击查找全部,可以看到搜索到的结果完美的符合要求, ID_SET
是设置菜单项对应的ID。
虚函数和纯虚函数的区别?
虚函数(Virtual Function)
定义:虚函数是在基类中使用关键字 virtual 声明的函数。它可以在派生类中被重写(Override)。
实现:虚函数在基类中通常有具体的实现。这意味着即使派生类没有重写这个函数,也可以使用基类中的实现。
目的:虚函数的主要目的是允许在派生类中对基类方法进行扩展或修改,同时使用基类指针或引用来实现多态。
示例:
class Base {
public:
virtual void display() {
std::cout << "Display of Base" << std::endl;
}
};
多态:通过基类指针或引用调用虚函数时,会根据对象的实际类型来调用相应的函数实现,实现多态。
纯虚函数(Pure Virtual Function)
定义:纯虚函数是在基类中使用 virtual
关键字声明的,但没有提供任何实现,并以 = 0
结尾。例如,virtual void display() = 0;
。
实现:纯虚函数在基类中没有实现,派生类必须重写这个函数,除非派生类也是抽象类。
目的:纯虚函数的主要目的是定义一个接口,强制派生类实现特定的函数。这是创建抽象基类(即不能直接实例化的类)的一种方式。
示例:
class Base {
public:
virtual void display() = 0; // 纯虚函数
};
抽象类:包含至少一个纯虚函数的类称为抽象类。不能创建抽象类的实例。这是因为抽象类定义了一个尚未完全实现的接口。
总结
虚函数允许在基类中有一个默认实现,而派生类可以选择是否提供新的实现。
纯虚函数不在基类中提供实现,强制派生类提供自己的实现。包含纯虚函数的类成为抽象类,无法实例化。
MFC中什么是UPDATE_COMMAND_UI 消息
在 MFC (Microsoft Foundation Classes) 应用程序中,UPDATE_COMMAND_UI
消息是一种用于实时更新用户界面元素状态的机制。它不是由用户直接操作触发的,而是由框架在特定时刻自动发送,用于查询命令(如菜单项、工具栏按钮等)的当前状态。这允许程序动态地调整用户界面元素,以反映当前的应用程序逻辑和数据状态。
作用
UPDATE_COMMAND_UI
消息的主要作用是维护用户界面的一致性和正确性。例如,根据当前文档的状态,一个菜单项可能需要被启用或禁用,或者一个工具栏按钮可能需要显示为被选中或未选中状态。
如何工作
当 MFC 应用程序的主消息循环运行时,框架会定期发送 UPDATE_COMMAND_UI 消息给所有的用户界面元素(如菜单项和工具栏按钮)。接收到这个消息的用户界面元素需要决定自己的显示状态。这通常涉及以下几个方面:
是否启用:元素是否可用或灰显(禁用)。
是否选中:例如,切换按钮的选中/未选中状态。
其他视觉状态:如文本标签的变更等。
示例
假设您的应用程序有一个 “保存” 菜单项,您希望在文档被修改后启用它,在文档未修改时禁用它。您可以添加一个处理 UPDATE_COMMAND_UI 消息的函数来实现这一点:
void CMyApp::OnUpdateFileSave(CCmdUI* pCmdUI)
{
// 检查文档是否已修改
bool isModified = /* 获取文档状态 */;
// 根据文档的修改状态启用或禁用 "保存" 菜单项
pCmdUI->Enable(isModified);
}
在这个例子中,每当用户界面需要更新时(例如,当用户编辑文档时),这个函数会根据文档的当前修改状态来启用或禁用 “保存” 菜单项。
如何查看控件对应的成员变量
打开类向导中的对话框类对,可以看到成员变量和对应的控件ID。
模态对话框的理解
HGDIOBJ" 类型的值不能用于初始化 “CBrush *” 类型的实体错误
在CGraph类的绘制函数中,在选中画笔画刷的过程中出现了一点小问题,错误是
严重性 代码 说明 项目 文件 行 禁止显示状态
错误(活动) E0144 “HGDIOBJ” 类型的值不能用于初始化 “CBrush *” 类型的实体 dcdraw E:\LearningMaterial\大四上\OO报告\综合实验二\202000800341_XXX_综合2_1\CGraph.cpp 448
//选中画笔画刷
CPen* pOldpen=pDC->SelectObject(&bpen);
if (bfill) { //填充
CBrush* pOldBrush=pDC->SelectObject(&br);
}
else{
CBrush* pOldBrush= pDC->SelectObject(GetStockObject(NULL_BRUSH));
}
可以看到上下两行的画刷选中代码几乎一致,为什么会出现这样的问题呢?
在 MFC 中,CDC::SelectObject
方法期望一个与设备上下文相关的对象(例如 CBrush
、CPen
等),但是 GetStockObject
函数返回的是一个 HGDIOBJ
(一个更通用的GDI对象句柄)。HGDIOBJ 是 Windows API 中的一个术语,是 “Handle to a GDI Object” 的缩写。
因此,从 HGDIOBJ 转换到 CBrush* 需要显式的类型转换,而不是隐式的。修改成如下的形式,即没有问题。
//选中画笔画刷
CPen* pOldpen=pDC->SelectObject(&bpen);
if (bfill) { //填充
CBrush* pOldBrush=pDC->SelectObject(&br);
}
else{
CBrush* pOldBrush= (CBrush*)pDC->SelectObject(GetStockObject(NULL_BRUSH));
}
MFC编程中CDC类型和HDC类型有什么区别?
在Windows编程中,CDC 和 HDC 是两种不同的,但相关的概念,它们都用于图形绘制,但在不同层面上提供功能:
- HDC (Handle to Device Context):
- HDC 是一个指向设备上下文的句柄。在Windows API(特别是在底层的C或C++编程中)中使用。
- 它是一个低层次的图形绘制接口,提供了直接访问GDI(图形设备接口)的功能。
- HDC 可以用于屏幕、内存位图、打印机等多种绘图表面。
- 使用 HDC 通常涉及直接的Windows API调用,例如 MoveToEx, LineTo, TextOut, Ellipse 等。
- CDC (Class for Device Context):
- CDC 是MFC(Microsoft Foundation Classes)框架中的一个类,它封装了 HDC。
- 它是一个更高层次的、面向对象的接口,使得在使用MFC编写的C++程序中绘图变得更简单和更直观。
- CDC 提供了更多的安全性和易用性,因为它自动管理资源,如选择和取消选择GDI对象(例如画笔、字体)。
- CDC 类包含了多个用于绘图的成员函数,这些函数实际上是对 HDC 函数的封装。例如,CDC::LineTo 是 LineTo 的封装。
简而言之,HDC 是一个更接近于Windows底层的、基于句柄的设备上下文表示,而 CDC
是MFC框架中的一个类,它提供了一个更高级别和更面向对象的接口来处理设备上下文。在MFC程序中,通常使用 CDC 而不是直接操作 HDC,因为它简化了代码并提高了安全性。但在底层的WinAPI编程中,HDC 是必不可少的。
关于WIDING和ALTERNATE填充方式的问题
这个问题其实并不重要,但是没想到随便一搜迟迟难以找到答案,给我精神造成了极大的打击,务必要记录下来。
- ALTERANATE:从左到右水平扫描!系统只填充每个扫描行的多边行的奇数边到偶数边的部分,不填充偶数边到奇数边的部分;
MSDN中的原话如下:
When the fill mode is ALTERNATE, GDI fills the area between odd-numbered and even-numbered polygon sides on each scan line. That is, GDI fills the area between the first and second side, between the third and fourth side, and so on.
水平扫描:
就是显示器上的从左到右一行一行的水平扫描,假如你客户区大小是 500 × 600 500\times600 500×600,那就是从 ( 0 , 0 ) (0,0) (0,0) 到 ( 500 , 0 ) (500,0) (500,0) 这是第一次水平扫描,…一直到 ( 0 , 600 ) (0,600) (0,600) 到 ( 500 , 600 ) (500,600) (500,600)。共扫描了600次就扫描完了客户
奇数边和偶数边:
这里说的奇数和偶数边完全是相对而言的啊,只是我们便于理解的一种方法,就是说在水平扫描一行时,第一次遇到对线就把它定义为第一条边(注意:同一条线的话在不同的扫描行上相对的边可能不是一样的,这点一定要注意思了),第二次遇到的线就是第二条边,以此类推;那么对于一个矩形( 就单单一个矩形),左是第一条边,右是第二条边,上下它就什么都不是了,不会存在第三条边和第四条边的.
- WINDING模式下:填充奇数边到偶数边和ALTERANATE模式一样,但填充偶数边到奇数边的部分就不同了,你必须记主画线的方向,你可以取一个方向为正向,用一个计数器
cnt = 0
,当线经过正向时cnt就加1,反向时cnt就减1,如果最后cnt为0就填充这个区域,不为0就不填充!
MSDN中的原话如下:
When the fill mode is WINDING, GDI fills any region that has a nonzero winding value. This value is defined as the number of times a pen used to draw the polygon would go around the region. The direction of each edge of the polygon is important.
这是WIDING模式下的填充结果,可以看到存在奇数边到偶数边问题的只有4和5。
- 我们首先观察4,根据从区域4得到的射线,设定正方向为顺时针,经过的第一条边-1,第二条边+1,
cnt = 0
,所以区域4不填充。 - 接着从区域5观察,设定正方向为顺时针,经过的第一条边+1,第二条边+1,所以区域5填充!
一种简单的理解方式是看能不能环绕成功,能环绕成功则可以填充,下图五角星是一笔画绘制的,可以自己思考一下