之前看到朋友曬流星雨屏幕圖,今天就來實現這個流星雨屏幕程序。
語言: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;
}
註冊成功之後就調用API創建窗口,指定窗口創建的位置、大小、邊框風格、菜單等,即完成創建窗口。
因爲是全屏,所以不能帶標題欄、菜單欄等窗口類型,同時創建的窗口是彈出式窗口。
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中,這樣就可以在內存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;
}
這樣就完成整個流星雨屏幕程序的實現。如果需要源代碼,可以在CSDN博客上面下載,如果沒有積分,也可以私聊郵箱,在個人簡介裏面。
網址:https://download.csdn.net/download/qq_34430371/12283490