Click here to Skip to main content
15,884,629 members
Articles / Programming Languages / C++

Using CodeProject - A Day In the Life of an Application - Part 3 of 5

Rate me:
Please Sign up or sign in to vote.
4.63/5 (20 votes)
27 Jan 2007CPOL14 min read 43.2K   436   17  
The right way to code using CodeProject for occasional support
<html><body>
<h2>Introduction</h2>

<p><b><i>The following text is identical to <a href="" target="top">Part 1</a>. If you 
haven't already read that article, this article will be useless to you, so by 
all means, catch up. We'll wait here.  If you have read the Part 1 article, you 
can skip to the next section.</i></b></p>

<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>What We've Already Done</h2>

<p>In part one of this article series, we went through the steps of creating a 
MFC SDI application and making the view a little more interesting by adding the 
MFC Grid Control to it.  In Part 2, we'll create a flat splitter window that 
can switch views in the primary pane.</p>


<h2>Adding a Splitter Window with Swappable Views</h2>

<p>Adding a splitter window is really fairly simple in a MFC application, with 
all of the work is done in the CMainFrame class. Of course, there's a MSDN 
article available that describes theprocess of adding a splitter window, but I 
hate chasing links to find out how to do stuff, and I bet you probably do, too. 
So, in the interest of just gettng the job done, we'll skip the basic splitter, 
and go right to the one we really want - the flat splitter window.</p>

<p>In the case of my real-world application, I only needed a horizontal 
splitter, so this discussion is limited to that requirement.  Further, 
I started with 
<a href="http://www.codeproject.com/splitter/flatsplitter.asp" target="top">
Marc Richarme's Flat Splitter Window</a> article, and then added most of the 
code from Dan Clark's <a href="" target="top">http://www.codeproject.com/splitter/DanCMultiViewSplitter.asp</a>
Multi-View Splitter Window article</a> to get the swappable view functionality. 
This is a perfect example of using two separate articles on CodeProject to 
create a single specialized class. Because of the combining of code from these 
two articles, you should probably use the code I provided in my sample project 
unless you just want to go through the experience of doing the same thing on 
your own.

<p>In Part 1, we added the MFC Grid Control to the <code>CSDIMultSpliView</code> 
class (created by the App Wizard). While this is all fine and dandy, we're about 
to get a bit fancier with swappable views. Toward this end, we need to create 
the views that we'll be swapping.


<h3>Create New View Class - CPrimaryView</h3>

<p>This view will be used to display the grid.</p>

<ul><li>Right-click the <b>SDIMultiApp1</b> project item in the Solution 
Explorer pane, in the context menu select <b>Add | Class...</b>

<p><center><img border="1" src="add_viewclass_01.png"></center></p>
</li></ul>

<ul><li>In the subsequent <b>Add Class</b> dialog, select <i>MFC</i> in the 
<b>Categories</b> tree (left side of dialog box) and then <i>MFC Class</i> in 
the <b>Templates</b> list (right side of dialog). Click ADD.

<p><center><img border="1" src="add_viewclass_02.png"></center></p>
</li></ul>

<ul><li>In the next dialog box, specify a class name (this sample uses 
<code>CPrimaryView</code>, and select the base class. For our sample, we'll 
use <code>CView</code>. Click FINISH.</li></ul>

<p><center><img border="1" src="add_viewclass_03.png"></center></p>
</li></ul>

<ul><li>Move all of the code related to the grid control from the 
<code>CSDIMultiSplitView</code> class to the <code>CPrimaryView</code> class. 
You should use the IDE to add the appropriate overrides in this class. If you 
need detailed instructions, refer to Part 1 of this article series. In short, 
you need to override <code>OnInitialUpdate</code> and <code>OnCmdMsg</code>, 
and add message handlers for <code>OnSize</code> and <code>OnEraseBkgnd</code>. 
Just copy the code from within the resulting functions from 
<code>CSDIMultiApp1View</code> to this class.
</li></ul>

<ul><li><b>Optional step</b> - remove the grid control code from the 
CSDIMultiSplit class. We don't need it there because that class is going to be 
reduced to be just a place holder view for the swappable views.  In the sample 
app provided with this article, I just <code>ifdef</code>'d around the grid control 
code so that it wouldn't be included when the app was compiled.</li></ul>


<h3>Create New View Class - CSecondaryView</h3>

<p>This will be a simple GDI view that contains text in the form of a report 
(actually, it will be a simple collection of lines created and displayed in a 
<code>for</code> loop). This view will be able to print as well (no easy 
task as you will soon see).</p>

Since you just did it for the class above, I won't detail the act of creating a 
new class in this step.  When you get the class wizard dialog box, the class 
name should be CSecondaryView, and it should be derived from CScrollView, as 
shown below:

<p><center><img border="1" src="add_viewclass_04.png"></center></p>

<p>Here's a new version of the <code>OnDraw()</code> function to make the view 
interesting. Don't be alarmed at the number of lines we're putting on the screen 
because we'll be using this to test the printing functionality we'll be adding a 
little later.</p>

<pre>void CSecondaryView::OnDraw(CDC* pDC)
{
	CDocument* pDoc = GetDocument();

	CFont docFont;
	CFont* pOldFont;

	// construct and select our font
	BuildFont(pDC, &docFont, 11, false, false);
	pOldFont = pDC->SelectObject(&docFont);

	// Get a baseline lineheight value. We add 2 pixels to the calculated 
	// lineheight so we have a little white space betwen lines of text.
	CString sText = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	int nLineHeight = pDC->GetTextExtent(sText).cy + 2;

	// set our x position
	int nMargin = 10;
	// limit how many lines we draw
	int nMaxLines = 70;
	
	// draw the lines
	for (int i = 1; i <= nMaxLines; i++)
	{
		sText.Format("This is line number %02d of %d", i, nMaxLines);
	}
	pDC->SelectObject(pOldFont);
}
</pre>

<p>And to support the font we need, here's the BuildFont() function that you 
have to add to the class. The more-eagle-eyed user may recognize this 
function as being the same one we used in the from the CFlatSplitterWnd 
class.</p>

<pre>BOOL CSecondaryView::BuildFont(CDC* pDC, CFont* pFont, int nFontHeight, bool bBold, bool bItalic)
{
	nFontHeight = -MulDiv(nFontHeight, pDC->GetDeviceCaps(LOGPIXELSY), 72);
    CString sFontName = _T("Arial");

	int   nWeight = (bBold) ? FW_BOLD : FW_NORMAL;
	BYTE  nItalic = (bItalic) ? 1 : 0;

	return pFont->CreateFont(nFontHeight, 0, 0, 0, nWeight, nItalic, 0, 0, DEFAULT_CHARSET,
                             OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY,
                             DEFAULT_PITCH | FF_DONTCARE, sFontName);
}
</pre>


<h3>Create New View Class - CInfoView</h3>

<p>Following the same steps described above, create another new view class 
called <code>CInfoView</code>, and once again, set the base class to <code>
CSrollView</code>.

<p><center><img border="1" src="add_viewclass_04.png"></center></p>

<p>Here's some code to make the view more intersting looking. The only diffrence 
between this code and the code we used inthe <code>CSecondaryView</code> class 
is the number of lines we're putting on the screen. Since we won't be making 
this view printable, we don't need as many lines with which to test the view.
</p>

<pre>void CInfoView::OnDraw(CDC* pDC)
{
	CDocument* pDoc = GetDocument();

	CFont docFont;
	CFont* pOldFont;

	// construct and select our font
	BuildFont(pDC, &docFont, 11, false, false);
	pOldFont = pDC->SelectObject(&docFont);

	// Get a baseline lineheight value. We add 2 pixels to the calculated 
	// lineheight so we have a little white space betwen lines of text.
	CString sText = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	int nLineHeight = pDC->GetTextExtent(sText).cy + 2;

	// set our x position
	int nMargin = 10;
	// limit how many lines we draw
	int nMaxLines = 20;
	
	// draw the lines
	for (int i = 1; i <= nMaxLines; i++)
	{
		sText.Format("This is line number %02d of %d", i, nMaxLines);
	}
	pDC->SelectObject(pOldFont);
}
</pre>

<p>Finally, put a copy of the <code>BuildFont()</code> function into this class 
as well.</p>


<p>Go ahead and check the Solution Explorer pane for the appropriate files (or 
the ClassView pane if that's what trips your trigger).  All that's left to 
complete this view's basic functionality is to add some code in OnDraw that 
fills the view up with text.  This will server two purposes - providing 
immediate and gratuitous visual feedback when we switch between this view and 
the grid view, and setting us up to add some printing functionality a little 
later in the article. For now, here's a new OnDraw function:

<pre></pre>

<h3>Create New View Class - CSecondaryView</h3>

<p>Following the same steps described above, create another new view class 
called <code>CSecondaryView</code>.

<h3>Create New View Class - CInfoView</h3>

<p>Following the same steps described above, create another new view class 
called <code>CInfoView</code>.

<p>Here's some code to make the view more intersting looking.</p>

<pre></pre>


<h3>Implement The Splitter Window with Swappable View Support</h3>

<p>One of the tasks for our real-world app was to provide a split view that 
allows the user to see some specific type of data in another window. The only 
real requirements provided were that the splitter bar be visible all the time, 
and that it split the main window horizontally.  In the interest of brevity and 
keeping everything within the intended scope, we'll ignore other kinds of 
splits and issues regarding the combination of horizontal and vertical 
splitters.</p>

<p>Beyond the stated requirements, it was left to me to make it happen anyway I 
wanted. After looking around a bit on CodeProject, I found a good starting point 
with the <a href="http://www.codeproject.com/splitter/flatsplitter.asp" target="top">
FlatSplitterWnd</a> class from Marc Richarme. Essentially, this class provided 
me with an always-visible flat splitter window, and was my job to make it pretty. 
I chose to make the splitter bar itself yeloow, and put the word "Info" on the bar 
with two arrowheads pointinf down.  The idea here was to make it easy to see. 
Without further delay, here are the steps I followed to implement this feature.
</p>

<ul><li>So, first we're going to add the CFlatSplitterWnd files to our project. Once 
again, I prefer to place files from external sources into their own folders (yes, 
even if I modify them like we're going to do in this case), so after you've 
downloaded the FlatSplitterWnd article's source code, extract the <code>
FlatSplitterWnd.CPP</code> and <code>FlatSplitterWnd.H</code> files to the folder 
of your choice (I put them into <code>\SDIMultiSplit\FlatSplit</code>).<br /><br />

Since we've performed similar steps when we added the MFC Grid Ctrl, I don't 
think there's any reason to illustrate how to add files to a project a second 
time. Just Right click on the SDIMultiSplit project in the Solution Explorer, 
click <b>Add | Existing...</b> in the context menu, and the browse to the folder 
in which you placed the FlatSplitterWnd files, and click ADD.</li></ul>

<ul><li>If you put these files into their own folder like I did, you have to 
add the folder to your project settings <b>Addition include directories</b>. 
Make sure you do this for all configurations.</li></ul>

<ul><li>Now that we have our files where we want them, let's look at the 
changes I made to implement my visual styling on the splitter bar. Open 
<code>FlatSplitterWnd.CPP, and look at the constructor for the class.</code></p>

<pre>CFlatSplitterWnd::CFlatSplitterWnd()
{
	m_cxSplitter = m_cySplitter = 3 + 1 + 1;
	m_cxBorderShare = m_cyBorderShare = 0;
	m_cxSplitterGap = m_cySplitterGap = 3 + 1 + 1;
	m_cxBorder = m_cyBorder = 1;
}
</pre>

<p>Since we need room for our bar text, we need to make the bar taller, so I 
changed the code above to this:</p>

<pre>CFlatSplitterWnd::CFlatSplitterWnd()
{
	// we need our splitter bar to be really wide/tall to 
	// support the text - jms
	m_cxSplitter    = m_cySplitter    = 15 + 1 + 1;
	m_cxBorderShare = m_cyBorderShare = 0;
	m_cxSplitterGap = m_cySplitterGap = 15 + 1 + 1;
	m_cxBorder      = m_cyBorder      = 1;

	// we need this for swappable views
	m_bFirstView    = true;
}
</pre>
</li></ul>

<ul><li>Next, we need to modify the OnDrawSplitter function. This is where all of 
the excitement happens regarding the color/style of the splitter bar. Here's 
the original function.

<pre>void CFlatSplitterWnd::OnDrawSplitter(CDC* pDC, ESplitType nType, const CRect& rectArg)
{
	// Let CSplitterWnd handle everything but the border-drawing
	if((nType != splitBorder) || (pDC == NULL))
	{
		CSplitterWnd::OnDrawSplitter(pDC, nType, rectArg);
		return;
	}

	ASSERT_VALID(pDC);

	pDC->Draw3dRect(rectArg, GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHIGHLIGHT));
}
</pre>

<p>The key to my modification lies in the <code>ESplitType</code> parameter. 
There are four possible values we can expect, but we're only interested in one 
- <code>splitBar</code>.  The revised version of the OnDrawSplitter function 
(shown below) includes sufficient comments to preclude me from providing further 
descriptions.

<pre>	if((nType != splitBorder && nType != splitBar) || (pDC == NULL))
	{
		CSplitterWnd::OnDrawSplitter(pDC, nType, rectArg);
		return;
	}

	ASSERT_VALID(pDC);

	switch (nType)
	{
		case splitBorder :
			{
				pDC->Draw3dRect(rectArg, GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHIGHLIGHT));
			}
			break;
		case splitBar :
			{
				// we need to get the window rect so we can center our text
				CRect   wndRect;
				GetWindowRect(&wndRect);

				// get ready to draw our yellow rectangle
				CRect   tempRect = rectArg;

				// draw the 3D rectangle (do I really need to do this?)
				pDC->Draw3dRect(rectArg, GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHIGHLIGHT));

				CFont   docFont;
				CFont*  pOldFont;
				CBrush  newBrush;
				CBrush* pOldBrush;

				// make the brush yellow and select it
				newBrush.CreateSolidBrush(RGB(255,255,0));
				pOldBrush = pDC->SelectObject(&newBrush);

				// construct and select our font
				BuildFont(pDC, &docFont, 9, false, false);
				pOldFont = pDC->SelectObject(&docFont);
				
				// build our title text
				CString sTitle = "INFO";
				CSize sz = pDC->GetTextExtent(sTitle);

				// make our rectangle 1 pixel smaller on all sides
				tempRect.DeflateRect(1,1,1,1);

				// calculate start position for text
				int xPos = (int)((wndRect.Width() - sz.cx) * 0.50);

				// make the text black, and the background (for drawing text) transparent
				pDC->SetTextColor(RGB(0,0,0));
				pDC->SetBkMode(TRANSPARENT);

				// draw our rectangle (uses default black pen)
				pDC->Rectangle(&tempRect);
				
				// free the brush resources
				pDC->SelectObject(pOldBrush);
				newBrush.DeleteObject();

				// make sure our drawing rectangle is large enough to 
				// accomodate the text AND the arrowheads, and draw the
				// text if we can
				bool bCanDrawText = (xPos - 30 > tempRect.left);
				if (bCanDrawText)
				{
					pDC->TextOut(xPos, tempRect.top, sTitle);
				}
				
				// free the font resource
				pDC->SelectObject(pOldFont);

				// if we have room to draw the text, then we have room for 
				/ the arrowheads as well
				if (bCanDrawText)
				{
					// create a brush for the down-arrow heads
					newBrush.CreateSolidBrush(RGB(0,0,0));
					pOldBrush = pDC->SelectObject(&newBrush);

					// adjust the rectangle
					tempRect.DeflateRect(0,3,0,4);
					CPoint pts[4];

					// draw the down-pointing arrowheads
					for (int i = 0; i <=1; i++)
					{
						int x = (i == 0) ? tempRect.Width() - sz.cx - 30 : tempRect.Width() + sz.cx + 15;
						int y = tempRect.top + 2;
						x = (int)(x * 0.50);
						pts[0] = CPoint(x,      y);
						pts[1] = CPoint(x + 10, y);
						pts[2] = CPoint(x + 5,  y + 5);
						pts[3] = CPoint(x,      y);
						pDC->Polygon(pts, 4);
					}

					// free our resources
					pDC->SelectObject(pOldBrush);
					newBrush.DeleteObject();
				}
			}
			break;
	}
</pre>

<p>I also added a function to build the font we need. To keep the 
<code>OnDrawSplitter()</code> function as clean as possible, I moved this code 
into its own function, as shown below.</p>

<pre>//------------------------------------------------------------------------------
// Added this function to build a suitable font for the INFO text - jms
//------------------------------------------------------------------------------
BOOL CFlatSplitterWnd::BuildFont(CDC* pDC, CFont* pFont, int nFontHeight, bool bBold, bool bItalic)
{
	// set the font properties
    CString sFontName = _T("Arial");
	int     nWeight   = (bBold) ? FW_BOLD : FW_NORMAL;
	BYTE    nItalic   = (bItalic) ? 1 : 0;
	nFontHeight       = -MulDiv(nFontHeight, pDC->GetDeviceCaps(LOGPIXELSY), 72);
	// create the font
	return pFont->CreateFont(nFontHeight, 0, 0, 0, nWeight, nItalic, 0, 0, DEFAULT_CHARSET,
                             OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY,
                             DEFAULT_PITCH | FF_DONTCARE, sFontName);
}
</pre>
</li></ul>

<ul><li>Now, we're going to add support for swappable views inside the splitter 
window. For this feature, I modified the CFlatSplitterWnd class to include the 
necessary code from another article here on CodeProject - 
<a href="http://www.codeproject.com/splitter/DanCMultiViewSplitter.asp" target="top">
Unlimited number of switchable views within a Splitter window</a>, by Dan Clark.

<p>Because we're just copy/pastng from another article into our existing 
splitter window class, there's no need to actually use any of the files from 
Dan's article. Well just talk about the functions I copy/pasted, and the 
changes I made to those functions.  Of course, you could alternately just 
derive this class from CFlatSplitterWnd (or vice-versa), but I personally don't 
like a lot of files, not to mention taxing VS2005's ability to keep up with 
everything (the more files/classes you have, the longer it takes the IDE to 
perform certain funcitons, like updating intellisense).</p>

<p>The AddSwitchableView function is called from the CMainFrame OnCreateClient() 
function, and is called for each view you want to make swappable.  When I first 
looked at this code, I decided there were slight improvements that could be 
made, so these functions don't quite match Dan's article.  Changes to the 
original code are noted in the comments.</p>

<pre>
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////                            Replaceable Views                           ////
////                                                                        ////
////          Source - copied from CodeProject article by Dan Clark         ////
////      http://www.codeproject.com/splitter/DanCMultiViewSplitter.asp     ////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------------
// Adds a view - jms version implements the following changes from the original 
// code.
//
// 1) I think a global variable that inidcates the first view works better than 
//    having the calling function determine the condition.  I default this 
//    variable to true in the constructor.
//
// 2) Eliminating the isFirstView parameter also allowed me to eliminate the 
//    original altID from the parameter list.
//
// The two changes described above resulted in a shorter parameter list and 
// cleaner code.
//------------------------------------------------------------------------------
bool CFlatSplitterWnd::AddSwitchableView(CRuntimeClass* pView, 
                                         CCreateContext* pContext, 
                                         CRect& size,  
                                         UINT viewID)
{

	CWnd* pWin  = (CWnd*) pView->CreateObject();
	DWORD style = WS_CHILD;
	if (m_bFirstView) 
	{
		style |= WS_VISIBLE;
		m_bFirstView = false;
	}
	pWin->Create(NULL, NULL, style, size , this, viewID, pContext);
	views[pWin] = viewID;
	return true;
}
</pre>

<p>The <code>SwitchView()</code> function actually does the work of switching 
views. I changed most of the comments and mad a small change regarding the 
setting of the window ID for the old view.<p>

<pre>//------------------------------------------------------------------------------
// Hides one view and shows another.
//
//Note from jms - notice that any splitter pane can hold swappable views.
//------------------------------------------------------------------------------
bool CFlatSplitterWnd::SwitchView(UINT id, int paneRow, int paneCol)
{
	CView* pOldView = (CView*) GetPane(paneRow, paneCol); // get current view

	// if we don't have an old view set, get out.
	// if you assert here, you haven't properly initilaized the splitter window.
	ASSERT(pOldView != NULL);
	if (pOldView == NULL)
	{
		MessageBeep(0);
		return false;
	}

	// get the new view specified by the id parameter
	CView* pNewView = (CView*)GetDlgItem(id);
	
	// if the new view is null, get out
	// if you assert here, you haven't properly initilaized the splitter window.
	ASSERT(pNewView != NULL);
	if (pNewView == NULL ) 
	{
		return false;
	}

	CFrameWnd* mainWnd = (CFrameWnd*)AfxGetMainWnd();
	// if you assert here, something REALLY bad happend.
	ASSERT(mainWnd != NULL);
	if (mainWnd == NULL) // serious prob
	{
		return false;
	}
	// set the active view to the specified view
	if (mainWnd->GetActiveView() == pOldView)
	{
		mainWnd->SetActiveView(pNewView);
	}
	// show/hide the windows - do it in this order to avoid flashing
	pNewView->ShowWindow(SW_SHOW);
	pOldView->ShowWindow(SW_HIDE);

	// set the window id from our view map
	pNewView->SetDlgCtrlID(IdFromRowCol(paneRow, paneCol));

	// we need to reset the old views ID so we can look it up again later
	// jms change - since we actually got to this point in the code, it's 
	// safe to assume that the old view is really somewhere in the view map.
	CWnd* pCwnd =(CWnd*)pOldView;
	if (views.find(pCwnd) != views.end())
	{
		UINT oldId = views[pCwnd];
		pOldView->SetDlgCtrlID(oldId);
	}

	// clean up
	RecalcLayout();
	pOldView->Invalidate();
	pNewView->Invalidate();

	return true;
}
</pre>

<p>The <code>GetViewPtr()</code> function wasn't changed from the original version. 
It simply retrieves a pointer to the view mapped to the specified ID.</p>

<pre>//------------------------------------------------------------------------------
// Gets the window corresponding to the specified ID
//------------------------------------------------------------------------------
CWnd* CFlatSplitterWnd::GetViewPtr(UINT id, int paneRow, int paneCol)
{
	map<CWnd*, UINT>::iterator It, Iend = views.end();
	for (It = views.begin(); It != Iend; It++)
	{
		if ((*It).second == id)
		{
			return (*It).first;
		}
	}
	return NULL;
}
</pre>

<p>The <code>GetIsActiveView()</code> function was added by yours truly to assist in 
determining a given view's active status, and is called froim CMainFrame.</p>

<pre>//------------------------------------------------------------------------------
// This function is called by CMainFrame OnUpdate functions for the view 
// selection menu items.
//------------------------------------------------------------------------------
BOOL CFlatSplitterWnd::GetIsActiveView(UINT nID, int nPaneRow, int nPaneCol)
{
	CWnd* pOldWnd = GetPane(nPaneRow, nPaneCol);
	CWnd* pNewWnd = GetViewPtr(nID, nPaneRow, nPaneCol);
	return (pOldWnd == pNewWnd);
}
</pre>

<p>The following lines were added to <code>CFlatSplitterWnd.H</code>:</p>

<pre>class CFlatSplitterWnd : public CSplitterWnd
{
public:
	// from http://www.codeproject.com/splitter/DanCMultiViewSplitter.asp
	map<CWnd*, UINT> views;
	bool m_bFirstView;
	CWnd* GetViewPtr       (UINT id, int paneRow, int paneCol);
	bool  SwitchView       (UINT id, int paneRow, int paneCol);
	bool  AddSwitchableView(CRuntimeClass* pView, CCreateContext* pContext, CRect& size,  UINT viewID);
	BOOL  GetIsActiveView  (UINT nID, int nPaneRow, int nPaneCol);
</pre>
</li></ul>

<ul><li>Finally, we need to create IDs for our views. Remember the Constants.h file 
we created in Part 1?.  Open it adnd add the following code right under our 
defintiion for the grid control:

<pre>#define IDC_PANE0_SECONDARY_VIEW 49001
#define IDC_PANE0_PRIMARY_VIEW   49002
#define IDC_PANE1_PRIMARY_VIEW   49003
</pre>

</li></ul>


<h3>Hook up CMainFrame</h3>

<ul><li>Add an override for OnCreateClient.  The class wizard will put this 
function at the bottom of CMainFrame:

<pre>BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
	// TODO: Add your specialized code here and/or call the base class

	return CFrameWnd::OnCreateClient(lpcs, pContext);
}
</pre>

<p>Replace that function with the following version.  The comments in this code 
block should sufficiently explain what's happening.</p>

<pre>BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
	CRect cr;
	GetWindowRect(&cr);
	// we need to subtract a reasonable value from the returned screen height 
	// so that when we actually create the view, it won't cover up part of the 
	// screen with it's soon-to-be-resized rectangle.
	int nHeight = ::GetSystemMetrics(SM_CYSCREEN) - 100;

	// our splitter window will be static
	if (!m_mainSplitter.CreateStatic(this, 2, 1))
	{
		AfxMessageBox("Error setting up splitter window", MB_ICONERROR);
		return FALSE;
	}

	// add our replacable views - the view IDs are defined in constants.h
	m_mainSplitter.AddSwitchableView(	RUNTIME_CLASS(CPrimaryView  ), 
										pContext, 
										CRect(0, 1, cr.Width(), nHeight), 
										IDC_PANE0_PRIMARY_VIEW);
	m_mainSplitter.AddSwitchableView(	RUNTIME_CLASS(CSecondaryView), 
										pContext, 
										CRect(0, 1, cr.Width(), nHeight), 
										IDC_PANE0_SECONDARY_VIEW);

	// create views in the splitter
	// we need a blank view to start off in the upper pane as the "old view 
	// because part of the initialization of the app is changing to the proper 
	// view. We'll use the view created by the app wizard.
	m_mainSplitter.CreateView(0, 0, RUNTIME_CLASS(CSDIMultiApp1View), CSize(cr.Width(), nHeight), pContext);
	// and for the lower pane, we'll use our CInfoView class.
	m_mainSplitter.CreateView(1, 0, RUNTIME_CLASS(CInfoView), CSize(cr.Width(), 0), pContext);

	return TRUE;
}
</pre>
</li></ul>

<ul<li>
Next, we need to add some helper functions to make our life eaiser in the future.

<pre>CPrimaryView* CMainFrame::GetPrimaryView()
{
	return (dynamic_cast<CPrimaryView*>(m_mainSplitter.GetPane(0,0)));
}

CSecondaryView* CMainFrame::GetSecondaryView()
{
	return (dynamic_cast<CSecondaryView*>(m_mainSplitter.GetPane(0,0)));
}

CInfoView* CMainFrame::GetInfoView()
{
	return (dynamic_cast<CInfoView*>(m_mainSplitter.GetPane(1,0)));
}
</pre>

As you can seethese functions merely return a pointer to the active view that is 
indicated in the name of the function. Since we're using <code>dynamic_cast</code>, 
the pointer will be NULL if the view returned is not of the type specified between 
the <code>&lt; &gt;</code> symbols.
</li></ul>

<p>Now that we have all of our view classes coded and the splitter window 
implemented, we need to provide the user wth a way to switch the views.</p>


<h3>Menus Make It Go</h3>

<p>The user will be able to switch between the Primary View and the Secondary View 
via menu items.  Adding menus in a MFC app is fairly trivial.</p>

<ul><li>In the IDE's Resource View, open up the resource, expand the tree until 
you see a list of resource categories, expand the <b>Menu</b> item, and 
double-click IDR_MAINFRAME.  A new window will open in the IDE showing the 
program's current menu.</li></ul>

<ul><li>Find the <b>View</b> Item, click it, and then right-click on the line 
that reads "Type Here" (just beneath the <b>Status Bar</b> item).</li></ul>

<ul><li>Right click the click it, and select the <b>Insert Separator</b> item 
from the subsequent menu. A separator line will be added to the menu, and a 
new "Type Here" line will appear below the separator.</li></ul>

<p><center><img border="1" src="add_menu_items_01.png"></center></p>

<ul><li>Click on the new <b>Type Here</b> item, and type in "Primary View". 
Notice that a new "Type Here" item is added directly below.
</li></ul>

<ul><li>Click on the new <b>Type Here</b> item, and type in "Secondary View".
</li></ul>

<ul><li>Go back and right-click on the <b>Primary View</b> item</li></ul>, 
and select <b>Add Event Handler...</b>  from the menu.

<p><center><img border="1" src="add_msg_handler_01.png"></center></p>

</li><ul>

<ul><li>The Event Handler Wizard dialog box should now be displayed (see below). 
This dialog allows you to set one event handler at a time. Notice there are two 
possible events we can set. We need them both for the sample but since we can 
only do one at a time, we'll pcik ON_COMMAND. Make double-damn sure that you've 
selected the cirrect class to which we are adding this handler - it MUST be 
CMainFrame. You'll have to repeat the previous step to get back here to set a 
handler for ON_COMMAND_UI.  Again, make sure that you add the handler to the 
correct class. </li></ul>

<ul><li>Repeat the last two steps for the <b>Secondary View</b> menu item.

<p>If you performed the previous steps corrcetly, you should see the following 
functions at the bottom of your CMainFrame class:</p>

<pre>void CMainFrame::OnViewPrimaryview()
{
}

void CMainFrame::OnUpdateViewPrimaryview(CCmdUI *pCmdUI)
{
}

void CMainFrame::OnViewSecondaryview()
{
}

void CMainFrame::OnUpdateViewSecondaryview(CCmdUI *pCmdUI)
{
}
</pre>

</li></ul>

<ul><li>Add code to the new message handling functions to make them switch views 
and enable/disable the menu items depending onwhich view is currently active.

<pre>void CMainFrame::OnViewPrimaryview()
{
	m_mainSplitter.SwitchView(IDC_PANE0_PRIMARY_VIEW, 0, 0);
}

void CMainFrame::OnUpdateViewPrimaryview(CCmdUI *pCmdUI)
{
	pCmdUI->Enable(!m_mainSplitter.GetIsActiveView(IDC_PANE0_PRIMARY_VIEW,0,0));
}

void CMainFrame::OnViewSecondaryview()
{
	m_mainSplitter.SwitchView(IDC_PANE0_SECONDARY_VIEW, 0, 0);
}

void CMainFrame::OnUpdateViewSecondaryview(CCmdUI *pCmdUI)
{
	pCmdUI->Enable(!m_mainSplitter.GetIsActiveView(IDC_PANE0_SECONDARY_VIEW,0,0));
}
</pre>
</li></ul>

<h2>Compile The App</h2>

<p>Rebuild the solution, and run the app.  You should see something that 
resembles the following. This is the way the app looks when you first run 
it. The primary (grid) view is displayed, and the splitter bar is located 
at the bottom of the client area of the window.</p>

<p><center><img border="1" src="part_02_done_01.png"></center></p>

<p>When you click/drag the splitter bar up the screen, you'll see the following:</p>

<p><center><img border="1" src="part_02_done_02.png"></center></p>

<p>Finally, click the <b>View</b> menu item. When the menu drops downm, you'll 
see that the <b>Primary View</b> item is disabled (because the Primary View is 
already active)

<p><center><img border="1" src="part_02_done_03.png"></center></p>

Go ahead and click the Secondary View item. You should see 
the following:

<p><center><img border="1" src="part_02_done_04.png"></center></p>

<h2>What's Next?</h2>

<p>In Part 3, we'll be adding a custom status bar class and some multi-threading 
components, again using articles found here on CodeProject.</p>


<h2>End of Part 2</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>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions