首先,需要让控制台程序的屏幕缓冲区高度 > 窗口高度(此时窗口右侧会产生滚动条),屏幕缓冲区宽度 > 窗口宽度(此时窗口下侧会产生滚动条),否则无需滚动窗口。
可以通过下列代码来设置控制台屏幕缓冲区大小和窗口大小:
// 设置屏幕缓冲区大小(单位:字符数) width: 100 height: 30HANDLE hConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);COORD BuffSize;BuffSize.X = 100;BuffSize.Y = 30;SetConsoleScreenBufferSize( hConsoleHandle, BuffSize );// 设置窗口大小(单位:字符数) width: 80 height: 27_SMALL_RECT Rect; Rect.Top = 0; Rect.Left = 0;Rect.Right = 80;Rect.Bottom = 27; Rect.Right -= 1; Rect.Bottom -= 1;SetConsoleWindowInfo(hConsoleHandle, TRUE, &Rect);
控制台程序默认只能通过拖动滚动条来查看窗口中打印的内容,操作起来十分不方便。
可以通过添加如下简单的代码来实现鼠标滚轮滑动功能:
DWORD nMode;HANDLE hConsoleHandle = GetStdHandle(STD_INPUT_HANDLE);GetConsoleMode(hConsoleHandle, &nMode);SetConsoleMode(hConsoleHandle, nMode & ~ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
注:(1)windows默认滚轮的滑动行数为:3
(2)无法通过滚轮来滑动横向滚动条
关于函数dwMode说明:
-->输入缓冲区(Console Input Buffer)相关
Value | Meaning |
---|---|
| or 读取字符时,同时将字符放置到屏幕输出缓冲区,必须先启用ENABLE_LINE_INPUT 在用户输入内容时,内容也会在屏幕上显示出来;如果是输入密码一类的内容,可以禁止该选项,这样输入的密码就不会打印在屏幕上 |
| 启用扩展标志位. 注:以下ENABLE_INSERT_MODE和ENABLE_QUICK_EDIT_MODE为扩展标志位 |
| 扩展标志位,必须要先启用ENABLE_EXTENDED_FLAGS 在光标处插入字符时后续字符依次后移不会被覆盖 |
| or 遇到回车才将字符放置到输入缓冲区 禁用该标志位时, or 读取字符立即放置到输入缓冲区 |
| 为当前活动窗口,且鼠标在窗口范围内,鼠标事件将被放置到输入缓冲区中(注: or 是不处理鼠标事件) |
| CTRL+C由系统处理,不放置到输入缓冲区中 控制键(Ctrl、Shift、Alt等)由系统处理,不通过 or 来读取并放置到输入缓冲区 若此时同时启用ENABLE_LINE_INPUT 标志位,Backspace、回车、换行也有系统处理 |
| 扩展标志位,必须要先启用ENABLE_EXTENDED_FLAGS 允许用户使用鼠标选择和编辑字符 |
| 修改控制台屏幕buffer大小事件将被放置到输入缓冲区中,并能通过 函数来获取该事件 |
-->输出缓冲区(Console Screen Buffer)相关
Value | Meaning |
---|---|
| 由 or 函数或 or 函数(启用ENABLE_ECHO_INPUT时)放置到输出缓冲区中的字符(包括Backspace、制表符、响铃、回车、换行)将按顺序处理并显示在屏幕上 |
| 启用正常输出(由 or 函数)和回显输出(由 or 函数)的自动换行功能。 也就是说,当光标到达命令行窗口边界时会自动切换到下一行,在整个命令行窗口满时,会自动下滚并输出内容。
|
更好地解决方案是:通过多线程技术为控制台窗体添加鼠标滚轮滑动功能。
值得注意的是,在有内容输出时,窗口会自动定位到输出的光标处;
这种情况最好是先暂停住主线程,然后再滚动鼠标查看打印的内容,查看完毕后,再继续执行主线程。
下列代码实现了如下功能:
(1)滚动鼠标滑动窗口【自定义滑动行数和列数; 滚轮:滑动垂直滚动条 Ctrl+滚轮:滑动水平滚动条】
(2)按空格键,暂停/继续主线程
#include/*** Scroll console window by relative coordinate*/static int ScrollByRelativeCoord(int nSteps, bool bPressControlKey){ CONSOLE_SCREEN_BUFFER_INFO csbiInfo; SMALL_RECT srctWindow; // Get the current screen buffer window position. HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); if (! GetConsoleScreenBufferInfo(hConsoleOutput, &csbiInfo)) { return 0; } if (!bPressControlKey) { // Check whether the window is too close to the screen buffer top or bottom if (csbiInfo.srWindow.Top < nSteps) { nSteps = csbiInfo.srWindow.Top; } else if (csbiInfo.srWindow.Bottom > csbiInfo.dwSize.Y+nSteps-1) { nSteps = -1* (csbiInfo.dwSize.Y -1 - csbiInfo.srWindow.Bottom); } srctWindow.Top =- (SHORT)nSteps; // move top up srctWindow.Bottom =- (SHORT)nSteps; // move bottom up srctWindow.Left = 0; // no change srctWindow.Right = 0; // no change } else { // Check whether the window is too close to the screen buffer top or bottom if (csbiInfo.srWindow.Left < nSteps) { nSteps = csbiInfo.srWindow.Left; } else if (csbiInfo.srWindow.Right > csbiInfo.dwSize.X+nSteps-1) { nSteps = -1* (csbiInfo.dwSize.X -1 - csbiInfo.srWindow.Right); } srctWindow.Top = 0; // no change srctWindow.Bottom = 0; // no change srctWindow.Left =- (SHORT)nSteps; // move left srctWindow.Right =- (SHORT)nSteps; // move Right } if (! SetConsoleWindowInfo( hConsoleOutput, // screen buffer handle FALSE, // relative coordinates &srctWindow)) // specifies new location { return 0; } return nSteps;}DWORD WINAPI ConsoleInputEventProc(LPVOID lParam){ // vc6 version sdk don't has OpenThread API, need get by call GetProcAddress; HANDLE hMainThreadHandle = NULL;#if _MSC_VER <= 1200 HMODULE hDll =::LoadLibrary("Kernel32.dll"); if (hDll) { typedef HANDLE (__stdcall *OPENTHREAD) (DWORD, BOOL, DWORD); OPENTHREAD fnOpenThread = (OPENTHREAD)::GetProcAddress(hDll, "OpenThread"); if (fnOpenThread) { hMainThreadHandle = fnOpenThread(THREAD_SUSPEND_RESUME, FALSE, (DWORD)lParam); } }#else hMainThreadHandle = OpenThread(THREAD_SUSPEND_RESUME, FALSE, (DWORD)lParam);#endif HANDLE hConsoleInput = GetStdHandle(STD_INPUT_HANDLE); BOOL bSuspend = FALSE; BOOL bContinue = TRUE; DWORD dwEvents; INPUT_RECORD input; while (bContinue && ReadConsoleInput(hConsoleInput, &input, 1, &dwEvents) && dwEvents > 0) { switch (input.EventType) { case KEY_EVENT: if (input.Event.KeyEvent.wVirtualKeyCode == VK_SPACE) { if (input.Event.KeyEvent.bKeyDown && hMainThreadHandle != NULL) { HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); if (bSuspend) { SetConsoleTextAttribute(hConsoleOutput, FOREGROUND_GREEN); printf("Resume MainThread\n"); ResumeThread(hMainThreadHandle); } else { SetConsoleTextAttribute(hConsoleOutput, FOREGROUND_RED); printf("Suspend MainThread\n"); SuspendThread(hMainThreadHandle); } SetConsoleTextAttribute(hConsoleOutput, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); bSuspend = !bSuspend; } } case MOUSE_EVENT: if (input.Event.MouseEvent.dwEventFlags==MOUSE_WHEELED)// mousewheel { int nLine = 5; bool bPressControlKey = false; if ((input.Event.MouseEvent.dwControlKeyState & LEFT_CTRL_PRESSED) || (input.Event.MouseEvent.dwControlKeyState & RIGHT_CTRL_PRESSED)) { nLine = 3; bPressControlKey = true; } if ((int)input.Event.MouseEvent.dwButtonState>0)// scroll up { ScrollByRelativeCoord(nLine, bPressControlKey); } else// scroll up { ScrollByRelativeCoord(-1*nLine, bPressControlKey); } } break; } } CloseHandle(hMainThreadHandle); return 0;}int main(int argc, char* argv[]){ CreateThread(NULL, 0, ConsoleInputEventProc, (LPVOID)GetCurrentThreadId(), 0, NULL); int nCouter = 0; while (nCouter++<100) { printf("%d Hello World!\n", nCouter); Sleep(1000); } Sleep(INFINITE); return 0;}
参考