Прикрепление и отделение объектов - Разделение оберток и объектов: Detach()
ОГЛАВЛЕНИЕ
Разделение оберток и объектов: Detach()
Часто программисты используют следующее решение:
{
CFont * f;
f = new CFont;
f->CreateFont(...);
c_EditControl.SetFont(f);
}
Эмпирическое наблюдение показывает, что этот код работает, и работает правильно. Это так. Код работает, но он неаккуратный. Что именно произошло с тем объектом CFont, на который ссылались через CFont *? Ничего не произошло. Есть нестандартный CFont, недоступный и неразрушимый. Он сохраняется бесконечно. Это может быть безобидно, но не является хорошей практикой программирования.
Надо применять следующую очень важную хитрость при использовании MFC в интерфейсе MFC-к-Windows. Используется метод Detach(отделить):
{
CFont f;
f.CreateFont(...);
c_InputData.SetFont(&f);
f.Detach(); // очень важно!!!
}
Операция Detach отделяет объект Windows от его обертки и возвращает в качестве его значения описатель нижележащего объекта Windows. Он не присваивается ничему, так как не нужен. Но сейчас, когда CFont уничтожен, связанный с ним описатель m_hObject равен NULL, и нижележащий HFONT не уничтожается.
Внутри кода вы нашли бы много экземпляров Detach. Пример – функция захвата битовой карты окна, извлекающая битовую карту для заданного окна и помещающая ее в буфер обмена. Чтобы предотвратить уничтожение битовой карты при выходе из области видимости, она отделяет битовую карту от объекта CBitmap.
Когда Detach недостаточно хорош: CDialog и немодальные диалоговые окна
На разных форумах часто встречаются вопросы типа "Пытался создать немодальное диалоговое окно, и не удалось. Вообще не получил диалоговое окно. Что не так?" с приложением следующего фрагмента кода:
void CMyClass::OnLaunchDialog()
{
CMyToolbox dlg;
dlg.Create(dlg.IDD);
}
Сейчас вы должны понять, что произошло. Создается диалоговое окно, но как только происходит выход из области видимости, содержащей переменную, вступает в дело деструктор, и так как это окно, вызывает DestroyWindow для связанного объекта. Конечно, этот прием всегда работает для модального диалога, как, например:
{
CMyDataEntryScreen dlg;
dlg.DoModal();
}
потому что после выхода из модального диалога не нужна(я) переменная dlg. (Неясно, зачем надо задавать идентификатор диалогового окна для метода Create, так как он подразумевается в классе!)
Но здесь нельзя использовать Detach, потому что диалоговое окно хочет обрабатывать сообщения и хочет иметь состояние. Это не проверялось, но думается, что если действительно сделать Detach, то вы начнете получать кучу ошибок ASSERT или ошибки доступа. Нужен объект немодального диалогового окна.
В данном случае правильный метод - создать ссылку CDialog, например, добавить следующую строку в класс CWinApp (предполагается, что для всех экземпляров окон нужен лишь один инструментарий):
// в классе CWinApp
CMyToolbox * tools;
// в конструкторе CWinApp:
tools = NULL;
// в активаторе:
void CWinApp::OnOpenToolbox()
{
if(tools != NULL)
{ /* использовать существующий */
if(tools->IsIconic())
tools->ShowWindow(SW_RESTORE;
tools->SetForegroundWindow();
return;
} /* использовать существующий */
tools = new CMyToolbox;
tools->Create(CMyToolBox::IDD);
}
Этот код создаст окно, если оно не существует, в противном случае он поднимет окно наверх. Код предполагает, что окно можно минимизировать, поэтому он восстановит окно при необходимости. Если вы не поддерживаете минимизацию диалогового окна, вам не понадобится операция SW_RESTORE. С другой стороны, иногда удобно на самом деле не уничтожать окно при закрытии, а просто скрывать его, в этом случае используется SW_SHOW в качестве операции.
Почему бы не создать переменную CMyToolbox (не ссылку)? Потому что надо знать, когда удалять объект. Это легче, если вы полностью последовательны и никогда не выделяете объект в стеке или как член класса, а используете только указатели на объект, выделенный в куче. Надо добавить в класс обработчик PostNcDestroy, являющийся виртуальным методом, в виде:
void CMyToolbox::PostNcDestroy()
{
CDialog::PostNcDestroy();
// добавить эту строку
delete this;
}
Это гарантирует, что при закрытии окна экземпляр окна будет удален. Замечание: это не меняет указатель, в данном примере tools, поэтому если явно не установить его в NULL, будут большие проблемы.
Это обрабатывается разными способами. Самый распространенный метод – передать заданное пользователем сообщение обратно родительскому окну, что немодальное диалоговое окно было уничтожено. Это говорит классу CWinApp, что он может обнулить переменную. Заметьте, что CWinApp, хоть и CCmdTarget, не является CWnd, поэтому ему нельзя передать сообщение с помощью PostMessage. Вместо этого надо сделать PostThreadMessage и сделать ON_THREAD_MESSAGE или ON_REGISTERED_THREAD_MESSAGE, чтобы обработать его. Если не знаете, о чем идет речь, прочитайте статью об управлении сообщениями.
void CMyToolbox::PostNcDestroy()
{
CDialog::PostNcDestroy();
// добавить эти строки
delete this;
AfxGetApp()->PostThreadMessage(UWM_TOOLBOX_CLOSED, 0, 0);
}
Добавьте в определение класса CWinApp:
afx_msg LRESULT OnToolboxClosed(WPARAM, LPARAM);
добавьте в карту сообщений CWinApp:
ON_THREAD_MESSAGE(UWM_TOOLBOX_CLOSED, OnToolboxClosed)
или
ON_REGISTERED_THREAD_MESSAGE(UWM_TOOLBOX_CLOSED, OnToolboxClosed)
и метод реализации
LRESULT CMyApp::OnToolboxClosed(WPARAM, LPARAM)
{
tools = NULL;
return 0;
}
Различие между обычными сообщениями и зарегистрированными сообщениями объясняется в статье об управлении сообщениями. С этим связан ряд рисков, потому что если приложение окажется в цикле обработки сообщений, отличном от главного конвейера сообщений (например, имеет модальное диалоговое окно или активный MessageBox), то PostThreadMessage не будет виден, и придется обрабатывать PostMessage в классе MainFrame.