Introduction
The JLib
library encapsulates the Win32 API functions responsible for the console text formatting. These features include:
- Output formatting
- Horizontal & Vertical text wrapping
- Memory of MRU cursor position and formatting
- Getting input from the keyboard
- User defined menus, including standard, windowed, scrolling, and windowed scrolling
- 256 color palette (4 bits for background, 4 bits for foreground)
- Character Boxes
- Windowed Boxes with titles
- Saving/Restoring a screens worth of characters & formatting
- Saving/Restoring a portion of the screen's characters & formatting
Scope
This article covers the usage of the JLib
library
functions only. It is only intended to describe the features it provides
and how to use them. It does not explain the fundamentals behind the
Windows functions that this library uses.
Background
Five years ago, when I began studying software engineering, I was
inspired by a classmate's ability to add color formatting to a text
output in a console application in Windows XP. Eventually I learned how
to manipulate it myself but it grew tedious having to deal with all the
aspects of it. As a result, I created a library of functions that went
beyond simply slapping an array of formatted text to the screen.
At the time, I was studying the use of C, C++ and MFC. I was very
fond, and still am, of the MFC library, so I wrote some of the code to
mimic the way MFC code is shown in the MSDN documents.
Four years later I took another look at the original JLib
.
I've grown as a developer since I originally wrote it and I was embarrased
at how sloppy it was. I decided to give it an overhaul and finally
fix the smart color formatting once and for all.
Why is it named JLib
? Because I wrote the library, and, my first name begins with the letter J.
Using the Code
This version of JLib
compiles with Microsoft Visual
Studio .NET 2010. It is possible to compile it in any older version of
Microsoft Visual Studio, as far back as 6.0. You'll have to substitute
the secure functions, such as _itoa_s
, _kbhit
, and _getch
, to their deprecated versions where needed.
The library is composed of the class ConsoleCore
which wraps up console formatting calls in the windows.h header. ConsoleCore
is a singleton which is accessed through the ConsoleCore::GetInstance
method.
Note: the ConsoleCore
is not thread safe!
The library also explicitly uses the Standard Template Library for color formatting, vector, and list containers.
The main points of using this library that this article will cover are:
- How do I add it to my project?
- Definitions and Classes
- How do I print a
string
- How do I print numbers
- How do I use
ConsoleFormat
- How do I format output
- How do I read a
string
from the keyboard - How do I read a number from the keyboard
- How do I draw a character box
- How do I draw a window character box with/without a title
- How do I create a menu
- How do I use a menu
- How do I save/load/clear portions, or all, of the screen
How do I Add it to My Project?
Place the source code in the appropriate folder within your project folder and include the header:
#include "ConsoleCore.h"
Defintions and Classes
It all begins with the console definitions:
#define MAXSCREENX (80) // Standard sized window dimensions
#define MAXSCREENY (25)
#define SCREEN_BUFFER_SIZE (MAXSCREENX * MAXSCREENY)
#define BADMENU (60000)
#define USERESC (60001)
#define UP_KEY (72)
#define DOWN_KEY (80)
#define RETURN (13)
#define ESCAPE (27)
#define KB_EXTENDED (224) // Returned from kbhit if an extended key is pressed
JLib
is based on consoles that are 80x25 characters. Menu functionality operates on the arrow keys, return key,
and escape key. JLib
also use _kbhit
to catch user input while menus or prompts are active.
ConsoleCore
handles ouput to the screen and input from the keyboard. Alone it provides basic
IO functionality with the keyboard and the screen but allows you control over the formatting and position of the cursor.
The JLib
library comes with a handful of classes that use ConsoleCore
to handle some
more interesting and tedious tasks:
class ConsoleFormat;
class CharacterBox;
class CharacterWindow : public CharacterBox;
class ConsoleMenuItem;
class ConsoleMenu;
class ScrollingMenu : public ConsoleMenu;
class WindowedMenu : public ScrollingMenu;
How do I Print a String
To print a string
, you use one of the ConsoleCore::Print
methods.
The signatures look like this:
void Prints(string text, BOOL endLine = FALSE, const ConsoleFormat* color = NULL, SHORT x = -1, SHORT y = -1);
Example usage:
ConsoleCore::GetInstance()->Prints("Hello World!",TRUE,NULL,0,0);
ConsoleCore::GetInstance()->Prints("Console output is fun!");
The code above will output...
Hello World!
Console output is fun!
... and the console cursor would be left after the second exclamation mark.
How do I Print Numbers
Printing numbers is identical to printing string
s except you pass a number.
void Printn(DWORD number, BOOL endLine = FALSE, ConsoleFormat* color = NULL, SHORT x = -1, SHORT y = -1);
This library also provides a function for printing double precision numbers. Again, it is similar to printing string
s and integers but you must take extra care when using it. It uses _gcvt_s
to convert the double
into a string
. However, that function will also cause the exponential notation to be appended to the end of the string
conversion if necessary. The signature is explained below, and the meaning of the digits
parameter is further explained after.
void Printd(DOUBLE number, int characterLength, BOOL endLine = FALSE, ConsoleFormat* color = NULL, SHORT x = -1, SHORT y = -1);
FPUs can be fickle. Sometimes you get more precision and sometimes you don't. The double
to string
conversion
may or may not add exponents based on the value you give. Here are a
few examples and their outputs, note how in the last example, digits
is 4
, but the actual length of the converted string
is larger:
ConsoleCore::GetInstance()->Printd(2.3449L,2); ConsoleCore::GetInstance()->Printd(2.39L,2); ConsoleCore::GetInstance()->Printd(1.001L,4); ConsoleCore::GetInstance()->Printd(12000L,4);
If you plan on working with large numbers, plan on the possibility of
the output taking up to 5 more characters in width than the number
itself. The value of a double ranges from 2.2250738585072014 E – 308 to
1.7976931348623158 E + 308 according to MSDN 2005 documentation. To get
one of those extremes to print, you'd need to specify digits to be
atleast 20, but it would still take 25 characters to print it with the
exponent appended... then again you could always convert the number to a
string
yourself and format it however you see fit before printing it with Prints()
.
How do I Use ConsoleFormat
The ConsoleFormat
encapsulates the data in context
of output. Technically, 4 bits are for the foreground color, which is
the character being displayed, and 4 bits are for the background for a
total of 8 bits. The last 3 bits for both represent the Red, Green and
Blue components and 1st bit represents the Alpha or Intensity
(brightness).
There are several different ways to manipulate a ConsoleFormat
object:
The simplest way is to create a format based on the bitwise OR
of predefined common colors from the ConsoleFormat::Colors enum
. You can do this at construction or through the ConsoleFormat::Color()
mutator:
ConsoleFormat ccf = CONSOLECHARFORMAT::BRIGHTBLUE | CONSOLECHARFORMAT::ONYELLOW;
ConsoleFormat ccf2(CONSOLECHARFORMAT::BRIGHTBLUE | CONSOLECHARFORMAT::ONYELLOW);
This creates a format which uses bright blue for the text color, and yellow as the background color. You can also bitwise OR ConsoleFormat
objects together, or invoke the unary invert method to get the logically opposite color:
Sometimes you want a color that can't be created out of the predefined colors in the enum
. ConsoleFormat
provides you with facilitators to help you set specific color bits in the bitset without having to know STL. The ConsoleFormat
contains methods which allow you to get
or set
the value of each bit. The signature of the method which sets the bits is void ConsoleFormat::Set(Bit bit, bool value)
. The value is assigned to the corresponding bit unless you used some vlaue that doesn't exist in ConsoleFormat::Bit
. The value of bit
determines whether the value applies to the foreground
or the background
. Additionally, there is the signature bool ConsoleFormat::Get(Bit bit)
for retrieving the value of any bit.
Here are two ways of creating the same color:
ConsoleFormat cf = ConsoleFormat::SYSTEM; ConsoleFormat cf2;
cf2.Set(ConsoleFormat::FRONT_RED,TRUE);
cf2.Set(ConsoleFormat::FRONT_GREEN,TRUE);
cf2.Set(ConsoleFormat::FRONT_BLUE,TRUE);
ConsoleFormat
also contains several conversion constructors as well as a conversion from a unsigned char
.
How do I Format Output
Here's how to write a string
or number to the screen using your custom ConsoleFormat
:
ConsoleFormat cfString, cfNumber;
... ConsoleCore::GetInstance()->Prints("Hello World!",TRUE,&cfString);
ConsoleCore::GetInstance()->Printn(1024,TRUE,&cfNumber);
Sometimes you may want to print a string
, but have some portions colored differently from others. One way to do that is to create multiple ConsoleFormat
s and print each section explicitly like this:
ConsoleFormat red, green, blue;
... ConsoleCore::GetInstance()->Prints("Red ",FALSE,&red);
ConsoleCore::GetInstance()->Prints("Green ",FALSE,&green);
ConsoleCore::GetInstance()->Prints("Blue",FALSE,&blue);
That can quickly become a pain. I gave the ConsoleCore
the ability to parse text passed to Prints for just that reason. Instead of writing the code above, you can do this...
cConsoleCore::GetInstance()->Prints("$004Red $002Green $001Blue");
... and get the same result.
Facilitators of ConsoleCore
scan your string
and pick up all of the $###
portions. The format that was retrieved is used to color all text up until the next $###
format code. Unfortunately this does not work with Printn()
, as it takes a DWORD
and not a string
. Codes must be exactly 3 digits in length and begin with '$'
.
You should also be careful when including numbers within your string
while trying to take advantage of this feature:
ConsoleCore::GetInstance()->Prints("I have $$002000 dollars!")
... displays "I have $000 dollars!" with "000 dollars!" in green on black format. The following might be unexpected as well:
console.Prints("I have $$2000 dollars!")
... which displays "I have $0 dollars!" with "$000 dollars!" in grey on red format.
If the intent were to write "I have $2000 dollars!" to the screen with "$2000" in green on black format, and everything after that in standard white on black, the effective way to do this would be:
ConsoleCore::GetInstance()->Prints("I have$002 $$0022000 $007dollars!",TRUE);
How do I Read a String from the Keyboard
The ScanString()
method does just that, and its signature looks like this:
void ScanString(COORD origin, char* buffer, UINT maxLength);
How do I Read a Number from the Keyboard
The ScanNumber()
method does just that. It does not
allow the user to enter any characters other than a digit or the minus
sign, and the minus sign can only be entered as the first character. It
optionally allows you to specify the maximum number of digits allowed.
You can specify more than 10 digits but a DWORD
only needs 10 digits to represent its maximum or minimum value:
void ScanNumber(COORD origin, LPDWORD lpNumber, int digitCount = 10);
You can also read double
s from the keyboard with this library using ScanDouble()
which is explained below:
void ScanDouble(COORD origin, DOUBLE* pDouble);
You can enter a double
in various formats, e.g. "1.01",
"0.1", ".1", even "1243.2e+001" and "23.09e-203". If you are going to
use E during input, it must be followed by + or -, or you will not be
permitted to input the exponential portion. And the function is case
insensitive.
How do I Draw a Character Box
CharacterBox
and CharacterWindow
are objects that represent ASCII boxes.
CharacterBox
has two COORD
structures which define the upper left (also the origin) and the lower right corners. It also has two ConsoleFormat
members which are used to define the coloring of the border and client (inside) portions. You can also specify the character that is used to draw the outline. This character is the fill character. A box can draw itself using the Draw
method. The CharacterBox
also contains a static Draw
method that will make a temporary box and draw it. The only difference is that the static
version will always have a ConsoleFormat::BLACK
client area and uses the ConsoleFormat::SYSTEM
color for the border. Instances of CharacterBox
will automatically normalize their corners. This normalization ensures that the corners are within the 80x25 character dimensions of a console window. However, this method does not ensure that the upper left corner truely is above and to the left of the bottom right corner.
The CharacterWindow
class is a child of CharacterBox
. It has an extra string
member for the title and it varies in how it draws. It creates two instances of the parent class when drawn. One is used to draw the top box which is 1 line tall and will have the title drawn in it. The second box is the client area. The color formatting is essentially the same, except the window's title is drawn using the client format. If you create your own CharacterWindow
object you should take care that its width is at least the title string
's length + 2. If you don't, the title may draw outside of the box.
Here is how you draw a box that encompasses the entire screen using the static method:
COORD upperLeft,
lowerRight;
char fill = '*';
upperLeft.X = upperLeft.Y = 0;
lowerRight.X = MAXSCREENX - 1;
lowerRight.Y = MAXSCREENY - 1;
CharacterBox::Draw(upperLeft,lowerRight,fill);
Of course, you may find yourself wanting to draw a box with a
specific formatting for both the border and client area, and reuse it in:
CharacterBox box(upperLeft,lowerRight,ConsoleFormat::BRIGHTWHITE,~ConsoleFormat::BRIGHTWHITE);
box.Draw();
How do I Draw a Window Character Box with/without a Title
Here's an example of using a CharacterWindow
object. Notice how the dimensions specified apply to the ENTIRE area of the box, not just the client area.
ConsoleCore* pCore = ConsoleCore::GetInstance();
char* title = "example";
COORD ul = {31,0}
,br = {37,6};
ConsoleFormat client = ConsoleFormat::BRIGHTYELLOW
,old = pCore->Color();
CharacterWindow window(ul,br,title);
window.ClientColor(client);
window.Draw();
ul.X = 39;
br.X = ul.X + strlen(title) + 2;
window.UpperLeft(ul);
window.LowerRight(br);
window.Draw();
And the output looks something like this:
...... .........
.example .example.
...... .........
. . . .
. . . .
...... .........
CharacterWindow
will not try to center the title text. If you desire that, you may find drawing two boxes and then the title, each manually, will work.
How do I Create a Menu
One of the most annoying things about console programming is menus.
Without direct contact with the screen, your options are rather limited.
Here is one example of what we might do to create a console menu:
char ch;
do
{
printf("Choose an option\n");
printf("(C)reate\n");
printf("(M)odify\n");
printf("(Q)uit\n");
scanf("%c",&ch);
ch = tolower(ch);
switch(ch)
{
case 'c':
{...}break;
case 'm':
{...}break;
default: break;
}
}
while(ch != 'q');
Also, whether you choose to use stdio
or STL iostream
you
have to "baby sit" the user in case they give you screwy data. We all
know what happens when you don't check your streams... excess data falls
through to the next input and the whole application decides to go
bananas.
JLib
makes console menus an intelligent, elegant (as
elegant as a console app can be) experience and using them is a breeze.
They start with user defined ConsoleMenuItem
s. A ConsoleMenuItem
is a text label with a value. When creating your menus you specify the
origin, the color format for the currently selected item, and the color
format for all other items. All of the menus in JLib
offer several methods
for inserting and appending menu items. Menus also provide the ability to remove
all items.
For the most part, menus in JLib
are idiot proof from
the user's perspective. Because only arrow, enter, and escape keys are
used to make a selection, it is impossible to get bad data back unless your menu was created with no items. The
user presses up or down to move to a different menu item and presses
enter to make a selection. Also, if the user presses the escape key, a
value defined as USERESC
is returned. It's provided to you as an alternative method to detect
whether a user wanted to "cancel" making a selection or that wanted to dismiss the menu.
There are three types of menus: ConsoleMenu
, ScrollingMenu
, and WindowedMenu
.
The last type has two modes which are scrollable and non-scrollable. Here are examples of creating each type:
COORD origin = {31,0};
ConsoleFormat ccfRed = ConsoleFormat::BRIGHTRED
,ccfOld;
ConsoleMenu menu(origin);
menu.Append("Menu Item 1",0);
menu.Append("Menu Item 2",0);
menu.Append("Menu Item 3",0);
ScrollingMenu menu(origin,4,ccfRed,~ccfRed);
menu.Append("Menu Item 1",0);
menu.Append("Menu Item 2",1);
menu.Append("Menu Item 3",2);
menu.Append("Menu Item 4",3);
menu.Append("Menu Item 5",4);
menu.Append("Menu Item 6",5);
menu.Append("Menu Item 7",6);
menu.Append("Menu Item 8",7);
COORD origin = {31,0};
WindowedMenu menu(origin,4,"Wnd Menu");
menu.WindowColor(ConsoleFormat::BRIGHTBLUE);
menu.ClientColor(ConsoleFormat::ONYELLOW);
menu.Append("Menu Item 1",0);
menu.Append("Menu Item 2",1);
menu.Append("Menu Item 3",2);
menu.Append("Menu Item 4",3);
menu.Append("Menu Item 5",4);
menu.Append("Menu Item 6",5);
menu.Append("Menu Item 7",6);
menu.Append("Menu Item 8",7);
menu.Scrollable(FALSE);
menu.Scrollable(TRUE);
How do I Use a Menu
All menus have a method called Show()
. Here is an example of generic usage:
switch(menu.Show())
{
case USERESC:
break;
case BADMENU:
break;
default:
ConsoleMenuItem selection = menu.SelectedItem();
break;
}
You must take care when working with scrollable menus. One of the members of these types of menus
is the amount of items that are visible at one time. If you set this value greater than or equal to the
number of items in the menu then the behavior of Show()
is undefined. In the case of a
WindowedMenu
, if you have set Scrollable(FALSE)
then you don't have to
worry about this problem.
That's all there is to it. Each type of menu draws and behaves slightly different but
implemented basic polymoprhism in the Show()
method. Also, take advantage of the fact
that ConsoleMenuItem
s have user defined values associated with them that are the size
of a DWORD
. You could design a system where you treat those values as addresses!
How do I Save/Load/Clear Portions or All of the Screen
ConsoleCore
has a couple of handy methods for these situations. You can clear the entire screen by using ClearScreen()
, which wipes out all text and formatting. This also puts the cursor back in the upper left corner.
From time to time, you may find it useful to save some or all of the screen in order to use it later. The function SaveScreen()
does this and it has two overloads. Their signatures look like this:
void SaveScreen();
void SaveScreen(PCHAR_INFO buffer, COORD bufferSize, COORD saveOrigin);
Here are some important things to note about the second signature.
You are responsible for allocating the appropriate memory for buffer
as well as destroying it. Allocate it as a single dimension array with
enough elements to encompass the width and height of the area you want
to capture. The low level functions treat it as a 2D array and you must
provide the height and width. If you wonder why bufferSize
is a COORD
, rather than a SIZE
, structure, well that's what the low level API function wanted for the argument and I'm not going to fight it.
Once you've saved your screen, or portions, you can load them back on to the screen using LoadScreen()
which also has two overloads similar to the related save functions:
void LoadScreen();
void LoadScreen(PCHAR_INFO buffer, COORD bufferSize, COORD loadOrigin);
Saving and loading the screen are used internally by the provided menus
to prevent some of the flicker that occurs when scrolling a
menu. They may or may not be useful but they are provided for those who
can think of other reasons to use them. For an example of saving and
loading portions of the screen, refer to the CustomBuffer()
function in the testharness.cpp of the source.
History
- 1st April, 2008
- 3rd April, 2008
- Fixed screen position problems with the demo app
- 4th April, 2008
- Menus
- Using menus no longer modifies the internal buffer that is used by the parameterless versions of
SaveScreen()
and LoadScreen()
- Implemented saving/loading of portions of the screen which has greatly reduced flickering during menu selection
- Removed redundancies in
WindowedMenuBox
and WindowedScrollMenuBox
- Input
- Improved
ScanNumber
. It no longer accepts invalid characters and can be restricted to only allowing a specified number of digits to be entered. Double
s can now be read from the keyboard using ScanDouble()
- Output
Double
s can now be written to the screen using Printd()
- 14th April, 2008
- Output
Prints()
now functions correctly while containing $### smart coloring regardless of whether the string is const
- 5th August, 2011
- Output
- Fixed the $### formatting once and for all. Now it should work 100% of the time.
- Library
- Destroyed
JLib
. Broke it up into smaller parts and distributed the work load to other objects.
ConsoleCore
is now responsible for writing simple data and reading input from the keyboard.
ConsoleFormat
replaced CONSOLECHARFORMAT
.
- Added
ConsoleMenuItem
, ConsoleMenu
, ScrollingMenu
, and WindowedMenu
.
- Added
CharacterBox
and CharacterWindow
.
ConsoleCore
is no longer responsible for the creation or destruction of these new objects.