之前看到朋友晒流星雨屏幕图,今天就来实现这个流星雨屏幕程序。
语言:C++,平台:VS,框架:win32。
首先来看看程序运行的情况。
接着来介绍一下程序的主题部分,分为三个部分:窗口的建立、数据的存储、消息的处理。
窗口的建立
主要功能就是创建一个窗口来承载要显示的东西,否则不可能直接在屏幕上面就操作吧,那你的屏幕上面岂不是会被搞成乱七八糟,还如何使用?
窗口类(WNDCLASS),来定义一下要创建的窗口类型,主要类型就是:窗口风格,窗口的消息处理函数,窗口的图标、光标。背景颜色等等。之后注册该窗口类。
TCHAR szClassName[] = TEXT("数字雨"); //窗口类名
HWND hwnd;
MSG msg; //消息函数
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格,垂直水平窗口改变就重新绘制
wndclass.lpfnWndProc = WndProc; //消息处理函数
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)); //加载一个图标
wndclass.hCursor = NULL;
wndclass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szClassName; //窗口类名
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, _T("注册窗口失败"), _T("提示"), MB_ICONERROR);
return -1;
}
窗口创建的位置、大小、边框风格、菜单等,即完成创建窗口。
不能带标题栏、菜单栏等窗口类型,同时创建的窗口是弹出式窗口。
hwnd = CreateWindow(szClassName, NULL,WS_DLGFRAME | WS_THICKFRAME | WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL);
if (!hwnd)
{
MessageBox(NULL, _T("创建窗口失败"), _T("提示"), MB_ICONERROR);
return -1;
}
最大化显示窗口。
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
UpdateWindow(hwnd);
ShowCursor(FALSE);
消息循环机制。
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
数据的存储
由于要显示一些数字,当然也可以是其他汉字或者英文字母,所以这里定义一个队列来存储这些数据。
双向链表来存储字符。
typedef struct charList
{
struct charList * prev;
TCHAR ch; //放字符
struct charList * next;
}CharList;
循环队列来存放这些字符串以及类型其属性,比如字符数多少,显示的位置等等。
typedef struct tagCharColumn
{
struct charList * head, *cur; //头结点和尾结点
int x, y, iShownLen, iStrNum; //显示位置,显示字数,字符数
}CharQueue;
7组字符串,为了随机显示,每次创建该队列的时候,就随机选取一组字符串,然后设置显示的位置。
struct showChar
{
TCHAR myChar[60];
int iNum; //字符个数
}charArr[7] = {
{ TEXT("10101010101010101011010"), 23 },
{ TEXT("1010110101010101010101"), 21 },
{ TEXT("10101111101100100001010100001"), 28 },
{ TEXT("10101000101"), 11 },
{ TEXT("101010101111011111110010000011110101010"), 40 },
{ TEXT("101011101001010101010"), 21 },
{ TEXT("101010101010101010101010101111"), 30 }
};
生成各个节点,最后收尾相连,形成循环队列。
void CreateQueue(CharQueue * cc, int cyScreen, int x)
{
CharList * front;
int NumTemp = rand() % 6; //七组字符串
cc->x = x;
cc->y = rand() % 10 ? rand() % cyScreen : 0; //大约9/10的概率从中间开始下落。
cc->iShownLen = 1; //一开始就显示一个字符,然后慢慢增加,增加到等于歌词字符数时保持不变
cc->iStrNum = charArr[NumTemp].iNum; //歌词字符数
cc->head = cc->cur = front = (CharList *)calloc(cc->iStrNum, sizeof(CharList)); //创建显示列
//生成每个节点
int i;
for (i = 0; i<cc->iStrNum - 1; i++)
{
cc->cur->prev = front;
cc->cur->ch = charArr[NumTemp].myChar[i];
front = cc->cur++;
front->next = cc->cur;
}
//最后一个是标点符号
cc->cur->prev = front;
cc->cur->ch = charArr[NumTemp].myChar[i];
cc->cur->next = cc->head;
cc->head->prev = cc->cur;
cc->cur = cc->head; //首尾相连
}
链表存储一个字符,而队列来存储整个字符串。
消息的处理
GDI绘图。
首先获取窗口大小,也就是屏幕大小,因为是整体显示,接着得到设备上下文(窗口)句柄。
cxScreen = GetSystemMetrics(SM_CXSCREEN); //获得屏幕的宽度
cyScreen = GetSystemMetrics(SM_CYSCREEN); //获得屏幕的高度
hdc = GetDC(hwnd);
内存DC。
画布,只有画布上面才可以绘制图像文字等,再然后创建画笔,把画布和画笔都放入到内存DC中,这样就可以在内存DC上面绘制图像文字了。
hdcMem = CreateCompatibleDC(hdc); //创建一个兼容的内存DC
hBitmap = CreateCompatibleBitmap(hdc, cxScreen, cyScreen); //创建一块画布
SelectObject(hdcMem, hBitmap); //将画布放到内存DC中
hFont = CreateFont(iFontHeight, iFontWidth, 0/*角度设置*/, 0/*角度设置*/, FW_DONTCARE/*黑体*/, 0, 0, 0,/*斜体 下划线 啊、删除线*/
DEFAULT_CHARSET/*字符集*/, OUT_DEFAULT_PRECIS/*指定输出精度*/, CLIP_DEFAULT_PRECIS/*指定裁剪精度*/,
DRAFT_QUALITY/*指向输出质量*/, FIXED_PITCH | FF_SWISS/*指定字体间距| 字体族*/, TEXT("CloudKaiTiGBK"));
SelectObject(hdcMem, hFont); //将字体放到内存DC中
SetBkMode(hdcMem, TRANSPARENT); //设置窗口背景
创建屏幕显示的队列(通过屏幕的分辨率以及显示文字直接的间隔来计算),播放音乐(音乐的播放需要加上相应的媒体库 #pragma comment(lib, “WINMM.LIB”)),开启定时器(主要功能就是下面文字的显示,控制显示的快慢)
PlaySound(L"123.wav", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);//异步循环播放
AllChar = (CharQueue *)calloc(NumOfColumn, sizeof(CharQueue));//自动初始化为0
for (i = 0; i<NumOfColumn; i++)
{
CreateQueue(AllChar + i, cyScreen, ColSpan * i + 17);
}
SetTimer(hwnd, 1, 100, NULL);
定义的数据类型如下:
#define NumOfColumn 25 //显示列的列数
#define ColSpan 64 //列字符串之间的间隔
HDC hdc=0;
static HDC hdcMem;
static HBITMAP hBitmap;
PAINTSTRUCT ps;
static CharQueue * AllChar;
HFONT hFont=0;
static int cxScreen, cyScreen;
static int iFontWidth = 20, iFontHeight = 30;
int i, j, y, greenToblack;
CharQueue * ccElem;
CharList * temp;
整块的程序如下:
case WM_CREATE:
cxScreen = GetSystemMetrics(SM_CXSCREEN); //获得屏幕的宽度
cyScreen = GetSystemMetrics(SM_CYSCREEN); //获得屏幕的高度
hdc = GetDC(hwnd); //获得设备上下文环境句柄
hdcMem = CreateCompatibleDC(hdc); //创建一个兼容的内存DC
hBitmap = CreateCompatibleBitmap(hdc, cxScreen, cyScreen); //创建一块画布
SelectObject(hdcMem, hBitmap); //将画布放到内存DC中
hFont = CreateFont(iFontHeight, iFontWidth, 0/*角度设置*/, 0/*角度设置*/, FW_DONTCARE/*黑体*/, 0, 0, 0,/*斜体 下划线 啊、删除线*/
DEFAULT_CHARSET/*字符集*/, OUT_DEFAULT_PRECIS/*指定输出精度*/, CLIP_DEFAULT_PRECIS/*指定裁剪精度*/,
DRAFT_QUALITY/*指向输出质量*/, FIXED_PITCH | FF_SWISS/*指定字体间距| 字体族*/, TEXT("CloudKaiTiGBK"));
SelectObject(hdcMem, hFont); //将字体放到内存DC中
SetBkMode(hdcMem, TRANSPARENT); //设置窗口背景
PlaySound(L"123.wav", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);//异步循环播放
AllChar = (CharQueue *)calloc(NumOfColumn, sizeof(CharQueue));//自动初始化为0
srand(time(0));
for (i = 0; i<NumOfColumn; i++)
{
CreateQueue(AllChar + i, cyScreen, ColSpan * i + 17);
}
SetTimer(hwnd, 1, 100, NULL);
return 0;
做完了这些初始化工作,然后就开始正式的文字显示工作(逻辑处理)。
队列中的字符由随机的位置从上往下逐个显示,最新的显示为最亮的颜色,显示过的文字逐步变为背景颜色(即逐步消失),然后当文字显示的超过屏幕的宽时,这删除该队列,重新创建一个新的队列,就这样循环显示。
具体步骤分为以下几步:
- 创建一个屏幕显示的队列文字的循环,
- 拿出队列中的一个字符进行显示,每次显示的文字书增加
- 将显示过的文字颜色进行逐步改变成背景颜色,逐步消失
- 改变下次显示文字的纵坐标,进行位置的变更
- 判断显示的文字是否超出屏幕,如果超出,就删除该队列,创建新的队列显示
- GDI绘图,将内存DC绘制的文字图案复制到环境DC(即窗口DC)中,完成显示。
- 接着就 一直循环这个过程就完成显示。
case WM_TIMER:
PatBlt(hdcMem, 0, 0, cxScreen, cyScreen,BLACKNESS);
for (i = 0; i<NumOfColumn; i++)
{
ccElem = AllChar + i;
temp = ccElem->head;
SetTextColor(hdcMem, RGB(255, 255, 255));
TextOut(hdcMem, ccElem->x, ccElem->y, &temp->ch, _tcslen(&(temp->ch)) /*字符个数*/);
y = ccElem->y;
greenToblack = 0;
ccElem->head = ccElem->head->next;
temp = temp->prev;
for (j = 1; j<ccElem->iShownLen; j++)
{
SetTextColor(hdcMem, RGB(0, 255 - 255 * (greenToblack++) / (ccElem->iStrNum), 0));
TextOut(hdcMem, ccElem->x, y -= iFontHeight, &temp->ch, _tcslen(&(temp->ch)));
temp = temp->prev;
}
if (ccElem->iShownLen<ccElem->iStrNum)
{
ccElem->iShownLen++;
}
ccElem->y += iFontHeight;
if (ccElem->y - ccElem->iStrNum*iFontHeight > cyScreen)
{
free(ccElem->cur);
CreateQueue(ccElem, cyScreen, ColSpan * i + 17);
}
}
hdc = GetDC(hwnd);
BitBlt(hdc, 0, 0, cxScreen, cyScreen, hdcMem, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hdc);
return 0;
按键程序响应函数,退出相应的进程。
WM_CLOSE消息,然后再调用WM_DESTROY消息,最后就退出消息循环,程序关闭。
case WM_KEYDOWN:
{
switch (wParam)
{
case VK_LEFT:
{
KillTimer(hwnd, 1);
for (i = 0; i<NumOfColumn; i++)
{
ccElem = AllChar + i;
free(ccElem->cur);
}
free(AllChar);
DeleteObject(hBitmap);
DeleteDC(hdcMem);
DeleteObject(hFont);
ReleaseDC(hwnd, hdc);
PostQuitMessage(0);
}
break;
case VK_ESCAPE:
{
KillTimer(hwnd, 1);
for (i = 0; i<NumOfColumn; i++)
{
ccElem = AllChar + i;
free(ccElem->cur);
}
free(AllChar);
DeleteObject(hBitmap);
DeleteDC(hdcMem);
DeleteObject(hFont);
ReleaseDC(hwnd, hdc);
PostQuitMessage(0);
}
break;
}
}
break;
case WM_CLOSE:
//确定退出,销毁窗口,抛出一个WM_DESTYRY的消息
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}