【MFC】实现字符雨(详细讲解,附完整代码)

字符雨实现,我们需要处理的消息:

  1. 视图类的OnDraw 随时用来重新绘制
  2. 结构:每一条雨滴当作一个结构
    struct Rain{
      int x;
      int y;
      char str[];
    }
    

代码具体实现:

  • 定义雨滴结构体:
    在这之前,我们定义一些常量:

    #define STRLEN 22    //定义数组长度
    #define COUNT 123    //定义
    
    typedef struct Rain{
      int x;
      int y;
      char szStr[STRLEN];
    }
    

    然后我们在我们自己的视图类中添加雨滴结构体成员:

    Rain RainArr[COUNT]
    
    • 初始化雨滴结构体,这个我们可以在视图类的WM_CREATE处理函数中来实现:
    int CMyView::OnCreate(LPCREATESTRUCT cs)
    {
    	x = cs->cx;
    	y = cs->cy;
    	for (size_t i = 0; i < COUNT; i++)
    	{
    		RainArr[i].x = i * 15;
    		RainArr[i].y = rand() % cs->cy;
    		for (size_t j = 0; j < STRLEN; j++)
    		{
    			RainArr[i].szStr[j] = CreateChar();
    		}
    	}
    	SetTimer(0, 100, NULL);
    	return 0;
    }
    
    • 给每一个雨滴结构体中的字符串赋值函数:
    char CMyView::CreateChar()
    {
    	int nFlag;
    	nFlag = rand() % 3;
    	if (nFlag == 0) {
    		return rand() % 10 + '0';
    	}
    	else if (nFlag == 1) {
    		return rand() % 26 + 'a';
    	}
    	else {
    		return rand() % 26 + 'A';
    	}
    	return 0;
    }
    

    我们这样创建了雨滴结构之后,我们还需要绘制,这时候,我们在视图类中创建一个定时器,当窗口有无效区域的时候,就去绘制

1.在OnCreate中,创建定时器:

SetTimer(0,1,NULL);

然后我们创建定时器消息,使用MFC的宏:ON_WM_TIMER()

处理函数:

afx_msg void OnTimer(UINT_PTR id){
  //制造无效区域:
  invalidateRect(NULL,FALSE);
  //
}

双缓冲机制:

因为我们直接使用设备句柄,往设备上画图的时候,如果数据量非常大,就会闪屏,这里使用双缓冲,即可解决

双缓冲机制后面会将,这里简单理解一下:

我们先将绘图绘制在一张图片上,然后将图片完整复制给屏幕

  • OnDraw:
    void CMyView::OnDraw(CDC* pDc)
    {
    	UpDate();
    	UpDateY();
    	//通过当前视图窗口拿到设备句柄
    	hDC = ::GetDC(this->m_hWnd);
    	// 通过当前设备句柄拿到一个内存句柄
    	hMemDC = ::CreateCompatibleDC(hDC);
    	// 创建图片句柄
    	hBitMap = ::CreateCompatibleBitmap(hDC, x, y);
    	// 把图片送到内存DC中
    	::SelectObject(hMemDC, hBitMap);
    
    	// 设置视图窗口背景色
    	::SetBkColor(hMemDC, RGB(0, 0, 0));
    
    	for (size_t i = 0; i < COUNT; i++)
    	{
    		for (size_t j = 0; j < STRLEN; j++)
    		{
    			// 设置字体渐变颜色
    			SetTextColor(hMemDC, RGB(0, 255 - j * 10, 0));
    			// 竖着打印字符
    			TextOut(hMemDC, RainArr[i].x, RainArr[i].y - j * 10, &(RainArr[i].szStr[j]), 1);
    		}
    	}
    	// 把图片送给设备
    	::BitBlt(hDC, 0, 0, x, y, hMemDC, 0, 0, SRCCOPY);
    
    	// 释放句柄
    	::DeleteDC(this->hDC);
    }
    
    • 随时改变雨滴的字符:
    void UpData(){
      for(int i = 0 ; i <COUNT ;i++){
        RainArr[i].szStr[rand()%STRLEN] = CreateChar();
      }
    }
    
    • 随时改变Y轴,以实现动态效果:
    void UpdataY(){
      for(int i = 0 ;i < COUNT ;i++){
      RainArr[i].y+=5;
      if(RainArr[i].y - STRLEN *10 > y){
        RainArr[i].y = 0;
      }
      }
    }
    
    

我们这样实现之后,已经实现了基本的代码雨,但是当我们放大窗口的时候,代码雨视图还是以前的大小,我们就需要处理WM_SIZE消息:

在消息映射中添加宏:

void CMyView::OnSize(UINT, int x, int y)
{
	this->x = x;
	this->y = y;
}

完整代码:

这里本人为了深刻理解MFC底层,创建的是控制台应用,自己写成了MFC类,如果大家有什么看不懂的,可以私信问我:

#include <afxwin.h>
#include <string>

#define STRLEN 22
#define COUNT 128

//实现我们自己的框架类
class CMyFrameWnd :public CFrameWnd {
	afx_msg int OnCreate(LPCREATESTRUCT cs);
	DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMyFrameWnd,CFrameWnd)
	ON_WM_CREATE()
END_MESSAGE_MAP()

//实现我们自己的应用程序类
class CMyApp :public CWinApp {
public:
	CMyApp() {};
	//重写虚函数
	virtual BOOL InitInstance() {
		CMyFrameWnd* pFrame = new CMyFrameWnd;
		pFrame->Create(NULL, "FirstMFC");
		m_pMainWnd = pFrame;
		pFrame->ShowWindow(SW_SHOW);
		pFrame->UpdateWindow();
		return TRUE;
	}
};

//实现我们自己的视图类:
class CMyView :public CView {
public:
	virtual void OnDraw(CDC* pDc);
	
private:
	int x;
	int y;
	struct Rain
	{
		int x;
		int y;
		char szStr[STRLEN];
	};
	Rain RainArr[COUNT] = { 0 };
	HDC hDC;
	HDC hMemDC;
	HBITMAP hBitMap;
	char CreateChar();
	void UpDateY();
	void UpDate();
	afx_msg int OnCreate(LPCREATESTRUCT cs);
	afx_msg void OnTimer(UINT_PTR);
	afx_msg void OnSize(UINT, int x, int y);
public:
	DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyView,CView)
	ON_WM_CREATE()
	ON_WM_TIMER()
	ON_WM_SIZE()
END_MESSAGE_MAP()


CMyView* m_View = new CMyView;
CMyApp theApp;

int CMyFrameWnd::OnCreate(LPCREATESTRUCT cs)
{
	RECT rect;
	GetWindowRect(&rect);
	m_View->Create(NULL, "MFCView", WS_CHILD | WS_VISIBLE | WS_BORDER, rect, this, AFX_IDW_PANE_FIRST);
	return 0;
}

void CMyView::OnDraw(CDC* pDc)
{
	UpDate();
	UpDateY();
	//通过当前视图窗口拿到设备句柄
	hDC = ::GetDC(this->m_hWnd);
	// 通过当前设备句柄拿到一个内存句柄
	hMemDC = ::CreateCompatibleDC(hDC);
	// 创建图片句柄
	hBitMap = ::CreateCompatibleBitmap(hDC, x, y);
	// 把图片送到内存DC中
	::SelectObject(hMemDC, hBitMap);

	// 设置视图窗口背景色
	::SetBkColor(hMemDC, RGB(0, 0, 0));

	for (size_t i = 0; i < COUNT; i++)
	{
		for (size_t j = 0; j < STRLEN; j++)
		{
			// 设置字体渐变颜色
			SetTextColor(hMemDC, RGB(0, 255 - j * 10, 0));
			// 竖着打印字符
			TextOut(hMemDC, RainArr[i].x, RainArr[i].y - j * 10, &(RainArr[i].szStr[j]), 1);
		}
	}
	// 把图片送给设备
	::BitBlt(hDC, 0, 0, x, y, hMemDC, 0, 0, SRCCOPY);

	// 释放句柄
	::DeleteDC(this->hDC);
}

char CMyView::CreateChar()
{
	int nFlag;
	nFlag = rand() % 3;
	if (nFlag == 0) {
		return rand() % 10 + '0';
	}
	else if (nFlag == 1) {
		return rand() % 26 + 'a';
	}
	else {
		return rand() % 26 + 'A';
	}
	return 0;
}

void CMyView::UpDateY()
{
	for (size_t i = 0; i < COUNT; i++)
	{
		RainArr[i].y += 5;
		if (RainArr[i].y - STRLEN * 10 > y) {
			RainArr[i].y = 0;
		}
	}
}

void CMyView::UpDate()
{
	for (int i = 0; i < COUNT; i++) {
		RainArr[i].szStr[rand() % STRLEN] = CreateChar();
	}
}

int CMyView::OnCreate(LPCREATESTRUCT cs)
{
	x = cs->cx;
	y = cs->cy;
	for (size_t i = 0; i < COUNT; i++)
	{
		RainArr[i].x = i * 15;
		RainArr[i].y = rand() % cs->cy;
		for (size_t j = 0; j < STRLEN; j++)
		{
			RainArr[i].szStr[j] = CreateChar();
		}
	}
	SetTimer(0, 100, NULL);
	return 0;
}

void CMyView::OnTimer(UINT_PTR id)
{
	
	InvalidateRect(NULL, FALSE);
}

void CMyView::OnSize(UINT, int x, int y)
{
	this->x = x;
	this->y = y;
}

这里需要注意,这里我们在视图类中实现了字符雨,但这是不科学的,因为视图类只负责显示窗口,数据不应该保存在视图类中,我们之后学习文档类,会解决该问题。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shad0w-2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值