Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Multi-Multi MDI

0.00/5 (No votes)
24 May 2006 1  
Some boilerplate code for your WTL applications.

Sample screen

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:

  1. 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 /*uMsg*/, WPARAM /*wParam*/, 
               LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        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 /*uMsg*/, WPARAM /*wParam*/, 
            LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        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;
    }
  2. 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.

  3. We need a variable in the CChildFrame class that allows the selection of the type of view to be constructed, hence:
    int iType; //View selector
    
    
    ...
    
    //Define a constructor that allows iType member initialization, 
    
    //and a destructor that deletes the view.
    
    
    CChildFrame(int iiType = 1) : m_view(0), iType(iiType)
    {
    }
    
    ~CChildFrame()
    {
        if (m_view)
            delete m_view;
    }
  4. We need to modify the OnCreate method of the CChildFrame class, which I've done this way:
    LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, 
            LPARAM /*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;
    }
    
    //The icon part and the Settitle part are not mandatory. 
    
    //They just contribute to highlight differences between views.
  5. 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:

    //Message map
    
    COMMAND_ID_HANDLER(ID_FILE_NEW2, OnFileNew2)
    ...
    
    //Original OnFileNew, remains unmodified, just takes 
    
    //advantage of the default CChildFrame constructor
    
    LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, 
            HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        CChildFrame* pChild = new CChildFrame;
        pChild->CreateEx(m_hWndClient);
    
        // TODO: add code to initialize document
    
    
        return 0;
    }
    
    //Added to provide for the construction of CMDI2View2, 
    
    //the key is CChildFrame construction parameter (2)
    
    LRESULT OnFileNew2(WORD /*wNotifyCode*/, WORD /*wID*/, 
            HWND /*hWndCtl*/, BOOL& /*bHandled*/)
    {
        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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here