[WTL] Implementing multiple SDI views

I came across a thread on WTL news group about multiple views in SDI application and also the answer which suggested to create all views on application start as hidden and change by using show window function. The solution definetely works, but from the implementation point of view seems a bit clumsy. I didn’t like that you had to show or hide the view list whenever  a new view is added to your application (or maybe it was just for the sake of simplicity?).

Anyhow I would much better prefer some sort implementation as below:

BEGIN_VIEW_MAP()
VIEW_ENTRY(1, CMultiviewSDIView )
VIEW_ENTRY(2, CHTMLView )
END_VIEW_MAP()
DEFINE_DEFAULT_VIEW(1);

In order to achieve the implementation it is assumed that each view would implement a common interface with two methods which are HWND Init(HWND hContainer) and PreTranslateMessage (just in case).

Using the approach each view can be implemented in its own class and avoids putting code inside MainFrame implementation. See a stub code for a view implementation:

class CMultiviewSDIView :
   public CWindowImpl<CMultiviewSDIView, CRichEditCtrl>,
   public IManagerViewClient
{
   /* implementation here */ 
}

The MainFrame class would extend CViewManager and call InitViewManager(HWND hContainer) on create event. The container is assumed to be the client area of the document handle. To change a view simply call ChangeView(UINT nID) method also provided by the view manager implementation. The nID is the ID specified inside the view map (defined at the start of this post). That’s it…

Here is the complete implementation of the view manager and the client interface:

#pragma once 
#include "stdafx.h"

#include <atlcoll.h>

interface IManagerViewClient
{
 public:
    virtual HWND Init(HWND hContainer) = 0; 
    virtual BOOL PreTranslateMessage(MSG* pMsg) = 0; 
};

struct _ViewMgrEntry {
   UINT nID;
   IManagerViewClient* _client;
};

struct _ViewMgrData { 
   HWND hClient; 
   const _ViewMgrEntry *_e;
};

#define BEGIN_VIEW_MAP() \
   static const _ViewMgrEntry* GetViewMap() \
   { \
      static const _ViewMgrEntry theMap[] = \
      {

#define VIEW_ENTRY(uid, cl) \
    { uid, new cl },

#define END_VIEW_MAP() \
       { (WORD) - 1, NULL },  \
   }; \

  return theMap; \
};

#define DEFINE_DEFAULT_VIEW(uid) \
static const UINT GetDefaultView() \
{ \
   return uid; \
}; \

template <class T>
class CViewManager {
private:
    _ViewMgrData m_pCurView; 
    ATL::CAtlMap<UINT, _ViewMgrData> m_viewMap; 

public: 
    IManagerViewClient* GetCurrentView() { 
    ATLASSERT(m_pCurView._e != NULL); 
    return m_pCurView._e->_client;
}

void ChangeView(UINT nView) 
{
   ATLASSERT(m_pCurView._e != NULL); 
   ATLASSERT(m_pCurView._e->nID != nView); 
 
   POSITION pos = m_viewMap.GetStartPosition(); 
   while (pos != NULL) {
      UINT nID = m_viewMap.GetKeyAt(pos);
      _ViewMgrData pDat = m_viewMap.GetNextValue(pos); 

      if (nID != nView)  {
        ::ShowWindow(pDat.hClient, SW_HIDE); 
      } else { 
        ::ShowWindow(pDat.hClient, SW_SHOW); 
         T* p = static_cast<T*>(this); 
         p->m_hWndClient = pDat.hClient; 
        m_pCurView = pDat; 
        p->UpdateLayout(); 
     }
  }
}

void InitViewManager(HWND hContainer) {
   T* p = static_cast<T*>(this); 
   const _ViewMgrEntry* theMap = p->GetViewMap(); 

   int nCount; 
   for (nCount = 0; theMap->nID != (WORD)-1; nCount++) {
      HWND _c = theMap->_client->Init(hContainer); 
      const _ViewMgrData pDat = { _c, theMap }; 
      
      if (theMap->nID == p->GetDefaultView()) 
      {
         m_pCurView = pDat;
         p->m_hWndClient = _c; 
      }
   
      m_viewMap.SetAt(theMap->nID, pDat); 
      theMap++; 
    } 
  }
};

P.S. I am not saying that this is a good solution or bad one, it is just an idea.

Advertisement

About this entry