C++ 控制台 - 大小合适仍然为滚动条留下空白空间

Posted

技术标签:

【中文标题】C++ 控制台 - 大小合适仍然为滚动条留下空白空间【英文标题】:C++ Console - properly sized still leaves empty space for scroll bars 【发布时间】:2018-05-18 16:25:30 【问题描述】:

我(和我之前的许多人一样,我已经做了很多搜索)试图让我的控制台显示没有滚动条的缓冲区。我根据系统字体大小和请求的缓冲区大小调整了窗口大小,但即使在更改(和更新)控制台的样式标志之后,我仍然在水平和垂直滚动条所在的位置留有空白。

谁能帮我解决这个问题?

#include <windows.h>
#include <iostream>

const bool adjustWindowSize( const unsigned int p_console_buffer_width, 
const unsigned int p_console_buffer_height )

   /// Get the handle to the active window
   HWND l_window_handle( GetConsoleWindow() );
   if( l_window_handle == NULL )
   
      std::cout << "GetConsoleWindow() failed\n";
      return false;
   

   /// Get the dimensions of the active window
   RECT l_window_rect;
   if( !GetWindowRect( l_window_handle, &l_window_rect ) )
   
      std::cout << "GetWindowRect() failed\n";
      return false;
   

   /// Remove unwanted WindowStyle flags
   LONG l_style( GetWindowLong( l_window_handle, GWL_STYLE ) );
   l_style &= ~( WS_VSCROLL | WS_HSCROLL | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | 
                 WS_SIZEBOX );
   SetWindowLong( l_window_handle, GWL_STYLE, l_style );

   /// Set new window size to update the style flags
   if( !SetWindowPos( l_window_handle, HWND_TOP, l_window_rect.left, 
                      l_window_rect.top, l_window_rect.right - 
                      l_window_rect.left, l_window_rect.bottom - 
                      l_window_rect.top, SWP_HIDEWINDOW ) )
   
      std::cout << "SetWindowPos() failed\n";
      return false;
   

   /// Get the dimensions of the client area within the window's borders
   RECT l_client_rect;
   if( !GetClientRect( l_window_handle, &l_client_rect ) )
   
      std::cout << "GetClientRect() failed\n";
      return false;
   

   /// Get handle to console
   HANDLE l_console_handle( GetStdHandle( STD_OUTPUT_HANDLE ) );
   if( l_console_handle == nullptr )
   
      std::cout << "GetStdHandle() failed\n";
      return false;
   

   /// Get font information
   CONSOLE_FONT_INFO l_font_info;
   if( !GetCurrentConsoleFont( l_console_handle, false, &l_font_info ) )
   
      std::cout << "GetCurrentConsoleFont() failed\n";
      return false;
   

   /// Prepare desired client area size
   unsigned int l_target_width(  l_font_info.dwFontSize.X * 
                                 p_console_buffer_width );
   unsigned int l_target_height( l_font_info.dwFontSize.Y * 
                                 p_console_buffer_height );

   POINT l_top_left;
   l_top_left.x = l_client_rect.left;
   l_top_left.y = l_client_rect.top;
   ClientToScreen( l_window_handle, &l_top_left );

   POINT l_bottom_right;
   l_bottom_right.x = l_client_rect.right;
   l_bottom_right.y = l_client_rect.bottom;
   ClientToScreen( l_window_handle, &l_bottom_right );

   unsigned int l_diff_x = l_window_rect.right - l_bottom_right.x + 
                           l_top_left.x - l_window_rect.left;
   unsigned int l_diff_y = l_window_rect.bottom - l_bottom_right.y + 
                           l_top_left.y - l_window_rect.top;

   /// Adjust window to fit exactly it's borders + the new client size
   l_window_rect.right = l_target_width + l_diff_x;
   l_window_rect.bottom = l_target_height + l_diff_y;

   /// Set new window size
   if( !SetWindowPos( l_window_handle, HWND_TOP, l_window_rect.left, 
                      l_window_rect.top, l_window_rect.right, 
                      l_window_rect.bottom, SWP_SHOWWINDOW ) )
   
      std::cout << "SetWindowPos() failed\n";
      return false;
   

   /// Set new console buffer size
   if( !SetConsoleScreenBufferSize( l_console_handle,
                                    COORD(  (SHORT)p_console_buffer_width, 
                                    (SHORT)p_console_buffer_height  ) ) )
   
      std::cout << "SetConsoleScreenBufferSize() failed\n";
      return false;
   

   return true;


int main()

   SetConsoleTitle( (LPCSTR)"Console Test" );

   unsigned int l_buffer_x( 100 );
   unsigned int l_buffer_y( 40 );

   if( !adjustWindowSize( l_buffer_x, l_buffer_y ) )
   
      std::cout << "adjustWindowSize() failed\n";
      return 1;
   

   for( unsigned int i( 0 ); i < l_buffer_x * l_buffer_y; ++i )
   
      std::cout << i % 10;
   

   SetConsoleCursorPosition( GetStdHandle( STD_OUTPUT_HANDLE ),
                             COORD(  0, 0  ) );

   return 0;

Picture of console with empty spaces where scroll bars were

【问题讨论】:

【参考方案1】:

正如我理解你的问题,你想创建没有滚动条的控制台窗口,并且可见区域正好是输出屏幕缓冲区的大小?您可以这样做:

#include <windows.h>
#include <iostream>

void SetConsoleWindow(HANDLE conout, SHORT cols, SHORT rows)

    CONSOLE_SCREEN_BUFFER_INFOEX sbInfoEx;
    sbInfoEx.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
    GetConsoleScreenBufferInfoEx(conout, &sbInfoEx);
    sbInfoEx.dwSize.X = cols;
    sbInfoEx.dwSize.Y = rows;
    sbInfoEx.srWindow =  0, 0, cols, rows ;
    sbInfoEx.dwMaximumWindowSize =  cols, rows ;
    SetConsoleScreenBufferInfoEx(conout, &sbInfoEx);

    DWORD mode;
    GetConsoleMode(conout, &mode);
    mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
    SetConsoleMode(conout, mode);

    SetConsoleTitle(L"Console Test");


int main()

    HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
    SHORT cols = 100, rows = 20;
    SetConsoleWindow(out, cols, rows);

    for (int y = 0; y < rows; ++y)
    
        for (int x = 0; x < cols; ++x)
            std::cout << y %10;

        if (y < rows - 1)
            std::cout << std::endl;
    

    SetConsoleCursorPosition(out,  0,0 );

    return 0;
 

这需要一些解释。 CONSOLE_SCREEN_BUFFER_INFOEX 结构似乎包含实现这一点所需的所有参数。如果您检查控制台窗口的属性并将它们与结构成员进行比较

你发现dwSize对应Screen Buffer SizesrWindow.Right - srWindow.LeftsrWindow.Bottom - srWindow.Top对应Window Size

如果未设置dwMaximumWindowSize,则默认为桌面边界,如果窗口较大,则添加滚动条。设置它也可以解决这个问题。

最后,ENABLE_WRAP_AT_EOL_OUTPUT 需要从控制台选项中删除,以停止光标跳转到行尾的下一行,从而使用最后打印的字符滚动缓冲区。我已经调整了打印循环来解决这个问题。

【讨论】:

窗口大小是屏幕缓冲区从0开始的视图,所以应该是cols - 1rows - 1。也就是说,SetConsoleScreenBufferInfoEx 设置的实际srWindow 大小(Windows 7-10;旧控制台,新控制台)存在问题,尤其是在增加屏幕缓冲区大小时。它需要额外的两次调用才能使其正常运行。调用GetLargestConsoleWindowSize 并取此行和列大小与目标屏幕大小中的最小值。然后通过SetConsoleWindowInfo设置这个计算的窗口大小。 另外,dwMaximumWindowSize 应该作为信息处理,至少对于 Windows 10 中的新控制台,它不允许将控制台窗口的大小调整为大于显示。您在旧版本的 Windows 上使用旧版控制台,不幸的是,这确实会造成如此不可行的混乱。它应该被视为实现中的错误,而不是功能。 @eryksun 感谢所有观点。您是否参考了描述上述怪癖的 MS 文档?我不认为我在通常的 MSDN 文档中看到过它们 SetConsoleScreenBufferInfoEx 的行为没有记录在案,我还没有看到 Wine 或 ReactOS 中的逆向工程实现。我已经在各种项目和论​​坛中看到了建议的解决方法,例如在 ConEmu 的源代码中使用对 SetConsoleWindowInfo 的额外调用。但大多数人不记得检查窗口大小是否允许(通过GetLargestConsoleWindowSizeGetConsoleScreenBufferInfoExdwMaximumWindowSize)。

以上是关于C++ 控制台 - 大小合适仍然为滚动条留下空白空间的主要内容,如果未能解决你的问题,请参考以下文章

表格调整大小并占用垂直滚动条留下的空间

有没有办法将控制台滚动到空白部分?

C# DataGridView 数据显示到最后一行后,如何使滚动条继续向下滚动。

UICollectionView 在滚动时留下空白单元格

CoordinatorLayout 滚动后在底部留下空白空间

UGUI 用手柄或者键盘控制选择Scroll View中的游戏对象时,滚动条跟着移动