| . | . |
| . | ![]() |
A Win32 console app is not a DOS box! | . |
| . | . |
| . |
Win32 Console Applications - Part 6.In this part of the tutorial, I want to discuss the "size" of the console. To do this, there are in fact, 2 items that need to be discussed. First is the actual window that contains the console, and secondly, there is the grid of character cells called the console's "screen buffer" that I described right back in part 1 of tutorial. To demostrate the inter-relation between the two, open a console, any of the programs in the tutorial so far will do. Place your mouse pointer in the on the bottom right corner of the console window and try to resize the window. Note that if you try to make the window smaller than it was, you can do so, at the same time, notice that the window does not resize smoothly, rather it moves in jumps. Note that if there were no scroll bars present before, they appear as necessary. If you try to make it wider than it was, (i.e. any wider than it is when the horizontal scroll bar disappears), it doesn't work.Depending on the defaults applied by the version of Windows you are using, you will probably be able to make the window taller to a certain extent, (on most system, the default is taller than the screen so there is a practical limit to the exercise). The reason for these observed behaviours is the tight relationship between the window and screen buffer sizes. The window shows a grid of character cells. The reason the window re-sizes in jumps rather than smoothly is because it will not show part cells, resize events smaller than one cell, (width or height), are ignored. It is possible for the window to show less than the whole screen buffer, (indeed, that is normally the case), thus you can make the window smaller. You cannot try to make the window display an area larger than the screen buffer, (width or height), hence, you cannot always make the window larger. Clearly, the screen buffer size is important, but how big is it? Windows provides an API routine to obtain it. This little program shows how to get this information.
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hOut;
CONSOLE_SCREEN_BUFFER_INFO SBInfo;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hOut,
&SBInfo);
cout << "Screen Buffer Size : ";
cout << SBInfo.dwSize.X << " x ";
cout << SBInfo.dwSize.Y << endl;
return 0;
}
The output on my system looks like this.
The function GetConsoleScreenBufferInfo() takes the handle to the output buffer, and a pointer to a CONSOLE_SCREEN_BUFFER_INFO structure. The dwSize member of the structure is a COORD which is filled with the X and Y dimensions of the screen buffer. On my system, it shows 80 x 300, i.e. there are 80 character cells in the 'X' direction, (horizontally), and 300 in the 'Y' direction, (vertically), thus it tells me I have 300 lines, each 80 characters long. Your system default might not be the same, you may need to alter some of the values in the following programs if your screen buffer size is different. Just a reminder, the screen buffer is zero based, i.e. the top left cell is [0,0], thus my screen buffer's top left is [0,0] and bottom right is [79,299] Let's have a quick look at the other members of SBInfo. dwCursorPosition is another COORD, and returns the coordinates of the cell currently holding the cursor. wAttributes is a 16bit buffer which returns the current foreground and background colour attributes discussed in part 4 of the tutorial. The other 2 members are srWindow and dwMaximumWindowSize, we'll be using those later, so I won't discuss them here. Next, we'll do programmatically the exercise you did earlier, i.e. we'll move the "bottom right corner around". Here's the program.
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hOut;
CONSOLE_SCREEN_BUFFER_INFO SBInfo;
SMALL_RECT DisplayArea = {0, 0, 0, 0};
int x;
int y;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hOut,
&SBInfo);
x = SBInfo.srWindow.Right;
y = SBInfo.srWindow.Bottom;
DisplayArea.Bottom = y;
while (x > (SBInfo.srWindow.Right / 2))
{
--x;
DisplayArea.Right = x;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
Sleep(50);
}
while (y > (SBInfo.srWindow.Bottom / 2))
{
--y;
DisplayArea.Bottom = y;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
Sleep(50);
}
while (x != SBInfo.srWindow.Right)
{
++x;
DisplayArea.Right = x;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
Sleep(50);
}
while (y != SBInfo.srWindow.Bottom)
{
++y;
DisplayArea.Bottom = y;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
Sleep(50);
}
return 0;
}
I won't show a picture of the output, like many of the examples in this part of the tutorial, things are moving around thus a simple .jpg does not show what is happening. I recommend you compile and run it. What you should see is the console window starts at a certain size, shrinks first horizontally, then vertically to half the size it was, the expands again, first horizontally then vertically until it is back to the size it was to start with. Let's go through it.
x = SBInfo.srWindow.Right;
y = SBInfo.srWindow.Bottom;
The first thing we do is call GetConsoleScreenBufferInfo() as before. This time we use the srWindow member. This is a SMALL_RECT structure containing 4 short intergers called, Top, Left, Bottom and Right. In this case, the function returns a SMALL_RECT containing the rectangular region of the screen buffer which is currently being displayed within the window, (on my system the area initially is 0,0 24,79 i.e. 25 lines each 80 characters long). I store the Bottom and Right members of the structure for later.
DisplayArea.Bottom = y;
while (x > (SBInfo.srWindow.Right / 2))
{
--x;
DisplayArea.Right = x;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
Sleep(50);
}
At the top of the program, I declared another SMALL_RECT structure called DisplayArea. I've done this because the routine we will use to change the size of the window does not, actually, want to know how big the window should be! Instead, it wants to know which part of the screen buffer it should show. There are 2 way of doing this, absolute or relative. In this program, we will use absolute specification. With absolute specification, we use a SMALL_RECT structure to indicate the top left, and bottom right corners of the screen buffer area we want to show. The window then adjusts itself to show that region. All members of the DisplayArea structure were initialised to zero, thus Top and Left are already correct, but I need to manually set the Bottom member to be equal to the initial height of the window. I then loop, decrementing x, updating the DisplayArea structure and call SetConsoleWindowInfo(). The first parameter is the output handle. The next is a boolean parameter, in this case I have set it TRUE meaning that the DisplayArea structure contains absolute co-ordinates within the screen buffer as described above. Finally, I pass a pointer to the DisplayArea structure, and the function reduces the width by one character cell. The loop continues until the width has been halved. The Sleep() in the loop just slows it down so you can see what is happening. The other 3 loops are basically the same as this one, and reduce the height by half in character cell steps, then expand the window again to it's original size. When using SetConsoleWindowInfo(), remember, you cannot ask the window to display non-existant parts of the screen buffer! If you ask it to display negative values, (in abosolute mode), or greater than the screen buffer width or length, the call will fail and return FALSE. Remember, for the sake of clarity, I do not check the return status of API routines in the examples, you should always do so, even if you don't know how to correct the fault, it is useful to know which routine failed and if possible, why. In this next program, we'll use both absolute and relative specification to produce a program which scrolls text up, down, left, right and even diagonally. Here's the source.
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hOut;
CONSOLE_SCREEN_BUFFER_INFO SBInfo;
SMALL_RECT DisplayArea = {5, 5, 0, 0};
SMALL_RECT ScrollRight = {-1, 0, -1, 0};
SMALL_RECT ScrollDown = {0, -1, 0, -1};
SMALL_RECT ScrollLeft = {1, 0, 1, 0};
SMALL_RECT ScrollUp = {0, 1, 0, 1};
int Lines;
int Characters;
int Random;
int i;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hOut,
&SBInfo);
DisplayArea.Bottom = SBInfo.srWindow.Bottom - 5;
DisplayArea.Right = SBInfo.srWindow.Right - 5;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
srand((unsigned)time(NULL));
for (Lines = 0; Lines < (SBInfo.srWindow.Bottom - 1); Lines++)
{
for (Characters = 0; Characters < 80; Characters++)
{
Random = ( rand() % 32);
if (Random > 25)
{
cout << " " << flush;
}
else
{
cout << (char)('a' + Random) << flush;
}
}
}
Sleep(250);
for (i=0; i<5; i++)
{
SetConsoleWindowInfo(hOut,
FALSE,
&ScrollRight);
Sleep(100);
}
Sleep(250);
for (i=0; i<5; i++)
{
SetConsoleWindowInfo(hOut,
FALSE,
&ScrollDown);
Sleep(100);
}
Sleep(250);
for (i=0; i<5; i++)
{
SetConsoleWindowInfo(hOut,
FALSE,
&ScrollLeft);
Sleep(100);
}
Sleep(250);
for (i=0; i<5; i++)
{
SetConsoleWindowInfo(hOut,
FALSE,
&ScrollUp);
Sleep(100);
}
Sleep(250);
for (i=0; i<5; i++)
{
SetConsoleWindowInfo(hOut,
FALSE,
&ScrollRight);
SetConsoleWindowInfo(hOut,
FALSE,
&ScrollDown);
Sleep(100);
}
Sleep(250);
DisplayArea.Top = 0;
DisplayArea.Left = 0;
DisplayArea.Bottom = SBInfo.srWindow.Bottom;
DisplayArea.Right = SBInfo.srWindow.Right;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
return 0;
}
Again, I won't try to show the output, scrolling text just doesn't show up on a .jpg. What you should see is a screen of pseudo-text scroll to the right, then down, then left, then up and finally diagonally down and to the right. Lets go through the code.
GetConsoleScreenBufferInfo(hOut,
&SBInfo);
DisplayArea.Bottom = SBInfo.srWindow.Bottom - 5;
DisplayArea.Right = SBInfo.srWindow.Right - 5;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
I get the window size information as before, set up the Bottom and Right members of the DisplayArea structure, (Top and Left were set up at initialisation), then use absolute specification to drop the window size down by 5 in all directions.
srand((unsigned)time(NULL));
for (Lines = 0; Lines < (SBInfo.srWindow.Bottom - 1); Lines++)
{
for (Characters = 0; Characters < 80; Characters++)
{
Random = ( rand() % 32);
if (Random > 25)
{
cout << " " << flush;
}
else
{
cout << (char)('a' + Random) << flush;
}
}
}
All this does is fill the console window with pseudo-text, random data that vaguely looks like text. Note the extra #includes at the top of the program, necessary for the time and random number generator to work.
for (i=0; i<5; i++)
{
SetConsoleWindowInfo(hOut,
FALSE,
&ScrollRight);
Sleep(100);
}
Sleep(250);
This loop uses relative mode to shift the screenbuffer area around, without changing the size of the window. Note the second parameter is now FALSE, this tells the function that the SMALL_RECT structure at parameter 3 holds relative values, not absolute. The ScrollRight structure, (initialised at the top of the program), has the Left and Right members set to -1. When we call SetConsoleWindowInfo() in relative mode with this structure, it subtracts 1 from whatever the current value of Left and Right are and displays the coresponding area of the screen buffer, effectively scrolling the screen buffer one character to the right in a fixed window, (alternatively, you could say it is scrolling the window one character to the left over a fixed screen buffer - it amounts to the same thing). I've used -1 in the structure, this is, of course, not essential, if I'd have said -2 for example, then the screen buffer would appear to scroll two characters at each iteration of the loop. The next 3 loops do the same thing using different structures to give different directions. The last loop calls the routine twice, once to move right, and once down, this shows a diagonal scrolling. It would be possible, of course, to use another version of the SMALL_RECT structure with appropriate values, i.e. have a ScrollRightDown structure, in that case, only 1 call would be required to effect the diagonal scroll. As before, there are numerous Sleep() calls all over the program, these simply slow things down so you can see it working.
DisplayArea.Top = 0;
DisplayArea.Left = 0;
DisplayArea.Bottom = SBInfo.srWindow.Bottom;
DisplayArea.Right = SBInfo.srWindow.Right;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
Finally, the program uses another absolute mode call to restore the window to it's full size. The screen buffer size has limited us in everything we have done so far. Let's change the size of the screen buffer so we can make things bigger instead of smaller. This first little program shows how to do it, and makes an important point. Here is the code.
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hOut;
CONSOLE_SCREEN_BUFFER_INFO SBInfo;
COORD NewSBSize;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hOut,
&SBInfo);
NewSBSize.X = SBInfo.dwSize.X + 5;
NewSBSize.Y = SBInfo.dwSize.Y;
Sleep(2000);
cout << "Increasing now" << endl;
SetConsoleScreenBufferSize(hOut,
NewSBSize);
Sleep(2000);
NewSBSize.X = SBInfo.dwSize.X;
cout << "Decreasing now" << endl;
SetConsoleScreenBufferSize(hOut,
NewSBSize);
return 0;
}
The program waits 2 seconds, increases the screen buffer size and waits another 2 seconds then returns it to it's original size again. It is so simple, I won't bother going through it, you simply pass a COORD to the function which indicates the new bottom right value. What is important to note, is that increasing the screen buffer size does not automatically increase the window size - they are related, but seperate things. What you might notice is a horizontal scroll bar appear, then disappear again. If you try to make the screen buffer smaller than the window size, the call will fail. How big can the window be? The first program I showed in this section reported the size of my screen buffer, (it was 80 x 300). My screen is easily wide enough to display an 80 character wide console, but not large enough to show 300 lines, (that would be some screen!). Instead, windows places a scroll bar to the right of the console that allows me to scroll through the screen buffer vertically. Similaly, in the last program, when the screen buffer was wider than the window could show, a scroll bar appeared at the bottom of the console window so I could scroll horizontally through the screen buffer. Ultimately, the screen buffer could be wider as well as longer than the screen can show, but you cannot create a window larger than the screen. What you need to know, is what is the largest possible window given the screen size and font. We actually have a partial answer to that already. The last remaining member of the CONSOLE_SCREEN_BUFFER_INFO structure we have been using throughout is dwMaximumWindowSize, another COORD.
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hOut;
CONSOLE_SCREEN_BUFFER_INFO SBInfo;
COORD NewSBSize;
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(hOut,
&SBInfo);
cout << "Maximum X : " << SBInfo.dwMaximumWindowSize.X << endl;
cout << "Maximum Y : " << SBInfo.dwMaximumWindowSize.Y << endl;
return 0;
}
The output looks like this.
What that is telling me is that given the screen buffer size, the physical screen size, the screens resolution and the font, I can make the window 80 characters wide, (we knew that already, that is the width of the screen buffer), and 81 lines high, (I use a high res 17" screen). The values for your system might not be the same of course. Now, I know I can make the screen buffer wider, but how wide a window can I make? Happily, there is a function give you this information. This next program retrieves the maximum size possible, then increases the screen buffer and window sizes to practically fill the screen.
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hOut;
COORD NewSBSize;
SMALL_RECT DisplayArea = {0, 0, 0, 0};
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
NewSBSize = GetLargestConsoleWindowSize(hOut);
SetConsoleScreenBufferSize(hOut,
NewSBSize);
DisplayArea.Right = NewSBSize.X - 1;
DisplayArea.Bottom = NewSBSize.Y - 1;
SetConsoleWindowInfo(hOut,
TRUE,
&DisplayArea);
return 0;
}
Notice that GetLargestConsoleWindowSize() returns a COORD, which contains the largest window size that will fit on the screen. I then set the screen buffer to this size, and increase the window size to show it, (remember the screen buffer is zero based so it is necessary to subtract 1 from the values returned). The last thing I want to cover in this section is "going full screen". The last program showed how to produce a screen sized window, it covered the screen, (you will probably have to move it to do so, but it does), and still retained the Minimise, Maximise and Close boxes, the system menu box and the resizing capabilities. There are those for whom that is not enough, they want the console area to occupy the whole screen. This has always struck me as a little odd. Why would you want your program to look like an "old fashioned" dumb terminal? However, it is asked often enough that I shall attempt to answer it. As with almost everything in Windows, there are several ways to do this, some more generally portable than others - ultimately, there is no single surefire way to do this for all current and future versions of Windows. One common starting point for this task is to look at Windows's own way of expanding a console to full size. Open a console, (most of the programs in the tutorial will do, anything that doesn't close quickly), and press Alt-Return or Alt-Enter. The console goes to "full screen", press the same combination again, and it returns to it's window. The method I'll use works from Win-95 to Win-XP which covers most eventualities at this time. It does, however, have a couple of problems. It uses an API function called keybd_event() which simulates keypresses. One of the problems with this function is the second parameter. If you read the current MSDN library entry, here, it tells you that this parameter is "Not Used". Previously, it would have told you that the required value here was the hardware scan code for the key to be simulated. In fact, if you do not supply this information, the function does not work reliably! Now the next problem, the scan code is not the same for all keyboard/language variants. The example I show uses common scan code values, but I stongly recommend you that you check these are correct. The program below is a modified version of the first program from part 5 of the tutorial, compile and run it, then press the alt key and note the scan code, and the enter key and note it's value. If they differ from the code further down, edit the code, do not try to run the code with incorrect scan codes, doing so amounts to randomly pressing keys which could damage your system. The ScanCode is output on the top line. To exit the program, press the 'x' key.
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hIn;
HANDLE hOut;
COORD KeyWhere;
COORD MouseWhere;
COORD EndWhere;
bool Continue = TRUE;
int KeyEvents = 0;
int MouseEvents = 0;
INPUT_RECORD InRec;
DWORD NumRead;
hIn = GetStdHandle(STD_INPUT_HANDLE);
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
cout << "Key Events : " << endl;
cout << "Mouse Events : " << flush;
KeyWhere.X = 15;
KeyWhere.Y = 0;
MouseWhere.X = 15;
MouseWhere.Y = 1;
EndWhere.X = 0;
EndWhere.Y = 3;
while (Continue)
{
ReadConsoleInput(hIn,
&InRec,
1,
&NumRead);
switch (InRec.EventType)
{
case KEY_EVENT:
++KeyEvents;
SetConsoleCursorPosition(hOut,
KeyWhere);
cout << KeyEvents << " ScanCode : ";
cout << hex << InRec.Event.KeyEvent.wVirtualScanCode << flush;
if (InRec.Event.KeyEvent.uChar.AsciiChar == 'x')
{
SetConsoleCursorPosition(hOut,
EndWhere);
cout << "Exiting..." << endl;
Continue = FALSE;
}
break;
case MOUSE_EVENT:
++MouseEvents;
SetConsoleCursorPosition(hOut,
MouseWhere);
cout << MouseEvents << flush;
break;
}
}
return 0;
}
This next short program declares a function AltEnter() which simulates the Alt-Enter key combination. I wait, call the function, wait, call the function again then exit. What you should see is the console starts normal, goes to full screen then comes back again. The VK_MENU key represents the Alt key, VK_ENTER the Enter or Return key.
#include <windows.h>
#include <iostream>
using namespace std;
void AltEnter(void);
int main()
{
Sleep(5000);
AltEnter();
cout << "Full screen." << endl;
Sleep(10000);
AltEnter();
cout << "Back again." << endl;
return 0;
}
void AltEnter()
{
keybd_event(VK_MENU,
0x38,
0,
0);
keybd_event(VK_RETURN,
0x1c,
0,
0);
keybd_event(VK_RETURN,
0x1c,
KEYEVENTF_KEYUP,
0);
keybd_event(VK_MENU,
0x38,
KEYEVENTF_KEYUP,
0);
return;
}
You will probably see a "white blob" on the screen somewhere, that is the mouse cursor. Another important thing to note is that changing to/from full screen is not instantaneous, the time taken to do so depends on your graphics card and screen, you'll need to experiment with that. After you have set the scan code for your system, if you try to run your code on a system with a radically different keyboard/language your code may not work and may produce unpredictable/undesirable results. A final problem is with the function itself. keybd_event() is a deprecated function, i.e. one whose functionality has been replaced by another function, in this case, by SendInput(). The newer function is a lot more flexible, but does not work on Win-95 or early Win-98 systems. It is widely rumoured that a lot of deprecated functions will not be supported by Longhorn, (the next version of Windows), if this turns out to be true, this code may no longer work, should I discover this to be the case, I'll update this tutorial with alternative methods. There are many ways this can be done, but they all suffer from some problem or another. There is a registry hack, but the key is not in the same place on all OS versions. There is a handle hack, but the values needed are not the same on all OS versions. It goes on I'm afraid. Let it run in a Window! |
. |
| . | . |
| . | . |
| . | . |