Иллюстрированный самоучитель по Visual Studio.Net

         

Отслеживание состояния команд


Текущее состояние команды меню или кнопки на панели инструментов легко определяется по их облику: недоступная команда или кнопка имеет блеклый (grayed) вид. Кроме того, бывают команды, работающие по принципу переключателя (включен — выключен). Их состояние определяется по наличию — отсутствию флажка слева от команды меню или утопленному — нормальному облику кнопки.

Отслеживание состояния команд производится каркасом приложения в паузах между обработкой сообщений. В эти моменты вызывается виртуальная функция Onldle, и если вы ее переопределите для выполнения какой-либо своей фоновой задачи, то можете нарушить или замедлить процесс отслеживания состояния команд. Логика перехода между состояниями определяется специальными функциями т- обработчиками событий UPDATE_COMMAND_UI. Мы должны создать такой обработчик для отслеживания состояния команды ID_EDIT_NEWPOLY. Схема создания точно такая же, как и для самой команды, за исключением того, что вы вместо строки COMMAND выбираете строку UPDATE_COMMAND_UI:

void CDrawView::OnUpdateEditNewpoly(CCradUI *pCmdUI)

{

pCmdUI->SetCheck(m_bNewPoints);

}

Метод SetCheck вспомогательного класса ccmdui устанавливает флажок рядом с командой меню, если параметр имеет значение TRUE, или снимает его, если параметр имеет значение FALSE. Состояние кнопки на инструментальной панели синхронизировано с состоянием команды меню, имеющей тот же идентификатор.

Следующим шагом в развитии приложения будет введение в действие второй панели инструментов IDR_Draw_TYPE. Загрузка из ресурсов панели инструментов осуществляется методом LoadToolBar класса CToolBar. Так как объект этого класса (m_wndToolBar) хранится в классе главного окна (CMainFrame), то и смену панелей инструментов целесообразно выполнять в этом же классе. Введите в него новый метод:

void CMainFrame::ChangeToolbar(UINT tb)

{

//=== в параметре tb будет передан идентификатор панели

m_wndToolBar.LoadToolBar (tb) ;

//=== Перерисовка toolbar

RecalcLayout();

}



Метод CFrameWnd::RecalcLayout занимается перерисовкой панели инструментов и пересчетом размеров клиентской области окна, так как панель инструментов хоть и управляется классом главного окна, но расположена в клиентской области окна, отнимая у нее часть полезной площади.



Имея в классе главного окна такую функцию, как ChangeToolbar, мы просто должны вызывать ее в нужные моменты, подавая на вход идентификатор той или иной панели. Осталось правильно определить те моменты, когда надо производить смену панелей. Очевидно, это моменты перевода фокуса из окна типа CTreeFrame в окно типа CDrawFrame и наоборот.

Примечание

Здесь важно понять, что фокус на самом деле попадает в одно из дочерних окон CLeftView или CRightView или CDrawView. Но это происходит после того, как он попадет в родительское окно-рамку. В принципе, возможны и другие варианты решения проблемы своевременной смены панелей инструментов. Например, переопределить в каждом из трех представлений виртуальную функцию OnActivateView и в ней вызывать ChangeToolbar.

Заметьте, что фокус может быть переведен в окно четырьмя разными способами:

  • активизация представления или его рамки при помощи левой кнопки мыши;

  • ввод клавишной комбинации (accelerator) Ctrl+F6, которая обрабатывается каркасом приложения и по очереди в цикле активизирует окна;

  • системная активизация следующего окна при закрытии одного из окон;

  • системная активизация окна при создании одного из окон (вспомните вызов CreateNewFrame В теле CTreeDoc: :MakeView) или открытии существующего документа.

    Во всех четырех случаях окну-рамке будет послано сообщение WM_SETFOCUS, что нам и надо. Создайте известным вам способом обработчики рассматриваемого сообщения в двух классах окон-рамок CTreeFrame и CDrawFrame и наполните заготовки кодами, как показано ниже:

    void CTreeFrame::OnSetFocus(CWnd* pOldWnd)

    //====== Родитель делает свое дело,

    CMDIChildWnd::OnSetFocus(pOldWnd);

    //====== а мы делаем свое

    ((CMainFrame*)GetParentFrame())

    ->ChangeToolbar(IDRJTreeTYPE);

    void CDrawFrame::OnSetFocus(CWnd* pOldWnd)

    CMDIChildWnd::OnSetFocus(pOldWnd);

    ((CMainFrame*)AfxGetMainWnd())

    ->ChangeToolbar(IDR_DrawTYPE);

    Функция GetParentFrame, полученная в наследство от класса CWnd, прбдвигаясь снизувверх, ищет среди родительских окон ближайшее окно-рамку.


    В нашем случае в этой цепи будет одно промежуточное окно типа MDICLIENT, управляемое классом cwnd. Отметим, что тип MDICLIENT не документирован, но известно, что он служит для управления окнами-рамками типа CMDlchildWnd, располагающимися в клиентской области главного окна приложения. Наши классы CTreeFrame и CDrawVrame являются потомками CMDlchildWnd, поэтому ими-то и управляет секретное окно типа MDICLIENT. Существует и другой способ получить адрес главного окна (CMainFrame). Это вызов глобальной функции MFC Af xGetMainWnd. Мы используем его во второй версии OnSetFocus только для того, чтобы продемонстрировать оба способа.

    Если вы запустите приложение в этот момент, то получите сообщение об ошибках, к которым пора привыкнуть, так как они встречаются довольно часто и вызваны тривиальной причиной — отсутствием видимости класса. Вставьте строку #include "MainFrm.h" в оба файла реализации окон-рамок. Затем запустите приложение вновь и, выбрав шаблон Tree, дайте команду View > Geometry. Вместе с окном другого типа вы увидите и другую панель инструментов. Дайте команду Window > Tile Vertically и проверьте все способы поочередной активизации окон. Панель инструментов и меню должны мгновенно отслеживать переход фокуса.

    При записи нового документа в текущую папку или удалении файла из текущей папки ситуация, которую призван отражать класс CRightView, меняется. Для синхронизации вида с изменившейся ситуацией была введена в меню IDR_TreeTYPE команда View > Refresh. Если мы хотим создать обработчик этой команды, то надо решить, в каком классе это лучше всего сделать. Тут есть проблема, которая может быть сначала и не видна. Вы помните, что мы поместили команду Refresh только в одно меню !DR__TreeTYPE. Поэтому она будет доступна только тогда, когда активно окно CTreeFrame, что соответствует логике изменения содержимого правого окна. Мы исходим из того, что изменяемое окно должно быть видно пользователю.

    Если создать обработчик только в одном из классов, то команда будет не всегда доступна.


    Ее доступность зависит от того, в каком из окон находится фокус. Например, пусть обработчик находится в классе CLef tview. Если щелкнуть мышью правое окно, то команда будет недоступна. Она станет вновь доступной, если щелкнуть мышью левое окно. Рассмотрите самостоятельно варианты размещения обработчика В классах CTreeFrame, CMainFrame, (CDrawFrame?). Наряду с доступностью обсудите, как добывать адреса нужных объектов.

    Мы решили поместить обработчик в класс документа, так как при этом команда будет относиться к окну CRightView активного документа, что логично. Известным вам способом создайте заготовку функции обработки команды ID_VIEW_ REFRESH и приведите ее в соответствие со следующим фрагментом:

    void CTreeDoc::OnViewRefresh(void)

    {

    //====== Получаем адрес левого представления

    CLeftView *pView = dynamic_cast<CLeftview*>

    (GetView(RUNTIME_CLASS(CLeftView)));

    //====== Запускаем цепочку действий для освежения

    //====== содержимого правого окна

    FreeDocs();

    pView->SearchForDocs

    (pView->GetPath(pView->m_Tree.GetSelectedItem()));

    ProcessDocs();

    }

    Запустив приложение, вы опять получите сообщения об ошибках, и причины будут теми же. Вставьте в TreeDoc.cpp строку #include "Lef tview.h", а в Lef tview.h уберите упреждающее объявление класса CTreeDoc, но вставьте внутрь объявления класса CLef tview декларацию односторонней дружбы:

    friend class CTreeDoc;

    Теперь запуск должен пройти гладко. Проверьте работу команды View > Refresh, предварительно сохранив документ Save as в ту же папку, которая выбрана в левом окне.




    Содержание раздела