В приложении классы книги, раздела, главы и страницы унаследованы от CObject. Это необходимо для реализации механизма загрузки документа из файла – метод Serialize() в этих классах перегружается также, как и в классе документа:
void CBookDoc::Serialize(CArchive& ar)
{
m_Book.Serialize(ar);
}
Классы CRazdels, CGlavas и CPages унаследованы от класса CObArray. Поэтому для объектов этих классов можно вызывать все члены-функции, присущие классу массива объектов.
Объектная модель книги
Реализация объектной модели на языке программирования C++
Реализация класса книги
BookClass.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Razdels.h"
class CBookClass;
extern void RenewTree(CTreeCtrl*,CBookClass*);
extern void RenewList(CListCtrl*,CBookClass*);
class CBookClass : public CObject
{
DECLARE_SERIAL(CBookClass)
private:
CRazdels m_Razdels;
CPtrArray m_pPages;
CTreeCtrl* pTreeCtrl;
CListCtrl* pListCtrl;
CString m_Name;
WORD m_ActivePage;
void RenewCtrls()
{
RenewTree(pTreeCtrl,this);
RenewList(pListCtrl,this);
}
public:
CRazdels* GetRazdels()
{
return &m_Razdels;
}
CPtrArray* GetPages()
{
return &m_pPages;
}
void SetTreeCtrl(CTreeCtrl* TreeCtrl)
{
pTreeCtrl=TreeCtrl; }
void SetListCtrl(CListCtrl* ListCtrl)
{
pListCtrl=ListCtrl;
}
CString* GetName()
{
return &m_Name;
}
WORD GetActivePage()
{
return m_ActivePage;
}
void SetActivePage(WORD Page)
{
m_ActivePage=Page;
RenewList(pListCtrl,this);
}
void SetPreviousActivePage()
{
if (m_ActivePage>0)
{
m_ActivePage--;
RenewCtrls();
}
}
void SetNextActivePage()
{
if (m_ActivePage+1<m_pPages.GetSize())
{
m_ActivePage++;
RenewCtrls();
}
}
CBookClass();
~CBookClass();
void Serialize(CArchive&);
};
BookClass.cpp:
#include "stdafx.h"
#include "BookClass.h"
IMPLEMENT_SERIAL(CBookClass,CObject,0)
CBookClass::CBookClass()
{
m_ActivePage=0;
}
CBookClass::~CBookClass()
{
m_Razdels.DeleteRazdels();
}
void CBookClass::Serialize(CArchive& ar)
{
m_Razdels.DeleteRazdels();
m_pPages.RemoveAll();
m_ActivePage=0;
CObject::Serialize(ar);
if (ar.IsLoading())
{
ar>>m_Name;
WORD Count,Counter;
ar>>Count;
for(Counter=0;Counter<Count;Counter++)
{
CRazdel* Razdel=new CRazdel;
Razdel->Serialize(ar);
m_Razdels.Add(Razdel);
}
for(Counter=0;Counter<m_Razdels.GetSize();Counter++)
{
CRazdel* Razdel=(CRazdel*)m_Razdels.GetAt(Counter);
for(WORD Counter2=0;Counter2<Razdel->GetGlavas()->GetSize();Counter2++)
{
CGlava* Glava=(CGlava*)Razdel->GetGlavas()->GetAt(Counter2);
for(WORD Counter3=0;Counter3<Glava->GetPages()->GetSize();Counter3++)
m_pPages.Add(Glava->GetPages()->GetAt(Counter3));
}
}
}
}
Razdels.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Razdel.h"
class CRazdels : public CObArray
{
public:
void DeleteRazdels();
};
Razdels.cpp:
#include "stdafx.h"
#include "Razdels.h"
void CRazdels::DeleteRazdels()
{
WORD Counter;
for(Counter=0;Counter<GetSize();Counter++)
{
CRazdel* Razdel=(CRazdel*)GetAt(Counter);
Razdel->GetGlavas()->DeleteGlavas();
delete Razdel;
}
RemoveAll();
}
Razdel.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Glavas.h"
class CRazdel : public CObject
{
DECLARE_SERIAL(CRazdel)
private:
CGlavas m_Glavas;
CString m_Name;
WORD m_FirstGlava;
WORD m_LastGlava;
WORD m_FirstPage;
WORD m_LastPage;
public:
CGlavas* GetGlavas()
{
return &m_Glavas;
}
CString* GetName()
{
return &m_Name;
}
WORD GetFirstGlava()
{
return m_FirstGlava;
}
void SetFirstGlava(WORD FirstGlava)
{
m_FirstGlava=FirstGlava;
}
WORD GetLastGlava()
{
return m_LastGlava;
}
void SetLastGlava(WORD LastGlava)
{
m_LastGlava=LastGlava;
}
WORD GetFirstPage()
{
return m_FirstPage;
}
void SetFirstPage(WORD FirstPage)
{
m_FirstPage=FirstPage;
}
WORD GetLastPage()
{
return m_LastPage;
}
void SetLastPage(WORD LastPage)
{
m_LastPage=LastPage;
}
CRazdel(){};
void Serialize(CArchive&);
};
Razdel.cpp:
#include "stdafx.h"
#include "Razdel.h"
IMPLEMENT_SERIAL(CRazdel,CObject,0)
void CRazdel::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsLoading())
{
ar>>m_Name>>m_FirstGlava>>m_LastGlava>>m_FirstPage>>
m_LastPage;
WORD Count,Counter;
ar>>Count;
for(Counter=0;Counter<Count;Counter++)
{
CGlava* Glava=new CGlava;
Glava->Serialize(ar);
m_Glavas.Add(Glava);
}
}
}
Glavas.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Glava.h"
class CGlavas : public CObArray
{
public:
void DeleteGlavas();
};
Glavas.cpp:
#include "stdafx.h"
#include "Glavas.h"
void CGlavas::DeleteGlavas()
{
WORD Counter;
for(Counter=0;Counter<GetSize();Counter++)
{
CGlava* Glava=(CGlava*)GetAt(Counter);
Glava->GetPages()->DeletePages();
delete Glava;
}
RemoveAll();
}
Glava.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Pages.h"
class CGlava : public CObject
{
DECLARE_SERIAL(CGlava)
private:
CPages m_Pages;
CString m_Name;
WORD m_FirstPage;
WORD m_LastPage;
public:
CPages* GetPages()
{
return &m_Pages;
}
CString* GetName()
{
return &m_Name;
}
WORD GetFirstPage()
{
return m_FirstPage; }
void SetFirstPage(WORD FirstPage)
{
m_FirstPage=FirstPage;
}
WORD GetLastPage()
{
return m_LastPage;
}
void SetLastPage(WORD LastPage)
{
m_LastPage=LastPage;
}
CGlava(){};
void Serialize(CArchive&);
};
Glava.cpp:
#include "stdafx.h"
#include "Glava.h"
IMPLEMENT_SERIAL(CGlava,CObject,0)
void CGlava::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsLoading())
{
ar>>m_Name>>m_FirstPage>>m_LastPage;
WORD Count,Counter;
ar>>Count;
for(Counter=0;Counter<Count;Counter++)
{
CPage* Page=new CPage;
Page->Serialize(ar);
m_Pages.Add(Page);
}
}
}
Pages.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "Page.h"
class CPages : public CObArray
{
public:
void DeletePages();
};
Pages.cpp:
#include "stdafx.h"
#include "Pages.h"
void CPages::DeletePages()
{
WORD Counter;
for(Counter=0;Counter<GetSize();Counter++)
{
CPage* Page=(CPage*)GetAt(Counter);
delete Page;
}
RemoveAll();
}
Page.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define CountOfStrings 37
class CPage : public CObject
{
DECLARE_SERIAL(CPage)
private:
CString m_Strings[CountOfStrings];
CString m_Name;
HTREEITEM m_TreeItem;
public:
CString* GetString(BYTE Index)
{
return &m_Strings[Index];
}
CString* GetName()
{
return &m_Name;
}
HTREEITEM GetTreeItem()
{
return m_TreeItem;
}
void SetTreeItem(HTREEITEM TreeItem)
{
m_TreeItem=TreeItem;
}
CPage(){};
void Serialize(CArchive&);
};
Page.cpp:
#include "stdafx.h"
#include "Page.h"
IMPLEMENT_SERIAL(CPage,CObject,0)
void CPage::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsLoading())
{
ar>>m_Name;
BYTE Counter;
for(Counter=0;Counter<CountOfStrings;Counter++)
ar>>m_Strings[Counter];
}
}
Порождение объектов
В файле Book.cpp объявляется глобальная переменная – объект класса приложения CBookApp:
CBookApp theApp;
В перегруженной функции-члене InitInstance() класса CBookApp создаются объекты классов документа, окна и представления оглавления:
BOOL CBookApp::InitInstance()
{
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CBookDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CLeftView));
AddDocTemplate(pDocTemplate);
return TRUE;
}
Класс окна CMainFrame содержит защищенный атрибут класса CSplitterWnd, предоставляющий доступ к представлению страницы посредством вызова открытой член-функции класса CMainFrame:
class CMainFrame : public CFrameWnd
{
protected:
CSplitterWnd m_wndSplitter;
public:
CBookView* GetRightPane();
};
Объект класса книги является открытым атрибутом класса документа:
class CBookDoc : public CDocument
{
public:
CBookClass m_Book;
};
Все структурные элементы книги (разделы, главы и страницы) создаются в момент загрузки файла книги с диска через оператор new:
CRazdel* Razdel=new CRazdel;
m_Razdels.Add(Razdel);
Вызов операций
Вызов операций для объектов в C++ может быть организован двумя способами:
1) Если требуется вызвать операцию для переменной, являющейся объектом какого-либо класса, то используется оператор .*:
Object.MemberFunc();
2) Если переменная является указателем на объект класса, то доступ к методам, поддерживаемым данным классом, организовывается через оператор ->*:
pObject->MemberFunc();
Т.к. объект класса книги является открытым атрибутом класса документа, то доступ к членам класса книги осуществляется через указатель на объект класса документа. Т.о., чтобы вызвать функцию-член класса CBookClass, необходимо получить вначале указатель на объект класса CBookDoc:
CBookDoc* pDoc = GetDocument();
pDoc->m_Book.SetTreeCtrl(&refCtrl);
Если члены-функции вызываются внутри их класса, то вызов этих функций осуществляется напрямую без указания имени класса. Например, внутри функции CRazdels::DeleteRazdels() осуществляется вызов членов-функций, наследуемых от базового класса CObArray:
void CRazdels::DeleteRazdels()
{
WORD Counter;
for(Counter=0;Counter<GetSize();Counter++)
{
CRazdel* Razdel=(CRazdel*)GetAt(Counter);
Razdel->GetGlavas()->DeleteGlavas();
delete Razdel;
}
RemoveAll();
}
Использование наследования
Согласно концепции объектно-ориентированного программирования функция загрузки файла книги с диска должна быть инкапсулирована в самом классе CBookClass. Основные этапы создания класса, который может самостоятельно организовать сохранение-восстановление (в документации на MFC применяется термин serialize-сериализация) собственных членов-переменных перечислены ниже:
1.Объявить класс как производный от CObject.
2.В объявление класса включить макрос DECLARE_SERIAL.
3.В реализацию класса включить макрос IMPLEMENT_SERIAL.
4.Перегрузить метод Serialize(), унаследованный от базового класса.