[Previous] [Next]

Splitter Windows

MDI applications inherently support multiple views of a document; SDI applications do not. For SDI applications, the best way to present two or more concurrent views of a document is to use a splitter window based on MFC's CSplitterWnd class. A splitter window is a window that can be divided into two or more panes horizontally, vertically, or both horizontally and vertically using movable splitter bars. Each pane contains one view of a document's data. The views are children of the splitter window, and the splitter window itself is normally a child of a frame window. In an SDI application, the splitter window is a child of the top-level frame window. In an MDI application, the splitter window is a child of an MDI document frame. A view positioned inside a splitter window can use CView::GetParentFrame to obtain a pointer to its parent frame window.

MFC supports two types of splitter windows: static and dynamic. The numbers of rows and columns in a static splitter window are set when the splitter is created and can't be changed by the user. The user is, however, free to resize individual rows and columns. A static splitter window can contain a maximum of 16 rows and 16 columns. For an example of an application that uses a static splitter, look no further than the Windows Explorer. Explorer's main window is divided in half vertically by a static splitter window.

A dynamic splitter window is limited to at most two rows and two columns, but it can be split and unsplit interactively. The views displayed in a dynamic splitter window's panes aren't entirely independent of each other: when a dynamic splitter window is split horizontally, the two rows have independent vertical scroll bars but share a horizontal scroll bar. Similarly, the two columns of a dynamic splitter window split vertically contain horizontal scroll bars of their own but share a vertical scroll bar. The maximum number of rows and columns a dynamic splitter window can be divided into are specified when the splitter is created. Thus, it's a simple matter to create a dynamic splitter window that can be split horizontally or vertically but not both. Visual C++ uses a dynamic splitter window to permit two or more sections of a source code file to be edited at once. (See Figure 11-4.)

Click to view at full size.

Figure 11-4. A dynamic splitter showing two views of a document in Visual C++.

One criterion for choosing between static and dynamic splitter windows is whether you want the user to be able to change the splitter's row and column configuration interactively. Use a dynamic splitter window if you do. Another factor in the decision is what kinds of views you plan to use in the splitter's panes. It's easy to use two or more different view classes in a static splitter window because you specify the type of view that goes in each pane. MFC manages the views in a dynamic splitter window, however, so a dynamic splitter uses the same view class for all of its views unless you derive a new class from CSplitterWnd and modify the splitter's default behavior.

Dynamic Splitter Windows

Dynamic splitter windows are created with MFC's CSplitterWnd::Create function. Creating and initializing a dynamic splitter window is a simple two-step procedure:

  1. Add a CSplitterWnd data member to the frame window class.
  2. Override the frame window's virtual OnCreateClient function, and call CSplitterWnd::Create to create a dynamic splitter window in the frame window's client area.

Assuming m_wndSplitter is a CSplitterWnd object that's a member of the frame window class CMainFrame, the following OnCreateClient override creates a dynamic splitter window inside the frame window:

BOOL CMainFrame::OnCreateClient (LPCREATESTRUCT lpcs,
    CCreateContext* pContext)
{
    return m_wndSplitter.Create (this, 2, 1, CSize (1, 1), pContext);
}

The first parameter to CSplitterWnd::Create identifies the splitter window's parent, which is the frame window. The second and third parameters specify the maximum number of rows and columns that the window can be split into. Because a dynamic splitter window supports a maximum of two rows and two columns, these parameter values will always be 1 or 2. The fourth parameter specifies each pane's minimum width and height in pixels. The framework uses these values to determine when panes should be created and destroyed as splitter bars are moved. CSize values equal to (1,1) specify that panes can be as little as 1 pixel wide and 1 pixel tall. The fifth parameter is a pointer to a CCreateContext structure provided by the framework. The structure's m_pNewViewClass member identifies the view class used to create views in the splitter's panes. The framework creates the initial view for you and puts it into the first pane. Other views of the same class are created automatically as additional panes are created.

CSplitterWnd::Create supports optional sixth and seventh parameters specifying the splitter window's style and its child window ID. In most instances, the defaults are fine. The default child window ID of AFX_IDW_PANE_FIRST is a magic number that enables a frame window to identify the splitter window associated with it. You need to modify the ID only if you create a second splitter window in a frame window that already contains a splitter.

Once a dynamic splitter window is created, the framework provides the logic to make it work. If the window is initially unsplit and the user drags a vertical splitter bar to the middle of the window, for example, MFC splits the window vertically and creates a view inside the new pane. Because the new view is created at run time, the view class must support dynamic creation. If the user later drags the vertical splitter bar to the left or right edge of the window (or close enough to the edge that either pane's width is less than the minimum width specified when the splitter window was created), MFC destroys the secondary pane and the view that appears inside it.

The CSplitterWnd class includes a number of useful member functions you can call on to query a splitter window for information. Among other things, you can ask for the number of rows or columns currently displayed, for the width or height of a row or a column, or for a CView pointer to the view in a particular row and column. If you'd like to add a Split command to your application's menu, include a menu item whose ID is ID_WINDOW_SPLIT. This ID is prewired to the command handler CView::OnSplitCmd and the update handler CView::OnUpdateSplitCmd in CView's message map. Internally, CView::OnSplitCmd calls CSplitterWnd::DoKeyboardSplit to begin a tracking process that allows phantom splitter bars to be moved with the up and down arrow keys. Tracking ends when Enter is pressed to accept the new splitter position or Esc is pressed to cancel the operation.

The Sketch Application

The application shown in Figure 11-5 is a sketching application that you can use to create simple line drawings. To draw a line, press and hold the left mouse button and drag with the button held down. Releasing the left mouse button replaces the rubber-band line that follows the cursor with a real line. The Grid command in the View menu enables and disables snapping. When snapping is enabled, endpoints automatically snap to the nearest grid point.

Click to view at full size.

Figure 11-5. The Sketch window halved by a dynamic splitter window.

Sketch's source code appears in Figure 11-6. In most respects, Sketch is a standard SDI document/view application. Lines drawn by the user are represented by instances of CLine, which includes CPoint member variables for storing a line's endpoints and a Draw function for drawing a line on the screen. The document object stores pointers to CLine objects in a dynamic array based on MFC's CTypedPtrArray class. Each time a line is drawn on the screen, the view, which uses mouse capturing to ensure that every WM_LBUTTONDOWN message is accompanied by a WM_LBUTTONUP message, calls the document's AddLine function and passes in the line's endpoints. AddLine, in turn, creates a new CLine object from those endpoints and records the CLine's address in the array:

// In SketchDoc.h
typedef CTypedPtrArray<CObArray, CLine*> CLineArray;
  
  
  
CLineArray m_arrLines;

// In SketchDoc.cpp
CLine* CSketchDoc::AddLine(POINT from, POINT to)
{
    CLine* pLine = NULL;

    try {
        pLine = new CLine (from, to);
        m_arrLines.Add (pLine);
        SetModifiedFlag (TRUE);
        UpdateAllViews (NULL, 0x7C, pLine);
    }
    catch (CMemoryException* e) {
        AfxMessageBox (_T ("Out of memory"));
        if (pLine != NULL) {
            delete pLine;
            pLine = NULL;
        }
        e->Delete ();   
    }       
    return pLine;
}

Because CLine is a serializable class, and because CTypedPtrArray is capable of serializing all of its serializable elements with a simple function call, one statement in CSketchDoc::Serialize saves or loads every line that the user has drawn:

m_arrLines.Serialize (ar);

CSketchDoc also overrides DeleteContents and uses it to delete all the CLine objects created by AddLine before the current document is discarded. Failure to dispose of the CLines in this manner would result in memory leaks each time a document is closed.

What sets Sketch apart from a run-of-the-mill SDI document/view application is the fact that it uses a dynamic splitter window. The splitter window is created by the following statement in CMainFrame::OnCreateClient:

return m_wndSplitter.Create (this, 2, 1, CSize (8, 8), pContext);

Significantly, this is the only code anywhere in Sketch that's provided specifically for splitter windows; MFC handles all other aspects of the splitter's operation.

Concurrent views of a document displayed in a splitter window must be synchronized just like concurrent views in an MDI application. The call to UpdateAllViews in CSketchDoc::AddLine ensures that both views are updated if the window is split when a line is drawn. Rather than rely on the default implementation of OnUpdate, CSketchView overrides OnUpdate and performs a "smart update" by relying on hint information passed to UpdateAllViews. Specifically, each time a line is added to the document, AddLine calls UpdateAllViews and passes a CLine pointer referencing the new line in pHint:

UpdateAllViews (NULL, 0x7C, pLine);

The view's OnUpdate function casts pHint back to a CLine and asks the CLine to draw itself on the screen:

void CSketchView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
    if (lHint == 0x7C) {
        CLine* pLine = (CLine*) pHint;
        ASSERT (pLine->IsKindOf (RUNTIME_CLASS (CLine)));
        CClientDC dc (this);
        OnPrepareDC (&dc);
        pLine->Draw (&dc);
        return;
    }    
    CScrollView::OnUpdate (pSender, lHint, pHint);
}

This is much more efficient than redrawing the entire view with OnDraw because updating a view involves drawing just the one new line no matter how many lines are stored in the document. As a result, Sketch doesn't exhibit the flashing effect that afflicts MdiSquares.

Figure 11-6. The Sketch application.

Sketch.h

// Sketch.h : main header file for the SKETCH application
//

#if !defined(AFX_SKETCH_H__1260AFC5_9CAC_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_SKETCH_H__1260AFC5_9CAC_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef __AFXWIN_H__
    #error include `stdafx.h' before including this file for PCH
#endif

#include "resource.h"       // main symbols

///////////////////////////////////////////////////////////////////////////
// CSketchApp:
// See Sketch.cpp for the implementation of this class
//

class CSketchApp : public CWinApp
{
public:
    CSketchApp();

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CSketchApp)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL

// Implementation
    //{{AFX_MSG(CSketchApp)
    afx_msg void OnAppAbout();

// NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(AFX_SKETCH_H__1260AFC5_9CAC_11D2_8E53_006008A82731__INCLUDED_)

Sketch.cpp

// Sketch.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "Line.h"
#include "Sketch.h"
#include "MainFrm.h"
#include "SketchDoc.h"
#include "SketchView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CSketchApp

BEGIN_MESSAGE_MAP(CSketchApp, CWinApp)
    //{{AFX_MSG_MAP(CSketchApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    // Standard file based document commands
    ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CSketchApp construction

CSketchApp::CSketchApp()
{
}

///////////////////////////////////////////////////////////////////////////
// The one and only CSketchApp object

CSketchApp theApp;

///////////////////////////////////////////////////////////////////////////
// CSketchApp initialization

BOOL CSketchApp::InitInstance()
{
    SetRegistryKey(_T("Local AppWizard-Generated Applications"));

    LoadStdProfileSettings();  // Load standard INI file 
                               // options (including MRU)

    CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(
        IDR_MAINFRAME,
        RUNTIME_CLASS(CSketchDoc),
        RUNTIME_CLASS(CMainFrame),       // main SDI frame window
        RUNTIME_CLASS(CSketchView));
    AddDocTemplate(pDocTemplate);

    // Enable DDE Execute open
    EnableShellOpen();
    RegisterShellFileTypes(TRUE);

    // Parse command line for standard shell commands, DDE, file open
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);


   // Dispatch commands specified on the command line
    if (!ProcessShellCommand(cmdInfo))
        return FALSE;

    // The one and only window has been initialized, so show and update it.
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();

    // Enable drag/drop open
    m_pMainWnd->DragAcceptFiles();

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
    CAboutDlg();

// Dialog Data
    //{{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
    //}}AFX_DATA

    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    //}}AFX_VIRTUAL

// Implementation
protected:
    //{{AFX_MSG(CAboutDlg)
        // No message handlers
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CAboutDlg)
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)
        // No message handlers
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CSketchApp::OnAppAbout()
{
    CAboutDlg aboutDlg;
    aboutDlg.DoModal();
}

///////////////////////////////////////////////////////////////////////////
// CSketchApp message handlers

MainFrm.h

// MainFrm.h : interface of the CMainFrame class
//
///////////////////////////////////////////////////////////////////////////
//

#if !defined(AFX_MAINFRM_H__1260AFC9_9CAC_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_MAINFRM_H__1260AFC9_9CAC_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd
{
    
protected: // create from serialization only
    CMainFrame();
    DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CMainFrame)
    public:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    protected:
    virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, 
        CCreateContext* pContext);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CMainFrame();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

// Generated message map functions
protected:
    CSplitterWnd m_wndSplitter;
    //{{AFX_MSG(CMainFrame)
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(AFX_MAINFRM_H__1260AFC9_9CAC_11D2_8E53_006008A82731__INCLUDED_)

MainFrm.cpp

// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "Sketch.h"

#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;
    return TRUE;
}



///////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
    CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
    CFrameWnd::Dump(dc);
}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers


BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) 
{
    return m_wndSplitter.Create (this, 2, 1, CSize (8, 8), pContext);
}

SketchDoc.h

// SketchDoc.h : interface of the CSketchDoc class
//
///////////////////////////////////////////////////////////////////////////

#if !defined(AFX_SKETCHDOC_H__1260AFCB_9CAC_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_SKETCHDOC_H__1260AFCB_9CAC_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

typedef CTypedPtrArray<CObArray, CLine*> CLineArray;

class CSketchDoc : public CDocument
{
protected: // create from serialization only
    CSketchDoc();
    DECLARE_DYNCREATE(CSketchDoc)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CSketchDoc)
    public:
    virtual BOOL OnNewDocument();
    virtual void Serialize(CArchive& ar);
    virtual void DeleteContents();
    //}}AFX_VIRTUAL

// Implementation
public:
    CLine* GetLine (int nIndex);
    int GetLineCount ();
    CLine* AddLine (POINT from, POINT to);
    BOOL IsGridVisible ();
    virtual ~CSketchDoc();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    CLineArray m_arrLines;
    BOOL m_bShowGrid;
    //{{AFX_MSG(CSketchDoc)
    afx_msg void OnViewGrid();
    afx_msg void OnUpdateViewGrid(CCmdUI* pCmdUI);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
//     AFX_SKETCHDOC_H__1260AFCB_9CAC_11D2_8E53_006008A82731__INCLUDED_)

SketchDoc.cpp

// SketchDoc.cpp : implementation of the CSketchDoc class
//

#include "stdafx.h"
#include "Line.h"
#include "Sketch.h"
#include "SketchDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CSketchDoc

IMPLEMENT_DYNCREATE(CSketchDoc, CDocument)

BEGIN_MESSAGE_MAP(CSketchDoc, CDocument)
    //{{AFX_MSG_MAP(CSketchDoc)
    ON_COMMAND(ID_VIEW_GRID, OnViewGrid)
    ON_UPDATE_COMMAND_UI(ID_VIEW_GRID, OnUpdateViewGrid)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CSketchDoc construction/destruction

CSketchDoc::CSketchDoc()
{
}

CSketchDoc::~CSketchDoc()
{
}

BOOL CSketchDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;

    m_bShowGrid = TRUE;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CSketchDoc serialization

void CSketchDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        ar << m_bShowGrid;
    }
    else
    {
        ar >> m_bShowGrid;
    }
    m_arrLines.Serialize (ar);
}

///////////////////////////////////////////////////////////////////////////
// CSketchDoc diagnostics

#ifdef _DEBUG
void CSketchDoc::AssertValid() const
{
    CDocument::AssertValid();
}

void CSketchDoc::Dump(CDumpContext& dc) const
{
    CDocument::Dump(dc);
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CSketchDoc commands

BOOL CSketchDoc::IsGridVisible()
{
    return m_bShowGrid;
}

void CSketchDoc::OnViewGrid() 
{
    if (m_bShowGrid)
        m_bShowGrid = FALSE;
    else
        m_bShowGrid = TRUE;

    SetModifiedFlag (TRUE);
    UpdateAllViews (NULL);    
}

void CSketchDoc::OnUpdateViewGrid(CCmdUI* pCmdUI) 
{
    pCmdUI->SetCheck (m_bShowGrid);
}

CLine* CSketchDoc::AddLine(POINT from, POINT to)
{
    CLine* pLine = NULL;

    try {
        pLine = new CLine (from, to);
        m_arrLines.Add (pLine);
        SetModifiedFlag (TRUE);
        UpdateAllViews (NULL, 0x7C, pLine);
    }
    catch (CMemoryException* e) {
        AfxMessageBox (_T ("Out of memory"));
        if (pLine != NULL) {
            delete pLine;
            pLine = NULL;
        }
        e->Delete ();   
    }       
    return pLine;
}

int CSketchDoc::GetLineCount()
{
    return m_arrLines.GetSize ();
}

CLine* CSketchDoc::GetLine(int nIndex)
{
    ASSERT (nIndex < GetLineCount ());
    return m_arrLines[nIndex];
}

void CSketchDoc::DeleteContents() 
{
    int nCount = GetLineCount ();

    if (nCount) {
        for (int i=0; i<nCount; i++)
            delete m_arrLines[i];
        m_arrLines.RemoveAll ();
    }
    CDocument::DeleteContents();
}

SketchView.h

// SketchView.h : interface of the CSketchView class
//
///////////////////////////////////////////////////////////////////////////
//

#if !defined(AFX_SKETCHVIEW_H__1260AFCD_9CAC_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_SKETCHVIEW_H__1260AFCD_9CAC_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CSketchView : public CScrollView
{
protected: // create from serialization only
    CSketchView();
    DECLARE_DYNCREATE(CSketchView)

// Attributes
public:
    CSketchDoc* GetDocument();

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CSketchView)
    public:
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    protected:
    virtual void OnInitialUpdate(); // called first time after construct
    virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CSketchView();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    void InvertLine (CDC* pDC, POINT from, POINT to);
    CPoint m_ptFrom;
    CPoint m_ptTo;
    HCURSOR m_hCursor;
    //{{AFX_MSG(CSketchView)
    afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in SketchView.cpp
inline CSketchDoc* CSketchView::GetDocument()
    { return (CSketchDoc*)m_pDocument; }
#endif

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
//     AFX_SKETCHVIEW_H__1260AFCD_9CAC_11D2_8E53_006008A82731__INCLUDED_)

SketchView.cpp

// SketchView.cpp : implementation of the CSketchView class
//

#include "stdafx.h"
#include "Line.h"
#include "Sketch.h"
#include "SketchDoc.h"
#include "SketchView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CSketchView

IMPLEMENT_DYNCREATE(CSketchView, CScrollView)

BEGIN_MESSAGE_MAP(CSketchView, CScrollView)
    //{{AFX_MSG_MAP(CSketchView)
    ON_WM_SETCURSOR()
    ON_WM_LBUTTONDOWN()


    ON_WM_MOUSEMOVE()
    ON_WM_LBUTTONUP()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CSketchView construction/destruction

CSketchView::CSketchView()
{
    m_hCursor = AfxGetApp ()->LoadStandardCursor (IDC_CROSS);
}

CSketchView::~CSketchView()
{
}

BOOL CSketchView::PreCreateWindow(CREATESTRUCT& cs)
{
    return CScrollView::PreCreateWindow(cs);
}

///////////////////////////////////////////////////////////////////////////
// CSketchView drawing

void CSketchView::OnDraw(CDC* pDC)
{
    CSketchDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    //
    // Draw the snap grid.
    //
    if (pDoc->IsGridVisible ()) {
        for (int x=25; x<1600; x+=25)
            for (int y=-25; y>-1200; y-=25)
                pDC->SetPixel (x, y, RGB (128, 128, 128));
    }

    //
    // Draw the lines.
    //
    int nCount = pDoc->GetLineCount ();
    if (nCount) {
        for (int i=0; i<nCount; i++)
            pDoc->GetLine (i)->Draw (pDC);
    }
}

void CSketchView::OnInitialUpdate()
{
    CScrollView::OnInitialUpdate();
    SetScrollSizes(MM_LOENGLISH, CSize (1600, 1200));
}

///////////////////////////////////////////////////////////////////////////
// CSketchView diagnostics

#ifdef _DEBUG
void CSketchView::AssertValid() const
{
    CScrollView::AssertValid();
}

void CSketchView::Dump(CDumpContext& dc) const
{
    CScrollView::Dump(dc);
}

CSketchDoc* CSketchView::GetDocument() // non-debug version is inline
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSketchDoc)));
    return (CSketchDoc*)m_pDocument;
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CSketchView message handlers

BOOL CSketchView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
    ::SetCursor (m_hCursor);
    return TRUE;    
}

void CSketchView::OnLButtonDown(UINT nFlags, CPoint point) 
{
    CScrollView::OnLButtonDown(nFlags, point);

    CPoint pos = point;


   CClientDC dc (this);
    OnPrepareDC (&dc);
    dc.DPtoLP (&pos);

    if (GetDocument ()->IsGridVisible ()) {
        pos.x = ((pos.x + 12) / 25) * 25;
        pos.y = ((pos.y - 12) / 25) * 25;
    }

    m_ptFrom = pos;
    m_ptTo = pos;
    SetCapture ();
}

void CSketchView::OnMouseMove(UINT nFlags, CPoint point) 
{
    CScrollView::OnMouseMove(nFlags, point);

    if (GetCapture () == this) {
        CPoint pos = point;
        CClientDC dc (this);
        OnPrepareDC (&dc);
        dc.DPtoLP (&pos);

        if (GetDocument ()->IsGridVisible ()) {
            pos.x = ((pos.x + 12) / 25) * 25;
            pos.y = ((pos.y - 12) / 25) * 25;
        }

        if (m_ptTo != pos) {
            InvertLine (&dc, m_ptFrom, m_ptTo);
            InvertLine (&dc, m_ptFrom, pos);
            m_ptTo = pos;
        }
    }
}

void CSketchView::OnLButtonUp(UINT nFlags, CPoint point) 
{
    CScrollView::OnLButtonUp(nFlags, point);

    if (GetCapture () == this) {
        ::ReleaseCapture ();

        CPoint pos = point;
        CClientDC dc (this);
        OnPrepareDC (&dc);
        dc.DPtoLP (&pos);

        if (GetDocument ()->IsGridVisible ()) {
            pos.x = ((pos.x + 12) / 25) * 25;
            pos.y = ((pos.y - 12) / 25) * 25;
        }

        InvertLine (&dc, m_ptFrom, m_ptTo);

        CSketchDoc* pDoc = GetDocument ();
        CLine* pLine = pDoc->AddLine (m_ptFrom, m_ptTo);
    }    
}

void CSketchView::InvertLine(CDC *pDC, POINT from, POINT to)
{
    int nOldMode = pDC->SetROP2 (R2_NOT);
    pDC->MoveTo (from);
    pDC->LineTo (to);
    pDC->SetROP2 (nOldMode);
}

void CSketchView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
    if (lHint == 0x7C) {
        CLine* pLine = (CLine*) pHint;
        ASSERT (pLine->IsKindOf (RUNTIME_CLASS (CLine)));
        CClientDC dc (this);
        OnPrepareDC (&dc);
        pLine->Draw (&dc);
        return;
    }    
    CScrollView::OnUpdate (pSender, lHint, pHint);
}

Static Splitter Windows

Static splitter windows are handled much like dynamic splitter windows except that an extra step is required to create them. Static splitters are created with CSplitterWnd::CreateStatic rather than CSplitterWnd::Create, and because MFC doesn't automatically create the views displayed in a static splitter window, it's up to you to create the views after CreateStatic returns. CSplitterWnd provides a function named CreateView for this purpose. The procedure for adding a static splitter window to a frame window goes like this.

  1. Add a CSplitterWnd data member to the frame window class.
  2. Override the frame window's OnCreateClient function, and call CSplitterWnd::CreateStatic to create a static splitter window.
  3. Use CSplitterWnd::CreateView to create a view in each of the splitter window's panes.

One of the chief advantages of using a static splitter window is that because you put the views in the panes, you control what kinds of views are placed there. The following example creates a static splitter window that contains two different kinds of views:

BOOL CMainFrame::OnCreateClient (LPCREATESTRUCT lpcs,
    CCreateContext* pContext)
{
    if (!m_wndSplitter.CreateStatic (this, 1, 2) ¦¦
        !m_wndSplitter.CreateView (0, 0, RUNTIME_CLASS (CTextView),
            CSize (128, 0), pContext) ¦¦
        !m_wndSplitter.CreateView (0, 1, RUNTIME_CLASS (CPictureView),
            CSize (0, 0), pContext))
        return FALSE;

    return TRUE;
}

The parameters passed to CreateStatic identify the splitter window's parent as well as the number of rows and columns that the splitter contains. CreateView is called once for each pane. Panes are identified by 0-based row and column numbers. In this example, the first call to CreateView inserts a view of type CTextView into the left pane (row 0, column 0), and the second inserts a view of type CPictureView into the right pane (row 0, column 1). The views aren't instantiated directly but are created by MFC. Therefore, you pass CRuntimeClass pointers to CreateView instead of pointers to existing CView objects. As with a dynamic splitter window, the views used in a static splitter window must be dynamically creatable or the framework can't use them.

The CSize objects passed to CreateView specify the panes' initial sizes. In this case, the CTextView pane will start out 128 pixels wide and the CPictureView pane will occupy the remaining width of the window. The width specified for the right pane and the heights specified for both the left and the right panes are 0 because the framework ignores these values. When a splitter window contains only one row, that row will occupy the full height of the parent's client area no matter what CSize values you specify. Similarly, if a splitter window contains n columns, the rightmost column will occupy all the space between the right edge of column n-1 and the edge of its parent.

The Wanderer Application

The Wanderer application shown in Figure 11-7 uses a static splitter window to mimic the look and feel of the Windows Explorer. The splitter window divides the frame window into two panes. The left pane contains a CDriveView, which is a CTreeView customized to display the directory structure of the host PC. The right pane contains a CFileView, which is a CListView that lists the files in the directory selected in the CDriveView.

Click to view at full size.

Figure 11-7. The Wanderer window halved by a static splitter window.

The CDriveView and CFileView classes that Wanderer uses are almost identical to the classes of the same name introduced in Chapter 10. I modified CDriveView slightly by adding a handler for reflected TVN_SELCHANGED notifications indicating that the tree view selection changed. That handler translates the selected item into a path name and uses UpdateAllViews' pHint parameter to transmit the path name to the CFileView:

void CDriveView::OnSelectionChanged(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*) pNMHDR;
    CString strPath = GetPathFromItem (pNMTreeView->itemNew.hItem);
    GetDocument ()->UpdateAllViews (this, 0x5A, 
        (CObject*) (LPCTSTR) strPath);
    *pResult = 0;
}

I also modified CFileView to respond to calls to OnUpdate by displaying the contents of the directory identified by pHint if lHint equals 0x5A:

void CFileView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
    if (lHint == 0x5A) {
        FreeItemMemory ();
        GetListCtrl ().DeleteAllItems ();
        Refresh ((LPCTSTR) pHint);
        return;
    }    
    CListView::OnUpdate (pSender, lHint, pHint);
}

Together, these two modifications couple the left and right panes in such a way that the view on the right-hand side is updated whenever the directory selected on the left-hand side changes.

The static splitter window is created and initialized in CMainFrame::OnCreateClient. After creating the splitter window, OnCreateClient uses CreateView to place a CDriveView in the left pane and a CFileView in the right pane. (See Figure 11-8.) The only thing that's unusual about Wanderer's implementation of OnCreateClient is that it creates the right-hand view first and the left-hand view second. The reason why is simple. The CDriveView's OnInitialUpdate function calls UpdateAllViews to tell the CFileView which directory is selected; the CFileView's OnUpdate function, in turn, displays the contents of that directory. But if the CDriveView is created first, the CFileView doesn't exist when CDriveView::OnInitialUpdate is called. Creating the CFileView first is one way to circumvent this problem.

Figure 11-8. The Wanderer application.

Wanderer.h

// Wanderer.h : main header file for the WANDERER application
//

#if !defined(AFX_WANDERER_H__AE0A6FFA_9B0F_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_WANDERER_H__AE0A6FFA_9B0F_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef __AFXWIN_H__
    #error include `stdafx.h' before including this file for PCH
#endif

#include "resource.h"       // main symbols

///////////////////////////////////////////////////////////////////////////
// CWandererApp:
// See Wanderer.cpp for the implementation of this class
//

class CWandererApp : public CWinApp
{
public:
    CWandererApp();

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CWandererApp)
    public:
    virtual BOOL InitInstance();
    //}}AFX_VIRTUAL

// Implementation
    //{{AFX_MSG(CWandererApp)
    afx_msg void OnAppAbout();
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};


///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
//     AFX_WANDERER_H__AE0A6FFA_9B0F_11D2_8E53_006008A82731__INCLUDED_)

Wanderer.cpp

// Wanderer.cpp : Defines the class behaviors for the application.
//

#include "stdafx.h"
#include "Wanderer.h"

#include "MainFrm.h"
#include "WandererDoc.h"
#include "DriveView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CWandererApp

BEGIN_MESSAGE_MAP(CWandererApp, CWinApp)
    //{{AFX_MSG_MAP(CWandererApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
    // Standard file based document commands
    ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CWandererApp construction

CWandererApp::CWandererApp()
{
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
}

///////////////////////////////////////////////////////////////////////////
// The one and only CWandererApp object

CWandererApp theApp;

///////////////////////////////////////////////////////////////////////////
// CWandererApp initialization

BOOL CWandererApp::InitInstance()
{
    // Standard initialization

    // If you are not using these features and wish to reduce the size
    //  of your final executable, you should remove from the following
    //  the specific initialization routines you do not need.

    // Change the registry key under which our settings are stored.
    // TODO: You should modify this string to be something appropriate
    // such as the name of your company or organization.
    SetRegistryKey(_T("Local AppWizard-Generated Applications"));

    LoadStdProfileSettings();  // Load standard INI file 
                               // options (including MRU)

    // Register the application's document templates.  Document templates
    //  serve as the connection between documents, frame windows and views.

    CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(
        IDR_MAINFRAME,
        RUNTIME_CLASS(CWandererDoc),
        RUNTIME_CLASS(CMainFrame),       // main SDI frame window
        RUNTIME_CLASS(CDriveView));
    AddDocTemplate(pDocTemplate);

    // Parse command line for standard shell commands, DDE, file open
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);

    // Dispatch commands specified on the command line
    if (!ProcessShellCommand(cmdInfo))
        return FALSE;

    // The one and only window has been initialized, so show and update it.
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();

    return TRUE;
}


///////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
    CAboutDlg();

	// Dialog Data
    //{{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
    //}}AFX_DATA

    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    //}}AFX_VIRTUAL

// Implementation
protected:
    //{{AFX_MSG(CAboutDlg)
        // No message handlers
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CAboutDlg)
    //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
    //{{AFX_MSG_MAP(CAboutDlg)
        // No message handlers
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

// App command to run the dialog
void CWandererApp::OnAppAbout()
{
    CAboutDlg aboutDlg;
    aboutDlg.DoModal();
}

///////////////////////////////////////////////////////////////////////////
// CWandererApp message handlers

MainFrm.h

// MainFrm.h : interface of the CMainFrame class
//
///////////////////////////////////////////////////////////////////////////
//

#if !defined(AFX_MAINFRM_H__AE0A6FFE_9B0F_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_MAINFRM_H__AE0A6FFE_9B0F_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd
{
    
protected: // create from serialization only
    CMainFrame();
    DECLARE_DYNCREATE(CMainFrame)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CMainFrame)
    public:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, 
        AFX_CMDHANDLERINFO* pHandlerInfo);
    protected:
    virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, 
        CCreateContext* pContext);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CMainFrame();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

// Generated message map functions
protected:
    CSplitterWnd m_wndSplitter;
    //{{AFX_MSG(CMainFrame)
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(AFX_MAINFRM_H__AE0A6FFE_9B0F_11D2_8E53_006008A82731__INCLUDED_)

MainFrm.cpp

// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "Wanderer.h"
#include "WandererDoc.h"
#include "DriveView.h"
#include "FileView.h"
#include "MainFrm.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)

        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;

    cs.style &= ~FWS_ADDTOTITLE;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
    CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
    CFrameWnd::Dump(dc);
}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers


BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, 
    CCreateContext* pContext) 


{
    //
    // Note: Create the CFileView first so the CDriveView's OnInitialUpdate
    // function can call OnUpdate on the CFileView.
    //
    if (!m_wndSplitter.CreateStatic (this, 1, 2) ||
        !m_wndSplitter.CreateView (0, 1, RUNTIME_CLASS
            (CFileView), CSize (0, 0), pContext) ||
        !m_wndSplitter.CreateView (0, 0, RUNTIME_CLASS (CDriveView),
            CSize (192, 0), pContext))
        return FALSE;

    return TRUE;
}

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, 
    AFX_CMDHANDLERINFO* pHandlerInfo) 
{
    //
    // Route to standard command targets first.
    //
    if (CFrameWnd::OnCmdMsg (nID, nCode, pExtra, pHandlerInfo))
        return TRUE;

    //
    // Route to inactive views second.
    //
    CWandererDoc* pDoc = (CWandererDoc*) GetActiveDocument ();
    if (pDoc != NULL) { // Important!
        return pDoc->RouteCmdToAllViews (GetActiveView (),
            nID, nCode, pExtra, pHandlerInfo);
    }
    return FALSE;
}

WandererDoc.h

// WandererDoc.h : interface of the CWandererDoc class
//
///////////////////////////////////////////////////////////////////////////
//

#if !defined(AFX_WANDERERDOC_H__AE0A7000_9B0F_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_WANDERERDOC_H__AE0A7000_9B0F_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CWandererDoc : public CDocument
{
protected: // create from serialization only
    CWandererDoc();
    DECLARE_DYNCREATE(CWandererDoc)

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CWandererDoc)
    public:
    virtual BOOL OnNewDocument();
    virtual void Serialize(CArchive& ar);
    //}}AFX_VIRTUAL

// Implementation
public:
    BOOL RouteCmdToAllViews (CView* pView, UINT nID, int nCode, 
        void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo);
    virtual ~CWandererDoc();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    //{{AFX_MSG(CWandererDoc)
       // NOTE - the ClassWizard will add and remove member functions here.
       //    DO NOT EDIT what you see in these blocks of generated code !
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
//     AFX_WANDERERDOC_H__AE0A7000_9B0F_11D2_8E53_006008A82731__INCLUDED_)

WandererDoc.cpp

// WandererDoc.cpp : implementation of the CWandererDoc class
//

#include "stdafx.h"
#include "Wanderer.h"

#include "WandererDoc.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CWandererDoc

IMPLEMENT_DYNCREATE(CWandererDoc, CDocument)

BEGIN_MESSAGE_MAP(CWandererDoc, CDocument)
    //{{AFX_MSG_MAP(CWandererDoc)
        // NOTE - the ClassWizard will add and remove mapping macros here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CWandererDoc construction/destruction

CWandererDoc::CWandererDoc()
{
}

CWandererDoc::~CWandererDoc()
{
}

BOOL CWandererDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CWandererDoc serialization

void CWandererDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        // TODO: add storing code here
    }
    else
    {
        // TODO: add loading code here
    }
}

///////////////////////////////////////////////////////////////////////////
// CWandererDoc diagnostics

#ifdef _DEBUG
void CWandererDoc::AssertValid() const
{
    CDocument::AssertValid();
}

void CWandererDoc::Dump(CDumpContext& dc) const
{
    CDocument::Dump(dc);
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CWandererDoc commands

BOOL CWandererDoc::RouteCmdToAllViews(CView *pView, UINT nID, int nCode, 
    void *pExtra, AFX_CMDHANDLERINFO *pHandlerInfo)
{
    POSITION pos = GetFirstViewPosition ();

    while (pos != NULL) {
        CView* pNextView = GetNextView (pos);
        if (pNextView != pView) {
            if (pNextView->OnCmdMsg (nID, nCode, pExtra, pHandlerInfo))
                return TRUE;
        }
    }
    return FALSE;
}

DriveView.h

// DriveTreeView.h : interface of the CDriveView class
//
/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_DRIVETREEVIEW_H__090B382D_959D_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_DRIVETREEVIEW_H__090B382D_959D_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000


class CDriveView : public CTreeView
{
protected: // create from serialization only
    CDriveView();
    DECLARE_DYNCREATE(CDriveView)

// Attributes
public:
    CWandererDoc* GetDocument();

// Operations
public:


// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CDriveView)
    public:
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    protected:
    virtual void OnInitialUpdate(); // called first time after construct
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CDriveView();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    BOOL AddDriveItem (LPCTSTR pszDrive);
    int AddDirectories (HTREEITEM hItem, LPCTSTR pszPath);
    void DeleteAllChildren (HTREEITEM hItem);
    void DeleteFirstChild (HTREEITEM hItem);
    CString GetPathFromItem (HTREEITEM hItem);
    BOOL SetButtonState (HTREEITEM hItem, LPCTSTR pszPath);
    int AddDrives ();
    CImageList m_ilDrives;
    //{{AFX_MSG(CDriveView)
    afx_msg void OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnSelectionChanged(NMHDR* pNMHDR, LRESULT* pResult);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in DriveTreeView.cpp
inline CWandererDoc* CDriveView::GetDocument()
    { return (CWandererDoc*)m_pDocument; }
#endif



///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(
//     AFX_DRIVETREEVIEW_H__090B382D_959D_11D2_8E53_006008A82731__INCLUDED_)

DriveView.cpp

// DriveTreeView.cpp : implementation of the CDriveView class
//

#include "stdafx.h"
#include "Wanderer.h"

#include "WandererDoc.h"
#include "DriveView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// Image indexes
#define ILI_HARD_DISK       0
#define ILI_FLOPPY          1
#define ILI_CD_ROM          2
#define ILI_NET_DRIVE       3
#define ILI_CLOSED_FOLDER   4
#define ILI_OPEN_FOLDER     5

///////////////////////////////////////////////////////////////////////////
// CDriveView

IMPLEMENT_DYNCREATE(CDriveView, CTreeView)

BEGIN_MESSAGE_MAP(CDriveView, CTreeView)
    //{{AFX_MSG_MAP(CDriveView)
    ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemExpanding)
    ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelectionChanged)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CDriveView construction/destruction

CDriveView::CDriveView()
{
}

CDriveView::~CDriveView()
{
}

BOOL CDriveView::PreCreateWindow(CREATESTRUCT& cs)
{
    if (!CTreeView::PreCreateWindow (cs))
        return FALSE;

    cs.style |= TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS |
        TVS_SHOWSELALWAYS;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CDriveView drawing

void CDriveView::OnDraw(CDC* pDC)
{
    CWandererDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
}

void CDriveView::OnInitialUpdate()
{
    CTreeView::OnInitialUpdate();

    //
    // Initialize the image list.
    //
    m_ilDrives.Create (IDB_DRIVEIMAGES, 16, 1, RGB (255, 0, 255));
    GetTreeCtrl ().SetImageList (&m_ilDrives, TVSIL_NORMAL);

    //
    // Populate the tree view with drive items.
    //


    AddDrives ();

    //
    // Show the folders on the current drive.
    //
    TCHAR szPath[MAX_PATH];
    ::GetCurrentDirectory (sizeof (szPath) / sizeof (TCHAR), szPath);
    CString strPath = szPath;
    strPath = strPath.Left (3);

    HTREEITEM hItem = GetTreeCtrl ().GetNextItem (NULL, TVGN_ROOT);
    while (hItem != NULL) {
        if (GetTreeCtrl ().GetItemText (hItem) == strPath)
            break;
        hItem = GetTreeCtrl ().GetNextSiblingItem (hItem);
    }

    if (hItem != NULL) {
        GetTreeCtrl ().Expand (hItem, TVE_EXPAND);
        GetTreeCtrl ().Select (hItem, TVGN_CARET);
    }

    //
    // Initialize the list view.
    //
    strPath = GetPathFromItem (GetTreeCtrl ().GetSelectedItem ());
    GetDocument ()->UpdateAllViews (this, 0x5A, 
        (CObject*) (LPCTSTR) strPath);
}

///////////////////////////////////////////////////////////////////////////
// CDriveView diagnostics

#ifdef _DEBUG
void CDriveView::AssertValid() const
{
    CTreeView::AssertValid();
}

void CDriveView::Dump(CDumpContext& dc) const
{
    CTreeView::Dump(dc);
}

CWandererDoc* CDriveView::GetDocument() // non-debug version is inline
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWandererDoc)));
    return (CWandererDoc*)m_pDocument;
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CDriveView message handlers

int CDriveView::AddDrives()
{
    int nPos = 0;
    int nDrivesAdded = 0;
    CString string = _T ("?:\\");

    DWORD dwDriveList = ::GetLogicalDrives ();

    while (dwDriveList) {
        if (dwDriveList & 1) {
            string.SetAt (0, _T (`A') + nPos);
            if (AddDriveItem (string))
                nDrivesAdded++;
        }
        dwDriveList >>= 1;
        nPos++;
    }
    return nDrivesAdded;
}

BOOL CDriveView::AddDriveItem(LPCTSTR pszDrive)
{
    CString string;
    HTREEITEM hItem;

    UINT nType = ::GetDriveType (pszDrive);

    switch (nType) {

    case DRIVE_REMOVABLE:
        hItem = GetTreeCtrl ().InsertItem (pszDrive, ILI_FLOPPY,
            ILI_FLOPPY);
        GetTreeCtrl ().InsertItem (_T (""), ILI_CLOSED_FOLDER,
            ILI_CLOSED_FOLDER, hItem);
        break;

 case DRIVE_FIXED:
    case DRIVE_RAMDISK:
        hItem = GetTreeCtrl ().InsertItem (pszDrive, ILI_HARD_DISK,
            ILI_HARD_DISK);
        SetButtonState (hItem, pszDrive);
        break;

    case DRIVE_REMOTE:
        hItem = GetTreeCtrl ().InsertItem (pszDrive, ILI_NET_DRIVE,
            ILI_NET_DRIVE);
        SetButtonState (hItem, pszDrive);
        break;

    case DRIVE_CDROM:
        hItem = GetTreeCtrl ().InsertItem (pszDrive, ILI_CD_ROM,
            ILI_CD_ROM);
        GetTreeCtrl ().InsertItem (_T (""), ILI_CLOSED_FOLDER,
            ILI_CLOSED_FOLDER, hItem);
        break;

    default:
        return FALSE;
    }
    return TRUE;
}

BOOL CDriveView::SetButtonState(HTREEITEM hItem, LPCTSTR pszPath)
{
    HANDLE hFind;
    WIN32_FIND_DATA fd;
    BOOL bResult = FALSE;

    CString strPath = pszPath;
    if (strPath.Right (1) != _T ("\\"))
        strPath += _T ("\\");
    strPath += _T ("*.*");

    if ((hFind = ::FindFirstFile (strPath, &fd)) == INVALID_HANDLE_VALUE)
        return bResult;

    do {
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            CString strComp = (LPCTSTR) &fd.cFileName;
            if ((strComp != _T (".")) && (strComp != _T (".."))) {
                GetTreeCtrl ().InsertItem (_T (""), ILI_CLOSED_FOLDER,
                    ILI_CLOSED_FOLDER, hItem);
                bResult = TRUE;
                break;
            }
        }
    } while (::FindNextFile (hFind, &fd));

    ::FindClose (hFind);
    return bResult;
}

void CDriveView::OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
    HTREEITEM hItem = pNMTreeView->itemNew.hItem;
    CString string = GetPathFromItem (hItem);

    *pResult = FALSE;

    if (pNMTreeView->action == TVE_EXPAND) {
        DeleteFirstChild (hItem);
        if (AddDirectories (hItem, string) == 0)
            *pResult = TRUE;
    }
    else { // pNMTreeView->action == TVE_COLLAPSE
        DeleteAllChildren (hItem);
        if (GetTreeCtrl ().GetParentItem (hItem) == NULL)
            GetTreeCtrl ().InsertItem (_T (""), ILI_CLOSED_FOLDER,
                ILI_CLOSED_FOLDER, hItem);
        else
            SetButtonState (hItem, string);
    }
}

CString CDriveView::GetPathFromItem(HTREEITEM hItem)
{
    CString strResult = GetTreeCtrl ().GetItemText (hItem);

    HTREEITEM hParent;
    while ((hParent = GetTreeCtrl ().GetParentItem (hItem)) != NULL) {
        CString string = GetTreeCtrl ().GetItemText (hParent);

   if (string.Right (1) != _T ("\\"))
            string += _T ("\\");
        strResult = string + strResult;
        hItem = hParent;
    }
    return strResult;
}

void CDriveView::DeleteFirstChild(HTREEITEM hItem)
{
    HTREEITEM hChildItem;
    if ((hChildItem = GetTreeCtrl ().GetChildItem (hItem)) != NULL)
        GetTreeCtrl ().DeleteItem (hChildItem);
}

void CDriveView::DeleteAllChildren(HTREEITEM hItem)
{
    HTREEITEM hChildItem;
    if ((hChildItem = GetTreeCtrl ().GetChildItem (hItem)) == NULL)
        return;

    do {
        HTREEITEM hNextItem = 
            GetTreeCtrl ().GetNextSiblingItem (hChildItem);
        GetTreeCtrl ().DeleteItem (hChildItem);
        hChildItem = hNextItem;
    } while (hChildItem != NULL);
}

int CDriveView::AddDirectories(HTREEITEM hItem, LPCTSTR pszPath)
{
    HANDLE hFind;
    WIN32_FIND_DATA fd;
    HTREEITEM hNewItem;

    int nCount = 0;

    CString strPath = pszPath;
    if (strPath.Right (1) != _T ("\\"))
        strPath += _T ("\\");
    strPath += _T ("*.*");

    if ((hFind = ::FindFirstFile (strPath, &fd)) == INVALID_HANDLE_VALUE) {
        if (GetTreeCtrl ().GetParentItem (hItem) == NULL)
            GetTreeCtrl ().InsertItem (_T (""), ILI_CLOSED_FOLDER,
                ILI_CLOSED_FOLDER, hItem);
        return 0;
    }

    do {
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            CString strComp = (LPCTSTR) &fd.cFileName;
            if ((strComp != _T (".")) && (strComp != _T (".."))) {
                hNewItem =
                    GetTreeCtrl ().InsertItem ((LPCTSTR) &fd.cFileName,
                    ILI_CLOSED_FOLDER, ILI_OPEN_FOLDER, hItem);

                CString strNewPath = pszPath;
                if (strNewPath.Right (1) != _T ("\\"))
                    strNewPath += _T ("\\");

                strNewPath += (LPCTSTR) &fd.cFileName;
                SetButtonState (hNewItem, strNewPath);
                nCount++;
            }
        }
    } while (::FindNextFile (hFind, &fd));

    ::FindClose (hFind);
    return nCount;
}

void CDriveView::OnSelectionChanged(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*) pNMHDR;
    CString strPath = GetPathFromItem (pNMTreeView->itemNew.hItem);
    GetDocument ()->UpdateAllViews (this, 0x5A, 
        (CObject*) (LPCTSTR) strPath);
    *pResult = 0;
}

FileView.h

// FileView.h : interface of the CFileView class
//
///////////////////////////////////////////////////////////////////////////
//

#if !defined(AFX_FILEVIEW_H__18BD7B80_95C6_11D2_8E53_006008A82731__INCLUDED_)
#define AFX_FILEVIEW_H__18BD7B80_95C6_11D2_8E53_006008A82731__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

typedef struct tagITEMINFO {
    CString     strFileName; 
    DWORD       nFileSizeLow; 
    FILETIME    ftLastWriteTime; 
} ITEMINFO;

class CFileView : public CListView
{
protected: // create from serialization only
    CFileView();
    DECLARE_DYNCREATE(CFileView)

// Attributes
public:
    CWandererDoc* GetDocument();

// Operations
public:
    static int CALLBACK CompareFunc (LPARAM lParam1, LPARAM lParam2,
        LPARAM lParamSort);

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CFileView)
    public:
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    protected:
    virtual void OnInitialUpdate(); // called first time after construct
    virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
    //}}AFX_VIRTUAL

// Implementation
public:
    int Refresh (LPCTSTR pszPath);
    virtual ~CFileView();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
    CString m_strPath;
    void FreeItemMemory ();
    BOOL AddItem (int nIndex, WIN32_FIND_DATA* pfd);
    CImageList m_ilSmall;
    CImageList m_ilLarge;
    //{{AFX_MSG(CFileView)
    afx_msg void OnDestroy();
    afx_msg void OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnViewLargeIcons();
    afx_msg void OnViewSmallIcons();
    afx_msg void OnViewList();
    afx_msg void OnViewDetails();
    afx_msg void OnUpdateViewLargeIcons(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewSmallIcons(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewList(CCmdUI* pCmdUI);
    afx_msg void OnUpdateViewDetails(CCmdUI* pCmdUI);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in FileView.cpp
inline CWandererDoc* CFileView::GetDocument()
    { return (CWandererDoc*)m_pDocument; }
#endif

///////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
// before the previous line.

#endif 
// !defined(AFX_FILEVIEW_H__18BD7B80_95C6_11D2_8E53_006008A82731__INCLUDED_)

FileView.cpp

// FileView.cpp : implementation of the CFileView class
//

#include "stdafx.h"
#include "Wanderer.h"
#include "WandererDoc.h"
#include "FileView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////
// CFileView

IMPLEMENT_DYNCREATE(CFileView, CListView)

BEGIN_MESSAGE_MAP(CFileView, CListView)
    //{{AFX_MSG_MAP(CFileView)
    ON_WM_DESTROY()
    ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo)
    ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick)
    ON_COMMAND(ID_VIEW_LARGE_ICONS, OnViewLargeIcons)
    ON_COMMAND(ID_VIEW_SMALL_ICONS, OnViewSmallIcons)
    ON_COMMAND(ID_VIEW_LIST, OnViewList)
    ON_COMMAND(ID_VIEW_DETAILS, OnViewDetails)
    ON_UPDATE_COMMAND_UI(ID_VIEW_LARGE_ICONS, OnUpdateViewLargeIcons)
    ON_UPDATE_COMMAND_UI(ID_VIEW_SMALL_ICONS, OnUpdateViewSmallIcons)
    ON_UPDATE_COMMAND_UI(ID_VIEW_LIST, OnUpdateViewList)
    ON_UPDATE_COMMAND_UI(ID_VIEW_DETAILS, OnUpdateViewDetails)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// CFileView construction/destruction

CFileView::CFileView()
{
}

CFileView::~CFileView()
{
}

BOOL CFileView::PreCreateWindow(CREATESTRUCT& cs)
{
    if (!CListView::PreCreateWindow (cs))
        return FALSE;

    cs.style &= ~LVS_TYPEMASK;
    cs.style |= LVS_REPORT;
    return TRUE;
}

///////////////////////////////////////////////////////////////////////////
// CFileView drawing

void CFileView::OnDraw(CDC* pDC)
{
    CWandererDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
}

void CFileView::OnInitialUpdate()
{
    CListView::OnInitialUpdate();

    //
    // Initialize the image list.
    //
    m_ilLarge.Create (IDB_LARGEDOC, 32, 1, RGB (255, 0, 255));
    m_ilSmall.Create (IDB_SMALLDOC, 16, 1, RGB (255, 0, 255));

    GetListCtrl ().SetImageList (&m_ilLarge, LVSIL_NORMAL);
    GetListCtrl ().SetImageList (&m_ilSmall, LVSIL_SMALL);

    //
    // Add columns to the list view.
    //
    GetListCtrl ().InsertColumn (0, _T ("File Name"), LVCFMT_LEFT, 192);
    GetListCtrl ().InsertColumn (1, _T ("Size"), LVCFMT_RIGHT, 96);
    GetListCtrl ().InsertColumn (2, _T ("Last Modified"), LVCFMT_CENTER,
        128);

    //
    // Populate the list view with items.
    //
    TCHAR szPath[MAX_PATH];
    ::GetCurrentDirectory (sizeof (szPath) / sizeof (TCHAR), szPath);
    Refresh (szPath);
}

///////////////////////////////////////////////////////////////////////////
// CFileView diagnostics

#ifdef _DEBUG
void CFileView::AssertValid() const
{
    CListView::AssertValid();
}
void CFileView::Dump(CDumpContext& dc) const
{
    CListView::Dump(dc);
}

CWandererDoc* CFileView::GetDocument() // non-debug version is inline
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWandererDoc)));
    return (CWandererDoc*)m_pDocument;
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////
// CFileView message handlers

int CFileView::Refresh(LPCTSTR pszPath)
{
    CString strPath = pszPath;
    if (strPath.Right (1) != _T ("\\"))
        strPath += _T ("\\");
    strPath += _T ("*.*");

    HANDLE hFind;
    WIN32_FIND_DATA fd;
    int nCount = 0;

    if ((hFind = ::FindFirstFile (strPath, &fd)) != INVALID_HANDLE_VALUE) {
        //
        // Delete existing items (if any).
        //
        GetListCtrl ().DeleteAllItems ();
    
        //
        // Show the path name in the frame window's title bar.
        //
        TCHAR szFullPath[MAX_PATH];
        ::GetFullPathName (pszPath, sizeof (szFullPath) / sizeof (TCHAR),
            szFullPath, NULL);
        m_strPath = szFullPath;

        CString strTitle = _T ("WinDir - ");
        strTitle += szFullPath;
        AfxGetMainWnd ()->SetWindowText (strTitle);

        //
        // Add items representing files to the list view.
        //
        if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
            AddItem (nCount++, &fd);

        while (::FindNextFile (hFind, &fd)) {
            if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
                if (!AddItem (nCount++, &fd))
                    break;
        }
        ::FindClose (hFind);
    }
    return nCount;
}

BOOL CFileView::AddItem(int nIndex, WIN32_FIND_DATA *pfd)
{
    //
    // Allocate a new ITEMINFO structure and initialize it with information
    // about the item.
    //
    ITEMINFO* pItem;
    try {
        pItem = new ITEMINFO;
    }
    catch (CMemoryException* e) {
        e->Delete ();
        return FALSE;
    }

    pItem->strFileName = pfd->cFileName;
    pItem->nFileSizeLow = pfd->nFileSizeLow;
    pItem->ftLastWriteTime = pfd->ftLastWriteTime;

    //
    // Add the item to the list view.
    //
    LV_ITEM lvi;
    lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM; 
    lvi.iItem = nIndex; 
    lvi.iSubItem = 0; 
    lvi.iImage = 0;
    lvi.pszText = LPSTR_TEXTCALLBACK; 
    lvi.lParam = (LPARAM) pItem;

    if (GetListCtrl ().InsertItem (&lvi) == -1)
        return FALSE;

    return TRUE;
}
void CFileView::FreeItemMemory()
{
    int nCount = GetListCtrl ().GetItemCount ();
    if (nCount) {
        for (int i=0; i<nCount; i++)
            delete (ITEMINFO*) GetListCtrl ().GetItemData (i);
    }
}

void CFileView::OnDestroy() 
{
    FreeItemMemory ();
    CListView::OnDestroy ();
}

void CFileView::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) 
{
    CString string;
    LV_DISPINFO* pDispInfo = (LV_DISPINFO*) pNMHDR;

    if (pDispInfo->item.mask & LVIF_TEXT) {
        ITEMINFO* pItem = (ITEMINFO*) pDispInfo->item.lParam;

        switch (pDispInfo->item.iSubItem) {

        case 0: // File name
            ::lstrcpy (pDispInfo->item.pszText, pItem->strFileName);
            break;

        case 1: // File size
            string.Format (_T ("%u"), pItem->nFileSizeLow);
            ::lstrcpy (pDispInfo->item.pszText, string);
            break;

        case 2: // Date and time
            CTime time (pItem->ftLastWriteTime);

            BOOL pm = FALSE;
            int nHour = time.GetHour ();
            if (nHour == 0)
                nHour = 12;
            else if (nHour == 12)
                pm = TRUE;
            else if (nHour > 12) {
                nHour -= 12;
                pm = TRUE;
            }

            string.Format (_T ("%d/%0.2d/%0.2d (%d:%0.2d%c)"),
                time.GetMonth (), time.GetDay (), time.GetYear () % 100,
                nHour, time.GetMinute (), pm ? _T (`p') : _T (`a'));
            ::lstrcpy (pDispInfo->item.pszText, string);
            break;
        }
    }
    *pResult = 0;
}

void CFileView::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;
    GetListCtrl ().SortItems (CompareFunc, pNMListView->iSubItem);
    *pResult = 0;
}

int CALLBACK CFileView::CompareFunc (LPARAM lParam1, LPARAM lParam2,
    LPARAM lParamSort)
{
    ITEMINFO* pItem1 = (ITEMINFO*) lParam1;
    ITEMINFO* pItem2 = (ITEMINFO*) lParam2;
    int nResult;

    switch (lParamSort) {

    case 0: // File name
        nResult = pItem1->strFileName.CompareNoCase (pItem2->strFileName);
        break;

    case 1: // File size
        nResult = pItem1->nFileSizeLow - pItem2->nFileSizeLow;
        break;

    case 2: // Date and time
        nResult = ::CompareFileTime (&pItem1->ftLastWriteTime,
            &pItem2->ftLastWriteTime);
        break;
    }
    return nResult;
}

void CFileView::OnViewLargeIcons() 
{
    ModifyStyle (LVS_TYPEMASK, LVS_ICON);
}

void CFileView::OnViewSmallIcons() 
{
    ModifyStyle (LVS_TYPEMASK, LVS_SMALLICON);
}

void CFileView::OnViewList() 
{
    ModifyStyle (LVS_TYPEMASK, LVS_LIST);
}

void CFileView::OnViewDetails() 
{
    ModifyStyle (LVS_TYPEMASK, LVS_REPORT);
}

void CFileView::OnUpdateViewLargeIcons(CCmdUI* pCmdUI) 
{
    DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;
    pCmdUI->SetRadio (dwCurrentStyle == LVS_ICON);
}

void CFileView::OnUpdateViewSmallIcons(CCmdUI* pCmdUI) 
{
    DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;
    pCmdUI->SetRadio (dwCurrentStyle == LVS_SMALLICON);
}

void CFileView::OnUpdateViewList(CCmdUI* pCmdUI) 
{
    DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;
    pCmdUI->SetRadio (dwCurrentStyle == LVS_LIST);
}

void CFileView::OnUpdateViewDetails(CCmdUI* pCmdUI) 
{
    DWORD dwCurrentStyle = GetStyle () & LVS_TYPEMASK;
    pCmdUI->SetRadio (dwCurrentStyle == LVS_REPORT);
}

void CFileView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
    if (lHint == 0x5A) {
        FreeItemMemory ();
        GetListCtrl ().DeleteAllItems ();
        Refresh ((LPCTSTR) pHint);
        return;
    }    
    CListView::OnUpdate (pSender, lHint, pHint);
}

I created Wanderer by using AppWizard to generate the source code for a standard SDI document/view application, plugging in Chapter 10's CDriveView and CFileView classes and modifying them as described above, adding a CSplitterWnd member variable to CMainFrame, overriding OnCreateClient, and inserting calls CreateStatic and CreateView. However, there is another way to create Explorer-like applications. If you select Windows Explorer instead of MFC Standard in AppWizard's Step 5 dialog box (shown in Figure 11-9), AppWizard adds code to create a static splitter window. It also derives a pair of view classes—one from CTreeView, the other from CListView or the view class of your choice—and places them in the splitter window's panes. Unfortunately, the AppWizard-generated view classes add little to the base classes from which they derive, so while AppWizard will get you started, you still have to write a fair amount of code to create an Explorer-type application.

Click to view at full size.

Figure 11-9. Using AppWizard to create an Explorer-style application.

Despite the outward similarities between Wanderer and the Windows Explorer, there is a fundamental difference between these applications that goes well beyond their feature lists. Wanderer is a file browser that displays drive, directory, and file names. Explorer is a namespace browser that serves as a virtual window into the shell's namespace. You can see how the shell's namespace is structured and what kinds of objects it includes by studying the left pane of an Explorer window. The desktop object sits at the uppermost level of the hierarchy, followed by My Computer, Network Neighborhood, and Recycle Bin at the next level, drives at the level beneath that, and so on. Drives, directories, and files are merely a subset of the shell's namespace. The namespace also includes printers, printer folders, and other objects for which there are no direct analogues in the file system. The operating system shell supports a set of API functions all its own that applications can use to access its namespace. Some are conventional API functions with names such as ::SHGetDesktopFolder; others are COM functions accessed through IShellFolder interfaces. For more information, search MSDN for articles on the shell's namespace.

Custom Command Routing

As you already know, MFC's CFrameWnd class routes the command messages and user interface (UI) update messages it receives to other objects so that the frame window doesn't have to process commands from menu items and other UI objects. Thanks to command routing, events involving menu items and toolbar buttons can be handled just as easily in the application class, the document class, or the view class as they can in the frame window class. Chapter 9 described the command routing mechanism, and Figure 9-2 documented the path a command or a UI update message follows after an SDI frame window receives it. The active view sees the message first, followed by the document, the document template, the frame window, and finally the application object. For most document/view applications, the command routing sequence depicted in Figure 9-2 is adequate because it gives each object that's likely to want to see a command or an update message a crack at processing it.

Every now and then you'll run into an application for which default command routing isn't sufficient. Wanderer is one of them, and here's why. Commands and UI updates for the view items in Wanderer's View menu are processed in the CFileView class. When CFileView is the active view, its command and update handlers work just fine because the active view is included in the framework's routing list. But when CDriveView is the active view, CFileView isn't notified of events involving View commands because it's not the active view. Consequently, the commands in the Options menu are grayed out and can't be selected when the CDriveView in the left pane has the input focus.

To circumvent this problem, Wanderer modifies the command routing sequence so that command and update messages that aren't handled by any of the standard command targets are routed to inactive views. The work is done in CMainFrame::OnCmdMsg, which first forwards command and update messages to the standard command targets by calling CFrameWnd::OnCmdMsg:

if (CFrameWnd::OnCmdMsg (nID, nCode, pExtra, pHandlerInfo))
    return TRUE;

If CFrameWnd::OnCmdMsg returns 0, indicating that none of the standard command targets handled the message, CMainFrame::OnCmdMsg calls a function in the document class to route the message to all the inactive views:

CWandererDoc* pDoc = (CWandererDoc*) GetActiveDocument ();
if (pDoc != NULL) { // Important!
    return pDoc->RouteCmdToAllViews (GetActiveView (),
        nID, nCode, pExtra, pHandlerInfo);
} 

CWandererDoc::RouteCmdToAllViews iterates through the views associated with the document and calls each view's OnCmdMsg function:

BOOL CWandererDoc::RouteCmdToAllViews(CView *pView, UINT nID, int nCode,
    void *pExtra, AFX_CMDHANDLERINFO *pHandlerInfo)
{
    POSITION pos = GetFirstViewPosition ();

    while (pos != NULL) {
        CView* pNextView = GetNextView (pos);
        if (pNextView != pView) {
            if (pNextView->OnCmdMsg (nID, nCode, pExtra, pHandlerInfo))
                return TRUE;
        }
    }
    return FALSE;
}

CMainFrame::OnCmdMsg passes RouteCmdToAllViews a pointer to the active view so that RouteCmdToAllViews can avoid calling the active view's OnCmdMsg function. The active view has already been called as part of the standard command routing sequence, so calling it again is wasteful. The frame window provides the pointer to the active view because the document class has no concept of active and inactive views. By the same token, a frame window knows which view is active but doesn't how many views there are. That's why CMainFrame calls a function in the document class to iterate through all the views rather than enumerate the views itself.

Note that the CView pointer returned by GetNextView must be cast upward to CCmdTarget pointers in some versions of MFC because those versions erroneously declare OnCmdMsg as protected in CView. Thankfully, this bug is fixed in MFC 6.

Custom routing is a powerful tool for routing commands and UI update messages to nonstandard command targets. You can tap into the command routing sequence just about anywhere you want to by overriding the right OnCmdMsg function. In general, you should call the base class version of OnCmdMsg from an override to keep default command routing intact. And be careful about whose OnCmdMsg functions you call because it's possible to fall into a recursive loop in which object A calls object B and object B calls object A. You wouldn't, for example, want to call a view's OnCmdMsg function from a document's OnCmdMsg function because the view calls the document as part of the standard command routing sequence.

Three-Way Splitter Windows

You can create a three-way splitter window similar to the one featured in Microsoft Outlook Express by nesting static splitter windows. The following OnCreateClient function creates a three-way static splitter that's divided into two columns. The right column is further subdivided into two rows. The user can adjust the relative sizes of the panes by dragging the splitter bars, but the basic layout of the splitter can't be changed because the splitters are static rather than dynamic:

BOOL CMainFrame::OnCreateClient (LPCREATESTRUCT lpCreateStruct,
    CCreateContext* pContext)
{
    if (!m_wndSplitter1.CreateStatic (this, 1, 2) ¦¦
        !m_wndSplitter1.CreateView (0, 0, RUNTIME_CLASS (CTextView),
            CSize (128, 0), pContext) ¦¦
        !m_wndSplitter2.CreateStatic (&m_wndSplitter1, 2, 1, WS_CHILD ¦
            WS_VISIBLE, m_wndSplitter1.IdFromRowCol (0, 1)) ¦¦
        !m_wndSplitter2.CreateView (0, 0, RUNTIME_CLASS (CPictureView),
            CSize (0, 128), pContext) ¦¦
        !m_wndSplitter2.CreateView (1, 0, RUNTIME_CLASS (CPictureView),
            CSize (0, 0), pContext))
        return FALSE;
    return TRUE;
}

Here's a synopsis of what happens in the if statement that creates and initializes the three-way splitter:

  1. The first splitter window is created by calling CreateStatic on the CSplitterWnd data member m_wndSplitter1. This splitter window contains one row and two columns.
  2. A CTextView is added to m_wndSplitter1's first (left) pane with CreateView.
  3. A second splitter window is created in the right pane of the first splitter window by calling m_wndSplitter2's CreateStatic function. m_wndSplitter2 is parented to m_wndSplitter1 rather than to the frame window and assigned a child window ID that identifies it as the pane in row 0, column 1. The proper ID for m_wndSplitter2 is obtained by calling CSplitterWnd::IdFromRowCol, which uses simple math to convert a row and column number into a numeric offset that's added to AFX_IDW_PANE_FIRST.
  4. CreateView is called twice to add a CPictureView to each m_wndSplitter2 pane.

Using a dynamic splitter window for m_wndSplitter2 would require a little more work because of some of the assumptions that MFC makes when it creates new views to fill dynamically created splitter panes. If you try to nest a dynamic splitter window inside a static splitter window like this:

BOOL CMainFrame::OnCreateClient (LPCREATESTRUCT lpCreateStruct,
    CCreateContext* pContext)
{
    if (!m_wndSplitter1.CreateStatic (this, 1, 2) ¦¦
        !m_wndSplitter1.CreateView (0, 0, RUNTIME_CLASS (CTextView),
            CSize (128, 0), pContext) ¦¦
        !m_wndSplitter2.Create (&m_wndSplitter1, 2, 1, CSize (1, 1),
            pContext, WS_CHILD ¦ WS_VISIBLE ¦ WS_HSCROLL ¦ 
            WS_VSCROLL ¦ SPLS_DYNAMIC_SPLIT,
            m_wndSplitter1.IdFromRowCol (0, 1)))
        return FALSE;
    return TRUE;
}

you'll sometimes generate access violations when splitting the dynamic splitter window. The reason why is rooted deep in the framework. When a dynamic splitter window splits, CSplitterWnd calls CreateView with a NULL pContext pointer to create a view for the new pane. Seeing that pContext is NULL, CreateView queries the frame window for a pointer to the active view and uses that view as a model for the new view. If the CTextView window happens to be the active view when a split occurs, the framework will see that the view isn't a child of the dynamic splitter and will create an "empty" view that isn't attached to a document object. The first time that view tries to access its document, an access violation will occur.

The secret to successfully nesting a dynamic splitter window inside a static splitter window involves two steps:

  1. Derive a class from CSplitterWnd, and replace CSplitterWnd::SplitRow in the derived class with the following implementation:

    BOOL CNestedSplitterWnd::SplitRow (int cyBefore)
    {
        GetParentFrame ()->
            SetActiveView ((CView*) GetPane (0, 0));
        return CSplitterWnd::SplitRow (cyBefore);
    }
    

  2. Make the nested dynamic splitter an instance of the derived class rather than an instance of CSplitterWnd.

SplitRow is a virtual CSplitterWnd function that's called when a horizontal splitter bar is dragged to create a new pane. The version of SplitRow shown above makes the view in the dynamic splitter window's uppermost pane the active view before the split occurs, which neatly circumvents the dynamic view creation problems that result when the active view is a child of the static splitter window. The override uses GetParentFrame instead of GetParent because the dynamic splitter window's parent is actually the static splitter window, not the frame window, and a frame window function (not a splitter window function) sets the active view.

Dynamic Splitter Windows with Multiple View Types

The previous section demonstrates one way in which a splitter window can be customized by deriving from CSplitterWnd and overriding CSplitterWnd::SplitRow. The CSplitterWnd class includes other virtual functions you can override to customize a splitter window's behavior. One of those functions is CreateView, which MFC calls to create a new view when a dynamic splitter window is split. You can create a dynamic splitter window that displays different types of views in different panes by deriving a class from CSplitterWnd, overriding CreateView, and calling CSplitterWnd::CreateView with a CRuntimeClass pointer to the view of your choice.

The following CreateView override forces a CTextView into the pane at row 1, column 0, regardless of the type of view contained in row 0, column 0:

BOOL CDynaSplitterWnd::CreateView (int row, int col,
    CRuntimeClass* pViewClass, SIZE sizeInit, 
    CCreateContext* pContext)
{
    if ((row == 1) && (col == 0))
        return CSplitterWnd::CreateView (row, col,
            RUNTIME_CLASS (CTextView), sizeInit, pContext);

    return CSplitterWnd::CreateView (row, col, pViewClass,
        sizeInit, pContext);
}

You'll probably have to modify this code for every different splitter window you use because the view class is hardwired to the row and column number. However, you could build a generic (and reusable) dynamic splitter class that supports multiple view types by adding a RegisterView function that correlates view types identified by CRuntimeClass pointers to row and column numbers. Before CSplitterWnd::Create is called, the splitter window could be initialized with information about the type of view that goes in each pane, and CreateView could then use that information to generate the appropriate views.

The CHM file was converted to HTML by chm2web software.