首页
论坛
专栏
课程

[原创]MFC界面重绘笔记

3天前 1183

[原创]MFC界面重绘笔记

3天前
1183

参考学习源码:https://bbs.pediy.com/thread-185555.htm

推荐开源的图形库libuidk:https://github.com/iUIShop/LibUIDK

阅读源码主界面Dlg对话框,界面重绘部分可以从以下入口阅读源码:

  1. OnInitDialog 查看是否有界面相关初始化数据(主要看数据初始化)
  2. OnPaint 处理WM_PAINT,重绘应用窗口程序(主要看重绘过程)
  3. OnCreate 创建响应, 以SDK形式进行封装重写,如CreateWindow(TEXT("Button")这种创建控件,再手动封装成类

     有些作者喜欢析构函数初始化(标准写法),查看析构函数是否初始化了全局变量,也许会有很多莫名的问题,如Gid Image类对象获取不到图片资源等,因为没有初始化,要去看构造函数是是否对类进行初始化了变量(读源码排了好久BUG这个),如下:
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
  1. OninitDialog 初始化一般控件风格与数据
  2. OnCreate 创建重绘的控件,如按钮控件,文本控件等
  3. OnPaint 应用重绘
  4. OnColor 颜色重绘

    阅读重绘类时候,不要盲目的粘贴复制.h与.cpp,应该看类向导,该类是否关联了窗口ID,如果关联了Dlg需要添加资源一个窗口在生成类,根据函数调用的方法来阅读重绘类中函数,逻辑才能清晰。

    个人阅读,一般先流程顺下来。而不是见到重绘类就要去放下当前梳理深入学习,先把流程顺下来,比如按钮、字体用了那个类,调用的函数,先怎么做后怎么做,能够编译实现效果后在看原理。先会用,后深入,也许效率应该会高一些。

   梳理作者代码后,作者创建了几个全局List容器保存按钮、字体属性,包括界面中的布局,先从背景图梳理,有很多种方式,OnPaint进行背景填充:

背景图绘画关键code:

// 需要先初始化,没用过所以不知道,排查了好久
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

// 获取窗口矩形位置
CRect rcPaint;
dcMem.GetClipBox(&rcPaint);
Gdiplus::Graphics graphics(dcMem.m_hDC);

// Image类加载资源
Image *aImage;
switch (g_index)
{
// 这里用了一张白色白色背景
case 0:aImage = Image::FromFile(L"repos\\NetworkFilter\\resource\\skin\\frame.jpg"); break;
}

// DrawImage方法画背景图
graphics.DrawImage(aImage, 0, 0, cClientRect.Width(), cClientRect.Height());  

绘画按钮与文字

     绘制图标,文字、三种状态。选中,点击,正常效果是不一样的,要有三种标志区分, 作者用了List链表容器m_ButtonList去装在这些数据对象,调用CImageInfo类,绘制图标按钮,我们展示部门代码与逻辑,重在理解作者思路与代码:

结构体如下:

// 图片切换所需资源信息
struct ButtonInfo
{
	CString sName;
	UINT nIcon;
	UINT nBack;
	RectF gdiRectZ;
	RectF gdiRectB;
};

OnCreate用来初始化位置名字,资源ID添加到结构体,保存到List容器中

// 初始化按钮属性
gdiRectZ.X = 93, gdiRectZ.Y = 83; gdiRectZ.Width = 58; gdiRectZ.Height = 15;
gdiRectB.X = 97, gdiRectB.Y = 30;
AddButton(L"保护网速", IDB_PNG_NETWORKPROTECT, IDB_PNG_NETWORKPROTECT, gdiRectZ, gdiRectB);

// AddButton函数就是对结构体赋值,push_back
ButtonInfo buttonInfo;
buttonInfo.sName = SzName; 

// "保护网速"
buttonInfo.nIcon = nIcon;
 //  IDB_PNG_NETWORKPROTECT
buttonInfo.nBack = nBack; 
// IDB_PNG_NETWORKPROTECT
buttonInfo.gdiRectZ = gdiRectZ;   // gdiRectZ
buttonInfo.gdiRectB = gdiRectB;  // gdiRectB
//保存到容器
m_ButtonList.push_back(buttonInfo); 

    OnPaint用来重绘界面,因为OnCreate初始化了按钮相关数据,保存在了m_ButtonList,所以遍历容器中的元素ButtonInfo结构,利用DrawImage的方法进行绘画:

for (int i = 0; i < (int)m_ButtonList.size(); i++)
{
	Image* pIconImage = CImageInfo::Instance()->ImageFromResource(m_ButtonList[i].nIcon);
	m_ButtonList[i].gdiRectB.Width = pIconImage->GetWidth();
	m_ButtonList[i].gdiRectB.Height = pIconImage->GetHeight();
	graphics.DrawImage(pIconImage, m_ButtonList[i].gdiRectB);
}

绘制文字,有个问题:当用纯白背景色的时候,字体颜色如果是白色会覆盖的,而并不是没有显示字体,所以注意颜色搭配,并不是BUG:

// 先要初始化字体
StringFormat stringFormat;
LOGFONT lfFont;
memset(&lfFont, 0, sizeof(lfFont));
lfFont.lfHeight = -13;
lfFont.lfWeight |= 400;
lstrcpy(lfFont.lfFaceName, _T("宋体"));
Gdiplus::Font font(dcMem.GetSafeHdc(), &lfFont);

/*
	绘制文字
*/
CString sName = m_ButtonList[i].sName;
CString wName = T2W(sName.GetBuffer());
SolidBrush brush((ARGB)Color::Black);
graphics.DrawString(wName, wName.GetLength(), &font, m_ButtonList[i].gdiRectZ, &stringFormat, &brush);

三种状态按钮,ID资源使用PNG图片不一样,其余完全一样。

// 这里只举例一种,默认是第一个选中,需要初始化做相关定义
switch (状态)
{
case 选中:
{
	// 加载资源
	Image *pBackHover = CImageInfo::Instance()->ImageFromResource(IDB_PNG_PUSHED);
	// m_ButtonList获取布局
	gdiRect.X = m_ButtonList[i].gdiRectB.X - 13, gdiRect.Y = m_ButtonList[i].gdiRectB.Y - 8;
	gdiRect.Width = pBackHover->GetWidth();
	gdiRect.Height = pBackHover->GetHeight();
	// 重绘字体
	graphics.DrawImage(pBackHover, gdiRect, 0, 0, pBackHover->GetWidth(), pBackHover->GetHeight(), UnitPixel);
}
break;
}

其实到这里作者代码逻辑很清晰,OnCreate初始化按钮、字体等控件数据,OnPaint通过容器遍历绘画界面。先来看一下当前效果,如下所示:

    上述可以看出按钮、字体效果都是没有问题,只不过布局比较难看.....,这些都是微调整而已,无需太折腾。

   

    下面思考如何响应按钮?进行不同页面切换与操作。正常来说需要点击按钮,发送WM消息至消息队列,处理分发函数。因为是绘画出来的按钮与字体,没有实体控件BUTTON控件可以给响应,那么应该是通过鼠标响应来找到对应的按钮布局(按钮位置固定)。如果你不想读源码那么麻烦,百度:MFC如何捕获鼠标移动与响应消息应该就能找到,自己实现也可以

这里还是读源码为主,先看原类中有那些WM消息,如下所示:

找到与鼠标移动Mouse相关的WM,后面再看按钮消息WM_LBUTTONDOWN相关数据,不熟悉百度就完事了:

  1. OnMouseWheel 滚动条相关
  2. OnMouseHover与OnMouseLeave 捕获鼠标消息的
  3. OnMouseMove 响应鼠标移动事件

OnMouseLeave、OnMouseHover、OnMouseMove阅读源码,以前没用过这三个消息响应,百度一下用法,如何协作工作,然后对比源码进行理解与修改:

void   CXXXWnd::OnMouseHover()
{
	MessageBox("鼠标已进入 ");
	m_bTrackLeave = FALSE;
}

void   CXXXWnd::OnMouseLeave()
{
	MessageBox("鼠标已离开 ");
	m_bTrackLeave = FALSE;
}

void  CXXXWnd::OnMouseMove(UINT   nFlags, CPoint   point)
{
	if (!m_bTracking)
	{
		//   鼠标移入窗时,请求WM_MOUSEHOVER和WM_MOUSELEAVE 消息
		TRACKMOUSEEVENT tme;
		tme.cbSize = sizeof(tme);
		tme.hwndTrack = m_hWnd;
		tme.dwFlags = TME_LEAVE | TME_HOVER;
		tme.dwHoverTime = 1;
		m_bTracking = _TrackMouseEvent(&tme);
	}
	CWnd::OnMouseMove(nFlags, point);
}

    需要写函数获取跟踪当前鼠标移动,鼠标移动到按钮或者点击按钮,设置标志位全局变量,单纯鼠标移动不需要获取当前移动到第几个按钮,直接用坐标重绘,而点击,需要判断是否在当前按钮上,然后在重绘与响应,根据坐标OnPaint绘画出图片效果,选中框与鼠标移动:

RectF gdiRect;
if (m_iSelected == i)
{
	Image *pBackHover = CImageInfo::Instance()->ImageFromResource(IDB_PNG_PUSHED);
	gdiRect.X = m_ButtonList[i].gdiRectB.X - 13, gdiRect.Y = m_ButtonList[i].gdiRectB.Y - 8; 
	gdiRect.Width = pBackHover->GetWidth(); 
	gdiRect.Height = pBackHover->GetHeight();
	graphics.DrawImage(pBackHover, gdiRect, 0, 0, pBackHover->GetWidth(), pBackHover->GetHeight(), UnitPixel);
}
else if (m_iHovering == i)
{
	Image *pBackHover = CImageInfo::Instance()->ImageFromResource(IDB_PNG_HOVER);
	gdiRect.X = m_ButtonList[i].gdiRectB.X - 13, gdiRect.Y = m_ButtonList[i].gdiRectB.Y - 8; 
	gdiRect.Width = pBackHover->GetWidth(); 
	gdiRect.Height = pBackHover->GetHeight();
	graphics.DrawImage(pBackHover, gdiRect, 0, 0, pBackHover->GetWidth(), pBackHover->GetHeight(), UnitPixel);
}
else
{
	Image *pBackHover = CImageInfo::Instance()->ImageFromResource(IDB_PNG_NORAMAL);
	gdiRect.X = m_ButtonList[i].gdiRectB.X - 13, gdiRect.Y = m_ButtonList[i].gdiRectB.Y - 8; 
	gdiRect.Width = pBackHover->GetWidth(); 
	gdiRect.Height = pBackHover->GetHeight();
	graphics.DrawImage(pBackHover, gdiRect, 0, 0, pBackHover->GetWidth(), pBackHover->GetHeight(), UnitPixel);
}

    按键消息需要响应,按钮状态切换与展示页面切换。响应按钮消息WM_LBUTTONDOWN,然后判断当前鼠标是否在按钮坐标内,初始化时候已经将按钮坐标x,y保存在容器中,对比切换。

    后续List重绘我选择用了控件,当按钮响应的时候进行显示与控件风格初始化就好,所以没有使用绘画来去响应,抛砖引玉。

注意:

  1. 也许变量会没有数据,善用查找所有引用,看在变量被初始化或者函数,便可快速定位到函数与关键点。

  2. 当头文件交互错乱,一些.h添加到自己工程会出现重定义,不明确编译失败,需要排查头文件是否相互包含,解决不明确最快的有效的方法,包含命名空间与类,如下:

using namespace Gdiplus; 
Gdiplus::Font 



[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 3天前 被一半人生编辑 ,原因:
最新回复 (19)
刘铠文 3天前
2
0
mark
yimingqpa 1 3天前
3
0
windows都快不行了,还在mfc.......
Duilib,soui,DuiVision,QT,minigui,GTK,GuiLite,炫彩,imgui,REDM等等等,随便一个界面库都可以弄的狂拽炫酷吊炸天.......
敏而好学 3天前
4
0
yimingqpa windows都快不行了,还在mfc....... Duilib,soui,DuiVision,QT,minigui,GTK,GuiLite,炫彩,imgui,REDM等等等,随便一个界面库都可以弄 ...
圈子不同,你只看到QQ、迅雷这些互联网产品,没有看到其它领域。你让医院用手机办公?
敏而好学 3天前
5
0
1、2线城市有人说拼多多山寨多,可他们不知道3、4线城市无数家庭月收入不会达到5000。
Mxixihaha 2天前
6
0
对于一般的程序  MFC确实没什么用处了.    还不如delphi来得快速,  等你画完,一个程序都搞完了. 

对于真正的需要用C++开发又要美化的产品,也不需要MFC 完全 WIN SDK  或者 一些UI库来达到更好的控制效果.

所以... MFC 就是一个鸡肋. 上不上中不中下不下

很少有专业软件选择MFC开发. 一是成本和时间,后期维护 转平台 都是问题.
最后于 2天前 被Mxixihaha编辑 ,原因:
tmflxw 2天前
7
0
Mxixihaha 对于一般的程序&nbsp;&nbsp;MFC确实没什么用处了.&nbsp;&nbsp;&nbsp;&nbsp;还不如delphi来得快速,&nb ...
那么问题来了,用c++到底应该怎么解决效率与维护的问题?
hkfans 3 2天前
8
0
QT啊,MFC只能用来写小工具,不在乎界面的可以。。要漂亮界面的用MFC就是浪费时间
最后于 2天前 被hkfans编辑 ,原因:
敏而好学 2天前
9
0
hkfans QT啊,MFC只能用来写小工具,不在乎界面的可以。。要漂亮界面的用MFC就是浪费时间
圈子不同,我最近发现的,我去买电脑问能不能装VS,说运行C语言的,4个大学毕业的,居然C语言都不知道。有时我们常常认为某事是尽人皆知的,其实程序员圈子也很小,医院办公用花里胡哨的界面,医生去欣赏界面,金融机构去运行QT这种效率低下的软件看期指数据。
hkfans 3 2天前
10
0
要运行效率而不是开发效率,那用SDK绘制,类似OD那样
敏而好学 2天前
11
0
hkfans 要运行效率而不是开发效率,那用SDK绘制,类似OD那样
能用SDK写出很多程序的人,都是精英人物,如果有能力当然用了。
hkfans 3 2天前
12
0
你去研究下OD的绘制,你会发现用SDK绘制也比MFC简单
htpidk 2天前
13
0
敏而好学 能用SDK写出很多程序的人,都是精英人物,如果有能力当然用了。
其实用win32 sdk编程很简单的,我是自学的,编程是爱好,自学了汇编,c,c++还有简单的js,当时嫌弃mfc太臃肿,自己用winsdk写了一套界面库,其实非常简单,断断续续抽空写了一个月完成了15种windows标准控件
14
0
我大学第一年软件设计课程就是写mfc,当时的window消息机制搞的我们头晕目眩。后来还草出个基于socket的对战游戏。
结果大学一毕业就找了个java的工作,早知道学个屁的mfc。早该淘汰的东西,写界面用.net的winform WPF不好吗?
xie风腾 1天前
15
0

差点上了MFC的当,看来学QT好
xuenixiang 1 1天前
16
1
我前面也在学mfc,我感觉mfc挺好的的呀,对于逆向初学者来说,mfc可以认识很多函数,我感觉学习mfc对我最大的好处就是:提高逆向的水平,mfc可以解释一些根本上的原理问题
xiaopingxp 1天前
17
0
我就不想吐槽上面几位了,人家喜欢用,你们真的是咸吃萝卜,淡操心。。。。
shangde 6小时前
18
1
MFC挺好,没有那么差劲
shangde 5小时前
19
1
MFC开发速度挺快,而且界面效果不差,直画好看也不能大饭吃,MFC开发速度不差,现成的组件很多,框架,皮肤耶很多,性能比QT好,界面比SDK好,免费收费框架多,代码量少,开发速度快,成熟的商业案例也很多,所以MFC不差
最后于 5小时前 被shangde编辑 ,原因:
TX杀手 3小时前
20
0
VB6.0  了解下。谢谢
最后于 3小时前 被TX杀手编辑 ,原因:
游客
登录 | 注册 方可回帖
返回