C++ Q&A
Find Icons, Launch an App from List Control, and More
Paul DiLascia
Code download available at: CQA0303.exe (8,029 KB)
Browse the Code Online
Q When creating a MessageBox, I can easily display a question mark icon by using the flag MB_ICONQUESTION. But how can I put that same icon on my own dialog or form? Where or how can I find the icon?
Q When creating a MessageBox, I can easily display a question mark icon by using the flag MB_ICONQUESTION. But how can I put that same icon on my own dialog or form? Where or how can I find the icon?
Eric Lambrecht
A This question falls into the category of easily forgotten topics from Windows 101. You can grab any of the so-called system icons by calling ::LoadIcon with one of the predefined special IDI_XXX resource IDs. For example:
HICON hIconQuestion = ::LoadIcon(NULL, IDI_QUESTION);
A This question falls into the category of easily forgotten topics from Windows 101. You can grab any of the so-called system icons by calling ::LoadIcon with one of the predefined special IDI_XXX resource IDs. For example:
HICON hIconQuestion = ::LoadIcon(NULL, IDI_QUESTION);
I wrote a little program, SysIcons, that displays all the system icons. Figure 1 shows the relevant code from MainFrm.cpp; Figure 2 shows it in action. If you want to use one of these system icons in your own dialog or other window, create a static control the normal way and write the following code:
HICON hicon = ::LoadIcon(NULL, IDI_HAND);
m_wndStatic.SetIcon(hicon);
This assumes m_wndStatic is a CStatic control in your dialog or window. SysIcons has an About dialog that displays the question mark icon you asked about. For details, download the source from the link at the top of this article.
Figure 1 MainFrm.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — March 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#include "StdAfx.h"
#include "SysIcons.h"
#include "MainFrm.h"
•••
IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_WM_PAINT()
END_MESSAGE_MAP()
CMainFrame::CMainFrame()
{
}
CMainFrame::~CMainFrame()
{
}
const struct {
LPCTSTR nResID;
LPCTSTR name;
} SysIcons[] = {
{ IDI_APPLICATION, _T("IDI_APPLICATION") },
{ IDI_HAND, _T("IDI_HAND") },
{ IDI_QUESTION, _T("IDI_QUESTION") },
{ IDI_EXCLAMATION, _T("IDI_EXCLAMATION") },
{ IDI_ASTERISK, _T("IDI_ASTERISK") },
#if(WINVER >= 0x0400)
{ IDI_WINLOGO, _T("IDI_WINLOGO") },
{ IDI_WARNING, _T("IDI_WARNING") },
{ IDI_ERROR, _T("IDI_ERROR") },
{ IDI_INFORMATION, _T("IDI_INFORMATION") },
#endif
{ NULL, NULL }
};
void CMainFrame::OnPaint()
{
CPaintDC dc(this);
CRect rcClient;
GetClientRect(&rcClient);
int cyIcon = GetSystemMetrics(SM_CYICON);
int cxIcon = GetSystemMetrics(SM_CXICON);
CRect rcIcon(0,0,cxIcon,cyIcon);
CRect rcText(cxIcon, 0, rcClient.Width()-cxIcon, cyIcon);
for (UINT i=0; SysIcons[i].nResID; i++) {
HICON hicon = ::LoadIcon(NULL, SysIcons[i].nResID);
ASSERT(hicon);
CString name = SysIcons[i].name;
dc.DrawIcon(rcIcon.TopLeft(), hicon);
dc.DrawText(name, rcText, DT_LEFT);
rcIcon += CPoint(0, rcIcon.Height());
rcText += CPoint(0, rcIcon.Height());
}
}
Figure 2** System Icons **
While system icons would seem to merit no further discussion, Windows® makes even this simple subject more complex than expected. As Figure 2 shows, IDI_HAND doesn't resemble a hand at all, at least not on Windows XP. Likewise, contrary to its name, IDI_ASTERISK is a word balloon that contains the letter "i". The explanation for this lexical obfuscation lies in the historical record: older versions of Windows displayed the hand and asterisk; newer versions use the new icons. winuser.h reflects the changes, as shown here:
#if(WINVER >= 0x0400)
#define IDI_WARNING IDI_EXCLAMATION
#define IDI_ERROR IDI_HAND
#define IDI_INFORMATION IDI_ASTERISK
#endif /* WINVER >= 0x0400 */
If you want to keep up with the Redmondtonians, use the new symbols. Your code will be more readable too, since the symbols of warning, error, and information more accurately convey the purpose for using these icons.
Another perplexing tidbit is that while winuser.h has a new symbol IDI_WINLOGO, the icon that results from loading it in a Windows XP-based app is not the familiar flying flag we all know and love, but rather the same generic window you get with IDI_APPLICATION (see Figure 2). Careful reading of the documentation reveals that indeed this feature is by design. While other platforms presumably yield the flying window, on Windows XP IDI_WINLOGO yields the application icon. What corporate conspiracy, if any, lies behind this equivocation is beyond my humble ken. If you think you know, drop me a line.
What about if you're targeting Microsoft® .NET? How can you display system icons in your app built using the .NET Framework? Well, you can always call LoadIcon directly through the wonders of interop—but why bother when the .NET Framework already exposes the system icons through a special class, SystemIcons:
Icon icon = SystemIcons.Question;
Figure 3 shows the full list of SystemIcons and their Win32® IDI_XXX counterparts. Just for amusement, I translated SysIcons to C#. Figure 4 shows the result. SysIcons.cs uses reflection to probe all the static properties of SystemIcons. First, get all the public static properties, as shown here:
PropertyInfo[] props =
typeof(SystemIcons).GetProperties(
BindingFlags.Public|BindingFlags.Static);
Then display all the ones that are icons:
foreach (PropertyInfo p in props) {
Object obj = p.GetValue(null, null);
if (obj.GetType()==typeof(Icon)) {
Icon icon = (Icon)obj;
•••// display it
}
}
Figure 4 SysIcons
////////////////////////////////////////////////////////////////
// MSDN Magazine — March 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;
namespace WinApp
{
public class Form1 : System.Windows.Forms.Form
{
public Form1()
{
BackColor = SystemColors.Window;
}
protected override void OnPaint(PaintEventArgs e)
{
// get property info for all SystemIcons static properties
PropertyInfo[] props = typeof(SystemIcons).GetProperties(
BindingFlags.Public|BindingFlags.Static);
Graphics g = e.Graphics;
Font font = new Font("Verdana", 12, FontStyle.Bold);
SolidBrush brush = new SolidBrush(Color.Black);
int y = 0;
// Display each icon. Use reflection to get all the static
// members of SysIcons—cool!
//
foreach (PropertyInfo p in props) {
Object obj = p.GetValue(null, null);
if (obj.GetType()==typeof(Icon)) {
Icon icon = (Icon)obj;
g.DrawIcon(icon, 0, y);
g.DrawString(String.Format("SystemIcons.{0}",p.Name),
font, brush, icon.Width+2, y);
y += icon.Height;
}
}
}
protected override Size DefaultSize
{
get { return new Size(300,350); }
}
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
Figure 3 SystemIcons
Win32 Resource ID | .NET Framework SystemIcons |
---|---|
(LoadIcon) | Static Property |
IDI_APPLICATION | SystemIcons.Application |
IDI_ASTERISK | SystemIcons.Asterisk |
IDI_ERROR | SystemIcons.Error |
IDI_EXCLAMATION | SystemIcons.Exclamation |
IDI_HAND | SystemIcons.Hand |
IDI_INFORMATION | SystemIcons.Information |
IDI_QUESTION | SystemIcons.Question |
IDI_WARNING | SystemIcons.Warning |
IDI_WINLOGO | SystemIcons.WinLogo |
Currently, the only properties of SystemIcons are the actual Icons, but you never know what the Redmontonians might add, so it's safer to check the type for Icon. Using reflection is way cooler than hardcoding all possible symbols, the kind of thing that wins you guru points. Once you have an Icon, you can convert it to a Bitmap like so:
Icon icon = SystemIcons.Question;
Bitmap bm = icon.ToBitmap();
Since Bitmap derives from Image, you can use it anywhere you can use Image. For example, as the BackgroundImage in a PictureBox—though I confess I was not able to make any of the icons paint properly in a PictureBox. Even after calling Bitmap.MakeTransparent, the Framework still paints the icons with a black background.
Q I want to double-click on an item in a list control and then launch the registered program for that file. It sounds so simple, but I can't figure out how to do it. Can you help?
Q I want to double-click on an item in a list control and then launch the registered program for that file. It sounds so simple, but I can't figure out how to do it. Can you help?
David Rogers
A I get this question so frequently I'll answer it again for the newbies who may be reading this (don't feel embarrassed, we all begin as newbies). To open any file in Windows, call
ShellExecute(NULL, "open", lpFileName, NULL, NULL, SW_SHOWNORMAL);
where lpFileName is the full path name of the file. You can pass a string like "C:\\MyFinances.xls", or "https://msdn.microsoft.com" to launch the browser. If all you want to do is get the name of the program associated with a file without actually running it, you can call ::FindExecutable.
A I get this question so frequently I'll answer it again for the newbies who may be reading this (don't feel embarrassed, we all begin as newbies). To open any file in Windows, call
ShellExecute(NULL, "open", lpFileName, NULL, NULL, SW_SHOWNORMAL);
where lpFileName is the full path name of the file. You can pass a string like "C:\\MyFinances.xls", or "https://msdn.microsoft.com" to launch the browser. If all you want to do is get the name of the program associated with a file without actually running it, you can call ::FindExecutable.
Q I've long been a fan of your TraceWin program for MFC. I use it all the time for debugging. I recently started a C# project. Is there some way I can make TraceWin work with the .NET Framework? Also, since this is going to be a global application, how hard would it be to make TraceWin support Unicode? Can I just recompile it with #define _UNICODE?
Q I've long been a fan of your TraceWin program for MFC. I use it all the time for debugging. I recently started a C# project. Is there some way I can make TraceWin work with the .NET Framework? Also, since this is going to be a global application, how hard would it be to make TraceWin support Unicode? Can I just recompile it with #define _UNICODE?
Peter Kerensky
A First of all, thanks for using TraceWin! You may have missed my recent article ".NET GUI Bliss: Streamline Your Code and Simplify Localization Using an XML-based GUI Language Parser", where I briefly described a TraceWin listener I wrote that targets the .NET Framework. Since I only mentioned it without describing the details, and since MFC does tracing differently in version 7.0, now seems like a good time to revisit TraceWin.
A First of all, thanks for using TraceWin! You may have missed my recent article ".NET GUI Bliss: Streamline Your Code and Simplify Localization Using an XML-based GUI Language Parser", where I briefly described a TraceWin listener I wrote that targets the .NET Framework. Since I only mentioned it without describing the details, and since MFC does tracing differently in version 7.0, now seems like a good time to revisit TraceWin.
Figure 5** TraceWin in Action **
For those of you who have no idea what I'm talking about, TraceWin is a little applet that displays TRACE diagnostics so you can see them without running your program in the debugger. Figure 5 is a picture worth a thousand words. I first described TraceWin way back in the October 1995 issue of MSJ, before some of you were even born. (What, you don't believe the magazine has any seven-year-old readers?) In the old days, TRACE was #defined to AfxTrace, an MFC function that formats its parameters before calling afxDump, MFC's global CDumpContext. All trace diagnostics eventually went through CDumpContext::OutputString, which in version 6.0 looked something like this:
void CDumpContext::OutputString(LPCTSTR lpsz){
if (m_pFile != NULL) {
m_pFile->Write(lpsz, lstrlen(lpsz)*sizeof(TCHAR));
} else {
// normal debug thing
OutputDebugString(lpsz);
}
}
The special CFile member m_pFile is normally NULL, but if you set it to a CFile-derived object, you can effectively redirect all MFC trace output wherever you want. The original TraceWin.h described in 1995 implements a CFile-derived class with a Write method that sends the text to TraceWin. Pretty clever, huh? TraceWin.h has a self-initializing static object so all you have to do is #include TraceWin.h and away you go.
But like I said, that was then. This is now. Ever since its marriage to the ActiveX® Template Library (ATL), MFC does tracing somewhat differently. In version 7.0, a.k.a. Visual Studio® .NET, TRACE is now defined in afx.h like so:
#define TRACE ATLTRACE
MFC uses ATL. In short, ATL uses the C runtime (CRT) library's built-in debugging routines. So all trace diagnostics for both MFC and ATL now go through _CrtDbgReport, a low-level C runtime function for reporting debug diagnostics. There's only one downside to this: since _CrtDbgReport is declared with char*, not TCHAR, debug messages have to be ANSI (the old MFC system used TCHAR)—but overall the new scheme is better. All tracing now rests on the same foundation. The CRT has a function _CrtSetReportHook that lets you install a function to intercept the diagnostic stream. MFC installs its own hook that calls afxDump:
// details omitted to protect you
int _AfxCrtReportHook(int nRptType, char *szMsg, int* pResult)
{
// no hook on asserts or when m_pFile is NULL
if (nRptType==_CRT_ASSERT || afxDump.m_pFile==NULL)
return FALSE;
// non-NULL m_pFile: go through afxDump
*pResult = FALSE;
afxDump << szMsg;
return TRUE;
}
If afxDump has a file (m_pFile), MFC sends the message through afxDump; otherwise it lets the CRT do its thing (which ultimately displays the message in the debugger's output window). The upshot of this long digression is: even when using ATLTRACE and the CRT, the old TraceWin.h from 1995 still works by setting afxDump.m_pFile. Amazing.
You might be thinking, "if it works, don't fix it"—and generally you'd be right. But now that MFC has joined the ATL/C runtime bandwagon, I'm suffering from code envy. Why not upgrade TraceWin.h, too? If TraceWin.h intercepted trace diagnostics through the C runtime, then non-MFC programmers—even C programmers—could use TraceWin. Say no more. Figure 6 shows the code. No more CFile, no more MFC. The revised TraceWin.h defines a new class, CTraceWinListener, with a singleton instance that sets everything up. The constructor installs a debug hook that passes strings to TraceWin as before, using WM_COPYDATA. Everything is straightforward and simple; all you have to do is #include TraceWin.h in your application. For details, download the source code from the link at the top of this article.
Figure 6 TraceWin
////////////////////////////////////////////////////////////////
// TraceWin 1995-2003 MSDN Magazine
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// ***********************************************************************
// TraceWin is a tool that displays diagnostic (TRACE, ATLTRACE) output
// in the TraceWin applet window.
//
// To use TraceWin, you must #include this file in your main program
// file, but only once—i.e. NOT in StdAfx.h—or you'll get multiply-defined
// symbol errors in the linker. This file contains an auto-initializing
// singleton instance, so all you have to do is #include the file.
//
// To see the output, you also need TraceWin.exe, which if you don't
// have already, you can download from https://www.dilascia.com/TraceWin.htm
//
// ***********************************************************************
//
#ifdef _DEBUG
// Window class name used by the main window of the TRACEWIN applet.
#define TRACEWND_CLASSNAME _T("MfxTraceWindow") // backwards compat
// ID sent as COPYDATASRUCT::dwData to identify the WM_COPYDATA message
// as coming from an app using TraceWin.
#define ID_COPYDATA_TRACEMSG MAKELONG(MAKEWORD('t','w'),MAKEWORD('i','n'))
//////////////////
// Self-initializing CRT-based trace "listener" hooks
// itself into C runtime debug system.
//
class CTraceWinListener {
protected:
static CTraceWinListener singleton; // one-and-only instance
static _CRT_REPORT_HOOK pfnOldHook; // ptr to old reporting hook
// ctor installs hook
CTraceWinListener()
{
// if this bombs, you probably #included TraceWin.h twice
ASSERT(pfnOldHook == NULL);
pfnOldHook = _CrtSetReportHook(MyCrtReportHook);
}
// dtor removes hook
~CTraceWinListener()
{
pfnOldHook = _CrtSetReportHook(pfnOldHook);
}
// hook function installed in CRT: passes text to TraceWin
static int __cdecl MyCrtReportHook(int nRptType, char *szMsg, int* pRes)
{
// call the old report hook if there was one
if (pfnOldHook && (*pfnOldHook)(nRptType, szMsg, pRes)) {
return TRUE;
}
// don't hook asserts
if (nRptType == _CRT_ASSERT)
return FALSE;
// Find trace window. Note that it's not really safe to store
// this HWND if I want to allow closing/restarting TraceWin. The
// window handle could change!
HWND hTraceWnd = ::FindWindow(TRACEWND_CLASSNAME, NULL);
if (hTraceWnd) {
COPYDATASTRUCT cds;
cds.dwData = ID_COPYDATA_TRACEMSG;
cds.cbData = strlen(szMsg);
cds.lpData = (void*)szMsg;
WPARAM wParam = 0;
::SendMessage(hTraceWnd, WM_COPYDATA, wParam, (LPARAM)&cds);
*pRes = FALSE;
}
return FALSE; // don't stop next hook
}
};
// one-and-only singleton instance automatically installs debug hook
CTraceWinListener CTraceWinListener::singleton;
_CRT_REPORT_HOOK CTraceWinListener::pfnOldHook=NULL;
#endif // _DEBUG
Now that MFC, C++, and C programmers can all use TraceWin, what about programmers using the .NET Framework? Never fear, the Framework has a way to include them, too. It has a diagnostic system with the notion of trace "listeners." A trace listener is just a class derived from—what else?—TraceListener, with overloaded Write and WriteLine functions that can do whatever you want. For example, you could feed your debug messages through a text-to-speech converter linked to a geostationary satellite to broadcast your Trace messages live on Monday Night Football. But it costs a lot to buy your own satellite and, besides, who wants his bugs seen by millions? Fortunately, writing a listener for TraceWin is much easier. All that's required is a Write function that sends WM_COPYDATA messages to the TraceWin applet. Figure 7 shows the class. This is the kind of code you can write in your sleep, provided you know a little interop voodoo.
Figure 7 TraceWinListener
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
//////////////////
// This module implements a TraceWin listener for .NET.
// Use it to see trace diagnostics in my TraceWin applet window.
// To install in your app, add the following lines:
//
// using TraceWin;
// Debug.Listeners.Add(new TraceWinListener());
//
// You must add a reference to TraceWinListener.dll in your project and
// don't forget to compile with /d:TRACE.
//
namespace TraceWin {
//////////////////
// Handy trace listener writes to TraceWin. Neat!
//
public class TraceWinListener : TraceListener {
[StructLayout(LayoutKind.Sequential)]
private struct COPYDATASTRUCT {
public Int32 dwData;
public Int32 cbData;
public IntPtr lpData;
}
[DllImport("user32.dll")]
private static extern long SendMessage(Int32 hwnd, Int32 msg,
Int32 hwndFrom, ref COPYDATASTRUCT cd);
[DllImport("user32.dll")]
private static extern Int32 FindWindow(String classname, String text);
[DllImport("kernel32.dll")]
private static extern Int32 GlobalSize(IntPtr hmem);
public override void Write(String s) {
// Don't forget to use IndentLevel and IndentSize!
if (Trace.IndentLevel>0) {
s = s.Insert(0,new String(' ',
Trace.IndentSize * Trace.IndentLevel));
}
// Send WM_COPYDATA message to trace window
Int32 hTraceWnd = FindWindow("MfxTraceWindow",null);
if (hTraceWnd!=0) {
COPYDATASTRUCT cd = new COPYDATASTRUCT();
Int32 WM_COPYDATA = 0x004A; // Win32 API message id
cd.dwData = 0x6e697775; // magic message code TraceWin
cd.lpData = Marshal.StringToHGlobalUni(s);
cd.cbData = GlobalSize(cd.lpData);
SendMessage(hTraceWnd, WM_COPYDATA, 0, ref cd);
Marshal.FreeHGlobal(cd.lpData);
}
}
public override void WriteLine(String s) {
Write(s+"\n");
}
}
} // namespace
First, you need the magic declarations for the Win32 functions SendMessage, FindWindow, and so on, as well as a struct for COPYDATASTRUCT. These appear at the top of Figure 7. After inserting Trace.IndentLevel number of spaces ahead of the trace message (don't forget this if you ever write your own listener!), TraceWinListener.Write calls ::FindWindow to find TraceWin, then sends it a WM_COPYDATA message with the magic TraceWin code 0x6e697775. You could use interop to marshal the String through COPYDATASTRUCT as LPWStr, but WM_COPYDATA needs to know the exact number of bytes in your memory block, so TraceWinListener allocates global memory and passes it as an IntPtr, as shown in the following code:
COPYDATASTRUCT cd = new COPYDATASTRUCT();
Int32 WM_COPYDATA = 0x004A;
cd.dwData = 0x6e697775; // magic TraceWin code
cd.lpData = Marshal.StringToHGlobalUni(s);
cd.cbData = GlobalSize(cd.lpData);
SendMessage(hTraceWnd, WM_COPYDATA, 0, ref cd);
Marshal.FreeHGlobal(cd.lpData);
Marshal.StringToHGlobalUni copies the string to a newly allocated block in global memory, converting to Unicode along the way. I couldn't find any common language runtime function to get the length of a global memory block, so I used interop to call ::GlobalSize directly:
[DllImport("kernel32.dll")]
private static extern Int32 GlobalSize(IntPtr hmem);
•••
IntPtr data = Marshal.AllocHGlobal(...);
int size = GlobalSize(data); // it works!
Windows needs to know the size (in bytes) of the WM_COPYDATA memory block in order to copy it across the process boundary. It's not generally safe to assume the length of a Unicode string is always twice String.Length, so let the Marshaler convert the string, then call GlobalSize to get its true size. There's probably a more clever interop way to handle this situation, but I didn't have the time or patience to figure it out. In magazine-land we live on very tight schedules.
And speaking of Unicode (we were speaking of Unicode, remember?), how do you make TraceWin understand Rundi? _CrtDbgReport doesn't do Unicode, but the .NET Framework is multinational, so why limit TraceWin? This is where you have to modify TraceWin itself. If you've been paying close attention, you'll have noticed that so far I've only described changes to client-side code (TraceWin.h and TraceWinListener), not the TraceWin applet itself.
To make TraceWin global, the first thing I did was change the project settings (in Visual Studio, select "Use Unicode Character Set" in the project properties under General properties) and recompile. To my great astonishment, there were no errors. I guess _T and all those LPCTSTR definitions really work. But recompiling with _UNICODE isn't enough: TraceWin still has to support ANSI. I don't want to break all those ANSI-compliant apps out there already using TraceWin—I might get a pile of flame mail. To support Unicode without dropping ANSI clients, TraceWin needs a new WM_COPYDATA subcode, just like Windows does with all its A and W versions of every WM_ message:
// same as before
#define ID_COPYDATA_TRACEMSGA 0x6e697774
// new
#define ID_COPYDATA_TRACEMSGW (ID_COPYDATA_TRACEMSGA+1)
TraceWin processes the new message in CMainFrame::OnTraceMsg, the message handler for WM_COPYDATA. Here's the pseudocode from MainFrm.cpp:
LRESULT
CMainFrame::OnTraceMsg(WPARAM wp, LPARAM lp)
{
COPYDATASTRUCT* pcd = (COPYDATASTRUCT*)lp;
if (pcd->dwData==ID_COPYDATA_TRACEMSGW) {
// already Unicode
// string len = data len/2
} else if (pcd->dwData==ID_COPYDATA_TRACEMSGA) {
// convert from ANSI to Unicode
// (call MultiByteToWideChar)
}
•••
}
In short: whereas the old TraceWin was an ANSI app that accepted only ANSI strings, the new TraceWin is a Unicode app that accepts either ANSI or Unicode. If your app wants to send ANSI, it uses ID_COPYDATA_TRACEMSGA; for Unicode, use ID_COPYDATA_TRACEMSGW. Of course, you don't have to do anything personally, since TraceWin.h and TraceWinListener do the right thing automatically. All you have to do is use them. For C/C++ apps, just #include TraceWin.h. For the .NET Framework, add a reference to TraceWinListener.dll and insert the following lines somewhere:
using TraceWin;
Debug.Listeners.Add(new
TraceWinListener());
Don't forget to compile with /d:TRACE! Visual Studio .NET does it automatically in Debug builds. As usual, you can download the entire TraceWin source code from the link at the top of this article. Happy programming!
Send your questions and comments for Paul to cppqa@microsoft.com.
Paul DiLasciais 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 askpd@pobox.com or https://www.dilascia.com.