Программирование на Visual C++

Выпуск №21 от 29 октября 2000 г.

Все настоящие программисты делятся на три категории: на тех, кто пишет программы, завершающиеся по нажатию F10, Alt-F4 и Alt-X. Все  остальные  принципы  деления надуманны.

(авт. неизв.)

Рад снова приветствовать вас!

Сегодняшний выпуск посвящен вашим письмам с вопросами, ответами, комментариями, замечаниями и дополнениями, которых у меня накопилось изрядное количество.

ВОПРОС-ОТВЕТ

Q. У меня есть приложение MFC на базе диалога. Я решил организовать переключение некоторых режимов через главное меню, т.е. в меню присутствуют названия режимов и активный в данный момент режим помечен точкой. Для этого я создал обработчики ON_UPDATE_COMMAND_UI для соответствующих пунктов меню и в них вызов СCmdUI::SetRadio(). Например:

void CHeatDlg::OnUpdateSolve(CCmdUI* pCmdUI) {

 if (mode == 1) pCmdUI->SetRadio(TRUE);

 else pCmdUI->SetRadio(FALSE);

}

Это не сработало. Похоже, что сообщения ON_UPDATE_COMMAND_UI просто не посылаются. До этого я использовал такой же подход в приложениях SDI и MDI и все работало. Есть какие-нибудь мысли по этому поводу?

(Андрей Моисеев)

A1 Решение проблемы обработчика ON_UPDATE_COMMAND_UI частично кроется в одном из предыдущих выпусков — №18. Ведь этот обработчик вызывается Framework'ом MFC из OnIdle объекта приложения, а в dialog-based программах OnIdle не работает. Тут можно посоветовать использовать в качестве главного окна аппликации скрытое "фиктивное" окно и подсунуть его в качестве родителя диалога.

(Sergey Emantayev)

A2 Дело в том, что вся логика генерации user-interface update command message для меню (создание объекта CCmdUI и т.д.) реализована в CFrameWnd::OnInitMenuPopup. Соответственно в dialog-based приложениях вызов этой функции отсутствует. Очевидно, предполагалось что в диалогах меню не обязательно. Поэтому можно предложить два варианта. Общим в них является необходимость создания обработчика OnInitMenuPopup для вашего диалога. Первый вариант подойдёт, если вы уже повсеместно расставили обработчики ON_UPDATE_COMMAND_UI, и не хотите ничего переделывать: Вы просто копируте тело функции CFrameWnd::OnInitMenuPopup в созданную вами функцию (благо, что исходники доступны), чистите всё лишнее (разобраться достаточно легко) и созданные вами обработчики OnUpdateXXX начинают вызываться Второй вариант предполагает, что вы, что называется "ручками", изменяете состояния пунктов меню прямо в созданной вами выше функции OnInitMenuPopup, используя передаваемый ей как параметр CMenu*. Этот вариант вполне приемлем, если меню не очень большое и структура его не очень разветвлённая. Для некоторых этот вариант также покажется более привлекательным по той причине, что вы всё делаете сами, а не копируете фрагменты чужого кода.

(Роман Клепов)

A3 Когда Windows собирается отобразить всплывающее меню, она посылает сообщение WM_INITMENUPOPUP, чтобы программа могла на лету кое-что поменять: отключить некоторые пункты, расставить галочки и т. п. Поэтому базовый способ модификации всплывающих меню состоит в перехвате этого сообщения с последующим использованием функций EnableMenuItem, CheckMenuItem и т.п.

MFC предлагает альтернативный подход. В классе CFrameWnd есть готовый обработчик WM_INITMENUPOPUP, который инициализирует структуру CCmdUI для каждого пункта меню, после чего отправляет сообщение CN_UPDATE_COMMAND_UI, определённое в MFC, сперва классу представления, затем классу документа, затем классу главного окна и, наконец, классу приложения. Каждый из этих классов может внести свою лепту в инициализацию всплывающего меню. Можно инициировать этот процесс, вообще ничего не зная о CN_UPDATE_COMMAND_UI: достаточно вызвать CCmdUI::DoUpdate, и сообщеине дойдёт до написанных программистом обработчиков CN_UPDATE_COMMAND_UI.

Теперь внимание: класс диалога (CDialog) не имеет предопределённого обработчика WM_INITMENUPOPUP. Поэтому сообщения CN_UPDATE_COMMAND_UI никто не посылает, и обработчики ON_UPDATE_COMMAND_UI не вызываются. Необходимо вручную написать обработчик OnInitMenuPopup, воспроизведя в нём часть функциональности класса CFrameWnd. В простейшем случае он может выглядеть так:

void CMyDlg::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) {

 if (!bSysMenu) {

  CCmdUI state;

  state.m_pMenu = pPopupMenu;

  state.m_nIndexMax = pPopupMenu->GetMenuItemCount();

  for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++) {

   state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);

   state.m_pSubMenu = NULL;

   state.DoUpdate(this, state.m_nID < 0xF000);

  }

 }

}

В случае когда всплывающее меню имеет подменю обработчик будет более сложным. За примером такого обработчика можно обратиться к исходным текстам программы DLGCBR32, которые находятся в MSDN.

(Alexander Shargin)

A4 Это и не должно работать, так как диалоговое окно не посылает сообщение на обновление меню, следует заметить, что такие "казусы" происходят с диалогами достаточно часто, к примеру Диалог не посылает сообщение о командах меню своим детям, как это делают приложениях SDI и MDI, все дело в том что в MFC диалоги работают по другим правилам, чем обычные окна, так как в MFC главный упор сделан на архитектуру Document-View, а диалоги "искуственное" добавление. Я решал подобную проблему, самостоятельно посылать сообщение Меню на обновление, так:

BEGIN_MESSAGE_MAP(CMyDlg, CDialog)

 ...

 ON_MESSAGE(WM_KICKIDLE, OnKickIdle)

 ...

END_MESSAGE_MAP()


LRESULT CMyDlg::OnKickIdle(WPARAM w, LPARAM l) {

 Sleep(50);

 // Самостоятельно обновлять меню ...

 UpdateMenu(this , GetMenu());

 return TRUE;

}


// А это моя функция ...

void UpdateMenu(CWnd *pWnd, CMenu *pMenu) {

 CCmdUI cmdUI;

 cmdUI.m_pMenu = pMenu;

 cmdUI.m_nIndexMax = pMenu->GetMenuItemCount();

 for (cmdUI.m_nIndex = 0; cmdUI.m_nIndex < cmdUI.m_nIndexMax; ++cmdUI.m_nIndex) {

  CMenu* pSubMenu = pMenu->GetSubMenu(cmdUI.m_nIndex);

  if (pSubMenu == NULL) {

   cmdUI.m_nID = pMenu->GetMenuItemID(cmdUI.m_nIndex);

   cmdUI.DoUpdate(pWnd, FALSE);

  } else UpdateMenu(pWnd, pSubMenu);

 }

} /// и все.

(Oleg Zhuk)

Ответов на этот вопрос пришло довольно много, и я постарался отобрать самые лучшие из них. Заметьте, что хотя некоторые ответы похожи, в реализации имеются серьезные различия.

Авторам всех ответов, и опубликованных, и не опубликованных, большое спасибо!

ОБРАТНАЯ СВЯЗЬ

Из входящей почты:

День добрый,

По всем вопросам, ответы на которые публикуются в рассылке у меня сложилось мнение, которое вероятно ошибочно, что никто их ее читателей не знает такого сайта как www.codeguru.com, который содержит гараздо более полную и полезную информацию и ответы на вопросы, чем те, что публикуются…

(Юрий Карпенко )

Ну, в чем-то возможно и справедливо. Но далеко не во всем. Некоторые не могут свободно работать с MSDN или CodeGuru из-за недостаточного знания английского, или жалко времени в интернет, а кому-то просто лень…

И потом, я думаю, ответы на вопросы полезны не только для тех, кто задал вопрос – самое главное, они полезны тем, кто даже с этой проблемой никогда и не сталкивался. Рассылка позволяет им получать новые познания практически без труда. Ведь доказано, что в школе ученик, понявший новую тему раньше своего товарища, сможет ему ее объяснить гораздо лучше, чем сам учитель. В случае, если у кого-то в будущем возникнет такая задача, он уже будет знать, в каком направлении двигаться при ее решении.

Следующее письмо пришло в ответ на те строки, которые я написал в прошлом выпуске о классе CReBar. Напомню: "Использование CReBar действительно в некоторых случаях оправданно. Но необходимо знать, что у ReBar существует ряд существенных ограничений. В частности, они не могут быть "плавающими" и стыковаться с границами окна."

Полегче со словом "не могут", уважаемый.

http://codeproject.com/docking/tearoffrebars.asp

Вот яркий пример того чего не может быть.

(Paul Bludov)

Тот контрол, на который сослался автор письма, в самом деле, благодаря усилиям программиста, который его создал, может "плавать". Но полной функциональности тулбаров он все равно не достигает – стыковаться он по-прежнему может только с одной границей, не возвращается на свое место по double-click и т.д. И потом, я говорил о CReBar. Вряд ли этот модифицированный класс можно так назвать.

Так что полегче со словом "полегче", уважаемый Павел…

Еще письма:

Есть комментарий к ответам на вопрос из номера 18 (по поводу списка компьютеров в сети), точнее к первому из них.

-------------------------

В документации Микрософт сказано, что Функции Netxxx устарели и следует пользоваться функциями WNetxxx. Во-вторых, с использованием функций Netxxx есть проблемы из-за того, что для Windows NT и Windows 9x используются различные библиотеки (в первом случае netapi32.lib, во втором svrapi.lib). Также вызовы функций в этих библиотеках различаются параметрами (кстати, в MSDN приводится версия для WinNT, а для Win9x придется читать заголовочный файл svrapi.h). Кстати, для Windows 9x следует использовать именно svrapi.h, а не lmxxx.h.

(Sergey Shoumko)

Тут вспомнилось – к Вашему выпуску о читабельности кода:

"Отсутствие коментариев в программе – веский повод для увольнения программиста" – Дональд Кнут

(Роман)
В ПОИСКАХ ИСТИНЫ

Q. Все, наверное, знают программы, называемые Viewbar, которые показывают рекламные баннеры. Но вот как они ограничивают часть экрана, не позволяя другим окнам находиться поверх них? Например, если разрешение экрана 800×600, как они выделяют полосу сверху, в которой находятся, т.ч. программы, развернутые на полный экран, имеют высоту где-то на 60 пикселей меньше. Причем и немаксимизированные окна не могут "влезть" в эту полосу.

(Alexander Popov)

До встречи!

(Алекс Jenter jenter@mail.ru) (Красноярск, 2000.)







Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх