zoukankan      html  css  js  c++  java
  • 控制台程序添加滚轮滑动支持

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

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

    // 设置屏幕缓冲区大小(单位:字符数)  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

  • 相关阅读:
    HL7及PIX相关的测试工具
    HDU4570----Multi-bit Trie----简单的DP
    hdu2248
    poj 3693 Maximum repetition substring (后缀数组)
    高性能通道
    volyaire重振Infiniband
    利用iWARP/RDMA解决以太网高延迟
    linux 单网卡来绑定多IP实现多网段访问以及多网卡绑定单IP实现负载均衡
    C细节学习
    每2秒获取系统的赋值及内存使用率
  • 原文地址:https://www.cnblogs.com/kekec/p/4433649.html
Copyright © 2011-2022 走看看