C++ Q&A
Color Support, Console Apps, and Saving User Settings
Paul DiLascia
Code download available at: CQA0402.exe (619 KB)
Browse the Code Online
Contents
Update
Q I read your July 2001 column on how to create 256-color toolbars. However, I heard that Windows® 95 doesn't support 256-color toolbars unless you have Microsoft® Internet Explorer 3.0 or later. In these cases, the app should use a default 16-color toolbar. How can I determine if a 256-color or a high-color toolbar is supported.
Q I read your July 2001 column on how to create 256-color toolbars. However, I heard that Windows® 95 doesn't support 256-color toolbars unless you have Microsoft® Internet Explorer 3.0 or later. In these cases, the app should use a default 16-color toolbar. How can I determine if a 256-color or a high-color toolbar is supported.
Bayland Park
A I love it—this is 2004 and I'm still getting questions about Windows 95! There's an easy way to find out how many colors the screen or any window supports. Just get the handle to the device context (HDC) for the window or screen, call GetDeviceCaps (get device capabilities) to get the number of color planes (PLANES) and bits per pixel (BITSPIXEL), and multiply these to get the total number of bits in a pixel:
CWindowDC dc(NULL);
int nPlanes = dc.GetDeviceCaps(PLANES);
int nBitsPixel = dc.GetDeviceCaps(BITSPIXEL);
int totalBits = nPlanes * nBitsPixel;
Usually the number of color planes is 1 and the number of bits per pixel is 8 for a 256-color system or 16, 24, or 32 for high-color systems. GetDeviceCaps can get lots of useful information, such as the logical number of pixels per inch, the aspect ratio, raster capabilities, and other information. I wrote a little program, ColorBits (see Figure 1), to show this. You can download it from the link at the top of this article.
A I love it—this is 2004 and I'm still getting questions about Windows 95! There's an easy way to find out how many colors the screen or any window supports. Just get the handle to the device context (HDC) for the window or screen, call GetDeviceCaps (get device capabilities) to get the number of color planes (PLANES) and bits per pixel (BITSPIXEL), and multiply these to get the total number of bits in a pixel:
CWindowDC dc(NULL);
int nPlanes = dc.GetDeviceCaps(PLANES);
int nBitsPixel = dc.GetDeviceCaps(BITSPIXEL);
int totalBits = nPlanes * nBitsPixel;
Usually the number of color planes is 1 and the number of bits per pixel is 8 for a 256-color system or 16, 24, or 32 for high-color systems. GetDeviceCaps can get lots of useful information, such as the logical number of pixels per inch, the aspect ratio, raster capabilities, and other information. I wrote a little program, ColorBits (see Figure 1), to show this. You can download it from the link at the top of this article.
Figure 1** ColorBits **
Q I've seen programs that can run as either a console app or as a Windows-based app. That is, if you type the name of the program from the command prompt, it runs as a normal Windows-based app, but if you type a command-line option like "-batch", it runs in batch mode as a console app, with all output directed to the console. How does this work?
Q I've seen programs that can run as either a console app or as a Windows-based app. That is, if you type the name of the program from the command prompt, it runs as a normal Windows-based app, but if you type a command-line option like "-batch", it runs in batch mode as a console app, with all output directed to the console. How does this work?
Joe Tadmann
A I can't tell you how other apps do it because in Windows there's always more than one way to skin the operating system. But I can show you one way that works—the way Visual Studio® .NET does it. You may not have noticed, but you can run Visual Studio .NET by typing devenv at a command prompt, in which case Windows launches the GUI app. But if you type devenv with a command-line switch such as -? for help or -build to build your project, it runs in the console without a user interface. For example
devenv -build MyProject.sln
builds the solution file MyProject.sln.
A I can't tell you how other apps do it because in Windows there's always more than one way to skin the operating system. But I can show you one way that works—the way Visual Studio® .NET does it. You may not have noticed, but you can run Visual Studio .NET by typing devenv at a command prompt, in which case Windows launches the GUI app. But if you type devenv with a command-line switch such as -? for help or -build to build your project, it runs in the console without a user interface. For example
devenv -build MyProject.sln
builds the solution file MyProject.sln.
It's unfortunate that ever since the advent of GUI, too many programmers have forgotten the power of the command line, which lets users run your app from scripts in batch mode. You can be sure the folks in Redmond don't build their products by opening a project file in Visual Studio! If you've written a program that does something really useful such as converting .wav files to mp3 or computing stock projections, it behooves you to implement a batch interface. What good is your program if someone can't compress their entire music collection or perform stock analysis overnight, without babysitting the computer?
OK, now that I'm off my soapbox, how can you implement a combined GUI/console app? As nearly every programmer targeting Windows knows by now, Windows divides the EXE universe into two camps: console apps and GUI apps. This architecture goes back to the earliest days of Windows, when it first evolved from MS-DOS®. Nowadays, you tell the linker which kind of app you want to generate by using a switch: either /subsystem:Windows or /subsystem:console.
So if you want to build a combined app, the first question you should ask is: will it be a console app or a GUI app? At first, you might think the thing to do is build a console app since it can always run a GUI. There's nothing that says a console app can't create windows or process messages. What makes a console app a console app is that Windows will create a console for it if none exists. But therein lies the problem: if you run a console app from Windows—by double-clicking from Windows Explorer or a shortcut—then Windows will create a console. You can destroy the console by calling FreeConsole, but the console window flashes briefly, announcing to the whole world that you don't really know what you're doing.
OK then, make it a GUI app. But then how do you write to the console? There are many articles explaining how to reroute printf or cout to the console, but they all involve creating a new console window, not using the one that already exists if the program is invoked from the command line. Even if there were a way to use the existing console, how do you know whether your app was invoked from Windows Explorer or a command prompt?
There's a new (for Windows XP) function that would seem to be just the ticket: AttachConsole. This function lets you "attach" your program to a console window of another process. If you use the special process ID ATTACH_PARENT_CONSOLE, AttachConsole attaches to the console from which your program was invoked. Great! But there're two problems. First, AttachConsole only exists in Windows XP, so if you want your program to run in other versions of Windows, you're out of luck; and second, AttachConsole doesn't work exactly as hoped. You can write stuff to the console, but the command prompt is all messed up after your program exits—oops!
In short, a Windows-based app must be either a console app or a GUI app and there's no way to have your cake and eat it too. (Unless you want to write your own startup code—which is waaaay more than I'm up for tonight!) But you know it can be done because I already told you Visual Studio does it—but how?
If you look in your Visual Studio hierarchy, you'll discover that there are actually two—count 'em—programs: devenv.exe and devenv.com. Does anyone remember what a .com is? I don't mean the Web kind, I mean the executable kind. Way, way back when you were just a tot, Windows-based programs had large, small, and huge memory models. There was another model called the compact or tiny model, which produced a different kind of executable with a .com extension. (.com files were a sort of direct memory image that required no fixups on loading, which made them extremely fast—but they had to be small.) Well, memory models are no more, and now all executables use the PE format. But the command interpreter still recognizes .com files as executables, and you can rename any program from foo.exe to foo.com and it'll still execute by typing the name. So the trick is to create two programs: foo.com and foo.exe. One a console app; the other, a Windows-based app.
Figure 2** Process List in a Dialog **
To show how this works, I modified the lp (list processes) program from my July 2002 column to give it the hybrid GUI/console treatment. If you type ListProc with no arguments, it lists the processes in a dialog like the one in Figure 2. If you type ListProc -c, it lists them on the console as in Figure 3. ListProc has two main program files: ListProc.cpp, which is a normal MFC app; and ListProc-cons.cpp, which is a console app. Both programs call the same module, EnumProc, to actually generate the process list. ListProc-cons processes its command line and either displays information on the console or—if there are no command-line args—invokes the GUI version by calling ShellExecute:
// change myself.com to myself.exe and run
TCHAR lpExeName[_MAX_FNAME];
GetModuleFileName(NULL, lpExeName, _MAX_FNAME);
LPTSTR ext = lpExeName + _tcslen(lpExeName) - 3;
_tcscpy(ext,_T("exe"));
ShellExecute(NULL, _T("open"),
lpExeName, NULL, NULL, SW_SHOWNORMAL);
Figure 3** Process List in a Console **
Figure 4 shows the complete code for ListProc-cons. The Visual Studio solution file has two projects: ListProc and ListProc-cons. The latter has a custom build step to rename the output file ListProc-cons.exe to ListProc.com (see Figure 5). When you install your program, make sure you put both the .com and the .exe in the same directory, and make sure any shortcuts you create point to the .exe. That way, running directly from Windows invokes the EXE, whereas running from a console invokes the COM (Windows prefers .com to .exe if both exist in the user's PATH). Got it?
Figure 4 ListProc-cons
////////////////////////////////////////////////////////////////
// ListProc.cpp shows how to write an app that can run from a single EXE
// as either a console or GUI app. To run in GUI mode, just type the name
// of the program with command-line args. To run in console/batch mode,
// type the name followed by any option, such as -help.
//
#include "stdafx.h"
#include "EnumProc.h"
static void help();
static BOOL bBare=FALSE;
static BOOL bClassName=FALSE;
static BOOL bTitle=FALSE;
// check for switch: / or -
inline BOOL isswitch(TCHAR c) { return c==L'/' || c==L'-'; }
/////////////////
// Main entry point for console app.
//
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hcon = GetStdHandle(STD_OUTPUT_HANDLE);
TRACE("hcon=%p\n",hcon);
if (argc <= 1) {
// Invoked with no command-line args: run in GUI mode.
TCHAR lpExeName[_MAX_FNAME];
GetModuleFileName(NULL, lpExeName, _MAX_FNAME);
LPTSTR ext = lpExeName + _tcslen(lpExeName) - 3;
_tcscpy(ext,_T("exe"));
ShellExecute(NULL, _T("open"), lpExeName, NULL, NULL, SW_SHOWNORMAL);
return 0;
}
// Parse command line. Switches can come in any order.
for (int i=1; i<argc; i++) {
if (isswitch(argv[i][0])) {
for (UINT j=1; j<_tcslen(argv[i]); j++) {
switch(tolower(argv[i][j])) {
case 'b':
bBare=TRUE;
break;
case 'c': // console : do nothing
break;
case 'n':
bClassName=TRUE;
break;
case 't':
bTitle=TRUE;
break;
case '?':
default:
help();
return 0;
}
}
} else {
help();
return 0;
}
}
// command-line mode: printf process list
_tprintf(FormatProcessList(bClassName, bTitle, bBare));
return 0;
}
void help()
{
_tprintf(_T("lp: List top-level proceses.\n"));
_tprintf(_T(" Copyright 2002 Paul DiLascia.\n\n"));
_tprintf(_T("Format: lp [-bcnt]\n\n"));
_tprintf(_T("Options:\n"));
_tprintf(_T(" None: run GUI version\n"));
_tprintf(_T(" -b(are) no header\n"));
_tprintf(_T(" -c(onsole) display on console (no GUI)\n"));
_tprintf(_T(" -n(className) show window class names\n"));
_tprintf(_T(" -t(itle) show window titles\n"));
_tprintf(_T("\n"));
}
Figure 5** Renaming an EXE to a .com File **
Q I'm building a Windows-based app using the Microsoft .NET Framework and Windows Forms with C#. I'm trying to save the window position so my app remembers the previous position each time it starts up. Is there some special way to do this using the .NET Framework? Should I use the configuration file?
Q I'm building a Windows-based app using the Microsoft .NET Framework and Windows Forms with C#. I'm trying to save the window position so my app remembers the previous position each time it starts up. Is there some special way to do this using the .NET Framework? Should I use the configuration file?
Frank Jacobs
A The .NET Framework supports the notion of configuration files, which are XML files that store application configuration information, but this isn't really what you want here. Configuration files are intended to be used by administrators to configure your application, not by users to save settings. For that, you should use either the registry, INI files, or your own custom data file. The registry is a bad choice because it's difficult to edit and not easily copied. One of the explicit goals of .NET programming is XCOPY deployment, which means you should be able to move your app from point A to B just by copying files. So I would recommend using either an INI file or some other kind of data file.
A The .NET Framework supports the notion of configuration files, which are XML files that store application configuration information, but this isn't really what you want here. Configuration files are intended to be used by administrators to configure your application, not by users to save settings. For that, you should use either the registry, INI files, or your own custom data file. The registry is a bad choice because it's difficult to edit and not easily copied. One of the explicit goals of .NET programming is XCOPY deployment, which means you should be able to move your app from point A to B just by copying files. So I would recommend using either an INI file or some other kind of data file.
If you want to store complex data structures like linked lists, hash tables, or your own private data structures, you're better off using serialization (in either binary or XML) to read and write your settings to a special file such as MyApp.dat. Many common classes in the .NET Framework are already serializable, and you can easily make your own classes serializable just by declaring them in the following manner:
[Serializable]
public class MyClass {
public int myInt;
public String myStr;
}
For saving simple things like the window position, INI files are a better choice. They're straightforward, simple, human-readable, easily copied and easily edited with any ASCII text editor. Alas, there are no functions in the .NET Framework to access INI files directly, so I wrote a little class called IniFile to encapsulate them for you, and a test app called SavePos that uses IniFile to remember the main window position. SavePos is a typical Windows Forms app. The main form class creates an instance of IniFile like so:
using Ini;
public class Form1 : System.Windows.Forms.Form
{
private IniFile iniFile = new IniFile("SavePos.ini",true);
•••
}
SavePos.ini is the name of the file; I'll describe the second constructor argument in a moment. The Form constructor loads the window position when the form is first created, before it's displayed, as shown here:
// in Form1 class
public Form1()
{
•••
iniFile.RestoreWinPos(this,"MainWindow");
}
And finally, Form1 saves the window position when the form is destroyed, as shown in this code:
// in Form1 class
protected override void Dispose( bool disposing )
{
if( disposing ) {
// save window pos
iniFile.SaveWinPos(this,"MainWindow");
•••
}
The following code shows the resulting INI file:
[MainWindow]
X=282
Y=442
Width=417
Height=234
As you can see, IniFile provides two functions, SaveWinPos and RestoreWinPos, to save/restore the window position. Figure 6 shows the code; it's mostly straightforward. The save/restore functions call more primitive functions like GetIntVal and SetVal to read/write the key/value pairs. These functions in turn use the interop services to call the Win32® API profile functions GetPrivateProfileInt and WritePrivateProfileString to actually read/write the INI file. The only trick is that when you restore the window position, you have to remember to set the following line of code
form.StartPosition = FormStartPosition.Manual;
if you want your Form to pay attention to Form.Location. (For more on this, see my April 2003 column.)
Figure 6 IniFile
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
////////////////////
// .NET wrapper for an initialization file.
//
namespace IniProfile {
public class IniFile
{
[DllImport("Kernel32.dll")]
private static extern bool WritePrivateProfileString(
String section, String key, String val, String filename);
[DllImport("Kernel32.dll")]
private static extern int GetPrivateProfileInt(
String section, String key, int dflt, String filename);
[DllImport("Kernel32.dll")]
private static extern int GetPrivateProfileString(
String section, String key, String dflt,
StringBuilder buf, int nBufLen, String filename);
private String iniFileName; // .INI file name
private Hashtable settings = new Hashtable();
private String currentSection = "Settings";
public String Section
{
get { return currentSection; }
set { currentSection = value; }
}
// ctor: initialize file name
public IniFile(String fn, bool useAppDataPath) {
if (useAppDataPath) {
iniFileName = Application.UserAppDataPath + "\\" + fn;
} else {
iniFileName = fn;
}
}
public int GetIntVal(String key, int dflt)
{
return GetPrivateProfileInt(currentSection, key, dflt,
iniFileName);
}
public String GetStrVal(String key, String dflt)
{
StringBuilder sb = new StringBuilder(256);
GetPrivateProfileString(currentSection, key, dflt, sb,
sb.Capacity, iniFileName);
return sb.ToString();
}
public void SetVal(String key, String val)
{
WritePrivateProfileString(currentSection, key, val, iniFileName);
}
public void SetVal(String key, int val)
{
WritePrivateProfileString(currentSection, key, val.ToString(),
iniFileName);
}
public void RestoreWinPos(Form form, String section)
{
this.Section = section;
form.Location = new Point(
GetIntVal("X",form.Location.X),
GetIntVal("Y",form.Location.Y));
form.Size = new Size(
GetIntVal("Width", form.Size.Width),
GetIntVal("Height",form.Size.Height));
form.StartPosition = FormStartPosition.Manual; // important!
}
public void SaveWinPos(Form form, String section)
{
this.Section = section;
SetVal("X",form.Location.X);
SetVal("Y",form.Location.Y);
SetVal("Width", form.Size.Width);
SetVal("Height",form.Size.Height);
}
}
}
Astute readers will recall that I owe you an explanation about the second argument to the INI file constructor. The Win32 Get/WriteProfileXxx functions all take an INI file name as an argument. If you pass a relative name like "foo.ini", Windows looks for the file in the WINDOWS directory. If you give an absolute path name, it uses that instead. Since you probably want to save the window position on a per-user basis (Jane should get the last window position she used, not Fred's), you probably want to put the INI file in the user's application data folder for your application. That's what the second argument, useAppDataPath, is for. If you pass true for this value, IniFile looks for the INI file in the user's application data folder.
How does IniFile do this? The Application class makes it easy: Application.UserAppDataPath holds the path name of the user's data folder. This is some humongous path name like \Base\[CompanyName]\[ProductName]\[ProductVersion] where Base is something like C:\Documents and Settings\[username]\Application Data. Ever compulsive in its version management, the common language runtime (CLR) adds the product version number to the path, which is either a feature or a disaster depending on your perspective: if you use an asterisk in your version number (for example, 1.0.*), as many programmers do, to make the framework create a new version for each compile, then you'll end up with hundreds of folders—a new one every time you recompile and run. Perhaps the Redmondtonians are planning to go into the disk business. If you don't want hundreds or thousands of directories clogging up your disk, you can either remove the asterisk from the version number, remove the version part of the path name, or put the file somewhere else. As always you can grab the full source from the link at the top of this article). Happy programming!
Update
In my November 2003 column, I described how to get the HWND of a popup menu by enumerating windows and looking for one with the special class name "#32768". Reader Jim White pointed out the following trick: if you call GetMenuItemRect with NULL as the window handle, Windows will return the HWND of the popup window—amazing! It just goes to show that even supposed experts like me can learn something new every day. Jim reports that the GetMenuItemRect trick works "brillliantly on Windows 2000 and Windows XP, but not on Windows 98, despite MSDN's explicit insistence that it should." Thanks, Jim!
Send your questions and comments for Paul to cppqa@microsoft.com.
Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at https://www.dilascia.com.