Introduction
OK, we agree WTL is the way to go when it comes to modern UI development, especially if you want to keep native, lean, and mean. Don't we? WTL wizard does a nice job providing you with boilerplate code for the type of application you choose, including MDI, which is the focus of this article. Having stated that, there's only one thing that bothers me: the MDI document app you get allows you to work with multiple copies of the same template, that is, you can have only a type of document/form/whatever_window_you_chose (by the way, the MFC counterpart of the wizard offers you quite the same thing), hence getting the same functionality over and over. I mean, is this all too useful? But of course, this framework will allow you to develop Office-like applications.
The matter is, MS Word, MS Excel and the like all have already been developed and, let's face it, average programmers are not likely to develop next-generation replacements for them. In real world, we're most likely to engage in developing apps that deal with specific tasks that eventually will require independent windows/forms, all of them children of the same and only main application frame. At this point, the boilerplate code provided either by the WTL or the MFC MDI wizard will not help too much. VB programmers get this functionality nearly for free, though I won't delve into the details. For C++, specifically using WTL, there are a number of workarounds. In this article, I'll offer a solution which appears to me quite straightforward.
Background
This is the first version, developed using VS 2003 and WTL 7.5 (available from sourceforge.net).
Using the code
The basic idea relies on inheritance, but before we go deeper into it, let's examine the three main classes the WTL MDI wizard uses to build the app:
CMainFrame
provides the application's main window.
CChildFrame
does exactly what the name implies: provides the frame for the child windows.
CXXXView
- CMDI2View
, in the accompanying example, is the "meat" of the app. In MFC terms, it would constitute the document and the view. It's the place where your program's actions take place.
The main point is: to have more than one type of document/form/whatever in your app, you just inherit from the view class, like so:
class CMDI2View1 : public CMDI2View
and
class CMDI2View2 : public CMDI2View
And that's it!...Not really; there're a couple of things you must provide to make this functional:
- Add code to the inherited views to provide for different functionality.
In the demo app, I've done so by overriding the OnPaint
method, like so, in CMDI2View1
:
LRESULT OnPaint(UINT , WPARAM ,
LPARAM , BOOL& )
{
CPaintDC dc(m_hWnd);
RECT r;
GetClientRect(&r);
dc.SetBkColor(RGB(0,0,192));
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(RGB(255,255,255));
CBrush brush;
brush.CreateSolidBrush(RGB(0,0,192));
dc.SelectBrush(brush);
dc.Rectangle(0,0,r.right, r.bottom);
dc.DrawText(_T("Documento tipo Uno"),-1,
&r,DT_VCENTER | DT_CENTER | DT_SINGLELINE);
return 0;
}
...in CMDI2View2:
.
LRESULT OnPaint(UINT , WPARAM ,
LPARAM , BOOL& )
{
CPaintDC dc(m_hWnd);
RECT r;
GetClientRect(&r);
dc.SetBkColor(RGB(192,0,0));
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(RGB(192,192,192));
CBrush brush;
brush.CreateSolidBrush(RGB(192,0,0));
dc.SelectBrush(brush);
dc.Rectangle(0,0,r.right, r.bottom);
dc.DrawText(_T("Documento tipo Dos"),-1,
&r,DT_VCENTER | DT_CENTER | DT_SINGLELINE);
return 0;
}
- We need to redefine the
m_view
member of CChilFrame
to be a pointer to its base class (CMDIView
). Thus, the wizard generated line: CMDI2View m_view
becomes:
CMDI2View* m_view
This is necessary to allow polymorphic behavior in the view class.
- We need a variable in the
CChildFrame
class that allows the selection of the type of view to be constructed, hence: int iType;
...
CChildFrame(int iiType = 1) : m_view(0), iType(iiType)
{
}
~CChildFrame()
{
if (m_view)
delete m_view;
}
- We need to modify the
OnCreate
method of the CChildFrame
class, which I've done this way: LRESULT OnCreate(UINT , WPARAM ,
LPARAM , BOOL& bHandled)
{
if (iType == 1)
m_view = new CMDI2View1;
else
m_view = new CMDI2View2;
m_hWndClient = m_view->Create(m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
char buf[256] = {0};
if (iType == 1)
strcpy(buf, _T("Documento tipo I"));
else
strcpy(buf, _T("Documento tipo II"));
SetWindowText(buf);
if (iType == 1)
{
SetIcon(LoadIcon(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDR_MDICHILD)));
}
else
{
SetIcon(LoadIcon(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDR_MDICHILD2)));
}
bHandled = FALSE;
return 1;
}
- And finally, we need a method to select which view will be launched in the
CMainFrame
class. To achieve that, I've added a menu item (and a toolbar icon, for that matter) under the ID, ID_FILE_NEW2
.
Subsequently, I've got it handled like so:
COMMAND_ID_HANDLER(ID_FILE_NEW2, OnFileNew2)
...
LRESULT OnFileNew(WORD , WORD ,
HWND , BOOL& )
{
CChildFrame* pChild = new CChildFrame;
pChild->CreateEx(m_hWndClient);
return 0;
}
LRESULT OnFileNew2(WORD , WORD ,
HWND , BOOL& )
{
CChildFrame* pChild = new CChildFrame(2);
pChild->CreateEx(m_hWndClient);
return 0;
}
And now...really that's it.
After notes
There are many ways of achieving the same functionality, I just found the example quick and simple. Of course, you can add as many types of views as it suits your app, but for the purpose of this article, two sufficed. And CFormView
would have been a better base class for the view, using it wouldn't add much difficulty nor a significant overhead. Again, I chose a raw view to keep it simple.
Hope this helps.