How to enable
MFC edit control to accept drag-and-drop files?
Details
One might suggest the following answer:
- Set "Accept Files" property to True in Properties Window for the edit
control, which is False by default.
- Add WM_DROPFILES handler to the message map of the dialog that hosts the
edit control.
However this seemingly standard routine does not provide DAD capabilities for
the edit control. You can additionally set "Accept Files" property for the
dialog that hosts the child edit control to enable DAD for the whole dialog
area, but this DAD design would mislead the user a little bit since DAD icon
would show up not only over the edit control, but over the whole dialog area.
The right solution to enable DAD for the edit control only is to add WM_DROPFILES
handler to the edit control itself and you need to subclass edit control to
handle control messages in own procedure. The code from the
MFC_EditWithDAD sample shows how to implement subclassing starting from
boilerplate MFC dialog application with edit control added in dialog template
in the Resource Editor:
- Add a class CEditWithDAD derived from CEdit. You can use the code that
VS.NET generates when deriving from a standard MFC class:
// CEdit is subclassed to handle dropped files.
class CEditWithDAD : public CEdit
{
DECLARE_DYNAMIC(CEditWithDAD)
public:
CEditWithDAD();
virtual ~CEditWithDAD();
protected:
afx_msg void OnDropFiles(HDROP hDropInfo); // added manually
DECLARE_MESSAGE_MAP()
};
- Add CEditWithDAD class variable, m_ctlEditWithDAD, to the dialog class.
- Add WM_DROPFILES handler, ON_WM_DROPFILES(), to the message map of the
CEditWithDAD and its default function OnDropFiles:
// Works for single files and for a folder.
void CEditWithDAD::OnDropFiles(HDROP hDropInfo)
{
TCHAR szFFN[MAX_PATH];
// MSDN (second parameter): "If the value of the iFile
// parameter is between zero and the total number of files
// dropped, DragQueryFile copies the file name with the
// corresponding value to the buffer pointed to by the
// lpszFile parameter."
DragQueryFile(hDropInfo, 0 /* iFile */, szFFN,
sizeof(szFFN)/sizeof(TCHAR));
DragFinish(hDropInfo);
// Update text in the edit control.
SetWindowText(szFFN);
}
- Subclass "normal" edit control which was previously positioned in
the dialog template in the Resource Editor as:
BOOL CMFC_EditWithDADDlg::OnInitDialog()
{
<...>
// Subclass edit control.
m_ctlEditWithDAD.SubclassWindow(
GetDlgItem(IDC_EDIT)->m_hWnd);
return TRUE;
}
That's all. Now you can DAD (drag-and-drop) files to the edit control to
check that the full file name is automatically inserted with the drop. The same
code works for folders. Notice that the use of SubclassWindow has the advantage
for it allows to use positioning of the edit control in the dialog template.
How
to get CHM help file version information?
Details
To start with, there is no standard method to read CHM file version
information since it is not supposed to be in CHM file by design.
Right-click any CHM to verify that there is no Version tab. There are
no options in Microsoft Help Workshop to add version information.
Is there a simple possibility to add version information and read
it in Win32 or MFC application? The answer is yes: Microsoft compiler for
help files that produces CHM files does not encode file names included
into a help project. Hence there is a possibility to add version
information to a name of HTM file and read this information from an
application.
Microsoft Help Workshop compiles help files into CHM file (compiled
help file) that is essentially an ANSI text file. Look what Notepad++
shows:
It shows a plain name of HTM file added to help project. The file
name includes version information. Then the method to add read
version information is the following:
- In Microsoft Help Workshop, add _Version_.htm
file. The file can be empty and there is no need generally to add it
to Table of Contents (HHC file), the sole purpose of it is to keep
version information in its file name.
- In your application read CHM file as you would read any ANSI
file, search for "<projectname>_Version_<x.x>.htm"
string and parse it to extract version information.
This method is demonstrated in
Win32_GetCHMVersion sample application. In this sample the About
dialog shows version information in the static field.
As the following code shows it uses ReadFile to get CHM file content
into a buffer, and then searches for the version string. The only
little complication is that CHM file contains many NUL characters and
therefore C-functions that are used to find substrings or characters
in a string (like strstr, strchr) can not be used. In this sample the
memchr is used to find the first character of the string in raw CHM
file.
In general, the steps to employ this method for an application are
the following:
- Add <projectname>_Version_<x.x>.htm file with
version information to your existing Help Project file (HHP file)
and compile it.
- Modify the AboutDlg_OnInitDialog function updating the location of CHM
file, the search character (the first character in the name of
added version information file), and adjusting the size of szBuffer
according to the comments below. Update search string and constants in
the code.
- The result is the szVersion string that is shown in the sample
in the About dialog. Customize how the result is handled.
BOOL AboutDlg_OnInitDialog(HWND hDlg)
{
// Help file is supposed to reside in the project root folder
// while the executable is in the Release or Debug folder.
char szCHMFileName[] = "..\\GetCHMVersion.chm";
int ch = 'G'; // search character
HANDLE hCHMFile;
// The buffer should be big enough to hold characters beyond
// the "GetCHMVersion_Version_1.0.htm" string in CHM file,
// but generally less than CHM file size.
char szBuffer[2000];
DWORD dwBytesToRead = sizeof(szBuffer);
DWORD dwRead;
LPBYTE lpCurr = NULL;
int nSearchDepth;
int nNextPos;
LPBYTE lpStart = NULL;
char szTarget[100];
char szVersion[10];
char* pszVersion = NULL;
BOOL bFound = FALSE;
hCHMFile = CreateFile(szCHMFileName, GENERIC_READ,
FILE_SHARE_READ, NULL /* security is off */,
OPEN_EXISTING /* opens file if exists or fails */,
FILE_ATTRIBUTE_NORMAL /* normal use */, NULL);
if (hCHMFile != INVALID_HANDLE_VALUE)
{
ReadFile(hCHMFile, szBuffer /* out */,
dwBytesToRead /* in */, &dwRead /* out */, NULL);
// Since the szBuffer contains many NUL characters, memchr
// is used to search for "GetCHMVersion_Version_<x.x>.htm"
// string. The memchr-search is enabled up to the end of
// the buffer (the exact position where the search must be
// ended is not essential).
nSearchDepth = sizeof(szBuffer);
lpCurr = (LPBYTE)memchr(szBuffer, ch, nSearchDepth);
// Return FALSE if the search character was not found.
if (lpCurr == NULL) return FALSE;
// Calculate the position next to the found character
// relative to the beginning of the buffer.
nNextPos = (int)((char*)lpCurr - szBuffer + 1);
while (nSearchDepth > 0)
{
// The new position to start search.
lpStart = lpCurr + 1;
// The size of remaining chunk of characters that
// were not searched.
nSearchDepth = nSearchDepth - nNextPos;
lpCurr = (LPBYTE)memchr(lpStart, ch, nSearchDepth);
// Return FALSE if the search character was not found.
if (lpCurr == NULL) return FALSE;
nNextPos = (int)((char*)lpCurr - (char*)lpStart + 1);
// Check if found position corresponds to
// "GetCHMVersion_Version_<x.x>.htm" string, get the
// version, and if not, continue the search. First,
// extract the 29 characters that might be
// "GetCHMVersion_Version_<x.x>.htm" string (without
// NUL since anyway szTarget is NUL-terminated when
// initialized).
strncpy(szTarget, (char*)lpCurr, 29);
if (strncmp(szTarget,
"GetCHMVersion_Version_", 22) == 0 /* equal */)
{
bFound = TRUE;
break;
}
} // while cycle
if (bFound)
{
// Get the version string that is in format.
pszVersion = szTarget + 22;
*(pszVersion + 3) = '\0';
strcpy(szVersion, pszVersion);
// Show the version string in the About dialog.
SendMessage(GetDlgItem(hDlg,
IDC_ABOUT_STATIC_VERSION),
WM_SETTEXT, 0 /* not used */, (LPARAM)szVersion);
return TRUE;
}
CloseHandle(hCHMFile);
}
else
{
MessageBox(NULL, "CHM Help file was not found.",
"CHM Version", MB_ICONINFORMATION);
} // hCHMFile check
return FALSE; // either CHM file or version string is not found
}
The sample is limited to <x.x> version strings. However the
code that is easy to update to read version strings like 1.28 or
10.124 or 1.15.23.
How to provide context help ("?" style help) for an application?
Details
Have you ever noticed a question mark in the title area of virtually any
Windows XP dialog next to close button? The screenshot below is an example from
the application as familiar as WordPad. In the WordPad's Print dialog when you
click the question mark (the cursor takes immediately a question shape), then
click a control (or text) in question - a yellow (typically) popup window brings
up the context help.
This is an example of context help in action. In general, the following
context help support can be implemented:
- "?" Help: click with "?"-shaped cursor on the control in question after you
click "?" in the title area of a dialog.
- SHIFT+F1 Help: when control has focus SHIFT+F1 brings up the same context
help as for "?" Help.
- "What's This?" Context Menu Help: when control has focus right-click on the
control brings up "What's This?" window. The following click on the window
brings up the same context help as for "?" Help and SHIFT+F1 Help. You can check
out "What's This?" help with the WordPad application.
- SHIFT+F1 Help for the Menu Items: when menu item is active SHIFT+F1 bring up
the context the menu item. This usually does not work for root menu items and
menu items that have subitems. Notably typical Microsoft appications do not
always have this type of context help support. For example, SHIFT+F1 Help for
menu item is not implemented for Microsoft's WordPad and Notepad applications.
On the contrary, Microsoft Word, Excel, Outlook 2000 support this type of help.
What about F1 help and its relation to the context help? The answer is that with
native Windows XP and Microsoft Office 2000 applications F1 would typically
end up the same way as "?" Help, SHIFT+F1, and "What's This?" help for a control
that has focus. "What's the point?" would be fair to ask. Would it be nicer that
F1 is responsible for "bigger scale" help by opening up a specific topic from
CHM file for an active dialog, and context help ("?" Help, SHIFT+F1, "What's
This?" are all essentially making the same thing) is responsible for "smaller
scale" help for a particular control?
The following
download provides an example of F1 and context help implementation in the
MFC application. The application's Help system samples “bigger scale” overview
with CHM file invoked with F1 and “smaller scale” hints for the controls and
menu items in question invoked with different mehods of context help. At the
center of the sample is CContextHelpMappings class developed to facilitate
providing context help for typical Win32 and MFC applications. The details
of the logic are the following:
- F1 should always invoke CHM file (MFC_SHIFT_F1_Help.chm) except for the
case when cursor is inside the rectangle of the control that has focus. In this
case, the context help for the control is shown.
- The "?" Help, SHIFT+F1, "What's This?" context types of help should bring up
the same context help windows based on ID of the control in question.
- There is some difficulty with intercepting F1 since “?”, SHIFT+F1, F1 are
all generate WM_HELP message handled in MFC with OnHelpInfo handler. Therefore
the check is made in the beginning of OnHelpInfo if the WM_HELP relates to F1.
In the result, F1 help is handled in the separate handler (OnF1Help function of
the sample) of the application class albeit all help (F1 and context help)
originally handled with MFC's OnHelpInfo handler. The method used in the sample
to sort out F1 events figures if the cursor is inside the rectangle of a control
that has focus and if not it decides that this a general help request for F1. An
alternative method would be to use MFC's PreTranslateMessage to analyze help
request.
- The CHM file does not (although it can in general) handle context help
support. The rationale is that the frequently more convenient context help
options are: (a) keep context help tightly bundled with an application
(user/development perspective), (b) keep context help hard-coded (developer
perspective). For example, when CHM file is missing (not yet downloaded,
deleted) the context help is still available. Notably hard-coded context help
helps to reduce download traffic since CHM file can frequently reach the size
of the associated application or bigger. In this case, one can implement the
option to download CHM help while not including CHM file into the setup.
Let's suppose an application has many dialogs and windows that require context
help support. Where does one handle context help? In general, it is reasonable
to handle context help in one location such as application class. By this we
avoid duplicating context help functionality in each dialog and save quite a bit
of efforts. The following is CContextHelpMappings class that helps to handle
all context help in typical Win32 or MFC application. Notice that according to
the CContextHelpMappings template each control has a pair of IDs. The
m_MapMainDialog array keeps all these ID pairs in one place. The first ID in
each pair, is control ID (with "IDC_" prefix) and the second is help ID with
"IDH_" prefix. You should add help IDs manually according to the control namings
e.g. for IDC_MD_BUTTON1 add IDH_MD_BUTTON1.
#pragma once
class CContextHelpMappings
{
public:
// Initialize the context help mappings in the constructor.
CContextHelpMappings()
{
// Mappings for the main dialog.
m_MapMainDialog[0].nControlID = IDC_MD_BUTTON1;
m_MapMainDialog[0].nStringHelpID = IDH_MD_BUTTON1;
m_MapMainDialog[1].nControlID = IDC_MD_BUTTON_HELP;
m_MapMainDialog[1].nStringHelpID = IDH_MD_BUTTON_HELP;
<...>
// Mappings for the options dialog.
m_MapOptionsDialog[0].nControlID = IDC_OD_BUTTON_OPTION1;
m_MapOptionsDialog[0].nStringHelpID = IDH_OD_BUTTON_OPTION1;
m_MapOptionsDialog[1].nControlID = IDC_OD_BUTTON_OPTION2;
m_MapOptionsDialog[1].nStringHelpID = IDH_OD_BUTTON_OPTION2;
<...>
}
// Set the size of arrays. Notice that "const static" helps
// initialize outside the constructor, but inside the class and
// keeps initialization in the one place.
const static short m_nMapSizeMainDialog = 5;
const static short m_nMapSizeOptionsDialog = 5;
struct ContextHelpMap
{
DWORD nControlID;
DWORD nStringHelpID;
} m_MapMainDialog[m_nMapSizeMainDialog],
m_MapOptionsDialog[m_nMapSizeOptionsDialog];
};
The next are the more elaborative steps to implement context help using
CContextHelpMappings class in MFC application (Win32 steps are essentially the
same):
- Add CContextHelpMappings member variable to the application class so that
the variable would be accessible from any MFC dialog with theApp reference:
#include "ContextHelpMappings.h"
class CMFC_SHIFT_F1_HelpApp : public CWinApp
{
// Constructor(s) and destructor (constructors).
public:
CMFC_SHIFT_F1_HelpApp();
// Access member variables (attributes).
public:
CContextHelpMappings m_ContextHelp;
<...>
};
extern CMFC_SHIFT_F1_HelpApp theApp;
- Add to the resource.h the values for the new help IDs e.g. for
IDC_MD_BUTTON1 (value 123) add IDH_BUTTON1 (value 2123). The value for the help
ID is arbitrary as long it is unique. In this sample, the value for help ID is
the value of relative control ID plus 2000. This keeps help IDs ordered.
- Add help strings to the RC file. To keep all strings in one place is one
of the basic purposes. You can edit RC with any text editor adding strings like:
STRINGTABLE
BEGIN
IDH_MD_BUTTON1 "Test button."
IDH_MD_HELP_BUTTON "Show CHM help."
<...>
END
- In each dialog class where you want ot implement context help add OnHelpMenu
handler, which (a) handles all WM_HELP messages such as related to "?" Help,
SHIFT+F1, (b) redirects to OnF1Help messages related to F1, (b) process messages
redirected from OnContextMenu that handles "What's This?" context help. Since
"What's This?" help is originally handled (WM_CONTEXTMENU message) with
OnContextMenu handler, you should add it too. The sample shows the
implementations of OnHelpMenu and OnContextMenu functions that should be a bit
customized for each dialog to reflect CContextHelpMappings's names of variables
for context menu IDs mappings in the dialog.
- Finally, add to the application class OnF1Help handler to handle F1 help so
that that each dialog can access the handler.
How to set background color for owner-draw static control?
Details
When you need to provide color pickup option for an application, you may want
to show current color within some area without invoking color selection dialog,
and show newly selected color within the same area. One option to make this is
to use small colored bitmaps and set a bitmap with specific color for static
control. Another option, which is more economic (since does not require additional
bitmaps), is to paint the background area of static control with
specific color. Here is how the result looks like:
On the left is static control, and on the right is button, which invokes standard
Windows color selection dialog.
You can examine the Win32 code here. In a nutshell
you add SS_OWNERDRAW style to static control, and handle WM_DRAWITEM message in window procedure
of static control's owner window (main window in sample application) to paint
its background color. In sample application the static control is created with CreateWindow, but
you can surely use Visual Studio dialog template, drag-and-drop static control and add
SS_OWNERDRAW style to RC file.
Sounds simple? Indeed it is a fairly straightforward method,
which has however some pitfalls if you're are not careful about details:
- You should adhere to fairly standard receipt of painting an area. That is, firstly, you save current
device context before painting, and return it to the system when finished with painting.
Secondly, you should backup in similar way current system brush object (or more generally GDI
objects) used for painting background, and restore system brush when finished with painting.
In between, you use device context provided with painting method that is in our
case represented by the device context provided with WM_DRAWITEM message to paint an area:
case WM_DRAWITEM:
if (wParam == IDC_STATIC_OWNERDRAW)
{
lpdis = (LPDRAWITEMSTRUCT) lParam;
if (lpdis->CtlID == IDC_STATIC_OWNERDRAW)
{
// Standard painting procedure is to save
// system DC, brush (SaveDC, SelectObject)
// before using custom, and restore to saved when
// finished with SelectObject, RestoreDC.
nSavedDC = SaveDC(lpdis->hDC);
hBrush = CreateSolidBrush(g_clrBkgroundColor);
hOldBrush = (HBRUSH)SelectObject(lpdis->hDC,
hBrush);
FillRect(lpdis->hDC, &lpdis->rcItem, hBrush);
SelectObject(lpdis->hDC, hOldBrush);
DeleteObject(hBrush);
RestoreDC(lpdis->hDC, nSavedDC);
return TRUE;
}
}
break;
Notice that Select function serves two purposes: loads newly created brush and returns
system brush, which must be saved for later restore. This cooking receipt for painting
shows that when finished with painting, we still need to inform the system that our own
work is done by returning TRUE so that the system does not make own painting.
- Do not presume that if you set owner-draw style for static control with SS_OWNERDRAW style,
and other controls do not have this sort of style, WM_DRAWITEM will not be generated while
painting client areas for other controls e.g. button control. In fact, try to comment this
line in the handler above:
case WM_DRAWITEM:
// if (wParam == IDC_STATIC_OWNERDRAW)
lpdis = (LPDRAWITEMSTRUCT) lParam;
if (lpdis->CtlID == IDC_STATIC_OWNERDRAW)
As the result, the sample application may crash (access violation reading error) when you pickup
a color from color dialog or even when invoke About dialog and click OK. When repainting
the system sends also WM_DRAWITEM message for button control (you can check out wParam, which
is the control ID, to see that) even though the button does not have owner-draw style. For
some reason the WM_DRAWITEM message for button is spurious, and DRAWTIMESTRUCTURE members are
not defined as seen from the debugging output (VS.NET 2003), which corresponds to handling
of WM_DRAWITEM sent with wParam containing button ID:
The access violation can be easily avoided by sorting out WM_DRAWITEM messages to
handle only those messages where wParam equals ID of static control.
- When you finished with selecting a color from colors dialog and click OK, force application
to repaint static control with newly selected color. You can do in different fashions using
InvalidateRect, RedrawWindow, SetWindowPos, or sending WM_DRAWITEM to static control explicitly
with SendMessage provided that DRAWITEMSTRUCT is properly filled. For example as in the sample:
RedrawWindow(g_hwndStatic_OwnerDraw,
NULL /* invalidate entire client area */,
NULL /* invalidate entire client area */, RDW_INVALIDATE);
- The g_clrBackgroundColor variable that stores color from color selection dialog
should be global, not local to WndProc, since WM_DRAWITEM message relative to owner-draw static
control can be caused not only due to RedrawWindow when color selection dialog is
closed, but also due to main window resize, About dialog close.
The sample is generated with VS.NET 2003 (can be imported to VS.NET 2005 with no changes
in source files). You can see changes made to skeleton application generated with
Win32 Wizard of VS.NET 2003 by using UI-enabled compare tool such as Beyond Compare 3:
How to animate Win32 window?
Details
You can see how the animation works in sample Win32 application.
To start animation choose animation type from Commands menu:
- Slide left to right
- Slide right to left
- Slide top to bottom
- Slide bottom to top
- Collapse inwards
- Collapse outwards
- Blend (hide)
- Blend (show).
The sample demonstrates the animation that realized with AnimateWindow Win32 function. The steps to make
animation in MFC applications are essentially the same. To make AnimateWindow function operate correctly
there are a couple of essential points to notice:
- All painting relative to AnimateWindow occurs in respond to WM_PRINTCLIENT or WM_PRINT
messages. This differs from handling of painting in typical window, which occurs in respond
to WM_PAINT message. In fact, when implementing painting caused by AnimateWindow you can safely forget about
WM_PAINT message since it is not even sent. You don't need to implement painting in response to
both WM_PRINTCLIENT and WM_PRINT, it is enough to realize painting in response to WM_PRINTCLIENT
message only (alternatively you can use WM_PRINT only).
- Making a certain type of animation requires setting relative animation flags as third parameter
of AnimateWindow function. In fact, this parameter is frequently combination of flags and it can be
quite tedious and troublesome to figure out what combination is required for certain type of animation. As a
cooking receipt the following table enlists combination of flags that make possible corresponding animation
in sample application:
Animation Type | Animation flags |
Slide left to right | AW_SLIDE | AW_HOR_POSITIVE |
Slide right to left | AW_SLIDE | AW_HOR_NEGATIVE |
Slide top to bottom | AW_SLIDE | AW_VER_POSITIVE |
Slide bottom to top | AW_SLIDE | AW_VER_NEGATIVE |
Collapse inwards | AW_HIDE | AW_CENTER |
Collapse outwards | AW_ACTIVATE | AW_CENTER |
Blend (hide) | AW_BLEND |
Blend (show) | AW_BLEND | AW_HIDE |
- Though description of AnimateWindow function does not say it specifically, the call to AnimateWindow function should be
preceded by functions like ShowWindow or SetWindowPos. Without such call the animation does not work (I have never heard
that it is possible to make animation with a single call to AnimateWindow). Anyway such receipt works in sample
application.
- Finally, the window used for blend animation cannot be a child window, and must be a top-level window. This requirement
does not relate to other types of animation.
If you use screen resolution other than 1280x1024 the window used for blend animation will be somewhat
misadjusted in relation to the application window, but it has nothing to do with animation itself, which
should work fine (you may want to update one line of code in provided sample if you prefer another
resolution).
How to make URL link in Win32 dialog?
Details
There are frequently occasions when we need to navigate user to some
internet resource from desktop application. There are many posts regarding
how to do it with MFC or you can use novel SysLink Control, provided that your
minimum operation system is Windows XP (comctl32.dll version 6 is required).
What about a situation when we need to have URL link in compact Win32 application?
Below I'll describe steps required to implement URL link in Win32 dialog. This solution
provides URL link with minimum programming, and enables you to accomplish 3 tasks:
- Navigate user to internet location by single click.
- Set URL font and color.
- Set cursor (usually hand cursor).
This solution does not track if URL was previously visited by setting
specific hyperlink color since in most situations you don't need it. To begin
you can use boilerplate Win32 application generated with VS.NET 2003 and add static
control (IDC_STATIC_LINK) to About dialog to transform it to hyperlink. Importantly,
set Notify property to TRUE (default is FALSE) so that static control would send
notification messages when clicked or passed over with cursor.
Handling single click for internet navigation
You handle click event to launch IE with specified URL by handling command message in
About's dialog procedure (no subclassing is required for this task), and by doing
this you'd implement the major and most simple part of the task:
case WM_COMMAND:
if (LOWORD(wParam) == IDC_STATIC_LINK)
{
ShellExecute(NULL, "open",
"http://netston.tripod.com",
0, 0, SW_SHOWNORMAL);
}
Note that ShellExecute function requires inclusion of "shellapi.h" header
file and addition of shell32.lib library in project settings.
Setting font and color of static control
You can set font (size and underlining) by handling WM_INITDIALOG message in
About's dialog procedure with custom SetFont(HWND hDlg) function like this:
void SetFont(HWND hWnd)
{
HFONT hfnt;
LOGFONT lf;
// Specify the font to use (stored in a LOGFONT structure).
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = FW_DONTCARE;
lf.lfUnderline = TRUE; // underlining
lf.lfStrikeOut = FALSE;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfQuality = DEFAULT_QUALITY;
lf.lfPitchAndFamily = FF_DONTCARE;
_tcsncpy(lf.lfFaceName, _T("Arial"), 32);
hfnt = CreateFontIndirect(&lf);
SendMessage(GetDlgItem(hWnd, IDC_STATIC_LINK),
WM_SETFONT, (WPARAM)hfnt, TRUE);
}
In the same manner, you can set the font color (hyperlink color) by
handling WM_CTLCOLORSTATIC message in About's dialog procedure:
if (GetDlgItem (hDlg,IDC_STATIC_LINK) ==
(HWND)lParam /* handle to static control */)
{
SetTextColor((HDC)wParam /* handle to display context */,
RGB(0, 0, 255));
// Use TRANSPARENT so that background is not changed while
// drawing a text.
SetBkMode((HDC)wParam /* handle to display context */,
TRANSPARENT);
// If the dialog box procedure returns FALSE, then default
// message handling is performed.
return (BOOL)GetStockObject(HOLLOW_BRUSH);
}
Up to this point we have blue-colored URL control, which navigates us
to given HTTP resource (netston.tripod.com in this sample), but our
hyperlink is still missing a specific cursor (usually hand cursor) when
mouse passes over the link.
Setting hyperlink cursor
To perform this task a subclassing technique is used. Usually all messages
generated by controls of a dialog are handled in a single dialog procedure. You
can handle messages generated by specific control in own procedure. While
subclassing you set own control procedure with SetWindowLong, which return
an address of an older procedure. You can set own static control procedure
(StaticSubclassedFunc) by handling WM_INITDIALOG message in About's
dialog procedure:
case WM_INITDIALOG:
g_wpOldWndProc = (WNDPROC)SetWindowLong(
GetDlgItem (hDlg, IDC_STATIC_LINK), GWL_WNDPROC,
(DWORD)StaticSubclassedFunc);
Don't forget to declare g_wpOldWndProc at appplication level (global
variable).
Finally, when mouse passes over the static control, which is now a "normal"
URL hyperlink, the cursor is changed in subclassed function:
LRESULT APIENTRY StaticSubclassedFunc(HWND hWnd, UINT Message,
WPARAM wParam, LONG lParam)
{
::SetCursor(LoadCursor(NULL, IDC_HAND));
}
You can download sample application from here.
Also you can play with live example that employs just described method by downloading Rational Typist
program (free). The URL hyperlink is located on the right side of the toolbar of this program.
How to draw a single divider line in VS.NET's resource editor?
Details
The single divider line is a common feature of typical Windows applications. For example, most of tabs in Word 2003's
Tools/Options dialog have simple divider lines like this one in Save tab:
Interestingly, VS.NET's toolbox does not have a control to draw a single line in the resource
editor, and even if you'd play with group box control by resizing its height in RC file manually,
the resulting line would not look perfect (i.e. setting height to 0 or 1 does not work as you might
have desired). You may come to the point that group box control use for this purpose is not the answer,
and it should be a simple solution. You are right! There are two solutions. First, you can add
empty static text in resource editor, and then
edit resource script manually by adding style WS_EX_STATICEDGE and setting static control's height to 1
(mention also zero before WS_EX_STATICEDGE):
LTEXT "",IDC_STATIC,30,31,63,1,0,WS_EX_STATICEDGE
In the second solution, you also edit RC file manually by adding CONTROL element with
SS_ETCHEDHORZ style:
CONTROL "",-1,"Static",SS_ETCHEDHORZ,10,10,62,5
When drop-down control is expanded how to make all its items visible?
Details
When running your application you can see the following picture for the combo box
common (and also standard) control:
The problem is that you have populated combo box with many items and you don't see all of them. Where are the
missing items? Normally you would expect that combo box looks like:
Further, while working with VS.NET 2003 (or 2005) resource editor you might find it is not very intuitive
how to stretch down expanding area of combo box since in resource editor you'd see the following:
There are handles to resize the combo box area itself, but how to resize a drop-down area only? (At the run-time, this area is visible only when the down arrow is pressed.) The answer is that the combo box, unlike
other common controls, have 2 sets of handles to be resized: the first one is visible on the
picture above, and the second one becomes visible in resource editor when you press down arrow (you
can use only handles filled with color for resizing):
Finally, the middle-bottom solid handle enables you to resize drop-down area of the combo box control.
How to make toolbar icons for Win32 (or MFC application)?
Details
If you'd look into Visual Studio generated boilerplate files when you finish with
its application wizard for Win32 project, you will not find a resource containing toolbar icons
neither for the files generated with VS.NET 2005, 2003, nor with VS 98.
In contrast to project created by default with MFC wizard, you need
to start from scratch creating toolbar and its icons in Win32 project.
Before all, several bumpy things to note.
- Toolbar pictures are not separate resources. In fact, all pictures should
be located on a single bitmap strip (BMP file).
- Bitmap strip consists of 16 [width]x15[height] pictures, where sizes mesured in pixels. So if
your application has 10 toolbar pictures, you would need to provide 160x15 pixels bitmap resource.
Windows is quite untolerating to other bitmap heights. For example, if you use 16x16
pictures your application would likely crash. The use of not default picture sizes requires
a bit of additional work.
- You cannot change toolbar's color depth in Visual Studio image editor. By default, toolbar
pictures have 4-bit color depth (16 colors). In contrast, you can change color depth for icon resources, and
for the bitmap resources. The use of not default color depths requires
some additional work.
To change icon color depth, you need to create new icon at
first (since "device" property is read-only), and then choose image type in Image menu. The color depth for bitmaps
is read/write, and can be changed directly in its properties.
By the way, icon resources in VS.NET 2005/2003 can be 1 (monochrome), 4 (16 colors),
8 (256 colors), 32 (> 16 milions colors) bits depth. You can view, but cannot create 32-bit color icon in Visual Studio.
Up to this point we examined some pitfalls, and now we can return to the main question. One simple solution is to start from default toolbar from MFC application (Toolbar.bmp) by copying it into Win32 application. Here is the default MFC toolbar picture
for SDI application:
Once imported into Win32 project, you can edit toolbar and add pictures in toolbar editor like this:
Editing toolbar, and adding new pictures is simple, but there are several tricks worth
mentioning for reference:
- The toolbar's backgroud should be painted with menu color. In color palette position cursor over the
grey and right-click. The palette's symbolic rectangle representing backgroung color turns grey:
Now, erase tool can be used to paint with the grey background color.
- There is always fresh new area for the next image in the top editor's frame. If you leave it unpainted, this area does not matter
i.e. it is not translated finally into RC file. To tell image editor that you want to add a new image, merely begin to paint last button area
(new clean button area is automatically added to the right as soon as one pixel is painted).
- Pressing Delete does not removes the image from the toolbar, you need to drag it off the toolbar area. Dragging
is also the way to move a button.
- Last (not obvious!) trick is about making a separator area on the toolbar. You need to drag a button
to the right or left (approximately one half the button width) a make space between buttons. When you save RC file,
Visual Studio automatically adds SEPARATOR line in toolbar code. In the similar way, the separator space is removed
(guess how).
As we saw editing toolbar as toolbar resource is a bit tricky. There is also an alternative way to create a toolbar and you might find it more convenient. Start with a bitmap resource and then, after you finished with adding pictures, turn it into toolbar resource. It is more straigtforward process, but remember that toolbars are 4-bit resources, and
if you created your bitmap as 8-bit (256 colors) resource, and want to convert it into a toolbar (right-click in the any of image editor's panes and choose Toolbar Editor...), you will see a message "Toolbars can only contain 16 colors ..." that will prompt you to automatically decrease color depth to 4-bit. You might have avoided it if you had started your bitmap as 4-bit resource!
How to write own XML parser (Read/Write)?
Details
When your application uses persistense realized with small satellite XML file,
which does not use complex XML structures, you might need 2 function only: one for reading XML
tag value, and another for writing XML tag value provided that XML tag name is given
as parameter. Having in mind that standard XML parser shipped with Windows XP resides in 3 system DLL
- msxml4.dll (1.17 MB), msxml4a.dll (43.5 KB), and msxml4r.dll (80.5 KB) - own XML parser might
considerably decrease the size of your aplication if you plan to include XML parser into the
download.
The simple XML parser is built as Unicode version. This provides compatibility with
targeted multilanguage application (built with Unicode), which nevertheless uses ANSI names and
values to persist its state. The XML parser included in download has the following interface:
char* XMLReadValue(char* pszTagName /* always ANSI*/);
void XMLWriteValue(char* pszTagName /* always ANSI*/, char* pszTagValue /* always ANSI*/);
Because, the parser has been wrote to provide the very basic and simple XML persistense needs
of a small application, it has the following requirements and limitations.
- Open/closing tags should be within the same line.
- Only one pair of open/closing tags within the same line.
- No spaces between tag value and surrounding open/closing tags.
- XML file is ANSI text file (e.g. Windows-1252 encoding).
- Build with UNICODE.
Notice that some requirements/limitations are pretty artificial and came from the motive to
save programming efforts. You can modify the program to make it more general.
What is the algorithm realized in this functions? XMLReadValue open XML file as byte stream,
and simply reads one by one line. In a loop, we check - substr- whether the tag name passed as an input
parameter is inside the current line, make simple parsing and return the tag value if found. Notice that
I use C++ standard library <string> header since it provides convenient functions for reading
file line-by-line (getline) and parsing strings.
XMLReadValue algorithm is slightly more complicated, and it uses CreateFileA/WriteFile for writing.
- Read entire XML file, find a line with target line to update
(e.g. tag name "OrderBy").
- Read into a separate string top part of the XML file (before the targeted line).
- Read into a separate string bottom part of the XML file (after the targeted line).
- Updated targeted line into a separate string.
- Concatanate and write entire XML file as new one (delete file and create new
one, ANSI).
Now, when XML parsing algorithm is outlines here is the code.
Notice that I use CreateFile suffixed with "A" (CreateFileA) because our XML file is ANSI-type,
and the parser is built as Unicode version (SetFilePointer, WriteFile auto-determine what
type of writing to use based on whether CreateFileW or CreateFileA used, and they don't have
"W/A" suffixed versions).
How to determine encoding of a text file?
Details
One typical situation when you might need it is when importing dictionary items from
a text file into a Win32 or MFC application. Typically, this kind of application is
UNICODE-based, and we need to read dictionary items from a text file (i.e. French-English,
Japanese-English pairs). What kind of text file is used to keep these items? What hidden
symbols at the beginning of file we need to take into account when transforming text strings
from this file to UNICODE strings? For example, UTF-8 encoding has a 3-bytes preamble
sometimes called BOM (because UTF-8 has no notion of byte order, this name is a misnomer for UTF-8):
EF BB BF
Thus, if our application uses MultiByteToWideChar to transform UTF-8 string to
TCHAR string we should trim left 3 bytes before that. Interestingly, UTF-8 preamble is optional
so that UTF-8 file can start with actual dictionary item.
One sideway to determine the encoding of a text file is to open file in Windows
Notepad, and choose File/Save As... The encoding combo box shows encoding type (ANSI or UNICODE or
UNICODE big endian or UTF-8). Frequently it will work, but what if you need to know for sure
if UTF-8 file has a optional 3-bytes preamble? Notepad is no use for this. In general, Windows has no
tools to determine the encoding of UNICODE file. Typically, a Hex Text Viewer that comes with
third-party advanced text editors will help to determine it. Let's say one Russian character is written into
a text file encoded with UTF-8. Here is snapshot taken with Notepad++ v3.8 editor
(choose Plugins/HEX-Editor/View Editor):
Because 3 first bytes are EF, BB, and BF this is indeed UTF-8 file. The 5,6th byte is a single Russian character
(2 bytes), and 6,7th bytes are carriage return (CR: 0x0D - VK_RETURN) and line feed (LF: 0x0A) characters.
Notepad++ can show hidden characters in other view:
Notice that UTF-8 is a variable-length encoding that encodes ASCII characters with one byte, letters with diacritics,
Cyrillic (and Greek, Cyrillic, Armenian, Hebrew, Arabic, Syriac and Thaana) with 2 bytes. Three bytes are used for
the rest of Basic Multilingual Plane. Four bytes are used for the rest of characters. In most cases, UTF-8 saves
space (almost 2 times if only European languages are used).
Here is the list of BOM used in other typical encodings:
UTF-16 Big Endian: FE FF
UTF-16 Little Endian: FF FE (reversed to BE)
UTF-32 Big Endian: 00 00 FE FF
UTF-32 Little Endian: FF FE 00 00 (and one of the following byte sequences: [ 38 | 39 | 2B | 2F | 38 2D ])
You can experiment with Notepad++ to see these characters. By the way Notepad++ enables you to change
UNICODE encoding on the fly!
How to paint an area?
Details
This code paints red rectangle inside Win32 window:
RECT rc;
HBRUSH hbrush;
rc.left = 600; rc.top = 100; rc.right = 700; rc.bottom = 200;
hbrush = CreateSolidBrush(RGB(255,0,0));
HDC hdc = GetDC (hwnd);
FillRect(hdc, &rc, hbrush);
DeleteObject(hbrush);
ReleaseDC(hwnd, hdc);
How to delete directory in Win32?
Details
You might be surprised, but Windows platform SDK does not have a function to delete NOT
EMPTY directory! Only Win32's API RemoveDirectory function is provided, which deletes
directory when it does not contain other directories and files. Therefore this question
has at least three parts:
- How to delete an empty directory in Win32? The answer is to use simply
RemoveDirectory function.
- How to delete not empty directory in Win32 that has files, but does not have subdirectories?
- How to delete an empty directory in Win32 that has files and subdirectories? The answer
is to use the code.
How to share variables between different implementation files in Win32?
Details
One seemingly obvious solution is to declare global variable in shared header file (i.e.
stdafx.h) and set its value in one implementation file. Alas, this it not enough: you'd
likely face LNK2005 error if you have opted for this solution. The right solution is to use
C++ extern keyword:
- Declare in shared header file (such as stdafx.h) extern global variable:
#include <string>
using namespace std;
extern string g_s;
- Re-declare global variable in one implementation file and initialize it to some value:
string g_s; // file scope
g_s = "global variable"; // initialization function
How to handle double click in list view control (or tree view control)?
Details
You've probably noticed that CListCtrl (or CTreeCtrl) controls do not have handlers
for double click (or right click) explicitly. To handle double click event insert an entry into
the message map of a dialog that host the control and hanle event in OnDoubleClick function:
ON_NOTIFY(NM_DBLCLK, <controlid> OnDoubleClick)
What is the syntax to add/remove style for window, control, property sheet etc?
Details
The syntax is somewhat bizarre, you just need to memorize it:
m_psp.dwFlags |= PSP_HIDEHEADER; // add property page style
m_psp.dwFlags &=~ PSP_HIDEHEADER; // remove property page style
How to enable HTML style help in MFC property pages?
Details
This question might seem trivial, however default property page implementation
uses WinHelp, not HTML help (i.e. uses old-style HLP files instead of newer CHM files)
in VS 98 and even in VS.NET 2003! In the result, you need to invest some extra effort.
Because CPropertyPage class does not have OnHelp handler
you should provide PSN_HELP message handning in Win32 style and take care not to cause
malfunctioning of other handlers like OnWizardNext, OnWizardFinish etc if you implemented them
and which MFC CPropertyPage class provides. The solution is like:
BOOL CMySSPropPageWelcome::OnNotify(WPARAM wParam,
LPARAM lParam, LRESULT* pResult)
{
PSHNOTIFY* pPSHNOTIFY = (PSHNOTIFY*)lParam;
char szPath[200];
LPTSTR pszPath;
SearchPath(NULL, "MySS.chm", NULL, 200, szPath, &pszPath);
switch (pPSHNOTIFY->hdr.code)
{
case PSN_HELP:
::HtmlHelp(NULL, szPath, HH_DISPLAY_TOPIC, 0);
return -1; // message handled, no futher actions
break;
}
// Contitue handling notification messages other then PSN_HELP
return CPropertyPage::OnNotify(wParam, lParam, pResult);
}
LRESULT CMySSPropPageWelcome::OnWizardNext()
{
// ...
return CPropertyPage::OnWizardNext();
}
Notice that in MFC you don't need to add PSP_HASHELP flag in property page constructor to
show help button. In MFC's CPropertyPage implementation this flag is enabled by default.
How to customize fonts used in dialog e.g. make text of static control bold?
Details
Interestingly, VC++ 6.0 (or 7.0) dialog editor does not have options to setup font properties when
showing text. There is only programmatic solution! Suppose a dialog has static control with IDC_WP_STATIC_TITLE as ID. Then just write:
CFont fong;
font.CreatePointFont(12,"Tahoma");
GetDlgItem(IDC_WP_STATIC_TITLE)->SetFont(&font);
Notice that we used convenient CreatePointFont instead of much longer writing:
CFont font;
font.CreateFont(12, // nHeight
0, // nWidth
0, // nEscapement
0, // nOrientation
FW_NORMAL, // nWeight
TRUE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
"Tahoma"); // lpszFacename
|