#pragma once
#include "resource.h"
/*内存DC类简介:
1、BOOL LoadBitmap(UINT nBitmapID,CDC* pDC = NULL)
从资源中按照,按位图ID加载位图
2、BOOL LoadFile(LPCTSTR sFile, CDC* pDC = NULL)
从exe外部加载图片(调用LoadImage)
3、BOOL Create(int cx, int cy, CDC* pDC = NULL)
创建空白位图(默认是全黑)
4、总共有4种构造函数,包含以上3种还有空构造:
CMemoryDC(UINT nBitmapID)//从资源按照位图编号来加载
CMemoryDC(LPCTSTR sFile, CDC* pDC = NULL)//从exe外部加载
CMemoryDC(int cx, int cy, CDC* pDC=NULL)//指定高宽创建空白位图
5、void MakeRgn(CRgn& r,COLORREF col)
生成透明区域
6、int GetWidth() const
int GetHeight() const
方便地获取图片高宽:
7、
透明显示:BitTrans和StrecthTrans
*/
class CMemoryDC :public CDC
{
CSize m_size;
public:
CMemoryDC();
int GetWidth() const
{
return m_size.cx;
}
int GetHeight() const
{
return m_size.cy;
}
CSize GetSize() const
{
return m_size;
}
void BitTrans(
int nXDest, // 目标起点X
int nYDest, // 目标起点Y
int nWidthDest, // 目标宽度
int nHeightDest,// 目标高度
CDC* pDC, // 目标DC
int nXSrc, // 来源起点X
int nYSrc, // 来源起点Y
COLORREF crTrans// 透明色
)
{
CMemoryDC dcImage(nWidthDest, nHeightDest, pDC);//临时DC
CBitmap bmpMask;
bmpMask.CreateBitmap(nWidthDest, nHeightDest, 1, 1, NULL); // 创建单色掩码位图
CDC dcMask;//掩码DC
dcMask.CreateCompatibleDC(pDC);
dcMask.SelectObject(bmpMask);
//将载入位图的内存DC中的位图,拷贝到临时DC中
dcImage.BitBlt(0, 0, nWidthDest, nHeightDest, this, nXSrc, nYSrc, SRCCOPY);
// 设置临时DC的透明色
dcImage.SetBkColor(crTrans);
//掩码DC的透明区域为白色其它区域为黑色
dcMask.BitBlt(0, 0, nWidthDest, nHeightDest, &dcImage, 0, 0, SRCCOPY);
//临时DC透明区域为黑色,其它区域保持不变
dcImage.SetBkColor(RGB(0, 0, 0));
dcImage.SetTextColor(RGB(255, 255, 255));
dcImage.BitBlt(0, 0, nWidthDest, nHeightDest, &dcMask, 0, 0, SRCAND);
// 目标DC透明部分保持屏幕不变,其它部分变成黑色
pDC->SetBkColor(RGB(255, 255, 255));
pDC->SetTextColor(RGB(0, 0, 0));
pDC->BitBlt(nXDest, nYDest, nWidthDest, nHeightDest, &dcMask, 0, 0, SRCAND);
pDC->BitBlt(nXDest, nYDest, nWidthDest, nHeightDest, &dcImage, 0, 0, SRCPAINT);
}
void StretchTrans(
int nXDest, // 目标起点X
int nYDest, // 目标起点Y
int nWidthDest, // 目标宽度
int nHeightDest, // 目标高度
CDC* pDC, // 目标DC
int nXSrc, // 来源起点X
int nYSrc, // 来源起点Y
int nWidthSrc, // 来源宽度
int nHeightSrc, // 来源高度
COLORREF crTrans // 透明色
)
{
CMemoryDC dcImage(nWidthDest, nHeightDest, pDC);//临时DC
CBitmap bmpMask;
// 创建单色掩码位图
bmpMask.CreateBitmap(nWidthDest, nHeightDest, 1, 1, NULL);
CDC dcMask;
dcMask.CreateCompatibleDC(pDC);
dcMask.SelectObject(bmpMask);
// 将载入位图的内存DC中的位图,拷贝到临时DC中
if (nWidthDest == nWidthSrc && nHeightDest == nHeightSrc)
dcImage.BitBlt(0, 0, nWidthDest, nHeightDest, this, nXSrc, nYSrc, SRCCOPY);
else
dcImage.StretchBlt(0, 0, nWidthDest, nHeightDest,
this, nXSrc, nYSrc, nWidthSrc, nHeightSrc, SRCCOPY);
// 设置临时DC的透明色
dcImage.SetBkColor(crTrans);
//掩码DC的透明区域为白色其它区域为黑色
dcMask.BitBlt(0, 0, nWidthDest, nHeightDest, &dcImage, 0, 0, SRCCOPY);
//临时DC透明区域为黑色,其它区域保持不变
dcImage.SetBkColor(RGB(0, 0, 0));
dcImage.SetTextColor(RGB(255, 255, 255));
dcImage.BitBlt(0, 0, nWidthDest, nHeightDest, &dcMask, 0, 0, SRCAND);
// 目标DC透明部分保持屏幕不变,其它部分变成黑色
pDC->SetBkColor(RGB(255, 255, 255));
pDC->SetTextColor(RGB(0, 0, 0));
pDC->BitBlt(nXDest, nYDest, nWidthDest, nHeightDest, &dcMask, 0, 0, SRCAND);
pDC->BitBlt(nXDest, nYDest, nWidthDest, nHeightDest, &dcImage, 0, 0, SRCPAINT);
}
//创建空位图
BOOL Create(int cx, int cy, CDC* pDc = NULL)
{
if (!CreateCompatibleDC(NULL))
return FALSE;
CBitmap bmp;
if (pDc)
{
if (!bmp.CreateCompatibleBitmap(pDc, cx, cy))
{
DeleteDC();
return FALSE;
}
}
else
{
if (!bmp.CreateCompatibleBitmap(&CClientDC(NULL), cx, cy))
{
DeleteDC();
return FALSE;
}
}
this->SelectObject(&bmp);
m_size.SetSize(cx, cy);
return TRUE;
}
//加载一张exe外部位图
BOOL LoadFile(LPCTSTR sFile, CDC* pDC = NULL)
{
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, sFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hBitmap)
return FALSE;
if (!CreateCompatibleDC(NULL))
{
DeleteObject(hBitmap);
return FALSE;
}
SelectObject(hBitmap);
BITMAP bm;
GetObject(hBitmap, sizeof(bm), &bm);
m_size.SetSize(bm.bmWidth, bm.bmHeight);
DeleteObject(hBitmap);
return TRUE;
}
//加载一张资源位图
BOOL LoadBitmap(UINT nBitmapID, CDC* pDC = NULL)
{
if (!CreateCompatibleDC(NULL))
return FALSE;
CBitmap bmp;
if (!bmp.LoadBitmap(nBitmapID))
{
DeleteDC();
return FALSE;
}
SelectObject(&bmp);
BITMAP bm;
bmp.GetBitmap(&bm);
m_size.SetSize(bm.bmWidth, bm.bmHeight);
return TRUE;
}
CMemoryDC(UINT nBitmapID)
{
LoadBitmap(nBitmapID);
}
CMemoryDC(LPCTSTR sFile,CDC* pDC = NULL)
{
LoadFile(sFile, pDC);
}
CMemoryDC(int cx,int cy,CDC* pDC=NULL)
{
Create(cx, cy, pDC);
}
void MakeRgn(CRgn& r, COLORREF col)
{
r.CreateRectRgn(0, 0, 0, 0);
int i = -1, cx = m_size.cx;
int j = -1, cy = m_size.cy;
while (++j<cy)
{
i = -1;
while (++i<cx)
{
if (GetPixel(i, j) != col)
{
CRgn r1;
r1.CreateRectRgn(i, j, i + 1, j + 1); //1*1像素
r.CombineRgn(&r, &r1, RGN_OR);
}
}
}
}
};
#pragma once
#include "CMemoryDC.h"
class CCFlashDlg : public CDialogEx{
struct sData {
int nIndex;
CPoint pos, dir{ 5,5 };
};
CList<sData> m_list; //使用动态链表实现 角色的动态增加
// 构造
enum {Fly_nCount =7}; //图片总数
CMemoryDC m_dcBack{_T("./res/back.bmp")}; //背景图片
CMemoryDC m_dcFlys[Fly_nCount]; //蝴蝶
CRgn m_rs[Fly_nCount]; //取出背景颜色
void LoadPictures();
CDC m_dc; //作一个缓冲dc
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
};
#include "pch.h"
#include "framework.h"
#include "CFlash.h"
#include "CFlashDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CCFlashDlg::CCFlashDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_CFLASH_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CCFlashDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CCFlashDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_TIMER()
ON_WM_LBUTTONDOWN()
ON_WM_SIZE()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
BOOL CCFlashDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
int cx = GetSystemMetrics(SM_CXSCREEN);
int cy = GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(NULL, 0, 0, cx/2, cy/2, SWP_NOZORDER);
LoadPictures();
SetTimer(1, 16, NULL);
srand((int)time(NULL));
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CCFlashDlg::OnPaint()
{
CPaintDC dc(this); // 用于绘制的设备上下文
CRect rect;
GetClientRect(rect);
m_dc.SetStretchBltMode(STRETCH_HALFTONE); //拉伸图片质量的
m_dc.StretchBlt(0, 0, rect.Width(), rect.Height(), &m_dcBack, 0, 0, m_dcBack.GetWidth(), m_dcBack.GetHeight(), SRCCOPY);
CMemoryDC mdc(300,280); //创建默认的黑色
if (mdc.GetSafeHdc())
m_dc.BitBlt(200, 150, 300, 280, &mdc, 0, 0, SRCCOPY);
//CRgn r;
//r.CreateRectRgn(0, 0, 0, 0); //基址
//r.CopyRgn(m_rs+m_nIndex); //往那个跑先复制出来 不要破坏它 形状
//r.OffsetRgn(m_pos); //你要输出到那个位置
//dc.SelectObject(&r);
auto pos = m_list.GetHeadPosition();
int i = 0;
while (pos)
{
sData& d = m_list.GetNext(pos);
auto pDC = m_dcFlys + d.nIndex;
pDC->BitTrans(d.pos.x, d.pos.y, pDC->GetWidth(), pDC->GetHeight(), &m_dc, 0, 0, 0xff00ff);
if (++d.nIndex >= Fly_nCount)
d.nIndex = 0 ;
d.pos.Offset(d.dir);
if (d.pos.x + m_dcFlys->GetWidth() > rect.right || d.pos.x < 0)
d.dir.x *= -1;
if (d.pos.y + m_dcFlys->GetHeight() > rect.bottom || d.pos.y < 0)
d.dir.y *= -1;
CString str;
str.Format(_T("%d"), i++);
m_dc.TextOut(d.pos.x,d.pos.y,str);
} //闪烁 是因为交替输出
dc.BitBlt(0, 0, rect.Width(), rect.Height(), &m_dc, 0, 0, SRCCOPY); //双缓冲技术 防止闪烁 采用一次性输出全部由m_dc输出
}
void CCFlashDlg::LoadPictures() {
int i = -1;
CString str;
while (++i<_countof(m_dcFlys))
{
str.Format(_T("./res/%03d.bmp"), i + 1);
m_dcFlys[i].LoadFile(str);
m_dcFlys[i].MakeRgn(m_rs[i],0x00ff00ff);
}
}
void CCFlashDlg::OnTimer(UINT_PTR nIDEvent)
{
Invalidate(TRUE); //制造了闪烁 因为前景输出 会触发再一次的交替输出
CDialogEx::OnTimer(nIDEvent);
}
void CCFlashDlg::OnLButtonDown(UINT nFlags, CPoint point) {
sData d;
d.nIndex = rand() % Fly_nCount;
int n = rand() % 2 ? 1 : -1;
d.dir.x = 5 * n;
n = rand() % 2 ? 1 : -1;
d.dir.y = 5 * n;
d.pos = point;
d.pos.x -= m_dcFlys->GetWidth() / 2; //中心点
d.pos.y -= m_dcFlys->GetHeight() / 2; //中心点
m_list.AddTail(d);
CDialogEx::OnLButtonDown(nFlags, point);
}
void CCFlashDlg::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
m_dc.DeleteDC();
m_dc.CreateCompatibleDC(NULL);
CBitmap bmp;
bmp.CreateBitmap(cx, cy, 1, 32, NULL);
m_dc.SelectObject(&bmp);
}
BOOL CCFlashDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE; //给夺权了
}
WM_ERASEBKGND是一种消息类型,它是在窗口需要重绘背景时发送给窗口的。背景刷新的原理是在窗口绘制之前,系统首先向窗口发送WM_ERASEBKGND消息,窗口可以在处理此消息时进行背景擦除操作,即清除原有的背景后。系统会发送WM_PAINT消息,窗口可以在处理此消息时进行绘制操作,即绘制新的背景。
具体的背景刷新流程如下:
通过以上流程,窗口可以在接收到WM_ERASEBKGND和WM_PAINT消息时分别进行背景擦除和绘制操作,实现背景的刷新。
classname注册窗口类型名:有3种方式注册
a)原始API:ATOM RegisterClass(const WNDCLASS *lpWndClass); 注册窗口类型
b)MFC封装之后叫做AfxRegisterClass,类似于AfxMessageBox封装了MessageBox(API)
c)MFC再次封装了一个真正的简易注册函数叫做:AfxRegisterWndClass
返回值是自动生成字符串,字符串的内容包括三要素的句柄sprintf生成的文字
注册的内容主要包括:背景颜色,图标,光标,另外还有的类型包含有菜单
typedef struct tagWNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
typedef struct tagWNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
LPCTSTR AfxRegisterWndClass(
UINT nClassStyle,
HCURSOR hCursor = 0,
HBRUSH hbrBackground = 0,
HICON hIcon = 0
);
AfxRegisterClass比API就是多了GetClassInfo探测一下是否已注册过了。
BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass) {
WNDCLASS wndcls;
if (GetClassInfo(lpWndClass->hInstance, lpWndClass->lpszClassName,
&wndcls))
{
return TRUE; // class already registered
}
if (!RegisterClass(lpWndClass))
{
TRACE(traceAppMsg, 0, _T("Can't register window class named %Ts\n"),
lpWndClass->lpszClassName);
return FALSE;
}
注册的背景色的原理:
a)RegisterClass指定的背景色,依靠什么输出的呢?
b)WM_ERASEBKGND消息,是默认的背景输出管理消息。
c)WM_ERASEBKGND消息对比WM_PAINT,是微软设计的前景和背景管理消息。
d)他当年设计的原理是想要背景覆盖前景,方便刷新。 但是这个设计可能会制造闪烁问题?
解决办法
a)Invalidate函数代入TRUE,表示先触发WM_ERASEBKGRND再触发WM_PAINT消息。
b)Invalidate函数代入FALSE,表示不触发WM_ERASEBKGRND只触发WM_PAINT消息。
c)因此,好彻底解决闪烁问题还要注意,尽量Invalidate代入FALSE;
d)另外,也尽量把WM_ERASEBKGRND消息映射函数清空,只留WM_PAINT 的输出。
inline static bool IsWindowClassRegistered(LPCTSTR sClass) //用于检测注册类名 是否会重复
{
WNDCLASSEX wcex;
return (GetClassInfoEx(NULL, sClass, &wcex) != 0);
}
BOOL CLadderCtrl::OnEraseBkgnd(CDC* pDC)
// RegisterClass(&wc); wc.hbrBackground = m_brBack; 背景颜色依靠这个消息输出的
{
return CWnd::OnEraseBkgnd(pDC); //默认背景输出
}
BOOL CLadderCtrl::Create(DWORD dwStyle, CRect rect, CWnd* pParent, int nID)
{
if (!IsWindowClassRegistered(_T("Ladder_XQ"))) //用于判断注册类名会不会重复
{
WNDCLASS wc = { CS_HREDRAW | CS_VREDRAW }; //注册窗口类型名
wc.hCursor = theApp.LoadStandardCursor(IDC_HAND);
wc.hbrBackground = m_brBack;
wc.lpszClassName = _T("Ladder_XQ");
wc.lpfnWndProc = ::DefWindowProc;
RegisterClass(&wc);
}
return CWnd::Create(_T("Ladder_XQ"), NULL, dwStyle, rect, pParent, nID);
}
BOOL Clvxin21Dlg::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(rect);
pDC->FillSolidRect(rect, GetSysColor(COLOR_BTNFACE));
return TRUE;
}
MFC环境下创建框架窗口的必备条件:
a)InitInstance必须return TRUE;
b)theApp.m_pMainWnd 必须指向主窗口对象地址:
c)主窗口对象必须是堆空间或者生命期足够。
#include "CApp.h"
#include "CMainFrame.h"
CApp thApp;
//CWnd* pFrame = new CWnd;
can't use for desktop or pop-up windows (use CreateEx instead) create不允许用于创建主窗口
//pFrame->Create(NULL, _T("我的世界"), WS_VISIBLE, CRect(100, 100, 800, 600), NULL, 9999);
//CWnd* pFrame = new CWnd;
使用CreatEx时 首先要 make sure the default window class is registered no WNDCLASS provided - use child window default 没有的默认为子窗口创建
//pFrame->CreateEx(0, NULL, _T("我的世界"), WS_VISIBLE, CRect(100, 100, 800, 600), NULL, 9999);
BOOL CApp::InitInstance(){
/*LPCTSTR sClass = _T("MY_WORLD");
WNDCLASS wc{ CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS };
wc.lpfnWndProc = ::DefWindowProc;
wc.lpszClassName = sClass;
wc.hCursor = LoadStandardCursor(IDC_CROSS);
wc.hIcon = LoadStandardIcon(IDI_INFORMATION);
wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
RegisterClass(&wc);*/
auto pFrame = new CMainFrame;
BOOL b = pFrame->CreateEx(0, NULL, _T("我的世界"),
WS_VISIBLE | WS_MAXIMIZEBOX |WS_MINIMIZEBOX| WS_SYSMENU, 100, 100, 800, 600, NULL, NULL);
m_pMainWnd = pFrame;
return TRUE;
}
#include "CMainFrame.h"
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
//if (!CWnd::PreCreateWindow(cs)) //给子框架用来注册的,对主框架不好使
// return FALSE;
cs.lpszClass = AfxRegisterWndClass(0);
//自己添加的使用的AfxRegisterWndClass 也可以继续在添加属性 预处理
return CWnd::PreCreateWindow(cs);
}