Значит так.
Очень часто возникает следующая проблема:
Есть некий диалог (1) для создания объекта OBJECT_A. Внутри этого диалога можно вызвать ещё один диалог (2), который в свою очередь создает объект типа OBJECT_B. Но объект типа OBJECT_B зависит от пока ещё не созданного объекта типа OBJECT_B. Такая цепочка и взаимосвязь может быть большой...
Возникает проблема, как, например, работать диалогу (2), если данные, на которых он основан (диалог 1), ещё не созданы.
Для этого был написан классик, "Состояние":
class IDialogState
{
public:
virtual ~IDialogState();
void Save(void* pData);
IDialogState* Copy();
bool AddState(IDialogState* pState);
bool ReplaceState(IDialogState* pOldState, IDialogState* pNewState);
IDialogState* FindState(DWORD dwId) const;
bool DeleteState(DWORD dwId);
DWORD GetId() const;
void SetId(DWORD dwId);
protected:
IDialogState();
IDialogState* Clone();
virtual void* DoSave(void*) = 0;
virtual IDialogState* DoClone() = 0;
virtual void DoCopy(IDialogState* pCopyState) = 0;
typedef std::vector<IDialogState*> TStates;
typedef TStates::iterator TStatesIt;
typedef TStates::const_iterator TStatesConstIt;
TStates m_States;
DWORD m_dwId;
};
Класс описывает состояние диалога. Да к тому же содержит указатели на зависимые от него состояния. Так же каждое состояние имеет уникальное обозначение в рамках всей этой могучей иерархии состояний.
Далее. Был написан класс IBaseDialog, описывающий работу с диалогом и с его состоянием.
Любой производный от базового диалога должен иметь свое состояние, производное от базового.
Так же диалог имеет указатель на свой родительский диалог, если он был вызван из другого диалога.
/** Базовый класс для диалогов
*/
class IBaseDialog : public CDialog
{
public:
virtual ~IBaseDialog() {}
UINT GetResourceId() const;
/** Диалог всегда работает с каким-то данным,
вот это данное и должна возвращать эта функция...
*/
virtual void* GetData() { return NULL; }
/** Получить состояние диалога.
*/
IDialogState* GetState() const;
/** Добавить состояние в качестве дочернего текущему.
*/
bool AddState(IDialogState* pState);
IDialogState* FindState(DWORD dwId) const;
bool ReplaceState(IDialogState* pOldState, IDialogState* pNewState);
void SetUniqueId(DWORD dwId);
// создать ГУИ
virtual void CreateGUI();
/** Обновить ГУИ из состояния.
*/
virtual void UpdateGUIFromState() {};
/** Обновить состояние из данного, с которым работает диалог.
*/
virtual void UpdateStateFromData() {}
/** Обновить состояние из ГУИ.
*/
virtual void UpdateStateFromGUI() {};
// Проверка введенных данных в диалог.
virtual bool VerifyUserInput() { return true; }
virtual BOOL OnInitDialog();
virtual void OnOK();
virtual void OnCancel();
virtual BOOL DoInit();
protected:
IBaseDialog(UINT iResourceId, IBaseDialog* pParentDialog);
UINT m_uResourceId;
IBaseDialog* m_pParentDialog; // родительский диалог
IDialogState* m_pState; // текущее состояние диалога
IDialogState* m_pSrcState; // копия состояния
DWORD m_dwExternalUniqueId;
bool m_bDoUserInputVerification;
IDialogState* CreateState();
virtual IDialogState* CreateStatePtr() { return NULL; }
virtual DWORD GetUniqueId();
virtual bool DoOK();
virtual void DoCancel();
virtual void DoDataExchange(CDataExchange* pDX);
};
Есть функция DoInit, которая вызывается при инициализации диалога.
BOOL IBaseDialog::DoInit()
{
bool bRestored = false;
IDialogState* pState = NULL;
CreateGUI();
// запросим у родителя, если он есть свое состояние...
if (m_pParentDialog)
{
pState = m_pParentDialog->FindState(GetUniqueId());
if (pState)
bRestored = true;
}
// если нет состояния, то создадим его...
if (pState == NULL)
pState = CreateState();
if (bRestored)
{
m_pSrcState = pState;
if (pState)
m_pState = pState->Copy();
}
else // если было только что создано, то копию нет смысла делать...
{
m_pState = pState;
UpdateStateFromData(); // обновим состояние из данного, с которым работает диалог
}
UpdateGUIFromState(); // обновим ГУИ из состояния
return TRUE;
}
Есть функция DoOK, которая вызывается при нажатии OK в диалоге.
bool IBaseDialog::DoOK()
{
if (m_bDoUserInputVerification && !VerifyUserInput())
return false;
UpdateStateFromGUI();
if (m_pParentDialog)
{
if (m_pSrcState)
{
/* если не NULL, значит делали копию, и в этой переменной хранится
исходное состояние, которое как раз и надо удалить
а в родительский диалог на его место перезаписать ранее созданную копию
*/
m_pParentDialog->ReplaceState(m_pSrcState, m_pState);
delete m_pSrcState;
}
else // иначе... отдадим родителю на хранение свое состояние :)
m_pParentDialog->AddState(m_pState);
}
else // сам является родителем
{
// тогда сохранимся
if (m_pState)
m_pState->Save(GetData());
// удалим состояние
delete m_pState;
}
return true;
}
По нажатию кнопки "Отмена" состояние просто грохается.
Вопрос такой, как избавиться от void* в выделенных местах???
Не могу понять, как сюда прикрутить шаблоны...
Да и вообще, может можно как-то описывать на этапе компиляции взаимосвязь всех диалогов друг с другом, что от чего зависит и т.п.
по аналогии с конечными автоматами из boost.
отдели объекты от диалогов, сделай над ними удобный интерфейс, а из диалогов только вызывай его методы, жизнь станет легче, уверяю.
Что-бы этот интерфейс был доступен из всех диалогов, используй паттерн Singleton.
Посмотри также паттерн Composite, по моему это то, что тебе нужно.