Writing to the Console

We could use functions like printf(...) as usual, but they lack the control we need. We need to get slightly lower-level to have decent control of the console text.

Each character block on the console is represented by a structure called CHAR_INFO. This structure contains both the character we want to display and the colour we want to display it with. The colour information is held in the property Attributes and consists of a background and foreground colour (and a few other things we'll pass on, such as border options) and the character information can either be held as an ANSI character in the property Char.AsciiChar or as a Unicode "wide" character in the property Char.UnicodeChar.

Let's try and recreate the character above. We can use 'A' as a character literal to represent the letter; alternatively, we can use the ANSI code for it — here is a list of all of the ones available to us in the default code page (850) on an English edition of Windows:

All the values (to the left of each character) are in hexadecimal — so we could set the character 'A' with Char.AsciiChar = 0x41;

When creating colours, we have access to these predefined bits as constants:

  • FOREGROUND_BLUE
  • FOREGROUND_GREEN
  • FOREGROUND_RED
  • FOREGROUND_INTENSITY

When setting the background we swap FOREGROUND_ for BACKGROUND_. The foreground color here is clearly a nice bright red, so we'd use FOREGROUND_RED | FOREGROUND_INTENSITY. However, working out the background is a bit trickier — to make yellow, we need to mix red and green. The final Attribute would look like:

FOREGROUND_RED | FOREGROUND_INTENSITY |
BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY

The function we need to use to output ANSI characters to the console is WriteConsoleOutputA(...). This fairly chunky function requires quite a bit of setting up, as we need to pass it:

  • The write handle to the console window.
  • A pointer to the CHAR_INFO buffer we want to display.
  • The width and height of that buffer.
  • The coordinate we want to start outputting to on the console.
  • A pointer to a SMALL_RECT that dictates which area on the console we're writing to.

For Unicode characters you would use WriteConsoleOutputW(...).

In our case, we're only outputting one character, so our character buffer size coord will be {1,1}, the coordinate we're outputting to is going to be {0,0} and the SMALL_RECT that dictates the area we're writing to is simply {0,0,0,0}.

Implemented, the full code is:

#include <stdlib.h>
#include <stdio.h>
#include <Windows.h>
#include <Tchar.h>

HANDLE wHnd;    // Handle to write to the console.
HANDLE rHnd;    // Handle to read from the console.

int _tmain(int argc, _TCHAR* argv[]) {

    // Set up the handles for reading/writing:
    wHnd = GetStdHandle(STD_OUTPUT_HANDLE);
    rHnd = GetStdHandle(STD_INPUT_HANDLE);

    // Change the window title:
    SetConsoleTitle(TEXT("Win32 Console Control Demo"));

    // Set up the required window size:
    SMALL_RECT windowSize = {0, 0, 79, 49};
    
    // Change the console window size:
    SetConsoleWindowInfo(wHnd, TRUE, &windowSize);
    
    // Create a COORD to hold the buffer size:
    COORD bufferSize = {80, 50};

    // Change the internal buffer size:
    SetConsoleScreenBufferSize(wHnd, bufferSize);

    // Set up the character:
    CHAR_INFO letterA;
    letterA.Char.AsciiChar = 'A';
    
    letterA.Attributes = 
        FOREGROUND_RED | FOREGROUND_INTENSITY |
        BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY;

    // Set up the positions:
    COORD charBufSize = {1,1};
    COORD characterPos = {0,0};
    SMALL_RECT writeArea = {0,0,0,0}; 

    // Write the character:
    WriteConsoleOutputA(wHnd, &letterA, charBufSize, characterPos, &writeArea);

    // Move the cursor down a row so we can see the character:
    printf("\n");

}

Note the the addition of an extra #include directive for stdio.h, which is required for the printf(...) function.

When run, you should end up with something like the above screenshot. Seems like overkill just for the one character, no? Well, it's really easy to change that.

If you're used to graphics programming, you'll know that it's usually best to perform all the drawing on an off-screen buffer, then copy that over to the display in one go — this eliminates the flicker as the image is built up. We can easily use this double-buffering system to write a full screen of text and colour information to the console in one go.

Naturally, we'll have to change the data we copy to the display from a singe CHAR_INFO to an array of CHAR_INFOs. As we're using 80x50, it needs to be of size 80*50=4000 elements. We'll have to change the charBufSize to be 80x50 as well, and also change the writeArea to fill the entire display. In short, what we now have is this:

     // Set up the character buffer:
    CHAR_INFO consoleBuffer[80 * 50];

    // We'll fill the console buffer with random data:
    for (int y = 0; y < 50; ++y) {
        for (int x = 0; x < 80; ++x) {
        
            // An ANSI character is in the range 0-255,
            // so use % to keep it in this range.
            consoleBuffer[x + 80 * y].Char.AsciiChar = rand() % 256;

            // The colour is also in the range 0-255,
            // as it is a pair of 4-bit colours.
            consoleBuffer[x + 80 * y].Attributes = rand() % 256;
        }
    }

    // Set up the positions:
    COORD charBufSize = {80,50};
    COORD characterPos = {0,0};
    SMALL_RECT writeArea = {0,0,79,49}; 

    // Write the characters:
    WriteConsoleOutputA(wHnd, consoleBuffer, charBufSize, characterPos, &writeArea);

Hopefully you can see where the new code comes from. We have to use a linear array instead of a two-dimensional one for the buffer, which is why we use the formula

array_index = x + array_width * y

to change the (x,y) coordinates we're writing to into the index for the one-dimensional array.

Now, while this in itself is useful, it would be a good plan to see how we can get input from the user so we can put this knowledge to good use!


PreviousNextShow All Index