Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

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

4.58/5 (19 votes)
15 Apr 2008CPOL17 min read 1   437  
The right way to code using CodeProject for occasional support

Part 4 of 5 - Introduction

Links to Other Parts of the Series

The following text is identical to Part 1. 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 these intro sections.

This article series is another in my series of "code we really use" articles. There is no unnecessary 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.

Rant

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.

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.

So, What's the Point of This Article?

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.

Assumptions

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.

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. I also assume that you have a decent working knowledge of MFC. I'm not saying you have to be an expert, but I assume you can move around in a MFC project without bumping your head on the intricacies of CMainFrame.

Other Stuff

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.

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.

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.

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.

What We've Already Done

In Part 1 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 added a splitter window and the ability to swap views within one of the splitter panes. In Part 3, we added a custom status bar class and some simple multi-threading capabilities that update the contents of the status bar panes.

More Real-World Requirements

Our real-world application's primary purpose was to present "the big picture" to the Emergency Room staff via a 60-inch plasma display suspended from the ceiling (I know - it always seems like someone else gets all the neat toys :) ). Since this was going to be nothing more than an information display, no user-interaction is anticipated or desired. If something needs to change, the users can use the client application that we just finished.

With the exception of the user interaction stuff, the big board app is identical to the client app. For this reason, we moved the entire display portion of the client application into an extension DLL, and then just linked to that DLL from both the client and big board applications.

This article discusses the process of creating the extension DLL, and creating applications to use it.

A Couple Of Words About Part 4 Article Content

Due to the nature of the stuff we'll be adding in Part 4, I won't be talking about where to put stuff, but more about what I did and why. If you want to see the whole kit-n-kaboodle, you can unzip the code over the the Part 1, Part 2, or Part 3 code.

Generally Speaking

So that we don't hose up the existing code, we're going to cretae three new projects withoin our current solution:

  • SDIDisplay - our "big board" application. This application won't have a menu.
  • SDIClient - our client application. This application will have a menu
  • SDIViews - our extension DLL. This DLL will be used by both of the applications.

Creating the extension DLL

Be advised that I'm assuming you know how to perform all of these steps without hand-holding, and for that reason, the instructions might seem sparse. If you have questions, post them.

  • Create a new MFC DLL project called SDIViews. Make sure that you check the MFC Extension DLL checkbox when you are prompted to select the DLL type.
  • Change the following properties:
    • Configuration Properties | General | Character set = Multi-byte character set.
    If you don't do this, the project will not compile. I don't know why, and to be quite honest, I don't care.
  • Copy the additional include directories setting used in SDIMultiApp1 to this new project.
  • Copy the following files from the original project folder (SDIMultiApp1) to this new project's folder:
    • DlgOne.*
    • DlgTwo.*
    • InfoView.*
    • PrimaryView.*
    • SecondaryView.*
  • Add those eight files to your project.
  • This is an extension DLL, so this dictates that we're going to be exporting some MFC classes. In fact, we want to expoort our view classes. In the header files for the view classes, add this - __declspec(dllexport) - between "class" and the class name, like so:
    class __declspec(dllexport) CInfoView : public CScrollView
  • We no longer need to include the old application's header file in this project, so in each of the the copied CPP files, remove this line:
    #include "SDIMultiApp1.h"
  • Because of the previous step, we no longer have access to the project's resources, so in the header file of all of the classes we copied over, add this line:
    #include "resource.h"
  • Add the MFC Grid Control files to the project.
  • Next, we have to copy the resource IDs for the menu items we want to handle in the view classes. To make it faster for you, here are the lines you have to put into your resource.h file:
    #define ID_VIEW_PRIMARYVIEW             32771
    #define ID_VIEW_SECONDARYVIEW           32772
    #define ID_SAMPLESTUFF_DIALOGONE        32773
    #define ID_SAMPLESTUFF_DIALOGTWO        32774

At this point, verify that you can compile the new project. If not, I forgot to list one or more steps, or you did something wrong, so go back and fix it. We're not quite done with this project, but now is a good time to verify that we have a viable project. We're also going to skip ahead and create one of our new application projects.

Adding the Client and Display Projects To The Solution

Essentially, the client application is supposed to look exactly like the original application. The only difference is that it will use the extension DLL that we just created. Create both application projects using the following steps.

  • Create a new MFC SDI application (use Part 1 as a guide for the project settings). For the first one, name it "SDIClient". For the second one, name it "SDIDisplay".
  • Set the project's dependencies to require the SDIViews project. This will tell the compiler that this DLL must be compiled before this project can be compiled, and when running the app, the DLL will be implicitly loaded (the app will not run if the DLL is not loaded).
  • Change the additional include directories to the following (make sure you do this for both release and debug configurations):
    .,../include,../MFCGridControl_2-25,../CodeProject,../SDIViews
    

    I don't know why, but even though there's no reference to the MFC grid control in the client app, you have to have the path to that source code included in the additional include directories setting.
  • In InitInstance() in the SDIClient.CPP file, locate and change the CSingleDocTemplate constructor call to the following:
    pDocTemplate = new CSingleDocTemplate(
        IDR_MAINFRAME,
        RUNTIME_CLASS(CSDIClientDoc),
        RUNTIME_CLASS(CMainFrame),
        RUNTIME_CLASS(CPrimaryView));
    

    This will allow us to test the DLL before proceeding.

  • Set this project as the "Startup Project". This will cause the compiler to compile this project and it's dependencies.
  • Add the CodeProject article files (for the statusbar, thread, and splitter window) to this new project.
  • Compile and run the client application. It should look exactly like the screenshot at the end of Part 1 of this article.

At this point the programs diverge slightly in functionality. Let's get the old stuff out of the way, and do the SDIClient application first.

SDIClient Functionality

If you remember, the SDIClient appication is supposed to look exactly like the original SDIMultiApp1 program that we built in Parts 1 through 3. Fortunately, the process of duplicating that functionality is mostly a matter of copy/pasting code from that application into SDIClient. In all of the instructions below, when I say to change/delete/add, I'm talking about the SDIClient copy of the file.

  • In SDIClient.CPP:
    • In the InitInstance() function, change the pDocTemplate constructor call to once again use the CSDIClientView class, like so:
      pDocTemplate = new CSingleDocTemplate(
              IDR_MAINFRAME,
              RUNTIME_CLASS(CSDIClientDoc),
              RUNTIME_CLASS(CMainFrame),
              RUNTIME_CLASS(CSDIClientView)); //  <-------
      
    • In the InitInstance() function, copy/paste our thread/view function calls (this code sjhould be at/near the end of the InitInstance() function):
      if (!((CMainFrame*)m_pMainWnd)->PrepareViews())
      {
          return FALSE;
      }
      
      if (((CMainFrame*)m_pMainWnd)->CreateTimerThread())
      {
          ((CMainFrame*)m_pMainWnd)->StartTimerThread();
      }
      
      ((CMainFrame*)m_pMainWnd)->CreateActionThreads();
      
  • In MainFrm.H:
    • Copy/paste the entire contents of the file into the SDIClient projects MainFrm.h.
    • Remove the following lines:
      afx_msg void OnSamplestuffDialogone();
      afx_msg void OnSamplestuffDialogtwo();
      
    • Change the line #include "SDIMultiApp1View.h" to read #include "SDIClientView.h".
  • In MainFrm.CPP:
      • Start by copy/pasting the entire contents of the file into the SDIClient project's MainFrm.CPP.
      • Change the line #include "SDIMultiApp1.h" to read #include "SDIClient.h"
      • Comment out the following lines (we're going to copy/paste them someplace else later):
        #include "DlgOne.h"
        #include "DlgTwo.h"
        ...and...
        ON_COMMAND(ID_SAMPLESTUFF_DIALOGONE, 
                   &CMainFrame::OnSamplestuffDialogone)
        ON_COMMAND(ID_SAMPLESTUFF_DIALOGTWO, 
                   &CMainFrame::OnSamplestuffDialogtwo)
        ...and...
        //-------------------------------------------------------------
        //-------------------------------------------------------------
        void CMainFrame::OnSamplestuffDialogone()
        {
            PauseTimers();
            CDlgOne dlg;
            dlg.DoModal();
            ContinueTimers();
        }
        
        //-------------------------------------------------------------
        //-------------------------------------------------------------
        void CMainFrame::OnSamplestuffDialogtwo()
        {
            CDlgTwo dlg;
            dlg.DoModal();
        }
      • In OnCreateClient (near the end of the function), change the following line:
        m_mainSplitter.CreateView(0, 0,
             RUNTIME_CLASS(CSDIMultiApp1View), CSize(cr.Width(),
             nHeight), pContext);
        
        ...to read:
        m_mainSplitter.CreateView(0, 0,
             RUNTIME_CLASS(CSDIClientView), CSize(cr.Width(),
             nHeight), pContext);
        
      Coding Notes
      At this point, I realized that it would be much more efficient to create a folder that held just the thread classes since they were common between all of the applications we're building. I went ahead and did this for these two new projects, as well as the initial application we built. Look for the ThreadClasses folder in the sample project for this article.
      • Create a new folder called ThreadClasses in the solution folder.
      • Copy the following files from the SDIMultiApp1 project into the ThreadClasses folder, and then add the files to the project:
        • TimersTHread.*
        • ThreadActionBase.*
        • ThreadActionLong.*
        • ThreadActionShort.*
        • EDTimerStruct.h
    • In SDIClientView.h, ad the line #include "SDIClientDoc.h".
      • In stdafx.h, add the line #include "Constants.h"
      • Next, we have to copy the menu resources. Since we have all of our projects in the same solution, this is a fairly simply copy/paste step. Just open up the menu resource for the SDIMultiApp1 project, and then the one for the SDIClient project, and you can copy/paste from one to the other.
      • Then we need to copy the dialog resources. Once again, it's a simple copy/paste process.

Once More Into the SDIViews Project

We have to add message handlers to the applicable views. In our sample, we'll only be adding handlers in the CPrimaryView class. Unfortunately, the IDE didn't want to let me add message handlers through the Properties view, so I had to add them manually. Remember those lines we commented out when we added functionality to the SDIClient project? This is precisely the reason why we commented them out instead of deleting them. Go back to the SDIClient project and copy paste the message handler code that display the dialog boxes. Make sure you change all references of CMainFrame to CPrimaryView.

Coding Notes
We didn't have to put the dialog boxes into the SDIViews DLL, but I wanted to illustrate some of the issues we experienced when we did it in our real world application. I'm sure there's some elegant (yet obscure) method we could have used to get around this particular issue, but hey, we are under the gun to finish the app, and while clunky, this method works just fine.
  • We also need to add a function through which we can pass the timers thread pointer. Add the following to the SDIViews.H file:
    #include "TimersTHread.h"
    
    class __declspec(dllexport) CPrimaryView : public CView
    {
    private:
        CTimersThread* m_pTimersThread;
    public:
        /// Sets the timerthread pointer so we can pause/restart from here
        void SetTimersThreadPtr(CTimersThread* pThread) 
        { 
            m_pTimersThread = pThread; 
        };
    ...and add the following to the CPP file:
    m_pTimersThread = NULL;
    
  • We now need to add the CTimersThread files to the project (remember, they're in the ThreadClasses folder), and add the ThreadClasses folder to the Additional include directories setting.
  • Next, we need to copy/paste our PauseTimers and ContinueTimers functions from CMainFrame in the SDIClient project into the CPrimaryView class.
Coding Notes
At some point, you're going to ask yourself why I didn't just create a base view class with the timer support in it. First, we didn't need to for our application because only one view needed to handle any of the menu commands because most of the menu commands affected only the primary view.

An alternative approach would be to use multiple inheritance and simply create a class that handles the thread pointer and adds this functionality to any class that inherits from it. In fact, let's go ahead and do this right now. This is a convenient way to add common functionality to multiple existing classes.

Important Note:This is one of the few times you'll use multiple inheritance in a MFC application. This reason this is safe to do is because CThread (the base class for CTimersThread) is not derived from a MFC class.
  • Create a new C++ class (NOT a MFC class) called CTimersTHreadMgr. Check the Virtual destructor checkbox before clicking the Finish button
  • When the IDE presents the header file, change it to look like this:
    #pragma once
    
    #include "TimersTHread.h"
    
    class CTimersThreadMgr
    {
    private:
        CTimersThread* m_pTimersThread;
    
    public:
        CTimersThreadMgr(void);
        virtual ~CTimersThreadMgr(void);
    
        /// Sets the timerthread pointer so we can pause/restart from here
        void SetTimersThreadPtr(CTimersThread* pThread) 
        { 
           m_pTimersThread = pThread; 
        };
        
        void PauseTimers();
        void ContinueTimers();
    };
  • Now, open the CPP file for the class and replace the contents with this code:
    #include "StdAfx.h"
    #include "TimersThreadMgr.h"
    
    CTimersThreadMgr::CTimersThreadMgr(void)
    {
        m_pTimersThread = NULL;
    }
    
    CTimersThreadMgr::~CTimersThreadMgr(void)
    {
    }
    
    //----------------------------------------------------------------------
    // Pauses the timers thread 
    //----------------------------------------------------------------------
    void CTimersThreadMgr::PauseTimers()
    {
        // make sure we have a valid pointer
        if (m_pTimersThread)
        {
            m_pTimersThread->Pause();
        }
    }
    
    //----------------------------------------------------------------------
    // Un-pauses the timers thread 
    //----------------------------------------------------------------------
    void CTimersThreadMgr::ContinueTimers()
    {
        // make sure we have a valid pointer
        if (m_pTimersThread)
        {
            m_pTimersThread->Continue();
        }
    }
  • Next, comment out (or delete) the following line in CPrimaryView.H:
    CTimersThread* m_pTimersThread;
    void SetTimersThreadPtr(CTimersThread* pThread)
    { m_pTimersThread = pThread; };
    
  • Add the following line to CreateTimersThread() function in SDIClient MainFrmp.CPP (at the end of the if (m_pTimersThread) code block:
    GetPrimaryView()->SetTimersThreadPtr(m_pTimersThread);
    

Compile and run the SDIClient program. Once again, if I missed a step or two in here, you should be able to figure out what it was. Feel free to let me know and I'll update the article accordingly. The sample app should compile just fine, so you could always just skip all the mumbo-jumbo above and take the easy way out. :)

Wen you run the application, you should see a functionally (and visually) identical application to the one we created in Parts 1 through 3 of this article series.

SDIDisplay Functionality

This application differs moderately from the SDIClient application. The following features are NOT present in this application:

  • There is no splitter window
  • There is no user interaction via the menu

However, this app does show the primary view (the grid), and it does utilize the timers for periodic updates. Despite the fact that we don't have a splitter window to contend with, I still added the same basic infrastructure to support it - just in case a manager decided we need to add such a feature. This application will also use the SDIViews DLL because that's where our CPrimaryView lives.

In essence, we need to make the same changes to SDIDisplay as we did in SDIClient with the two following exceptions. To avoid repeating myself, and to save you a butt-load of time redin it, I'll just touch on the major differences.

  • In SDIDisplay.CPP, we use the CPrimaryView in our pDocTemplate constructor call.
    pDocTemplate = new CSingleDocTemplate(
        IDR_MAINFRAME,
        RUNTIME_CLASS(CSDIDisplayDoc),
        RUNTIME_CLASS(CMainFrame),
        RUNTIME_CLASS(CPrimaryView));
    
  • We also need to call a new function before calling PrepareViews():
    // delete the menu (this app doesn't need one)
    ((CMainFrame*)m_pMainWnd)->DeleteMenu();
    
  • In MainFrm.CPP, we have a modified version of the PrepareViews() function. Since we don't have to deal with multiple views or splitter windows, we have a much easier time of things. All we need to do is make sure that we have the correct active view.
    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    bool CMainFrame::PrepareViews()
    {
        bool bResult = true;
        CPrimaryView* pView = (CPrimaryView*)GetActiveView();
        if (pView)
        {
        }
        else
        {
            bResult = false;
        }
        return bResult;
    }

    Again, this is not really necessary, but we want the two apps to look as similar as possible for ease of maintenance and for common frames of reference.

  • Next, we add the following functions to MainFrm.CPP:
    //-----------------------------------------------------------------------
    //-----------------------------------------------------------------------
    void CMainFrame::DeleteMenu()
    {
        ::SetMenu(this->GetSafeHwnd(), NULL);
        ::DestroyMenu(m_hMenuDefault);
    }
    
    //------------------------------------------------------------------------
    //------------------------------------------------------------------------
    void CMainFrame::AddMenu()
    {
        CMenu hNewMenu;
        hNewMenu.LoadMenu(IDR_MAINFRAME);
        SetMenu(&hNewMenu);
        m_hMenuDefault = hNewMenu.GetSafeHmenu();
    }

    These functions allow us to manipulate the presence of the menu. The reason we have an AddMenu() funciton is because we need to have a menu attached to the application in order to close it (MFC expects it to be there).

  • As you may have guessed, we need to add a call to AddMenu in the overridden OnClose() handler.
  • Finally, we need to hide the toolbar window. Simply add the following line to the OnCreate function immediately after calling m_wndToolbar.Create():
    m_wndToolBar.ShowWindow(SW_HIDE);
    

End Result

At this point, we have broken the app up into the following descrete parts:

  • SDIViews.DLL - A shared MFC extension DLL that contains our views and non-menu user interface components
  • SDIClient.EXE - An executable file that implicitly links SDIViews.DLL, providing access to the all of the views via a swappable view splitter window, as well as some dialog boxes to demonstrate the pausing and continuing of the timers thread.
  • SDIDisplay.EXE - An executable file that implicitly links SDIViews.DLL, providing access to the CPrimaryView, and omitting any form of usable menu-based user interface components. Yes, the stuff provided by the app wizard for the standard file and edit commands are there, but they don't do anything.

Here are a couple of screenshots of what you should see:

Image 1
Image 2

What's Next?

In Part 5, we'll perform the "OH MY GOD THEY JUST GAVE US A CRAP-LOAD OF NEW REQUIREMENTS" jig. We'll be cleaning up some of the loose ends (like the word "Untitled" in the application's titlebar), enhancing the applications by modifying the tool bar, adding a couple more views, adding printing support, and enhancing the CFlatSplitterWnd to draw custom vertical splitter bars.

End of Part 4

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).

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.

License

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