Figure 5   CsharedDoc

doc.h


 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this code works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 // 
 
 /////////////////
 // CCharedDoc implements shareable documents that are kept open
 // for the duration of the edit session
 //
 class CSharedDoc : public CDocument {
 protected:
    CFile* m_pFile;      // the file kept open during editing
    BOOL   m_bReadOnly;  // whether read-only access
 
    // helpers
    CFile* OpenFile(LPCTSTR lpszPathName, BOOL& bReadOnly, BOOL bCreate=FALSE);
    void   CloseFile();
    DECLARE_DYNCREATE(CSharedDoc)
    DECLARE_MESSAGE_MAP()
 
 public:
    CSharedDoc();
    virtual ~CSharedDoc();
    virtual void Serialize(CArchive& ar);
    virtual BOOL OnNewDocument();
    virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
    virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
    virtual void OnCloseDocument();
    virtual void ReleaseFile(CFile* pFile, BOOL bAbort);
    virtual BOOL DoFileSave();
    virtual CFile* GetFile(LPCTSTR lpszFileName, UINT nOpenFlags,
       CFileException* pError);
 };
doc.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this code works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 // CSharedDoc implements an MFC doc class that does file sharing by
 // locking a file for the duration of an edit session.
 // Compiles with VC++ 5.0 or later
 // 
 #include "stdafx.h"
 #include "doc.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #undef THIS_FILE
 static char THIS_FILE[] = __FILE__;
 #endif
 
 IMPLEMENT_DYNCREATE(CSharedDoc, CDocument)
 
 BEGIN_MESSAGE_MAP(CSharedDoc, CDocument)
 END_MESSAGE_MAP()
 
 CSharedDoc::CSharedDoc()
 {
    m_pFile = NULL;
 }
 
 CSharedDoc::~CSharedDoc()
 {
 }
 
 //////////////////
 // Load/Save doc as normal (use edit view)
 //
 void CSharedDoc::Serialize(CArchive& ar)
 {
    ((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);
 }
 
 ////////////////////////////////////////////////////////////////
 // Below are overrides for shared stuff
 
 /////////////////
 // Map "Save" to "Save As" if doc is read-only
 //
 BOOL CSharedDoc::DoFileSave()
 {
    return m_bReadOnly ?
       DoSave(NULL) :             // do Save As
       CDocument::DoFileSave();   // save as normal
 }
 
 ////////////////
 // Create new doc. Close old one in case this is an SDI app
 //
 BOOL CSharedDoc::OnNewDocument()
 {
    CloseFile();  // Required for SDI app only, because MFC re-uses doc.
    BOOL bRet = CDocument::OnNewDocument();
 
    if (bRet)
       // do normal stuff
       ((CEditView*)m_viewList.GetHead())->SetWindowText(NULL);
    return bRet;
 }
 
 //////////////////
 // Open New doc. Close old one in case this is an SDI app
 //
 BOOL CSharedDoc::OnOpenDocument(LPCTSTR lpszPathName)
 {
    CloseFile();  // Required for SDI app only, because MFC re-uses doc
 
    // Open the file
    CFile* pFile = OpenFile(lpszPathName, m_bReadOnly);
    if (!pFile)
       return FALSE;
 
    if (m_bReadOnly) {
       // Doc was opened read-only: tell user
       CString s;
       s.Format("File '%s' is in use.\nIt will be opened read-only",
                lpszPathName);
       AfxMessageBox(s);
    }
    m_pFile = pFile;
 
    // Now do standard MFC Open, but close file if the open fails
    BOOL bRet = CDocument::OnOpenDocument(lpszPathName);
    if (!bRet)
       CloseFile();
    return bRet;
 }
 
 /////////////////
 // Save document. Use already-open file, unless saving to a new name.
 // Either way, lock the file and set length to zero before saving it.
 //
 BOOL CSharedDoc::OnSaveDocument(LPCTSTR lpszPathName)
 {
    BOOL bReadOnly = m_bReadOnly;
    CFile* pFile   = m_pFile;
    ASSERT_VALID(pFile);
 
    // Check for different file name
    CString sFileName = pFile->GetFilePath();
    BOOL bNewFile = (sFileName != lpszPathName);
    if (bNewFile) {
       // saving w/different name: open new file
       pFile = OpenFile(lpszPathName, bReadOnly, TRUE);
       if (!pFile)
          return FALSE;
    }
    ASSERT_VALID(pFile);
 
    // If can't get write access, can't save.
    // Display message and return FALSE.
    // 
    if (bReadOnly) {
       CString s;
       s.Format("File '%s' is in use.\nSave with a different name.",
                lpszPathName);
       AfxMessageBox(s);
       if (bNewFile)           // if new file was opened:
          pFile->Close();      // close it
       return FALSE;
    }
 
    if (bNewFile) {
       // new file was opened: install it and close the old one
       ASSERT(m_pFile);        // sanity check
       m_pFile->Close();       // close old one
       m_pFile = pFile;        // and replace w/new one
       m_bReadOnly = bReadOnly;// read-only flag too
    }
 
    // Now do normal Serialize. Lock the file first and set length to zero
    // This is required because I opened with modeNoTruncate. You might
    // want to consider "robust" saving here: that is, save to a temp file
    // before destroying the original file; then if the save succeeds, replace
    // the original file with the new one.
    //
    pFile->LockRange(0,(DWORD)-1);   // will throw exception if fails
    pFile->SetLength(0);             // otherwise will append
    BOOL bRet = CDocument::OnSaveDocument(lpszPathName); // normal MFC save
    pFile->UnlockRange(0,(DWORD)-1); // unlock
    return bRet;
 }
 
 //////////////////
 // Close document: time to really close the file too. MFC only calls this
 // function in a MDI app, not SDI.
 //
 void CSharedDoc::OnCloseDocument()
 {
    CloseFile(); // close file
    CDocument::OnCloseDocument(); // Warning: must call this last
                                  // because MFC will "delete this"
 }
 
 //////////////////
 // "Release" the file. This means either abort or close.
 // In the case of close, I don't really close it, but leave
 // file open for duration of user session.
 //
 void CSharedDoc::ReleaseFile(CFile* pFile, BOOL bAbort)
 {
    if (bAbort)
       CDocument::ReleaseFile(pFile, bAbort);
    else if (!m_bReadOnly) {
       pFile->Flush(); // write changes to disk, but don't close!
    }
 }
 
 //////////////////
 // Override to use my always-open CFile object instead
 // of creating and opening a new one.
 //
 CFile* CSharedDoc::GetFile(LPCTSTR, UINT, CFileException*)
 {
    ASSERT_VALID(m_pFile);
    return m_pFile;
 }
 
 ////////////////////////////////////////////////////////////////
 // Helper functions
 
 // CFile::Open mode flags
 const OPENREAD   = CFile::modeRead      | CFile::shareDenyNone;
 const OPENWRITE  = CFile::modeReadWrite | CFile::shareDenyWrite;
 const OPENCREATE = CFile::modeCreate    | CFile::modeReadWrite  |
    CFile::shareDenyWrite;
 
 //////////////////
 // Open the document file. Try to open with write access, else read-only.
 // bCreate says whether to create the file, used when saving to a new name.
 // Returns the CFile opened, and sets bReadOnly.
 //
 CFile* CSharedDoc::OpenFile(LPCTSTR lpszPathName, BOOL& bReadOnly, BOOL bCreate)
 {
    CFile* pFile = new CFile;
    ASSERT(pFile);
    bReadOnly = TRUE; // assume read only 
 
    // try opening for write
    CFileException fe;
    if (pFile->Open(lpszPathName, bCreate ? OPENCREATE : OPENWRITE, &fe)) {
       bReadOnly = FALSE;      // got write access
 
    } else if (bCreate || !pFile->Open(lpszPathName, OPENREAD, &fe)) {
       // can't open for read OR write--yikes! Time to punt
       delete pFile;
       pFile = NULL;
       ReportSaveLoadException(lpszPathName, &fe, FALSE, 
                               AFX_IDP_FAILED_TO_OPEN_DOC);
    }
    if (pFile)
       pFile->SeekToBegin();
 
    return pFile;
 }
 
 /////////////////
 // Close the file if it's open. Called from multiple places for SDI app
 //
 void CSharedDoc::CloseFile()
 {
    if (m_pFile) {
       m_pFile->Close();
       m_pFile = NULL;
    }
 }

Figure 8   Overview of CSharedDoc Functions

CDocument Overrides
BOOL OnNewDocument();
Override to call CloseFile in case of SDI app.
BOOL OnOpenDocument(LPCTSTR lpszPathName);
Override to call CloseFile in case of SDI app. Open the file (OpenFile) before calling default, CDocument::OnOpenDocument.
BOOL OnSaveDocument(LPCTSTR lpszPathName);
If the name saved to is different from the current name, re-open m_pFile to the new file, requiring write access. Before actually saving the file, lock it (exclusive access) and set length to zero. Unlock after saving.
void OnCloseDocument();
Override to call CloseFile.
void ReleaseFile(CFile* pFile, BOOL bAbort);
Override to NOT close the file if bAbort==FALSE.
BOOL DoFileSave();
Override to map File Save to File Save As if doc is read-only.
CFile* GetFile(LPCTSTR lpszFileName,
  UINT nOpenFlags, CFileException* pError);
Override to return the open file, m_pFile.
New Helper Functions
void CloseFile();
Close m_pFile if it is open.
CFile* OpenFile(LPCTSTR lpszPathName,
  BOOL& bReadOnly);
Open doc with read-write access and share-deny write if possible; otherwise, just mode Read and shareDenyNone. Sets bReadOnly to tell caller which. Seek to beginning of file.


Figure 9   MFC Message-Reflection Macros

Macro
Message
Used For
ON_CONTROL_REFLECT
WM_COMMAND
control notifications
ON_CONTROL_REFLECT_EX
WM_COMMAND
control notifications
ON_NOTIFY_REFLECT
WM_NOTIFY
control notifications
ON_NOTIFY_REFLECT_EX
WM_NOTIFY
control notifications
ON_UPDATE_COMMAND_UI_REFLECT
MFC UI update
updating buttons, status bar, and so on
ON_WM_CTLCOLOR_REFLECT
WM_CTLCOLOR
changing control color
ON_WM_DRAWITEM_REFLECT
WM_DRAWITEM
owner-draw items
ON_WM_MEASUREITEM_REFLECT
WM_MEASUREITEM
owner-draw items
ON_WM_DELETEITEM_REFLECT
WM_DELETEITEM
owner-draw items
ON_WM_CHARTOITEM_REFLECT
WM_CHARTOITEM
keystroke navigation in list box
ON_WM_VKEYTOITEM_REFLECT
WM_VKEYTOITEM
keystroke navigation in list box
ON_WM_COMPAREITEM_REFLECT
WM_COMPAREITEM
owner-draw items
ON_WM_HSCROLL_REFLECT
WM_HSCROLL
scroll bars
ON_WM_VSCROLL_REFLECT
WM_VSCROLL
scroll bars
ON_WM_PARENTNOTIFY_REFLECT
WM_PARENTNOTIFY
to notify parent of child window creation/destruction


Figure 10   MFC _EX Message Handler Macros

Macro
Message/Use
ON_COMMAND_EX
command ID
ON_COMMAND_EX_RANGE
range of command ID's
ON_NOTIFY_EX
WM_NOTIFY
ON_NOTIFY_EX_RANGE
range of WM_NOTIFY's
ON_CONTROL_REFLECT_EX
reflected WM_COMMAND
ON_NOTIFY_REFLECT_EX
reflected WM_NOTIFY