首先,需要让控制台程序的屏幕缓冲区高度 > 窗口高度(此时窗口右侧会产生滚动条),屏幕缓冲区宽度 > 窗口宽度(此时窗口下侧会产生滚动条),否则无需滚动窗口。
可以通过下列代码来设置控制台屏幕缓冲区大小和窗口大小:
// 设置屏幕缓冲区大小(单位:字符数) width: 100 height: 30
HANDLE 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)无法通过滚轮来滑动横向滚动条
关于SetConsoleMode函数dwMode说明:
-->输入缓冲区(Console Input Buffer)相关
Value | Meaning |
ENABLE_ECHO_INPUT 0x0004 | ReadFile or ReadConsole 读取字符时,同时将字符放置到屏幕输出缓冲区,必须先启用ENABLE_LINE_INPUT 在用户输入内容时,内容也会在屏幕上显示出来;如果是输入密码一类的内容,可以禁止该选项,这样输入的密码就不会打印在屏幕上 |
ENABLE_EXTENDED_FLAGS 0x0080 | 启用扩展标志位. 注:以下ENABLE_INSERT_MODE和ENABLE_QUICK_EDIT_MODE为扩展标志位 |
ENABLE_INSERT_MODE 0x0020 | 扩展标志位,必须要先启用ENABLE_EXTENDED_FLAGS 在光标处插入字符时后续字符依次后移不会被覆盖 |
ENABLE_LINE_INPUT 0x0002 | ReadFile or ReadConsole 遇到回车才将字符放置到输入缓冲区 禁用该标志位时,ReadFile or ReadConsole 读取字符立即放置到输入缓冲区 |
ENABLE_MOUSE_INPUT 0x0010 | 为当前活动窗口,且鼠标在窗口范围内,鼠标事件将被放置到输入缓冲区中(注:ReadFile or ReadConsole 是不处理鼠标事件) |
ENABLE_PROCESSED_INPUT 0x0001 | CTRL+C由系统处理,不放置到输入缓冲区中 控制键(Ctrl、Shift、Alt等)由系统处理,不通过ReadFile or ReadConsole 来读取并放置到输入缓冲区 若此时同时启用ENABLE_LINE_INPUT 标志位,Backspace、回车、换行也有系统处理 |
ENABLE_QUICK_EDIT_MODE 0x0040 | 扩展标志位,必须要先启用ENABLE_EXTENDED_FLAGS 允许用户使用鼠标选择和编辑字符 |
ENABLE_WINDOW_INPUT 0x0008 | 修改控制台屏幕buffer大小事件将被放置到输入缓冲区中,并能通过ReadConsoleInput 函数来获取该事件 |
-->输出缓冲区(Console Screen Buffer)相关
Value | Meaning |
ENABLE_PROCESSED_OUTPUT 0x0001 | 由WriteFile or WriteConsole 函数或ReadFile or ReadConsole 函数(启用ENABLE_ECHO_INPUT时)放置到输出缓冲区中的字符(包括Backspace、制表符、响铃、回车、换行)将按顺序处理并显示在屏幕上 |
ENABLE_WRAP_AT_EOL_OUTPUT 0x0002 | 启用正常输出(由WriteFile or WriteConsole 函数)和回显输出(由ReadFile or ReadConsole 函数)的自动换行功能。 也就是说,当光标到达命令行窗口边界时会自动切换到下一行,在整个命令行窗口满时,会自动下滚并输出内容。
|
更好地解决方案是:通过多线程技术为控制台窗体添加鼠标滚轮滑动功能。
值得注意的是,在有内容输出时,窗口会自动定位到输出的光标处;
这种情况最好是先暂停住主线程,然后再滚动鼠标查看打印的内容,查看完毕后,再继续执行主线程。
下列代码实现了如下功能:
(1)滚动鼠标滑动窗口【自定义滑动行数和列数; 滚轮:滑动垂直滚动条 Ctrl+滚轮:滑动水平滚动条】
(2)按空格键,暂停/继续主线程
#include <windows.h>
/**
* 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;
}