参考学习源码:https://bbs.pediy.com/thread-185555.htm
推荐开源的图形库libuidk:https://github.com/iUIShop/LibUIDK
有些作者喜欢析构函数初始化(标准写法),查看析构函数是否初始化了全局变量,也许会有很多莫名的问题,如Gid Image类对象获取不到图片资源等,因为没有初始化,要去看构造函数是是否对类进行初始化了变量(读源码排了好久BUG这个),如下:
GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
阅读重绘类时候,不要盲目的粘贴复制.h与.cpp,应该看类向导,该类是否关联了窗口ID,如果关联了Dlg需要添加资源一个窗口在生成类,根据函数调用的方法来阅读重绘类中函数,逻辑才能清晰。
个人阅读,一般先流程顺下来。而不是见到重绘类就要去放下当前梳理深入学习,先把流程顺下来,比如按钮、字体用了那个类,调用的函数,先怎么做后怎么做,能够编译实现效果后在看原理。先会用,后深入,也许效率应该会高一些。
梳理作者代码后,作者创建了几个全局List容器保存按钮、字体属性,包括界面中的布局,先从背景图梳理,有很多种方式,OnPaint进行背景填充:
// 需要先初始化,没用过所以不知道,排查了好久 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相关数据,不熟悉百度就完事了:
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天前 被一半人生编辑 ,原因: