Столкнулся с такой проблемой: есть класс State который переключает диалоги, есть классы диалогов Dlg1 и Dlg2 производные от CDialog (MFC).
Диалоги дергают соответствующие методы State, а метод удаляет текущий диалог и создает новый. Т.е. получается, что диалог косвенно удаляет сам себя.
Соответственно если обращаться к State как в Dlg1, где вызов onNext1 это последняя функция, все вроде бы работает нормально. А если как в диалоге Dlg2, где после вызова onNext2, а значит и удаления объекта, есть ещё какой-то код получаю ""First-chance exception".
Собственно вопрос, это нормально и можно ли в принципе удалять самого себя или я перемудрил?
class State
{
void onNext1()
{
delete dlg_;
dlg_ = new Dlg2(this);
}
void onNext2()
{
delete dlg_;
dlg_ = new Dlg1(this);
}
private:
CDialog *dlg_;
};
class Dlg1
{
public:
Dlg1(State *state) : state_(state) {}
void onNext()
{
state_->onNext1();
}
private:
State *state_;
};
class Dlg2
{
public:
Dlg1(State *state) : state_(state) {}
void onNext()
{
state_->onNext2();
...
ещё какой-то код
}
private:
State *state_;
};
Здравствуйте, win_ce, Вы писали:
_>Столкнулся с такой проблемой: есть класс State который переключает диалоги, есть классы диалогов Dlg1 и Dlg2 производные от CDialog (MFC). _>Диалоги дергают соответствующие методы State, а метод удаляет текущий диалог и создает новый. Т.е. получается, что диалог косвенно удаляет сам себя. _>Соответственно если обращаться к State как в Dlg1, где вызов onNext1 это последняя функция, все вроде бы работает нормально. А если как в диалоге Dlg2, где после вызова onNext2, а значит и удаления объекта, есть ещё какой-то код получаю ""First-chance exception". _>Собственно вопрос, это нормально и можно ли в принципе удалять самого себя или я перемудрил?
_>
_>class Dlg2
_>{
_>public:
_> Dlg1(State *state) : state_(state) {}
_> void onNext()
_> {
_> state_->onNext2();
_> ...
_> ещё какой-то код
_> }
_>private:
_> State *state_;
_>};
_>
Здравствуйте.
Объект конечно может удалять себя. В данном случае важно время удаления.
Насколько я понял, Вы реализуете систему с использованием паттерна "State".
В его классической формулировке ничего не сказано кто отвечает за создание и удаление состояний.
Это как раз основная проблема всех реализаций — сохранение контекста вызова.
К тому же в более сложных примерах последовательность смены диалогов может быть не столь тривиальной, поэтому
решение должно быть универсальным.
Симптом: невозможно гарантировать последовательность разрушения объектов.
Решение: применение подсчёта ссылок.
Ниже пример с подсчётом ссылок, при желании можно использовать шарептр-ы ( пример набросал, но не компилил )
Следующая проблема, которуя я вижу при такой реализации паттерна это соответствие внутренней переменной State::dlg_ и текущему коду метода.
Комментарии в примере я добавил. Это может проявиться в более сложных каскадных вызовах семейства функуий onNext() друг из друга.
class State
{
public:
void onNext1()
{
ChangeDialog( new Dlg2(this) );
}
class Dlg2
{
public:
Dlg1(State *state) : state_(state) {}
void onNext()
{
ref();
{
state_->onNext2();
...
ещё какой-то код //????????????? вот тут ОСТОРОЖНО, текущий диалог State::dlg_ уже не Dlg2
Здравствуйте, win_ce, Вы писали:
_>Столкнулся с такой проблемой: есть класс State который переключает диалоги, есть классы диалогов Dlg1 и Dlg2 производные от CDialog (MFC). _>Диалоги дергают соответствующие методы State, а метод удаляет текущий диалог и создает новый. Т.е. получается, что диалог косвенно удаляет сам себя. _>Соответственно если обращаться к State как в Dlg1, где вызов onNext1 это последняя функция, все вроде бы работает нормально. А если как в диалоге Dlg2, где после вызова onNext2, а значит и удаления объекта, есть ещё какой-то код получаю ""First-chance exception". _>Собственно вопрос, это нормально и можно ли в принципе удалять самого себя или я перемудрил?
Где-то перемудрил, похоже. То есть, удалять сам себя можно, но с предельной аккуратностью.
Как только объект входит в процедуру самоликвидации (включая разрушение окна), он не должен больше ничего делать, кроме как освобождать ресурсы, разрывать связи, отписываться и дерегистрироваться.
Если твои диалоги модальные, то нельзя просто так пристрелить диалог, пока живо его окно, а программа крутится в недрах модального цикла.
Там, скорее, должно быть примерно такое
StateInfo m_current_state, m_next_state;
.....
while(!IsStopped())
{
// показываем текущий диалог
CDialog* pDlg = NewDialogForCurrentState(); // создаём диалог нужного типа
pDlg->DoModal(); // диалог в своих недрах изменяет m_next_state и выходит из модального циклаdelete pDlg; // всю содержательную работу диалог выполнил ранее, скажем, в обработчике кнопки OK
m_current_state = m_next_state;
}
Если же диалоги немодальные... Тогда нужно сделать процедуру переключения окна (убийства-создания) и определиться с тем, кто ответственнен за её исполнение.
CDialog* m_pCurrentDialog;
.....
void ChangeDialog(CDialog* pNewDialog)
{
if(m_pCurrentDialog)
{
.....
m_pCurrentDialog->DestroyWindow();
delete m_pCurrentDialog; // в том случае, если в обработчике WM_DESTROY это не делается
}
m_pCurrentDialog = pNewDialog;
if(m_pCurrentDialog)
{
m_pCurrentDialog->Create(TheParentWindow());
.....
}
}
Вот с ответственностью — это самое интересное. Если у тебя есть родительское окно, и оно в курсе про танцы с диалогами — пусть оно и отвечает за происходящее.
Тогда из обработчика события в диалоге нужно послать этому родительскому окну звоночек, чтобы оно сделало ChangeDialog.
И лучше, если это будет не синхронное SendMessage, а отложенное PostMessage: тогда обработчик спокойно завершится.
А ещё лучше, если не просто PostMessage — которое можно обработать, и будучи глубоко в модальных циклах, — а строго в главном цикле прокачки сообщений. Что-нибудь типа OnIdle().
Подводя итог: идея в том, чтобы
1) Снять с убиваемых объектов (с диалогов) ответственность за убивание. Они могут лишь известить хозяина о том, что "уже пора".
2) Сделать процедуру убивания асинхронной. Убивать не в момент возбуждения этого извещения, а именно в подходящий момент.
я когда реализовывал этот патерн решил проблему при помощи умных указателей. Причем желательно чтоб указатель умел лочиться от удаление на время вызова по неу метода обьекта.
Здравствуйте, Кодт, Вы писали:
К>Вот с ответственностью — это самое интересное. Если у тебя есть родительское окно, и оно в курсе про танцы с диалогами — пусть оно и отвечает за происходящее. К>Тогда из обработчика события в диалоге нужно послать этому родительскому окну звоночек, чтобы оно сделало ChangeDialog. К>И лучше, если это будет не синхронное SendMessage, а отложенное PostMessage: тогда обработчик спокойно завершится. К>А ещё лучше, если не просто PostMessage — которое можно обработать, и будучи глубоко в модальных циклах, — а строго в главном цикле прокачки сообщений. Что-нибудь типа OnIdle().
К> К>Подводя итог: идея в том, чтобы К>1) Снять с убиваемых объектов (с диалогов) ответственность за убивание. Они могут лишь известить хозяина о том, что "уже пора". К>2) Сделать процедуру убивания асинхронной. Убивать не в момент возбуждения этого извещения, а именно в подходящий момент.
А можно развить тему с асинхронным удалением? Реальная картина такая — у CMainFrame есть переменная типа CChildView которая переключает наборы видов (состояний), т.е. логически связанные диалоги, переключение которых хотелось бы хранить только в одном классе, производном от ViewState, т.е. View1 хранит логику переключений между диалогами Dlg1, Dlg2, Dlg3 и т.д., View2 соответственно между Dlg4, Dlg5 и например Dlg2 ну так далее.
class CMyApp : public CWinApp
{
BOOL InitInstance()
{
CMainFrame *pFrame = new CMainFrame;
m_pMainWnd = pFrame;
pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, NULL);
pFrame->ShowWindow(m_nCmdShow);
pFrame->UpdateWindow();
}
};
class CMainFrame : public CFrameWnd
{
CChildView m_wndView;
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL);
}
};
class CChildView : public CWnd
{
ViewState *view_state_;
void SwitchView1()
{
delete view_state_;
view_state_ = new View1(this);
}
void SwitchView2()
{
delete view_state_;
view_state_ = new View2(this);
}
... и.т.д.
};
Соответственно ViewState реализует методы создания и удаления диалогов, а так же содержит указатель на CChildView через который производные классы могут переключаться между видами (состояниями).
Если я правильно понимаю, в методах типа View1::onNext1() вместо непосредственного удаления старого и создания нового диалога, предлагается отправить сообщение родительскому окну CChildView, чтобы он выполнил удаление/создание, верно? Но тогда получается, что CChildView должен знать все про производные диалоги, а это кажется не очень правильным. Или я что-то напутал?
Здравствуйте, win_ce, Вы писали:
_>Если я правильно понимаю, в методах типа View1::onNext1() вместо непосредственного удаления старого и создания нового диалога, предлагается отправить сообщение родительскому окну CChildView, чтобы он выполнил удаление/создание, верно? Но тогда получается, что CChildView должен знать все про производные диалоги, а это кажется не очень правильным. Или я что-то напутал?
Пусть про производные диалоги знает ViewState. А CChildView только прокачивает сообщение.
Здравствуйте, Кодт, Вы писали:
К>Пусть про производные диалоги знает ViewState. А CChildView только прокачивает сообщение.
К>Ну и теперь несложно всё это свалить в кучу
Спасибо большое за код, но я честно говоря не могу разобраться как это работатет. Зачем CChildView реализует оба интерфейса IHolder и Ipump? В конечном итоге запрос на удаление диалога и создание нового идет от какого-то конкретного диалога:
class Dlg1 : public CModelessDialog
{
void onNext()
{
// как нам тут послать сообщение на удаление и как гарантировать, что диалог будет удален после выхода из метода?
}
};
Здравствуйте, win_ce, Вы писали:
_>Спасибо большое за код, но я честно говоря не могу разобраться как это работатет.
_>Зачем CChildView реализует оба интерфейса IHolder и Ipump?
IPump — потому что именно он отвечает за прокачку отложенных вызовов.
В принципе, этим может заняться любое другое окно (и даже не обязательно окно), у кого есть доступ к виндовской очереди сообщений.
IHolder — потому что он является владельцем сменяемых ViewState.
Было бы очень естественно сделать замену ViewState таким же способом: инициировал — выждал подходящий момент — заменил.
_> В конечном итоге запрос на удаление диалога и создание нового идет от какого-то конкретного диалога:
_>
_>class Dlg1 : public CModelessDialog
_>{
_> void onNext()
_> {
_> // как нам тут послать сообщение на удаление и как гарантировать, что диалог будет удален после выхода из метода?
_> }
_>};
_>
Сообщение (точнее, вызов метода) посылается в адрес ViewState, который владеет этим диалогом. Т.е. this->state_->onNext1(), например.
Диалог будет удалён ПОСЛЕ выхода из метода — благодаря тому, что ViewState создаёт отложенный вызов своего метода commitChanges().
Подобно тому, как обычный модальный диалог закрывается:
1) где-нибудь, скажем, в OnOK вызывается функция EndDialog(IDOK);
2) эта функция взводит специальный флажок в данных окна (SetWindowLong(DWL_DLGRESULT), кажется? не дебажил, не уверен);
3) цикл прокачки сообщений в недрах DoModal (::DialogBox) проверяет этот флажок, и если он взведён, — убивает окно и выходит из функции
У нас история похожая, только цикл прокачки сообщений — не модальный, а основной.