控制台程序添加滚轮滑动支持

首先,需要让控制台程序的屏幕缓冲区高度 > 窗口高度(此时窗口右侧会产生滚动条),屏幕缓冲区宽度 > 窗口宽度(此时窗口下侧会产生滚动条),否则无需滚动窗口。

可以通过下列代码来设置控制台屏幕缓冲区大小和窗口大小:

// 设置屏幕缓冲区大小(单位:字符数)  100  height: 30
HANDLE hConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD BuffSize;
BuffSize.X = 100;
BuffSize.Y = 30;
SetConsoleScreenBufferSize( hConsoleHandle, BuffSize );

// 设置窗口大小(单位:字符数)  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)相关

ValueMeaning
ENABLE_ECHO_INPUT
0x0004

ReadFile or ReadConsole 读取字符时,同时将字符放置到屏幕输出缓冲区,必须先启用ENABLE_LINE_INPUT

在用户输入内容时,内容也会在屏幕上显示出来;如果是输入密码一类的内容,可以禁止该选项,这样输入的密码就不会打印在屏幕上

ENABLE_EXTENDED_FLAGS
0x0080

启用扩展标志位.

注:以下ENABLE_INSERT_MODEENABLE_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)相关

ValueMeaning
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
");
                        ResumeThread(hMainThreadHandle);
                    }
                    else 
                    {
                        SetConsoleTextAttribute(hConsoleOutput, FOREGROUND_RED);
                        printf("Suspend MainThread
");
                        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!
", nCouter);
        Sleep(1000);
    }

    Sleep(INFINITE);
    return 0;
}

参考

http://www.groad.net/bbs/thread-4655-1-1.html

https://msdn.microsoft.com/en-us/library/windows/desktop/ms685118(v=vs.85).aspx

https://msdn.microsoft.com/en-us/library/windows/desktop/ms685122%28v=vs.85%29.aspx

https://msdn.microsoft.com/en-us/library/windows/desktop/ms684206%28v=vs.85%29.aspx

https://msdn.microsoft.com/en-us/library/windows/desktop/ms682073%28v=vs.85%29.aspx

原文地址:https://www.cnblogs.com/kekec/p/4433649.html