0

我需要一种在不使用系统调用的情况下表现得像系统颜色的方法。我知道有 setConsoleTextAttribute() 但这并没有用只有颜色的新字符填充整个前景和背景。我正在使用 windows7 虽然我想让它与所有 windows 兼容

4

1 回答 1

0

根据您的评论,这是您要解决的问题的更完整的解决方案。它基于我的原始答案(可以在此答案的末尾找到)。

我发现了 Windows API 的一个限制,即它无法读取 80 列 x 300 行的默认控制台模式窗口的整个屏幕缓冲区。由于 Windows 进程堆不足(据我从谷歌搜索中得知),它会导致 ERROR_NOT_ENOUGH_MEMORY 错误。我最终实现了一个围绕 XxxConsoleOutput 函数的包装器,以支持根据需要自动细分屏幕缓冲区,直到函数成功或无法读取 1 X 1(单字符)区域。

同样,这段代码可能并不完美,它旨在解释概念,而不是(必然)为您提供完整的解决方案。但是,它现在填充了整个控制台(以前我只填充了窗口的可见部分)并设置了控制台的文本属性以供将来输出。

跳过此代码以阅读原始答案。

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <iostream>
#include <vector>

using namespace std;

// Split a rectangular region into two smaller rectangles
// based on the largest dimension.
void SplitRegion(
    SHORT width, SHORT height,
    COORD dwBufferCoord, const SMALL_RECT& readRegion,
    COORD& dwBufferCoordA, SMALL_RECT& readRegionA,
    COORD& dwBufferCoordB, SMALL_RECT& readRegionB)
{
    dwBufferCoordA = dwBufferCoordB = dwBufferCoord;
    readRegionA = readRegionB = readRegion;

    if (height >= width)
    {
        SHORT half = height / 2;
        dwBufferCoordB.Y += half;
        readRegionB.Top += half;
        readRegionA.Bottom = readRegionB.Top - 1;
    }
    else
    {
        SHORT half = width / 2;
        dwBufferCoordB.X += half;
        readRegionB.Left += half;
        readRegionA.Right = readRegionB.Left - 1;
    }
}

// Utility function to figure out the distance
// between two points.
template <typename type>
inline type DiffHelper(type first, type second)
{
    return (second >= first) ? (second - first + 1) : 0;
}

// A template that wraps up the shared code common between
// reading and writing the screen buffer. If it is ever
// given a region of zero width or height, it will
// "succeed". If it ever tries to subdivide a 1 by 1
// region, it will fail.
template <typename lpBufferType>
BOOL XferConsoleOutputWrapper(
    HANDLE hConsoleOutput, 
    lpBufferType lpBuffer, 
    COORD dwBufferSize, 
    COORD dwBufferCoord, 
    SMALL_RECT& xferRegion,
    BOOL (WINAPI * xferConsoleOutput)(
        HANDLE, lpBufferType, COORD, COORD, PSMALL_RECT))
{
    SHORT width = DiffHelper(xferRegion.Left, xferRegion.Right);
    SHORT height = DiffHelper(xferRegion.Top, xferRegion.Bottom);

    if ((width == 0) || (height == 0))
    {
        return TRUE;
    }

    BOOL success = xferConsoleOutput(hConsoleOutput, 
        lpBuffer, dwBufferSize, dwBufferCoord, &xferRegion);
    if (!success)
    {
        if ((GetLastError() == ERROR_NOT_ENOUGH_MEMORY) &&
            ((width * height) > 1))
        {
            COORD dwBufferCoordA, dwBufferCoordB;
            SMALL_RECT xferRegionA, xferRegionB;
            SplitRegion(
                width, height,
                dwBufferCoord, xferRegion,
                dwBufferCoordA, xferRegionA,
                dwBufferCoordB, xferRegionB);
            success =
                XferConsoleOutputWrapper(hConsoleOutput, lpBuffer, dwBufferSize,
                    dwBufferCoordA, xferRegionA, xferConsoleOutput) &&
                XferConsoleOutputWrapper(hConsoleOutput, lpBuffer, dwBufferSize,
                    dwBufferCoordB, xferRegionB, xferConsoleOutput);
        }
    }
    return success;
}

// ReadConsoleOutput failed to read an 80 by 300 character screen
// buffer in a single call, resulting in ERROR_NOT_ENOUGH_MEMORY.
// ReadConsoleOutputWrapper will subdivide the operation into
// smaller and smaller chunks as needed until it succeeds in reading
// the entire screen buffer.
inline BOOL ReadConsoleOutputWrapper(
    HANDLE hConsoleOutput, 
    PCHAR_INFO lpBuffer, 
    COORD dwBufferSize, 
    COORD dwBufferCoord, 
    SMALL_RECT& readRegion)
{
    return XferConsoleOutputWrapper(hConsoleOutput, lpBuffer, dwBufferSize,
        dwBufferCoord, readRegion, ReadConsoleOutput);
}

// WriteConsoleOutputWrapper will subdivide the operation into
// smaller and smaller chunks as needed until it succeeds in writing
// the entire screen buffer. This may not be necessary as
// WriteConsoleOutput never failed, but it was simple to implement
// so it was done just to be safe.
inline BOOL WriteConsoleOutputWrapper(
    HANDLE hConsoleOutput, 
    const CHAR_INFO* lpBuffer, 
    COORD dwBufferSize, 
    COORD dwBufferCoord, 
    SMALL_RECT& writeRegion)
{
    return XferConsoleOutputWrapper(hConsoleOutput, lpBuffer, dwBufferSize,
        dwBufferCoord, writeRegion, WriteConsoleOutput);
}

void ConsoleFillWithAttribute(WORD fillAttribute)
{
    // Get the handle to the output
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

    // Get the information for the current screen buffer
    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(hStdout, &info);

    // Allocate a vector to hold the visible screen buffer data
    vector<CHAR_INFO> buffer(info.dwSize.X * info.dwSize.Y);

    // Initialize a couple of pointers to the begin and end of the buffer
    CHAR_INFO* begin = buffer.data();
    CHAR_INFO* end = begin + buffer.size();

    // Start at the upper left corner of the screen buffer.
    COORD coord;
    coord.X = coord.Y = 0;

    // Initialize the region to encompass the entire screen buffer.
    SMALL_RECT region;
    region.Left = region.Top = 0;
    region.Right = info.dwSize.X - 1;
    region.Bottom = info.dwSize.Y - 1;

    // Read the buffer from the console into the CHAR_INFO vector.
    ReadConsoleOutputWrapper(hStdout, buffer.data(), info.dwSize, coord, region);

    // Change all the attributes to the specified fill attribute.
    while (begin != end)
    {
        begin->Attributes = fillAttribute;
        ++begin;
    }

    // Write the buffer from the CHAR_INFO vector back to the console.
    WriteConsoleOutputWrapper(hStdout, buffer.data(), info.dwSize, coord, region);

    // Finally, set the console text attribute to the fill attribute
    // so that all new text will be printed in the same manner as
    // the attributes we just changed.
    SetConsoleTextAttribute(hStdout, fillAttribute);
}

int main()
{
    cout << "I would like to fill up the console with some text." << endl;
    cout << "The quick brown fox jumped over the lazy dogs." << endl;

    for (int i = 0; i < 100; ++i)
    {
        cout << ' ' << i;
    }

    cout << endl;

    ConsoleFillWithAttribute(
        BACKGROUND_BLUE | FOREGROUND_INTENSITY | 
            FOREGROUND_RED | FOREGROUND_GREEN);

    cout << endl;

    cout << "This should also be printed in the new attribute" << endl;

    return 0;
}

原答案如下:

如果我理解您的要求,您希望能够更改整个控制台模式窗口的属性。您可能想研究ReadConsoleOutputandWriteConsoleOutput函数。您可以使用ReadConsoleOutput将部分或全部控制台缓冲区读入内存,然后根据应用程序的需要操作属性数据,然后WriteConsoleOutput将内存写回控制台输出缓冲区。

下面是一些代码,它将更改控制台当前显示部分的属性(假设输出尚未重定向到非控制台句柄):

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <iostream>
#include <vector>

using namespace std;

void ConsoleFillDisplayWithAttribute(WORD fillAttribute)
{
    // Get the handle to the output
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

    // Get the information for the current screen buffer
    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(hStdout, &info);

    // Calculate the size of the displayed portion of the screen buffer
    COORD size;
    size.X = (info.srWindow.Right - info.srWindow.Left + 1);
    size.Y = (info.srWindow.Bottom - info.srWindow.Top + 1);

    // Allocate a vector to hold the visible screen buffer data
    vector<CHAR_INFO> buffer(size.X * size.Y);

    COORD coord;
    coord.X = coord.Y = 0;

    // Read the buffer from the console into the CHAR_INFO vector
    ReadConsoleOutput(hStdout, buffer.data(), size, coord, &info.srWindow);

    // Initialize a couple of pointers to the begin and end of the buffer
    CHAR_INFO* begin = buffer.data();
    CHAR_INFO* end = begin + buffer.size();

    // Change all the attributes to the specified fill attribute
    while (begin != end)
    {
        begin->Attributes = fillAttribute;
        ++begin;
    }

    // Write the buffer from the CHAR_INFO vector back to the console
    WriteConsoleOutput(hStdout, buffer.data(), size, coord, &info.srWindow);
}

int main()
{
    cout << "I would like to fill up the console with some text." << endl;
    cout << "The quick brown fox jumped over the lazy dogs." << endl;

    for (int i = 0; i < 100; ++i)
    {
        cout << ' ' << i;
    }

    cout << endl;

    ConsoleFillDisplayWithAttribute(
        BACKGROUND_BLUE | FOREGROUND_INTENSITY | 
            FOREGROUND_RED | FOREGROUND_GREEN);

    return 0;
}
于 2013-02-05T04:48:38.820 回答