MFC界面重绘笔记
2019-12-04 00:59:06 Author: bbs.pediy.com(查看原文) 阅读量:288 收藏

参考学习源码: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

最后于 1天前 被一半人生编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-256025.htm
如有侵权请联系:admin#unsafe.sh