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 |