MFC中使用GDI+ 自定义等待界面
MFC中使用GDI+ 自定义等待界面
在工作中我们总是会遇到等待任务完成的场景,我们可以设置Windows自带的鼠标光标是等待的,沙漏(⏳),今天介绍使用GDI+在MFC程序中自定义简单的等待界面。
第一步:新建一个MFC程序,然后再添加一个对话框,添加类CWaitInfoDlg如下:

第二步:添加代码
1.加载GDI+库,并且编写自定义代码。WaitInfoDlg.h如下:
#pragma once
#include <afxcmn.h>
#include <gdiplus.h>
#include "resource.h"
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;// CWaitInfoDlg 对话框
class CWaitInfoDlg : public CDialog
{DECLARE_DYNAMIC(CWaitInfoDlg)public:CWaitInfoDlg(CWnd* pParent = NULL); // 标准构造函数virtual ~CWaitInfoDlg();// 对话框数据enum { IDD = IDD_DIALOG_WAITINFO };protected:virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持DECLARE_MESSAGE_MAP()public:virtual BOOL OnInitDialog();void StartAnimation(); // 启动定时器void StopAnimation(); // 停定时器void SetType(int nType); // 设置类型void SetTitle(CString strTitle); // 设置标题
private:UINT m_nAngle; // 当前旋转角UINT m_nFrame; // 当前帧 0-59int m_nType; // 等待效果类型CString m_strTitle; // 标题
public:afx_msg void OnPaint();afx_msg void OnTimer(UINT_PTR nIDEvent);
};
WaitInfoDlg.cpp
#include "pch.h"
#include "WaitInfoDlg.h"// CWaitInfoDlg 对话框IMPLEMENT_DYNAMIC(CWaitInfoDlg, CDialog)CWaitInfoDlg::CWaitInfoDlg(CWnd* pParent /*=NULL*/): CDialog(CWaitInfoDlg::IDD, pParent)
{m_nAngle = 0;m_nType = 0;
}CWaitInfoDlg::~CWaitInfoDlg()
{}void CWaitInfoDlg::DoDataExchange(CDataExchange* pDX)
{CDialog::DoDataExchange(pDX);
}BEGIN_MESSAGE_MAP(CWaitInfoDlg, CDialog)ON_WM_PAINT()ON_WM_TIMER()
END_MESSAGE_MAP()// CWaitInfoDlg 消息处理程序
BOOL CWaitInfoDlg::OnInitDialog()
{CDialog::OnInitDialog();// TODO: 在此添加额外的初始化// 初始化 GDI+Gdiplus::GdiplusStartupInput gdiplusStartupInput;ULONG_PTR gdiplusToken;Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);// 让窗口支持分层透明SetWindowLong(m_hWnd, GWL_EXSTYLE,GetWindowLong(m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);// 整窗透明(0-255)SetLayeredWindowAttributes(0, 200, LWA_ALPHA);SetWindowText(m_strTitle); // 设置标题return TRUE; // return TRUE unless you set the focus to a control// 异常: OCX 属性页应返回 FALSE
}void CWaitInfoDlg::OnPaint()
{CPaintDC dc(this); // device context for painting// TODO: 在此处添加消息处理程序代码// 不为绘图消息调用 CDialog::OnPaint()CRect rc;GetClientRect(&rc);//==== 1. 创建内存 DC + 位图(与屏幕同尺寸同色深)CDC memDC;memDC.CreateCompatibleDC(&dc);CBitmap memBmp;memBmp.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height());CBitmap* pOldBmp = memDC.SelectObject(&memBmp);//==== 2. 在内存里整帧绘制(先清背景,再画水泡)memDC.FillSolidRect(&rc, RGB(255, 255, 255)); // 背景Gdiplus::Graphics g(memDC.m_hDC); // 内存 DC 交给 GDI+g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);int cx = rc.Width() / 2;int cy = rc.Height() / 2;int radius = min(cx, cy) - 10;switch (m_nType){case 0:{// 画 5 个扇形组成旋转环for (int i = 0; i < 5; i++){int start = m_nAngle + i * 72;Gdiplus::GraphicsPath path;path.AddArc(cx - radius, cy - radius, 2 * radius, 2 * radius, start, 60);path.AddArc(cx - radius + 10, cy - radius + 10, 2 * (radius - 10), 2 * (radius - 10), start + 60, -60);path.CloseFigure();Gdiplus::SolidBrush br(Gdiplus::Color(255 - i * 40, 100, 255));g.FillPath(&br, &path);}}break;case 1:{for (int i = 0; i < 18; i++){int angle = (m_nFrame + i * 10) * 2;double rad = angle * 3.1415926 / 180.0;int x = cx + (int)(radius * cos(rad));int y = cy + (int)(radius * sin(rad));int r = 10 + i * 2;Gdiplus::GraphicsPath path;path.AddEllipse(x - r, y - r, 2 * r, 2 * r);Gdiplus::SolidBrush br(Gdiplus::Color(255 - i * 40, 100, 255));g.FillPath(&br, &path);}}break;}//==== 3. 一次性把整帧拷到屏幕(无闪烁)dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY);//==== 4. 清理memDC.SelectObject(pOldBmp);CDialog::OnPaint();
}void CWaitInfoDlg::OnTimer(UINT_PTR nIDEvent)
{// TODO: 在此添加消息处理程序代码和/或调用默认值if (nIDEvent == 1){switch (m_nType){case 0:{m_nAngle += 10;if (m_nAngle >= 360)m_nAngle = 0;break;}case 1:{m_nFrame = (m_nFrame + 1) % 20;break;}}Invalidate(FALSE);}CDialog::OnTimer(nIDEvent);
}void CWaitInfoDlg::StartAnimation()
{SetTimer(1, 30, NULL);
}
void CWaitInfoDlg::StopAnimation()
{KillTimer(1);
}void CWaitInfoDlg::SetType(int nType)
{m_nType = nType;
}void CWaitInfoDlg::SetTitle(CString strTitle)
{m_strTitle = strTitle;
}
代码说明:
1.这里定义了两种等待显示界面,一种是旋转环,一种是旋转气泡;通过调用SetType设置,选择显示哪一种。
2.另外我们通过定时器来实现界面刷新。
3.绘制代码中,使用的GDI+中的Graphics类来绘制圆环和圆。
4.代码使用了双缓冲显示,解决界面闪烁的问题。
第三步:代码测试
自定义两个消息:
#define UM_TASKSTART (WM_USER + 100)
#define UM_TASKEND (WM_USER + 101)
3.1 在CMainFrame.h中添加如下代码:
public:CWaitInfoDlg m_WaitInfoDlg;afx_msg LRESULT OnTaskStart(WPARAM wParam,LPARAM lParaam);afx_msg LRESULT OnTaskEnd(WPARAM wParam, LPARAM lParaam);afx_msg void OnTimer(UINT_PTR nIDEvent);
将自定义消息绑定到OnTaskStart、OnTaskEnd中:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)ON_WM_CREATE()ON_MESSAGE(UM_TASKSTART,&CMainFrame::OnTaskStart)ON_MESSAGE(UM_TASKEND, &CMainFrame::OnTaskEnd)ON_WM_TIMER()
END_MESSAGE_MAP()
在CMainFrame.cpp中实现自定义消息响应函数:
LRESULT CMainFrame::OnTaskStart(WPARAM wParam, LPARAM lParaam)
{if (m_WaitInfoDlg.m_hWnd == NULL){m_WaitInfoDlg.Create(IDD_DIALOG_WAITINFO, this);m_WaitInfoDlg.SetWindowPos(NULL, 0, 0, 500, 500, SWP_SHOWWINDOW);m_WaitInfoDlg.SetTitle(_T("任务处理中..."));m_WaitInfoDlg.SetType(1);m_WaitInfoDlg.ShowWindow(SW_SHOW);m_WaitInfoDlg.UpdateWindow();m_WaitInfoDlg.CenterWindow();m_WaitInfoDlg.StartAnimation();}return 0;
}LRESULT CMainFrame::OnTaskEnd(WPARAM wParam, LPARAM lParaam)
{// 任务结束,关闭界面SetTimer(0,500,NULL);return 0;
}
CMainFrame::OnTimer函数实现:
void CMainFrame::OnTimer(UINT_PTR nIDEvent)
{// TODO: 在此添加消息处理程序代码和/或调用默认值if (nIDEvent == 0){m_WaitInfoDlg.StopAnimation();m_WaitInfoDlg.DestroyWindow();KillTimer(0);}CFrameWnd::OnTimer(nIDEvent);
}
代码说明:
1.任务开始就调用Create函数创建等待对话框;任务结束,调用DestroyWindow函数销毁对话框。
简单的测试代码,在视图中响应菜单命令消息:
void CLoadingTestView::OnTasktest()
{// TODO: 在此添加命令处理程序代码::SendMessage(theApp.m_pMainWnd->m_hWnd,UM_TASKSTART,NULL,NULL);// 模拟任务处理int i = 0;while (i++ < 10000);::PostMessage(theApp.m_pMainWnd->m_hWnd,UM_TASKEND, NULL, NULL);}
代码运行效果:
1.圆环形等待界面。

2.水泡形等待界面。

好了,今天的介绍就到这里了。
Demo地址:Gitee
