ScrollConsoleScreenBuffer 影响裁剪矩形外的数据

ScrollConsoleScreenBuffer affect the data outside the clipping rect

我正在使用带有 ffi 的 nodejs 来调用 winapi。我包装了一些用于控制控制台的功能。

let kernel32 = ffi.Library('kernel32.dll', {
// ...
    'ScrollConsoleScreenBufferW': [ BOOL, [ HANDLE, PSMALL_RECT, PSMALL_RECT, COORD, PCHAR_INFO ]],
// ...
});
const wincon = {
// ...
    ScrollConsoleScreenBuffer: (handle, target, clip, dest, fill) => {
        let sr = ref.alloc(SMALL_RECT, target);
        let cr = clip == null ? ref.NULL : ref.alloc(SMALL_RECT, clip);
        let ci = ref.alloc(CHAR_INFO, fill);
        return {
            ret: kernel32.ScrollConsoleScreenBufferW(handle, sr, cr, new COORD(dest), ci) != 0
        };
    },
// ...
};

上面的代码是我声明ScrollConsoleScreenBuffer的方式。

const ui = {
// ...
    log: (hout, region) => {
        return {
            hout: hout,
            region: region,
            log: function (old_strs) {
                let strs = [];
                let width = region.R - region.L + 1;
                old_strs.forEach(s => {
                    while (s.length > width) {
                        strs.push(s.slice(0, width));
                        s = s.slice(width);
                    }
                    if (s.length > 0) {
                        strs.push(s);
                    }
                });
                wincon.ScrollConsoleScreenBuffer(this.hout, this.region, this.region, { X: this.region.L, Y: this.region.T - strs.length }, { C: 32, A: wincon.BACKGROUND_WHITE + wincon.BACKGROUND_INTENSIVE });
                for (let i = 0; i < strs.length; ++i) {
                    wincon.WriteConsoleOutputCharacter(this.hout, strs[i], { X: this.region.L, Y: this.region.B + 1 - strs.length + i });
                }
            }
        };
    },
}
// other file
let batLogRegion = { L: 60, T: 30, R: 119, B: 59 };
let batlog = ui.log(hstdout, batLogRegion);
batlog.log(info.log.slice(logged));

上面的代码就是我的使用方法

然而,当我记录一堆东西时,文本滚动到控制台的顶部,而不是被剪掉。并且经过多次调用,第30行以上的文字还在往上移动。

这不是因为日志包含太多文本。实际上它只有不到 20 行。

应该不是定义错误或者调用方法错误,其他很多函数都可以正常工作。

所以我使用ScrollConsoleScreenBuffer应该有一些问题。

谁能帮帮我?

部分图片:

裁剪矩形为{0.3,0.5,0.6,1}(如果window为{0,0,1,1}),文字移动到顶部。

use scan to generate much text

after doing sth. the text above is still going up

我不确定我是否会产生这个问题,我在 C++ 中使用以下示例:

#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void log(HANDLE hStdout, vector<wstring> old_strs, SMALL_RECT region)
{
    COORD coordDest;
    CHAR_INFO chiFill;
    vector<wstring> strs;
    size_t width = region.Right - region.Left;
    for (size_t i = 0; i < old_strs.size(); i++)
    {
        wstring s = old_strs[i];
        while (s.length() > width)
        {
            strs.push_back(s.substr(0, width-1));
            s = s.substr(width-1);
        }
        if (s.length() > 0) {
            strs.push_back(s);
        }
    }

    coordDest.X = region.Left;
    coordDest.Y = region.Top - strs.size();

    chiFill.Attributes = BACKGROUND_GREEN | FOREGROUND_RED;
    chiFill.Char.UnicodeChar = 32;

    if (!ScrollConsoleScreenBufferW(
        hStdout,         // screen buffer handle 
        &region, // scrolling rectangle 
        &region,   // clipping rectangle 
        coordDest,       // top left destination cell 
        &chiFill))       // fill character and color
    {
        printf("ScrollConsoleScreenBuffer failed %d\n", GetLastError());
        return;
    }
    DWORD len = 0;
    for (size_t i = 0; i < strs.size(); i++)
    {
        WriteConsoleOutputCharacterW(hStdout, strs[i].c_str(), strs[i].length(), { region.Left,region.Bottom + 1 - (SHORT)strs.size() + (SHORT)i }, &len);
    }
}
int main(void)
{
    HANDLE hStdout = NULL;
    CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
    for (int i = 0; i < 70; i++)
    {
        printf("%d", i);
        int k = (i > 9 ? 116 : 117);
        char x = (i > 25 ? 'a' : 'A');
        for (int j = 0; j < k; j++)
            printf("%c", x + i % 26);
        printf("\n");
    }
    
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

    if (hStdout == INVALID_HANDLE_VALUE)
    {
        printf("GetStdHandle failed with %d\n", GetLastError());
        return 1;
    }
    
    vector<wstring> old_strs = { L"this is a long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long string" };
    for (int i = 1; i < 30; i++)
        old_strs.push_back(std::to_wstring(i));
    SMALL_RECT batLogRegion = { 60,30,119,59 };
    log(hStdout,old_strs, batLogRegion);
    return 0;
}

结果如下:

old_strs切片得到的strs包含超过30(batLogRegion.Bottom - batLogRegion.Top + 1 = 30)行字符串时,虽然ScrollConsoleScreenBufferW不会影响其他区域超大裁剪rect,但在 WriteConsoleOutputCharacterW 中:region.Bottom + 1 - strs.size() + iregion 之上。因此,您可能需要根据需要 re-determine 裁剪矩形的大小(strs 的大小),例如:

if ((int)(region.Bottom - region.Top + 1 - strs.size())< 0)
{
    region.Bottom = region.Top + strs.size() - 1;
}

更新:

增加判断,当strs的长度大于clipping rect的高度时,超过top的字符串不打印

#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void log(HANDLE hStdout, vector<wstring> old_strs, SMALL_RECT region)
{
    COORD coordDest;
    CHAR_INFO chiFill;
    vector<wstring> strs;
    size_t width = region.Right - region.Left;
    for (size_t i = 0; i < old_strs.size(); i++)
    {
        wstring s = old_strs[i];
        while (s.length() > width)
        {
            strs.push_back(s.substr(0, width-1));
            s = s.substr(width-1);
        }
        if (s.length() > 0) {
            strs.push_back(s);
        }
    }

    coordDest.X = region.Left;
    coordDest.Y = region.Top - strs.size();
    
    chiFill.Attributes = BACKGROUND_GREEN | FOREGROUND_RED;
    chiFill.Char.UnicodeChar = 32;

    if (!ScrollConsoleScreenBufferW(
        hStdout,         // screen buffer handle 
        &region, // scrolling rectangle 
        &region,   // clipping rectangle 
        coordDest,       // top left destination cell 
        &chiFill))       // fill character and color
    {
        printf("ScrollConsoleScreenBuffer failed %d\n", GetLastError());
        return;
    }
    DWORD len = 0;
    for (size_t i = 0; i < strs.size(); i++)
    {
        if ((int)(region.Bottom - region.Top + 1 - strs.size()+i) < 0)
            continue;
        WriteConsoleOutputCharacterW(hStdout, strs[i].c_str(), strs[i].length(), { region.Left,region.Bottom + 1 - (SHORT)strs.size() + (SHORT)i }, &len);
    }
}
int main(void)
{
    HANDLE hStdout = NULL;
    CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
    for (int i = 0; i < 70; i++)
    {
        printf("%d", i);
        int k = (i > 9 ? 116 : 117);
        char x = (i > 25 ? 'a' : 'A');
        for (int j = 0; j < k; j++)
            printf("%c", x + i % 26);
        printf("\n");
    }
    
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

    if (hStdout == INVALID_HANDLE_VALUE)
    {
        printf("GetStdHandle failed with %d\n", GetLastError());
        return 1;
    }
    
    vector<wstring> old_strs = { L"this is a long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long string" };
    vector<wstring> old_strs2 = { L"this is a long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long string" };

    SMALL_RECT batLogRegion = { 60,30,119,59 };
    for (int i = 1; i < 30; i++)
        old_strs.push_back(std::to_wstring(i));

    log(hStdout, old_strs, batLogRegion);
    Sleep(1000);
    log(hStdout, old_strs2, batLogRegion);
    return 0;
}