<html><body>
<h2>Introduction</h2>
<p>This article series is another in my series of "code we really use" articles.
There is no unneccesary discussion about theory, no expounding on technique, and
no chest-thumping because I thought it all up myself. It's just a bunch of stuff
I did to stand one of our applications up. MOST of the stuff in this article is
based on other code that I got from CodeProject, and what follows describes the
basis for a project I am actively developing and how I integrated articles and
help I got from CodeProject.</p>
<h2>Rant</h2>
<p>I've been a member on CodeProject for over six years (as of this writing),
and I've come to discover some disturbing trends regarding articles. First,
article authors tend to post an article and as time goes by, the author
essentially abandons the article and people posting questions are greeted with
either silence from the author, or a response that says something like "I don't
code in this/that language any more". Let's face it, you can't blame them. Many
of the articles I use are three or four years old, and I understand that
programmers need to move on and that often means completely abandoning older
code.</p>
<p>On the other side of the fence are the people that download the source code
and samples associated with a given article. Many times, someone will post a
question in an article that has absolutely nothing to do with the article itself,
but the subject will be related to a certain aspect of the article. As an
example, I posted an article about dynamically building a menu. Recently, someone
posted a message in that article that asked about adding winhelp to their
dynamically built menu. Then there's the people that encounter an issue (real
or imagined) with an article, and expect someone else to fix it for them. These
people really annoy me. Afterall, we're all supposed to be programmers here.</p>
<h2>So, What's the Point of This Article?</h2>
<p>The entire point of this article is to illustrate real-world use of
code-snippets, classes and techniques I gleaned from CodeProject over the last
six years, including work-arounds to fit code into my sometimes bizarre
requirements. Many times, I'll use the VC++ forum to ask a question that will
help me understand an article, or massage the article's code for my own use.
<h2>Assumptions</h2>
<p>The original version of this article started out as a kind of detailed
tutorial describing how to use the IDE, and other inane items like that. After a
while, I realized this created a huge amount of overhead as far as the article's
weight was concerned. Beyond that, I was starting to become bored with the whole
thing and I could plainly see that the quality of my writing was beginning to
suffer as a result.</p>
<p>The only solution was to start over and make the assumption that you, the
user, have a working knowledge of the VS2005 IDE, especially as it relates to
creating VC++/MFC applications. This way, we can talk more about the important
stuff than suffer through stuff you should already know.</p>
<h2>Other Stuff</h2>
<p>Sprinkled throughout the article, you'll find "Coding Notes". These simply
describe the way I do things when coding, and why I do them. They are certainly
not requirements by any stretch of the imagination, but they often concern code
readability and maintainability. I'm sure that many of you have your own ways
of doing things, but please keep comments regarding these issues to a minimum.
Afterall, this article is not about style.</p>
<p>The total process of coding the complete demo app requires just an hour or
so (if you know all the steps ahead of time). Writing this article series has
taken me DAYS, so don't be put off by it's length.</p>
<p>The html and images for this article is included in the project download, but
doesn't include the pretty CodeProject formatting. If you can mentally handle
that, you can simply refer to this .HTML file and get on with your programming.
</p>
<p>Finally, I know there are folks out there that vote my stuff a 1 simply
because it's, well, something I wrote. I request that you be mature and
professional and restrict your politics to the soapbox when voting. Remember,
you're voting on the article, not on the author.</p>
<h2>My Real-World Application</h2>
<p>The application allows a hospital emergency department to monitor the status
of patients and beds in the Emergency Room. This application is broken into four
components:</p>
<ul><li>A "big board" application that allows little user interaction
(there's no menu, but several accelerator keys are available) and is displayed on
a 50-inch plasma screen suspened from the ceiling.</li></ul>
<ul><li>A "client" application that allows multiple users to manipulate the data
on their own screens. Cheanges made in the client app are eventually updated to
the "big board".</li></ul>
<ul><li>A data access MFC extension DLL This DLL holds the entire database access
capabilities.</li></ul>
<ul><li>A view manager MFC extension DLL. This is where all of the dialog boxes
and views are contained.</li></ul>
<ul><li>The whole thing is tied to an Oracle database.</li></ul>
<p>Here are some screen shots of the actual application we created. Keep in mind
that a lot of the visual elements you might notice are dictated by requirements,
and may not necessarily be the way I would have preferred to accomplish certain
aspects of the code. Knowing this, resist the urge to critique perceived design
aspects.</p>
<h2>Our Sample Application</h2>
Building our sample application provided me with an opportunity to re-factor
much of the app, ommitting code that is no longer used in the app and to
re-organize the code so that it's easier to maintain. The sample app
illustrates the following design elements:</p>
<ul><li>A multi-project solution.</li></ul>
<ul><li>MFC application using shared MFC DLLs</li></ul>
<ul><li>SDI application using the MFC document/view architecture</li></ul>
<ul><li>A horizontal custom flat-bar splitter window.</li></ul>
<ul><li>View-switching in one of the splitter panes. </li></ul>
<ul><li>Gives example of using the views in the app itself AND using the
views from an extension DLL.</li></ul>
<ul><li>The switchable views include a grid view, a formview, and a GDI
drawing view</li></ul>
<ul><li>Removing menus from an application</li></ul>
<ul><li>Creating a timer thread that controls one or more timers</li></ul>
<ul><li>Creating worker threads that respond to timer events posted by the
timer thread
</li></ul>
<ul><li>Loading/saving program settings in a XML file</li></ul>
<h2>Gathering The Components</h2>
<p>The sample doesn't include all of the code associated with the articles I
used, but instead only includes the parts of the code that I implemented in my
own project.</p>
includes all of the code I downloaded from CodeProject, but for
those who want to follow the same steps I did, or if you just want to organize
your source code folders differently than I did, here's a list of the articles
used to create the project:</p>
<ul>
<li><a href="http://www.codeproject.com/miscctrl/gridctrl.asp" target="top">MFC Grid Control</a>, by Chris Maunder</li>
<li><a href="http://www.codeproject.com/miscctrl/pptooltip.asp" target="top">CPPToolTip</a>, by Eugene Pustovoyt</li>
<li><a href="http://www.codeproject.com/splitter/flatsplitter.asp" target="top">A Flat Splitter Window</a>, by Marc Richarme</li>
<li><a href="http://www.codeproject.com/splitter/DanCMultiViewSplitter.asp" target="top">Unlimited number of switchable views within a Splitter window</a>, by Dan Clark</li>
<li><a href="http://www.codeproject.com/threads/cthread.asp target="top">CThread - a Worker Thread wrapper class</a>, by Dominik Filipp.</li>
<li><a href="http://www.codeproject.com/statusbar/ExtStatusControlBar.asp target="top">CExtStatusControlBar</a>, by Dmitriy Yakovlev</li>
<li><a href="http://www.codeproject.com/file/cxmlprofile.asp" target="top">CXMLProfile - Easy XML Profiles for applications</a>, by Emilio Guijarro.</li>
</ul>
<p>Generally, I put code from CodeProject into a root-level project folder called
"CodeProject" (as I've done in these articles) so that it's easy to a) see what
code I'm using that's not mine, and b) update that code if the author releases
new stuff. I realize that most of you probably have your own ways to do things,
so it should be fairly easy to rearrange things to your liking.</p>
<p>I'm a firm believer in using the "Additional include directories"
setting in the project's properties, so if you move things within the sample,
remember to remove and re-add the files in the project, and then change both the
debug and release "Addition include directories" property before re-compiling.
If I derive new classes from CodeProject stuff, I generally just put the
derived class files into the project's folder.</p>
<h2>Getting Started - Creating A Solution</h2>
<p>First, we need to create a new solution. Why? Because we will be creating a
number of projects that are related to each other and it's simply more
convenient to have them all bundled inside a solution than to have to deal with
several separate projects. Use "SDIMultiSplit" for the solution name.</p>
<p>Next, we add a new project to the solution. Since we're talking about VC++
and MFC here, that's the kind of project we need to add. Here's a series of
screen shots showing the settings you need to use when creating the project.</p>
<p><center><img border="1" src="create_app1_02.png"><br /><br />
<img border="1" src="create_app1_03.png"><br /><br />
<img border="1" src="create_app1_04.png"><br /><br />
<img border="1" src="create_app1_05.png"><br /><br />
<img border="1" src="create_app1_06.png"><br /><br />
<img border="1" src="create_app1_07.png"><br /><br />
<img border="1" src="create_app1_08.png"><br /><br />
<img border="1" src="create_app1_09.png"></center></p>
<p>Kinda boring, but we're about to fix that. We're ready to start adding the
fun stuff.</p>
<h2>Making The View Interesting</h2>
<p>Since we already have a view graciously provided by the app wizard, we'll
make it do something more interesting than just show up as a big white box.
Follow these steps.</p>
<h3>Adding The MFC Grid Control To The Project</h3>
<ul><li>If you don't already have it, go download the
<a href="http://www.codeproject.com/miscctrl/gridctrl.asp" target="top">MFC
Grid Control</a>. You only need the "source" (99k download). I'm using
version 2.25, but v2.26 beta is also available. While you're there, you might
want to browse the article for helpful hints on how to "make it go". The article
is a little sparse on examples, but a sample project has been provided that
illustrates some of the more intersting features. It's one of the most highly
voted articles (790 votes and counting as of this writing) and it's got a 4.81
rating.</li></ul>
<ul><li>Due to it's file-count and complexity, we're going to put the grid control
source into its own folder. Create a new folder in your solution folder called
<code>MFCGridControl_2-25</code>. Another reason to do this is so that you can
easily replace the 2.25 code with the newer 2.26 code without jeapordizing a
working codebase. Un-ZIP the file to this new folder.</li></ul>
<ul><li>Before we do anything else, there's a small bug that needs to be fixed. While
trying to have my parent view class respond to notification messages from the
grid control, I found that GVN_ENDLABELEDIT was not always being passed up to
the parent view. The problem was that if you started to edit a cell, and didn't
change anything, and then stopped editing, the grid control wouldn't send the
GVN_ENDLABELEDIT message because the contents of the cell didn't change. I fixed
it by replacing the <code>OnEndEditCell</code> function in <code>GridCtrl.cpp</code>
with this a slightly modified one. You're going to need this fix, so here's the new
version of the function:
<pre>
void CGridCtrl::OnEndEditCell(int nRow, int nCol, CString str)
{
/*
// this is the original version of the function
CString strCurrentText = GetItemText(nRow, nCol);
if (strCurrentText != str)
{
SetItemText(nRow, nCol, str);
if (ValidateEdit(nRow, nCol, str) &&
SendMessageToParent(nRow, nCol, GVN_ENDLABELEDIT) >= 0)
{
SetModified(TRUE, nRow, nCol);
RedrawCell(nRow, nCol);
}
else
{
SetItemText(nRow, nCol, strCurrentText);
}
}
CGridCellBase* pCell = GetCell(nRow, nCol);
if (pCell)
pCell->OnEndEdit();
*/
CString strCurrentText = GetItemText(nRow, nCol);
if (ValidateEdit(nRow, nCol, str))
{
if (strCurrentText != str)
{
strCurrentText = str;
SetItemText(nRow, nCol, strCurrentText);
SetModified(TRUE, nRow, nCol);
RedrawCell(nRow, nCol);
}
else
{
SetItemText(nRow, nCol, strCurrentText);
}
}
CGridCellBase* pCell = GetCell(nRow, nCol);
if (pCell)
{
pCell->OnEndEdit();
}
SendMessageToParent(nRow, nCol, GVN_ENDLABELEDIT);
}
</pre>
</li></ul>
<ul><li>Next, we have to add all of these files to our project. Open your Solution
Explorer pane, right click on the <b>SDIMultiApp1</b> item in the tree, click
<b>Add | Existing Item...</b>, and browse to the <code>MFCGridControl_2-25</code>
folder (or whatever you named it), select ALL of the files, and click the <b>ADD</b>
button. You can safely ignore the files in the Experimental Upgrades folder for
now.
<p>If you're curious, you can open the Solution Explorer pane and see the new files
listed in the project.</p>
</li></ul>
<ul><li>
The final preparation step for using the grid control is adding the folder to
your project settings. Right-click on the <b>SDIMultiApp1</b> item in the tree,
and click <b>Properties</b> at the bottom of the subsequent context menu.
<p>In the Properties dialog box, expand the <b>Configuration Properties</b> tree
item, and then the C++ tree item, and add the following text to the
<b>Additional Include Directories</b> setting (pointed at by the mouse cursor in
the image above):</pre>
<pre> .,../MFCGridControl_2-25</pre>
<p>PLEASE NOTICE that the first folder that shows up in this string is the "."
(single dot). This tells the compiler to look in the project's folder BEFORE
looking anywhere else for an include file. In fact, you should add folders to
this setting in the PRECISE order in which you want them searched. This is an
important aspect of including files in a project and should not be overlooked.
<p>Finally, make sure you add the same string to ALL required project
configurations (debug AND release).
</li></ul>
<h3> Hooking The Grid Control Up</h3>
"Making it go" is fairly simple and straightforward, especially since we're
just making the grid show up for purposes of example.
<ul><li>Open up the <b>SDIMultiApp1View.h</b> file and add the following code:</b>
<pre>#include "GridCtrl.h"
class CSDIMultiApp1View : public CView
{
private:
CGridCtrl* m_pGridCtrl;
public:
// give other parts of the program a method for retrieving the grid control.
// you may not need this right away, but it sure will be convenient when
// it's there becaus ethat's one less file you have to check out of source
// control to change later.
/// Retrieves the current gridcontrol pointer
CGridCtrl* GetGridControl() { return m_pGridCtrl; };
</pre>
<br /><center><table width="500" cellpadding="1" cellspacing="1" style="background-color:#000000;">
<tr style="background-color:#CCCCCC;"><td align="center"><font size="-1"></font><b><i>Coding Notes</i></b></font></td></tr>
<tr style="background-color:#EEEEEE;"><td style="padding:5px;"><font size="-1">
When I add stuff manually to a class, I generally add it at the top of the
class definition because VS2005 likes to add message handlers at the bottom, and
it just plain looks ugly after a while. Good code readability means good code
maintainability - always consider those who follow you. I also provide
extensive comments <b>AT THE TIME I WRITE THE CODE</b> because I often forget
why I do stuff, even as soon as an hour after I wrote the code.<br /><br />
Also notice that the final comment starts with 3 slashes. This is a trigger
for Intellisense to provide the text as a comment when you use a function. This
is quite handy for people new to the code. I recommend that you try to do this
as well.
</font></td></tr>
</table></center>
</li></ul>
<ul><li>Now, open up your <b>SDIMultiApp1View.cpp</b> file, and add the follwoing line to the constructor:
<pre> m_pGridCtrl = NULL;</pre>
<p>And then add the following code to the destructor:</p>
<pre> if (m_pGridCtrl)
{
delete m_pGridCtrl;
}
m_pGridCtrl = NULL;
</pre>
<br /><center><table width="500" cellpadding="1" cellspacing="1" style="background-color:#000000;">
<tr style="background-color:#CCCCCC;"><td align="center"><font size="-1"></font><b><i>Coding Notes</i></b></font></td></tr>
<tr style="background-color:#EEEEEE;"><td style="padding:5px;"><font size="-1">
I know, I don't *have* to set the pointer to NULL, but I'm old and IMHO, the old
ways are the best ways.
</font></td></tr>
</table></center>
</li></ul>
<p>The next few steps concern adding message handlers to the view class. Microsoft
has made it difficult for MFC programmers to make effective use the IDE. Adding
message handlers is one of the most annoying of these limitations via the IDE.</p>
<p>To add a message handler (other than manually) to a CCmdTarget-derived class
(CView being one of them), you must put your edit cursor on either the opening
or closing line of the message map macro, like so:</p>
<p><center><img border="1" src="code_notes_msgmap_01.png"></center></p>
<p>After you've done this, you can open the <b>Properties</b> pane and you'll
see this toolbar:</p>
<p><center><img border="1" src="code_notes_msgmap_02.png"></center></p>
<p>Well, *sometimes* you'll see that toolbar. VS2005 (yes, even with SP1 applied)
sometimes refuses to show the toolbar in the Properties pane. If this happens
to you, try shutting down the IDE and opoening it back up again (I offer no
guarantees that this will work every time).</p>
<p>In any case, there are three buttons you're interested in. Clicking the lightning
bolt button (tooltip says "Events") will show you message IDs that are defined in
the resource file. The button to the immediate right of the Events button is the
Messsage button. Clicking this button presents you with all of the appropriate
*Windows* messages for the class you're working on. The last button in the series
is the Overrides button which allows you to activate override functions, such as
OnExitInstance, OnInitialUpdate, etc.</p>
<p>We're interested in the <b>Messages</b> button, so let's continue.</p>
<ul><li>Open the Proeprties pane, and click the <b>Messages</b> button in the
Properties toolbar. You'll be presented with a list of Windows messages. Scroll
down the list until you find <code><b>WM_SIZE</b></code>. Click in the blank
field next to the message ID, and select <code>OnSize</code> in the dropdown
list. The IDE will add all the necessary code to handle the message, but we have
to change it, so make the function look like this (just copy/paste the contents
of the function):
<pre>void CSDIMultiApp1View::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if (m_pGridCtrl->GetSafeHwnd())
{
CRect rect;
GetClientRect(rect);
m_pGridCtrl->MoveWindow(rect);
}
}
</pre>
</li></ul>
<ul><li>Add a message handler for WM_ERASEBACKGROUND, using the method described
above for the WM_SIZE message, and change the IDE-generated code to look like
this:
<pre>BOOL CSDIMultiApp1View::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
</pre></li></ul>
<ul><li>Add an *override* handler for OnCmdMsg. Remember, you have to use the
Overrides button (the icon looks like a little box) to add an override. Change
the IDE-generated code to look like this:
<pre>BOOL CSDIMultiApp1View::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (m_pGridCtrl && IsWindow(m_pGridCtrl->m_hWnd))
{
if (m_pGridCtrl->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
{
return TRUE;
}
}
return CView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
</pre>
</li></ul>
<ul><li>Add an *override* handler for OnInitialUpdate. Remember, you have to use the
Overrides button (the icon looks like a little box) to add an override. Change
the IDE-generated code to look like this:
<pre>void CSDIMultiApp1View::OnInitialUpdate()
{
CView::OnInitialUpdate();
CRect rect;
GetClientRect(rect);
m_pGridCtrl = new CGridCtrl(0, 0, 10, 1, 1);
m_pGridCtrl->Create(rect, this, IDC_GRID_CTRL);
UpdateWindow();
}
</pre>
</li></ul>
<br /><center><table width="500" cellpadding="1" cellspacing="1" style="background-color:#000000;">
<tr style="background-color:#CCCCCC;"><td align="center"><font size="-1"></font><b><i>Coding Notes</i></b></font></td></tr>
<tr style="background-color:#EEEEEE;"><td style="padding:5px;"><font size="-1">
You can alternately try using the Class View t add message handlers, but for
some reason, I never got into the habit of doing that.</font></td></tr>
</table></center>
<ul><li>Create a new folder in your solution directory called
<b><code>Includes</code></b>.</li></ul>
<ul><li>Create a new file called Constants.h in your project folder, and insert
the following code:
<pre>#pragma once
#define IDC_GRID_CTRL 49000
</pre>
<p>We'll be coming back to this file in Part 2 and Part 3 of this series of
articles as we add more features to the program.</p></li></ul>
<ul><li>Add the following line to the bottom of the project's stdafx.h file:
<pre>#include "Constants.h"</pre>
<br /><center><table width="500" cellpadding="1" cellspacing="1" style="background-color:#000000;">
<tr style="background-color:#CCCCCC;"><td align="center"><font size="-1"></font><b><i>Coding Notes</i></b></font></td></tr>
<tr style="background-color:#EEEEEE;"><td style="padding:5px;"><font size="-1">
<p>I know, every time we change this file, it will cause a complete program
rebuild, but I have a couple of reasons for doing this; a) our app is so
small that it won't matter in terms of compilation time, and b) it's just
plain convenient because all CPP files in the project have to <code>#include
stdafx.h</code>, and this include is performed by default when you create
new classes in a MFC project (saving me typing time in this article series).
</font></td></tr>
</table></center>
</li></ul>
<ul><li>Add the new Includes folder to your "Additional Include Directories"
project setting. Your new entry should look like this:
<pre> .,../Includes,../MFCGridControl_2-25</pre>
</li></ul>
<ul><li>Optional step - Since we don't need the OnDraw function in our view, I
chose to minimize it's appearance in the code. CView is an abstract class, and
thus forces us to override OnDraw. However, the grid control takes care of it's
own drawing, and there's really no reason to keep OnDraw around. All you really
have to do is delete the function from yourview's CPP file and change the
function declaration in the view's header file to the following:
<pre>virtual void OnDraw(CDC* pDC) {};</pre>
<p>This helps to keep the CPP file uncluttered.</p>
</li></ul>
<p>Now, compile the code. You may notice a stream of warnings go by as the code
compiles. This is because the version of the grid control I'm using has not been
modified for use with VS2005. This will not impact the reliability of the
application, but if this bothers you, download version 2.26 beta, create a new
folder within the solution folder (I'd use <b>MFCGridControl_2-26</b>). If you
do put it into it's own folder, make sure you go back into the project
properties and change the include directory setting to the new folder (make sure
you do it for both the debug AND release configurations. After compiling, run the
application. You should see something like this:</p>
<p><center><img border="1" src="app1_stage_01b.png"></center></p>
<p>Congratulations. You've reached the end of Stage 1 - creating an interesting
basic application.</p>
<br /><center><table width="500" cellpadding="1" cellspacing="1" style="background-color:#000000;">
<tr style="background-color:#CCCCCC;"><td align="center"><font size="-1"></font><b><i>Coding Notes</i></b></font></td></tr>
<tr style="background-color:#EEEEEE;"><td style="padding:5px;"><font size="-1">
At this point in the development process, I usually take time to add comments
and start organizing code into sections, separating the overrides from the
windows message handlers, and stuff like that. The sample code provided in this
article illustrates that process. Again, it's just something I do to help keep
the code readable and is not a requirement on your part.
</font></td></tr>
</table></center>
<p>Feel free to play around with the grid control (add rows, use images, and
stuff like that. The grid control's demo source should help you figure out how
to do use some of the more interesting features. And while you're at it, make
sure you go vote on the Grid Control's page. Also, if you have any questions
regarding the use of the grid control, please post those questions on the grid
control's article page.</p>
<h2>End of Part 1</h2>
<p>Due to the length of this article, I've decided to break it up into several
parts. If the site editors did what I asked, all of the subsequent parts
should be in the same code section of the site. Each part has it's own source
code, so as youread the subsequent parts, make sure you download the source code
for that part (unless you're doing manually all the stuff I'm outlining in the
article in question).</p>
<p>In the interest of maintaining some cohesiveness (and sanity), please vote on
all of the parts, and vote the same way. This helps keep the articles together
in the section. Thanks for understanding.</p>
</body></html>