|
| 1 | +#include <windows.h> |
| 2 | + |
| 3 | +#define ID_TIMER 1 |
| 4 | +#define STRMAXLEN 25 //一个显示列的最大长度 |
| 5 | +#define STRMINLEN 8 //一个显示列的最小长度 |
| 6 | +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); |
| 7 | + |
| 8 | +////////////////////////////////////////////////////////////////// |
| 9 | + |
| 10 | +typedef struct tagCharChain //整个当作屏幕的一个显示列,这是个双向列表 |
| 11 | +{ |
| 12 | + struct tagCharChain *prev; //链表的前个元素 |
| 13 | + TCHAR ch; //一个显示列中的一个字符 |
| 14 | + struct tagCharChain *next; //链表的后个元素 |
| 15 | +}CharChain, *pCharChain; |
| 16 | + |
| 17 | +typedef struct tagCharColumn |
| 18 | +{ |
| 19 | + CharChain *head, *current, *point; |
| 20 | + int x, y, iStrLen; //显示列的开始显示的x,y坐标,iStrLen是这个列的长度 |
| 21 | + int iStopTimes, iMustStopTimes; //已经停滞的次数和必须停滞的次数,必须停滞的次数是随机的,其实就是每列的速度,速度=计时器大小*iMustStopTimes。 |
| 22 | +}CharColumn, *pCharColumn; |
| 23 | + |
| 24 | +int main(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) |
| 25 | +{ |
| 26 | + static TCHAR szAppName[] = TEXT("matrix"); |
| 27 | + HWND hwnd;//窗口句柄 |
| 28 | + MSG msg;// |
| 29 | + WNDCLASS wndclass; |
| 30 | + wndclass.style = CS_HREDRAW | CS_VREDRAW; |
| 31 | + wndclass.lpfnWndProc = WndProc; |
| 32 | + wndclass.cbClsExtra = 0; |
| 33 | + wndclass.cbWndExtra = 0; |
| 34 | + wndclass.hInstance = hInstance; |
| 35 | + wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); |
| 36 | + wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); |
| 37 | + wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); |
| 38 | + wndclass.lpszMenuName = NULL; |
| 39 | + wndclass.lpszClassName = szAppName; |
| 40 | + |
| 41 | + if (!RegisterClass(&wndclass)) |
| 42 | + { |
| 43 | + MessageBox(NULL, TEXT("此程序必须运行在NT下!"), szAppName, MB_ICONERROR); |
| 44 | + return 0; |
| 45 | + } |
| 46 | + |
| 47 | + hwnd = CreateWindow(szAppName, NULL, WS_DLGFRAME | WS_THICKFRAME | WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), |
| 48 | + NULL, NULL, hInstance, NULL); |
| 49 | + |
| 50 | + ShowWindow(hwnd, SW_SHOWMAXIMIZED); //最大化显示 |
| 51 | + UpdateWindow(hwnd); |
| 52 | + ShowCursor(FALSE); //隐藏鼠标光标 |
| 53 | + srand((int)GetCurrentTime()); //初始化随机数发生器 |
| 54 | + |
| 55 | + while (GetMessage(&msg, NULL, 0, 0)) |
| 56 | + { |
| 57 | + TranslateMessage(&msg); |
| 58 | + DispatchMessage(&msg); |
| 59 | + } |
| 60 | + |
| 61 | + ShowCursor(TRUE); //显示鼠标光标 |
| 62 | + return msg.wParam; |
| 63 | +} |
| 64 | + |
| 65 | +TCHAR randomChar() //随机字符产生函数 |
| 66 | +{ |
| 67 | + return (TCHAR)(rand() % (126 - 33) + 33); //33到126之间 |
| 68 | +} |
| 69 | + |
| 70 | +int init(CharColumn *cc, int cyScreen, int x) //初始化,cyScreen屏幕高度 |
| 71 | +{ |
| 72 | + int j; |
| 73 | + cc->iStrLen = rand() % (STRMAXLEN - STRMINLEN) + STRMINLEN; //显示列的长度 |
| 74 | + cc->x = x + 3; //显示列的开始显示的x坐标 |
| 75 | + cc->y = rand() % 3 ? rand() % cyScreen : 0; //显示列的开始显示的y坐标 |
| 76 | + cc->iMustStopTimes = rand() % 6;//cc->iMustStopTimes = rand()%(200-100)+100;读者可以把速度调慢点,我调的是1s~2s,注意此时计时器需要调为10ms |
| 77 | + cc->iStopTimes = 0; |
| 78 | + cc->head = cc->current = (pCharChain)calloc(cc->iStrLen, sizeof(CharChain)); //分配连续iStrLen个CharChain类型空间 |
| 79 | + |
| 80 | + for (j = 0; j<cc->iStrLen - 1; j++)//遍历 iStrLen-1 次 |
| 81 | + { |
| 82 | + cc->current->prev = cc->point; //cc->point一个显示列的前个元素 |
| 83 | + cc->current->ch = '\0'; |
| 84 | + cc->current->next = cc->current + 1; //cc->current+1一个显示列的后个元素 |
| 85 | + cc->point = cc->current++; //cc->point = cc->current; cc->current++; |
| 86 | + } |
| 87 | + cc->current->prev = cc->point; //最后一个节点 |
| 88 | + cc->current->ch = '\0'; |
| 89 | + cc->current->next = cc->head; |
| 90 | + cc->head->prev = cc->current; //头节点的前一个为此链的最后一个元素 |
| 91 | + |
| 92 | + cc->current = cc->point = cc->head; //free掉申请的内存要用current当参数 |
| 93 | + cc->head->ch = randomChar(); //对链表头的元素填充 |
| 94 | + |
| 95 | + return 0; |
| 96 | +} |
| 97 | + |
| 98 | +LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) |
| 99 | +{ |
| 100 | + HDC hdc; |
| 101 | + |
| 102 | + //j 为一个显示链中除链表头外的在屏幕上显示的y坐标,temp绿色过度到黑色之用 |
| 103 | + //ctn 用来确定一个显示链是否向下前进,如果等待次数超过必须等待的次数,ctn就代表要向下前进 |
| 104 | + int i, j, temp, ctn; |
| 105 | + static HDC hdcMem; |
| 106 | + HFONT hFont; |
| 107 | + static HBITMAP hBitmap; |
| 108 | + static int cxScreen, cyScreen; //屏幕的宽度 高度. |
| 109 | + static int iFontWidth = 10, iFontHeight = 15, iColumnCount; //字体的宽度 高度, 列数 |
| 110 | + static CharColumn *ccChain; |
| 111 | + |
| 112 | + switch (message) |
| 113 | + { |
| 114 | + //系统给消息处理程序发送的消息,每次窗口创建,有且只有1个WM_CREATE消息,所以只运行1次,可以在这里初始化滚动条,获取字体宽度、高度,创建子窗口、控件等等 |
| 115 | + case WM_CREATE: |
| 116 | + cxScreen = GetSystemMetrics(SM_CXSCREEN); //屏幕宽度 |
| 117 | + cyScreen = GetSystemMetrics(SM_CYSCREEN); //屏幕高度 |
| 118 | + |
| 119 | + //hwnd调用,计时器命名为ID_TIMER,时间间隔10ms。给读者一个小技巧,因为10ms太小,读者难免很难发现玄机,所以建议读者在理解代码时将时间改为1000ms |
| 120 | + SetTimer(hwnd, ID_TIMER, 10, NULL); |
| 121 | + hdc = GetDC(hwnd); |
| 122 | + hdcMem = CreateCompatibleDC(hdc); |
| 123 | + hBitmap = CreateCompatibleBitmap(hdc, cxScreen, cyScreen); |
| 124 | + SelectObject(hdcMem, hBitmap); |
| 125 | + ReleaseDC(hwnd, hdc); |
| 126 | + |
| 127 | + //创建字体 |
| 128 | + hFont = CreateFont(iFontHeight, iFontWidth - 5, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, |
| 129 | + DRAFT_QUALITY, FIXED_PITCH | FF_SWISS, TEXT("Fixedsys")); |
| 130 | + |
| 131 | + SelectObject(hdcMem, hFont); |
| 132 | + DeleteObject(hFont); |
| 133 | + |
| 134 | + SetBkMode(hdcMem, TRANSPARENT); //设置背景模式为透明 |
| 135 | + iColumnCount = cxScreen / (iFontWidth * 3 / 2); //屏幕所显示字母雨的列数 |
| 136 | + ccChain = (pCharColumn)calloc(iColumnCount, sizeof(CharColumn)); |
| 137 | + |
| 138 | + for (i = 0; i < iColumnCount; i++) |
| 139 | + init(ccChain + i, cyScreen, (iFontWidth * 3 / 2)*i); |
| 140 | + return 0; |
| 141 | + |
| 142 | + //计时器间隔10ms,该消息每隔10ms触发一次 |
| 143 | + case WM_TIMER: |
| 144 | + hdc = GetDC(hwnd); |
| 145 | + PatBlt(hdcMem, 0, 0, cxScreen, cyScreen, BLACKNESS); //将内存设备映像刷成黑色,不理解含义可以注释此句再运行,对比一下即可 |
| 146 | + |
| 147 | + for (i = 0; i<iColumnCount; i++) |
| 148 | + { |
| 149 | + ctn = (ccChain + i)->iStopTimes++ >(ccChain + i)->iMustStopTimes; |
| 150 | + |
| 151 | + (ccChain + i)->point = (ccChain + i)->head; //point用于遍历整个显示列 |
| 152 | + |
| 153 | + SetTextColor(hdcMem, RGB(255, 255, 255));//第一个字符显示为白色 |
| 154 | + TextOut(hdcMem, (ccChain + i)->x, (ccChain + i)->y, &((ccChain + i)->point->ch), 1); |
| 155 | + |
| 156 | + j = (ccChain + i)->y; |
| 157 | + (ccChain + i)->point = (ccChain + i)->point->next; |
| 158 | + //遍历整个显示列,将这个显示列里的字符从下往上显示 |
| 159 | + temp = 0; //temp绿色过度到黑色之用 |
| 160 | + |
| 161 | + //注意第二个判断条件,init函数里除了head节点,其他节点都是'\0',正是第二个判断才给我们运行起初字母是一个一个增多的效果,读者可以换成下面的句子运行看看 |
| 162 | + //while ((ccChain + i)->point != (ccChain + i)->head) |
| 163 | + while ((ccChain + i)->point != (ccChain + i)->head && (ccChain + i)->point->ch) |
| 164 | + { |
| 165 | + SetTextColor(hdcMem, RGB(0, 255 - (255 * (temp++) / (ccChain + i)->iStrLen), 0)); |
| 166 | + TextOut(hdcMem, (ccChain + i)->x, j -= iFontHeight, &((ccChain + i)->point->ch), 1); |
| 167 | + (ccChain + i)->point = (ccChain + i)->point->next; |
| 168 | + } |
| 169 | + |
| 170 | + if (ctn) |
| 171 | + (ccChain + i)->iStopTimes = 0; |
| 172 | + else |
| 173 | + continue; |
| 174 | + |
| 175 | + (ccChain + i)->y += iFontHeight; //下次开始显示的y坐标 为当前的y坐标加上一个字符的高度 |
| 176 | + |
| 177 | + //如果开始显示的y坐标减去 整个显示列的长度超过了屏幕的高度 |
| 178 | + if ((ccChain + i)->y - (ccChain + i)->iStrLen*iFontHeight > cyScreen) |
| 179 | + { |
| 180 | + free((ccChain + i)->current); |
| 181 | + init(ccChain + i, cyScreen, (iFontWidth * 3 / 2)*i); |
| 182 | + } |
| 183 | + |
| 184 | + //链表的头 为此链表的前个元素,因为下次开始显示的时候 就相当于在整个显示列的开头添加个元素,然后再开始往上显示 |
| 185 | + (ccChain + i)->head = (ccChain + i)->head->prev; |
| 186 | + (ccChain + i)->head->ch = randomChar(); |
| 187 | + } |
| 188 | + |
| 189 | + BitBlt(hdc, 0, 0, cxScreen, cyScreen, hdcMem, 0, 0, SRCCOPY); |
| 190 | + ReleaseDC(hwnd, hdc); |
| 191 | + return 0; |
| 192 | + |
| 193 | + case WM_RBUTTONDOWN: |
| 194 | + KillTimer(hwnd, ID_TIMER); |
| 195 | + return 0; |
| 196 | + |
| 197 | + case WM_RBUTTONUP: |
| 198 | + SetTimer(hwnd, ID_TIMER, 10, NULL); |
| 199 | + return 0; |
| 200 | + |
| 201 | + //处理善后工作 |
| 202 | + case WM_KEYDOWN: //任意键退出 |
| 203 | + case WM_LBUTTONDOWN: //点击鼠标退出 |
| 204 | + case WM_DESTROY: |
| 205 | + KillTimer(hwnd, ID_TIMER); |
| 206 | + DeleteObject(hBitmap); |
| 207 | + DeleteDC(hdcMem); |
| 208 | + |
| 209 | + for (i = 0; i<iColumnCount; i++) |
| 210 | + free((ccChain + i)->current); |
| 211 | + free(ccChain); |
| 212 | + PostQuitMessage(0); |
| 213 | + return 0; |
| 214 | + } |
| 215 | + |
| 216 | + return DefWindowProc(hwnd, message, wParam, lParam); |
| 217 | +} |
0 commit comments