Introduction
In order to extend the first article I wrote about a simple AppWizard for Screen Savers, I would like to contribute with this other article about a more complex AppWizard to generate the skeleton of a Win32 Application (without MFC) that lets the user choose between a Window based Win32 application and a Dialog based Win32 application, featuring the possibility of integrating the Common Controls Library (commctl32.lib) and/or the Winsock Library (ws2_32.lib).
Background
It will be useful reading the first article I wrote: Screen Saver AppWizard, to get the general idea of working with the Custom AppWizard that the Visual C++ 6.0 exports, and to get the hints for a correct compilation of the Wizard.
Using the code
As said before, this is a little more complex wizard than the Screen Saver one. In fact, this one features a step dialog needed by the wizard to determine the type of application the user requests and the types of libraries needed. Let's start creating the wizard, selecting the Custom AppWizard from the VC++ Projects tab:
Here you can notice that we specify that we'll need 1 step to configure our wizard.
Initial Environment
The initial environment after the creation of our Wizard project comes like the pane snapshots below:
The Class View Pane
The File View Pane
The Resource View Pane
As you can see in the figures, there's quite more files than the Screen Saver Wizard. This is because we indicated that we need one step configuration: this is obtained using a Dialog Resource (IDD_CUSTOM1
) mapped to Dialog class CCustom1Dlg
:
Customizing the Step 1 Dialog
In order to get to our goal, we need to customize the IDD_CUSTOM1
resource to feature our application's needs:
- Choosing between two types of applications (Window Based or Dialog Based).
- Letting specify the use of Common Controls Library.
- Letting specify the use of the Winsock Library.
So, the final result of our customization will be something like:
Keep in mind that this approach is valid for any number of steps (dialogs) you specify. And you do not have to mind about the switch (to next or to previous) between step dialogs in your wizard because this behavior is handled by a specific class that the AppWizard framework supplies: the CDialogChooser
class (see Class Pane figure).
Implementing the Step 1 Dialog
All the controls included in the dialog must be mapped to methods or to instance variables of the Dialog itself. So, we'll map the selection of the radio buttons (that specifies the type of application) to two relative class methods (using the ClassWizard) that will set a boolean value to indicate the application type, BOOL m_bWindowBased
:
And the relative afx mapping:
afx_msg void OnRadioDialogBased();
afx_msg void OnRadioWindowBased();
BEGIN_MESSAGE_MAP(CCustom1Dlg, CAppWizStepDlg)
ON_BN_CLICKED(IDC_RADIO_DIALOG_BASED, OnRadioDialogBased)
ON_BN_CLICKED(IDC_RADIO_WINDOW_BASED, OnRadioWindowBased)
END_MESSAGE_MAP()
...
void CCustom1Dlg::OnRadioDialogBased()
{
m_bWindowBased = FALSE;
}
void CCustom1Dlg::OnRadioWindowBased()
{
m_bWindowBased = TRUE;
}
In the same way, we have to map checkboxes that indicate the type of library we want to include and initialize for the application. So, we map every checkbox resource, still using the ClassWizard, to two boolean values:
These values will be mapped and handled through DDX Data exchange:
void CCustom1Dlg::DoDataExchange(CDataExchange* pDX)
{
CAppWizStepDlg::DoDataExchange(pDX);
DDX_Check(pDX, IDC_CHECK_INIT_COMMON_CONTROLS, m_bInitCommonControls);
DDX_Check(pDX, IDC_CHECK_USE_WINSOCK, m_bUseWinsock);
}
Done with the initial implementations, we can now concentrate in the logic part of creating the wizard. The CCustom1Dlg
method needed to be implemented to let the wizard know the selections made by the user on what kind of application he wants, is OnDismiss()
:
BOOL CCustom1Dlg::OnDismiss()
{
if (!UpdateData(TRUE))
return FALSE;
if(!m_bWindowBased){
SimpleApplicationWizardaw.m_Dictionary.SetAt("DIALOG_BASED","Yes");
SimpleApplicationWizardaw.m_Dictionary.RemoveKey("WINDOW_BASED");
}else{
SimpleApplicationWizardaw.m_Dictionary.SetAt("WINDOW_BASED","Yes");
SimpleApplicationWizardaw.m_Dictionary.RemoveKey("DIALOG_BASED");
}
if(m_bInitCommonControls)
{
SimpleApplicationWizardaw.m_Dictionary.SetAt("INIT_COMMON_CONTROLS","Yes");
}
else
{
SimpleApplicationWizardaw.m_Dictionary.RemoveKey("INIT_COMMON_CONTROLS");
}
if(m_bUseWinsock)
{
SimpleApplicationWizardaw.m_Dictionary.SetAt("USE_WINSOCK","Yes");
}
else
{
SimpleApplicationWizardaw.m_Dictionary.RemoveKey("USE_WINSOCK");
}
return TRUE;
}
This part needs a little attention, even if it is extremely easy to understand. Every wizard uses variables to understand what kind of decisions must be made, and it achieves this through a particular member of the main AppWizard (SimpleApplicationWizardAw
) class that is a 'dictionary'. That keeps trace of the variables needed by the wizard.
These variables are set by this method:
SimpleApplicationWizardaw.m_Dictionary.SetAt("<SOME VARIABLE TO BE SET>","Yes");
and removed by this other method:
SimpleApplicationWizardaw.m_Dictionary.RemoveKey("<SOME VARIABLE TO BE REMOVED>");
These variables are then got by the framework during the creation of an application in order to do something or not to do.
The variables are useful even during the compiler/linker configuration in the main AppWizard class method void CSimpleAppWizardAppWiz::CustomizeProject(IBuildProject* pProject)
:
void CSimpleApplicationWizardAppWiz::CustomizeProject(IBuildProject* pProject)
{
CComPtr<IConfigurations> pConfigs;
HRESULT hr=pProject->get_Configurations(&pConfigs);
if(FAILED(hr))
{
AfxMessageBox("An error occurred while
obtaining the IConfigurations interface pointer");
return;
}
CComPtr<IConfiguration> pConfig;
CComVariant index;
VARIANT dummy = {0};
CComBSTR Name;
CString text;
CString output;
long Count=0;
pConfigs->get_Count(&Count);
for(int i=1; i <= Count; i++)
{
index=i;
hr=pConfigs->Item(index, &pConfig);
if(FAILED(hr))
{
AfxMessageBox("An error occurred
while obtaining the IConfiguration pointer");
return;
}
pConfig->get_Name(&Name);
text = Name;
if (text.Find("Debug") == -1)
output = "Release";
else
output = "Debug";
text.Format("/out:\"%s/%s.exe\"",output,m_Dictionary["Root"]);
pConfig->AddToolSettings(L"link.exe", text.AllocSysString(), dummy);
pConfig->AddToolSettings(L"mfc", L"0", dummy);
pConfig->AddToolSettings(L"link.exe", L"/subsystem:windows", dummy);
pConfig->AddToolSettings(L"link.exe", L"/incremental:yes", dummy);
pConfig->AddToolSettings(L"link.exe", L"/machine:I386", dummy);
pConfig->AddToolSettings(L"link.exe",
L"/nodefaultlib:\"MSVCRTD\"", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/D \"_WINDOWS\"", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/nologo", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/D \"_MBCS\"", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/D \"WIN32\"", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Od", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/MD", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/W3", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/ZI", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/GZ", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Zi", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Oi", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);
pConfig->AddToolSettings(L"cl.exe", L"/Gz", dummy);
pConfig->AddToolSettings(L"link.exe", L"kernel32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"user32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"gdi32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"winspool.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"comdlg32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"advapi32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"shell32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"ole32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"oleaut32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"uuid.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"odbc32.lib", dummy);
pConfig->AddToolSettings(L"link.exe", L"odbccp32.lib", dummy);
CString cstr;
if(m_Dictionary.Lookup(TEXT("INIT_COMMON_CONTROLS"),cstr))
{
pConfig->AddToolSettings(L"link.exe", L"comctl32.lib", dummy);
}
if(m_Dictionary.Lookup(TEXT("USE_WINSOCK"),cstr))
{
pConfig->AddToolSettings(L"link.exe", L"ws2_32.lib", dummy);
}
pConfig=NULL;
}
pConfigs=NULL;
}
Template Files
As seen for the Screen Saver AppWizard, the Wizard needs files as templates to create the appropriate application. There are two files that are always present: confirm.inf and newproj.inf; and some others that must be added and that describe and implement the application the Wizard will create:
The files:
- root.cpp
- root.rc
- resource.h
- Safx.h
- Safx.cpp
are the template files that implement the Win32 application the Wizard will create, and must be registered in the file newproj.inf.
Let's analyze the root.cpp file that is the main program of the target application:
#include "stdafx.h"
LRESULT CALLBACK WindProc(HWND hwnd,
UINT message, WPARAM wParam, LPARAM lParam);
void Init(HWND);
$$IF(USE_WINSOCK)
WSADATA wsaData;
$$ENDIF
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
WNDCLASS wnd;
HWND hWnd;
static TCHAR szAppName[] = TEXT ("$$Root$$") ;
wnd.style = CS_HREDRAW | CS_VREDRAW;
$$IF(WINDOW_BASED)
wnd.cbWndExtra = 0;
$$ELSE
wnd.cbWndExtra = DLGWINDOWEXTRA;
$$ENDIF
wnd.cbClsExtra = 0;
wnd.hCursor = LoadCursor(NULL,MAKEINTRESOURCE(IDC_ARROW));
wnd.hIcon = LoadIcon(NULL,MAKEINTRESOURCE(IDI_WINLOGO));
wnd.hInstance = hInstance;
wnd.lpfnWndProc = WindProc;
wnd.lpszClassName = szAppName;
wnd.lpszMenuName = NULL;
wnd.hbrBackground = (HBRUSH)(COLOR_WINDOW);
if(!RegisterClass(&wnd))
{
return 0;
}
$$IF(WINDOW_BASED)
hWnd = CreateWindow(wnd.lpszClassName,
"$$Root$$",
WS_BORDER |
WS_OVERLAPPED|
WS_VISIBLE|
WS_SYSMENU,
CW_USEDEFAULT,
CW_USEDEFAULT,
300,
300,
NULL,
NULL,
hInstance,
0
);
$$ELSE
hWnd = CreateDialog(hInstance,szAppName,NULL,NULL);
$$ENDIF
$$IF(INIT_COMMON_CONTROLS)
InitCommonControls();
$$ENDIF
PostMessage(hWnd,WM_COMMAND,WM_INITAPPLICATION,0);
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WindProc(HWND hwnd,
UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message) {
case WM_CREATE:
{
break;
}
case WM_DESTROY:
{
$$IF(USE_WINSOCK)
WSACleanup();
$$ENDIF
PostQuitMessage(0);
break;
}
case WM_COMMAND:
{
switch(LOWORD(wParam)) {
case WM_INITAPPLICATION:
{
Init(hwnd);
break;
}
default:
break;
}
break;
}
case WM_PAINT:
{
break;
}
default:
return DefWindowProc(hwnd,message,wParam, lParam);
}
return DefWindowProc(hwnd,message,wParam, lParam);
}
void Init(HWND hWnd)
{
$$IF(USE_WINSOCK)
WORD wVersionRequested;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
MessageBox(hWnd,
"Couldn't find a usable Winsock DLL",
"Winsock Error",MB_OK);
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
MessageBox(hWnd,
"Couldn't find a usable Winsock DLL",
"Winsock Error",MB_OK);
return;
}
$$ENDIF
}
There are some points of interest here: first, for sure you'll have noticed those strange macros $$IF
... $$ELSE
... $$ENDIF
; these ones are needed by the AppWizard framework to check if some variables are present in the dictionary, and the Wizard will decide to include or not blocks of code relatively to these variables. In fact, the wizard will specify DLGWINDOWEXTRA
only in the case the user selected the radio button for the Dialog Based application (and the CCustom1Dlg::OnDismiss()
will create the variable in the dictionary); or again the initialization of the common controls will be included only if the checkbox in the Wizard Step Dialog will be checked. Same for the initialization code for the Winsock Library. This approach is done even in the Safx.h:
#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
$$IF(INIT_COMMON_CONTROLS)
#include <commctrl.h>
$$ENDIF
$$IF(USE_WINSOCK)
#include <winsock2.h>
$$ENDIF
#include "resource.h"
#endif
Only the header files of the appropriate kind of application will be included.
Macros are even in the newproj.inf to decide the inclusion of the .rc file (only in the case of a Dialog App):
$$
$$
$$
$$
$$
$$
$$
$$
$$
$$
$$
$$
$$
ROOT.CPP $$Root$$.cpp
RESOURCE.H resource.h
$$IF(DIALOG_BASED)
ROOT.RC $$Root$$.rc
$$ENDIF
SAFX.CPP StdAfx.cpp
SAFX.H StdAfx.h
Notice that the SAfx.h and SAfx.cpp files will be remapped to their normal names here.
And even in confirm.inf to describe the files that will be created by the wizard:
These files will be created:
$$Root$$.cpp
StdAfx.h
Stdafx.cpp
resource.h
$$IF(DIALOG_BASED)
$$Root$$.rc
$$ENDIF
$$IF(INIT_COMMON_CONTROLS)
This application will use the Common Controls Library
$$ENDIF
$$IF(USE_WINSOCK)
This application will use the Winsock 2 Library
$$ENDIF
Building and Running the Wizard
Building the Wizard
During compilation, the compiler could complain about not recognizing the IBuildProject
interface. Three header files are then needed to be included in your Stdafx.h Custom AppWizard Project:
#include <atlbase.h>
#include <ObjModel/bldauto.h>
#include <ObjModel/bldguid.h>
Running the Wizard
After compilation, the Wizard files produced are automatically copied into the Visual Studio Template directory. So, the only thing you have to do is open a new instance of Visual Studio (even if it is not needed to open a new one) and see if the 'SimpleApplicationWizard AppWizard' is present. If yes, choose it and try it:
Conclusion
Hope this article could be useful for those who want to get more from the VC++ IDE. Comments and hints are welcome, thanks.
History
22 June 2004 v1.0.