Визуальное редактирование данных


Несмотря на то что разрабатываемое приложение носит учебный характер, оно моделирует вполне реальные ситуации, когда путем навигации по дереву файлов пользователь ищет и выбирает документ, для того чтобы открыть его в окне представления, специализированного для внесения изменений в данные. В отличие от
Windows Explorer мы даем возможность пользователю выбрать документ не по его имени и значку, а по его содержимому в виде чертежа конструкции.
Современным подходом к редактированию данных является использование таблиц (grids) типа Excel, в которых отражены данные открытого документа и которые позволяют редактировать их, мгновенно получая обратную связь в виде изменившейся геометрии устройства. Таблицы удобно разместить на одной из панелей расщепленного окна с регулируемой перегородкой (split bar).
К сожалению, в MFC нет классов, поддерживающих функционирование таблиц. Реализация их в виде внедряемых СОМ-объектов обладает рядом недостатков. Во-первых, существующие grid-элементы обладают весьма ограниченными возможностями. Во-вторых, интерфейсы обмена данными между внедренной (embedded) таблицей и приложением-контейнером громоздки и неуклюжи. Самым лучшим, известным автору, решением этой проблемы является использование библиотеки классов objective Grids, разработанных компанией stingray Software. Библиотека полностью совместима с MFC. В ней есть множество классов, поддерживающих работу разнообразных элементов управления: combo box, check box, radio button, spinner, progress и др. Управление grid-элементами или окнами типа CGXGridWnd на уровне исходных кодов дает полную свободу в воплощении замыслов разработчика.
Однако, не имея лицензии на использование данного продукта, я не могу использовать его в разработке даже этого учебного приложения. Поэтому мы пойдем традиционным путем и внесем в проект возможность визуального редактирования данных с помощью обычных мышиных манипуляций. Представление, поддерживаемое классом CDrawView, как было уже отмечено, должено служить посредником между пользователем и данными текущего полигона.
Изменение координат вершин полигона в диапазоне, ограниченном размерами логической области (2000x2000), можно производить простым перетаскиванием его вершин с помощью указателя мыши. Чтобы намекнуть пользователю нашего приложения о возможности произведения таких операций (вряд ли он будет читать инструкцию), мы используем стандартный прием, заключающийся в изменении формы курсора в те моменты, когда указатель мыши находится вблизи характерных точек изображения. Это те точки, которые можно перетаскивать. В нашем случае — вершины полигона. Очевидной реакцией на курсор в виде четырех перекрещенных стрелок является нажатие левой кнопки и начало перетаскивания. Заканчивают перетаскивание либо отпусканием кнопки мыши, либо повторным ее нажатием. Во втором варианте при перетаскивании не обязательно держать кнопку нажатой. Остановимся именно на нем.
В процессе перемещения можно постоянно перерисовывать весь объект, что обычно сопровождается неприятным мельканием, а можно пользоваться приемом, сходным с технологией rubber-band (резиновая лента). Вы используете ее, когда выделяете несколько объектов на рабочем столе Windows. Прием характеризуется упрощенной перерисовкой контура перемещаемого объекта. При этом объект обыч-
но обесцвечивается. Такую функциональность мы уже ввели в класс CPolygon. Тонким местом в этой технологии является особый режим рисования линий контура. Каждое положение перемещаемой линии рисуется дважды. Первый раз линия рисуется, второй — стирается. Этот эффект достигается благодаря предварительной настройке контекста устройства, которую производит функция SetROP2. Если вызвать ее с параметром R2_xoRPEN, то рисование будет происходить по законам логической операции XOR (исключающее ИЛИ). В булевой алгебре эта операция имеет еще одно имя — сложение по модулю два. Законы эти просты: 0+0=0; 0+1 = 1; 1+0=1; 1 + 1=0. Ситуацию повторного рисования можно представить так:

  • цвет каждого пиксела (каждой точки растра) при рисовании определяется путем суммирования цвета фона и цвета пера по законам операции XOR;
  • если перо красное (8 младших бит цвета установлены в 1), а фон белый (то есть присутствуют все 3 компонента цвета — 3 байта установлены в 1), то результатом операции XOR будет цвет Cyan, так как красный компонент исчезнет (1+1=0). Оставшиеся же компоненты, зеленый и синий, дают цвет Cyan;
  • если еще раз пройтись красной линией по тому же месту (по линии цвета Cyan), то при сложении цветов единицы попадут на нули и цвет будет белый (все 3 байта станут равны 1).

Итак, повторный проход стирает линию. В качестве упражнения повторите выкладки при условии, что перо белое (затем — черное). Такие упражнения шлифуют самое главное качество программиста — упорство. При черном пере вы должны получить что-то не то. Тем не менее мы берем черное перо, но при этом задаем стиль PS_DOT, что в принципе равносильно черно-белому перу. Белые участки работают как описано, а черные своей инертностью помогают создать довольно интересный эффект переливания пунктира или эффект натягивания и сжимания резинки. Есть еще одно значение (К2_ыот) параметра функции SetROP2, которое работает успешно, но не без эффекта резинки.
Примечание
Я думаю, что цифра 2 в имени функции означает намек на фонетическую близость английских слов «two» и «to». Если предположение верно, то имя функции SetROP2 можно прочесть как «Set Raster Operation To», что имеет смысл установки режима растровой операции в положение (значение), заданное параметром функции. Обязательно просмотрите справку по этой функции (методу класса CDC), для того чтобы узнать ваши возможности при выборе конкретного режима рисования.
Режим перетаскивания вершин полигона готов к использованию в момент вхождения указателя мыши в область чувствительности вершины (за этим следит флаг m_bReady). Кроме данного режима мы реализуем еще один режим — режим создания нового полигона (флаг m_bNewPoints), который вступает в действие при выборе команды меню Edit > New Poly. При анализе кода обратите внимание на то, что мы получаем от системы координаты точек в аппаратной системе, а запоминать в контейнере точек должны мировые (World) координаты. Преобразование координат осуществляется в два этапа:

  • сначала из Device-пространства в пространство Page (функция DPtoLP — Device Point to Logical Point);
  • затем из Page-пространства в пространство World (наша функция MapToWorldPt).

Теперь вы, вероятно, подготовлены к восприятию того, что происходит в следующих трех методах класса CDrawView. Первые два вы должны создать как реакции на сообщения WM_LBUTTONDOWN и WM_MOUSEMOVE, а последний (member function) — просто поместить в файл реализации класса, так как его прототип уже существует:
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
//====== В режиме создания нового полигона
if (m_bNewPoints)
{
CTreeDoc *pDoc = GetDocument();
//====== Ссылка на массив точек текущего полигона
VECPTSS pts = pDoc->m_Poly.m_Points;
//=== Получаем адрес текущего контекста устройства
CDC *pDC = GetDC() ;
//====== Настраиваем его с учетом размеров окна
SetDC(pDC) ;
//=== Преобразуем аппаратные координаты в логические
pDC->DPtoLP(ipoint);
//=== Преобразуем Page-координаты в World-координаты
CDPoint pt = pDoc->MapToWorldPt(point);
//====== Запоминаем в контейнере
pts.push_back (pt);
}
//====== В режиме готовности к захвату
else if (m_bReady)
{
ra_bLock = true; // Запоминаем состояние захвата
m_bReady = false; // Снимаем флаг готовности
}
//====== В режиме повторного нажатия
else if (mJbLock)
m_bLock = false; // Снимаем флаг захвата
else
//В случае бездумного нажатия
return; // уходим
Invalidated; // Просим перерисовать
}
void CDrawView::OnMouseMove(UINT nFlags, CPoint point)
{
//=== В режиме создания нового полигона не участвуем
if (m_bNewPoints) return;
//====== Получаем и настраиваем контекст
CDC *pDC = GetDCO ;
SetDC(pDC);
//=== Преобразуем аппаратные координаты в логические
pDC->DPtoLP(Spoint);
//=== Преобразуем Page-координаты в World-координаты
CTreeDoc *pDoc = GetDocument();
CDPoint pt = pDoc->MapToWorldPt(point);
//====== Если был захват, то перерисовываем
//====== контуры двух соседних с узлом линий
if (m_bLock)
{
// Курсор должен показывать операцию перемещения
SetCursor(m_hGrab);
//====== Установка режима
pDC->SetROP2(R2_XORPEN);
//====== Двойное рисование
//====== Сначала стираем старые линии
RedrawLines(pDC, pDoc->MapToLogPt (pDoc->
m_Poly.m_Points[ra_CurID]));
//====== Затем рисуем новые
RedrawLines(pDC, point);
//====== Запоминаем новое положение вершины
pDoc->m_Poly.m_Points[m_CurID] = pt;
}
//====== Обычный режим поиска близости к вершине
else
{
m_CurID = pDoc->FindPoint(pt);
// Если близко, то m_CurID получит индекс вершины
// Если далеко, то индекс будет равен -1
m_bReady = m_CurID >= 0;
//=== Если близко, то меняем курсор
if (m_bReady)
SetCursor(m_hGrab);
}
}
//====== Перерисовка двух линий, соединяющих
//====== перемещаемую вершину с двумя соседними
void CDrawView::RedrawLines (CDC *pDC, CPointS point)
{
CTreeDoc *pDoc = GetDocument();
//====== Ссылка на массив точек текущего полигона
VECPTS& pts = pDoc->m_Poly.m_Points;
UINT size = pts.sizeO;
//====== Если полигон вырожден, уходим
if (size < 2) return;
//====== Индексы соседних вершин
int il = m_CurID == 0 ? size - 1 : m_CurID - 1;
int 12 = m_CurID == size - 1 ? 0 : m_CurID + 1;
// ====== Берем перо и рисуем две линии
pDC->SelectObject(Sm_penLine);
pDC->MoveTo(pDoc->MapToLogPt(pts[11] ) ) ;
pDC->LineTo(point);
pDC->LineTo(pDoc->MapToLogPt(pts[12]));
}
Определение индекса вершины, к которой достаточно близко подобрался указатель мыши, производится в методе FindPoint класса документа. В случае если степень близости недостаточна, функция возвращает значение -1. Вставьте этот метод в файл реализации класса (TreeDoc.cpp):
int CTreeDoc::FindPoint(CDPointS pt)
{
//====== Пессимистический прогноз
int id = -1;
//====== Поиск среди точек дежуоного полигона
for (UINT 1=0; i<m_Poly.m_Points.size(); i++)
{
//=== Степень близости в World-пространстве.
//=== Здесь мы используем операцию взятия нормы
//=== вектора, которую определили в классе CDPoint
if ( !(m_Poly.m_Points[i) - pt) <= 5e-2)
(
id = i;
break; // Нашли
}
}
//====== Возвращаем результат
return id;
}
В этот момент вы можете запустить приложение, выбрать шаблон Draw и проверить возможности визуального редактирования, перетаскивая вершины звезды в пределах клиентской области окна документа.
Включение или выключение второго режима редактирования, служащего для создания нового полигона и ввода координат вершин с помощью мыши, потребует меньше усилий, так как логика самого режима уже реализована в обработчике нажатия левой кнопки мыши. Для включения или выключения (toggle) второго режима используется одна и та же команда. Создайте обработчик команды Edit > New Poly. Для этого:

  1. Поставьте фокус на элемент CDrawView в представлении классов (Class View) и перейдите в окно Properties.
  2. Нажав кнопку Events, выберите идентификатор ID_EDIT_NEWPOLY, раскройте маркер (+) и выберите COMMAND (первую из двух выпавших строк).
  3. Создайте обработчик, выбрав <Add> в выпадающем списке справа от COMMAND.

В теле обработчика следует установить флаги состояния, уничтожить все вершины дежурного полигона и перерисовать представление:
void CDrawView::OnEditNewpoly(void)
{
//====== Включаем/Выключаем режим ввода вершин
m_bNewPoints = !m_bNewPoints;
//=== Снимаем флаги редактирования перетаскиванием
m_bReady = false;
m_bLock = false;
//====== Если режим включен, то уничтожаем вершины
if (m_bNewPoints)
{
GetDocument()->m_Poly.m_Points.clear() ;
Invalidate();
}
}
Запустите приложение, выберите шаблон Draw и дайте команду Edit > New Poly. Щелкайте левой кнопкой мыши разные места клиентской области окна и наблюдайте за трансформациями полигона m_Poly при добавлении в контейнер его точек новых значений. Мысленно проследите за преобразованиями координат, которые происходят в эти моменты. Вы помните, что мышь дает аппаратные координаты, а в контейнер попадают World-координаты вершин полигона?
  
Отслеживание состояния команд
Текущее состояние команды меню или кнопки на панели инструментов легко определяется по их облику: недоступная команда или кнопка имеет блеклый (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 в ту же папку, которая выбрана в левом окне.
  
Тестирование
Приложения, даже если они на первый взгляд функционируют корректно, надо тщательно тестировать, с тем чтобы проверить максимальное число состояний и ситуаций, в которых оно может оказаться. Так, тестируя настоящее приложение, я обнаружил два дефекта (не удивлюсь, если вы найдете еще больше). Первый состоит в том, что, если до выполнения команды Refresh изменить вручную позицию полос прокрутки, после выполнения команды происходит рассинхронизация полос. Лекарство оказалось простым — вставить в нужное место строку с вызовом:
ScrollToPosition(CPoint(0,0));
Эта функция является методом класса CScrollview, она устанавливает обе полосы прокрутки в исходные состояния. Определение места вставки мы оставляем читателю.
Кроме того, логика приложения нарушается, если окна мини-чертежей (cwndGeom) видны не полностью, а только частично. Курсор в этом случае не изменяет свою форму, несмотря на то что он выходит за границы окна CRightView, то есть за границы клиентской области окна CTreeFrame. Этот эффект наблюдается только при выходе в сторону гипотетического продолжения окна cwndGeom. Объяснение в том, что мы захватили мышиные сообщения (SetCapture) и направляем их в частично скрытое окно типа CWndGeom, которое не отпускает мышь, так как справедливо считает, что курсор находится над его прямоугольником. Окно не знает, что та его часть, которая находится под курсором, в данный момент скрыта окном-рамкой или полосами прокрутки. Вы помните, что полосы прокрутки являются частью клиентской области окна? Если диагноз поставлен точно, то и лечение будет эффективным. Ниже приведена новая версия обработки сообщения
WM_MOUSEMOVE В Классе CWndGeom:
void CWndGeom::OnMouseMove(UINT nFlags, CPoint point)
{
//====== Два прямоугольника (CWndGeom и CRightView)
CRect rChild, rParent;
//=== Определяем экранные координаты (не клиентские!)
GetWindowRect(rChild) ;
m_pView->GetWindowRect (rParent) ;
//=== Если есть полосы прокрутки, то уменьшаем
//=== прямоугольник окна на толщину полос
if (m_pView->m_szScroll.cx - m_j>View->m_szView.cx > 0)
rParent . right -= SM_CXHSCROLL;
if (m_pView->m_szScroll.cy - m_pView->m_szView.cy > 0)
rParent. bottom -= SM_CYVSCROLL ;
//=== Ищем пересечение прямоугольников, обрезая rChild
rChild.IntersectRect (rChild, rParent);
//=== Приводим к экранным координаты указателя мыши
ClientToScreen (Spoint) ;
//=== Если мышь попала в усеченный прямоугольник,
if ( rChild. PtlnRect (point))
{
//=== то демонстрируем активное состояние,
// изображая рамку внутри прямоугольника CWndGeom
if (GetCaptureO != this)
{
SetCapture() ;
//=== Координаты относительные (клиентские)
CRect r (mJRect) ;
r.DeflateRect (4, 4);
CClientDC do (this) ;
//====== Обрамляем выбранный рисунок
dc.FrameRect (Sr, SCBrush (RGB (192, 192, 255) ) ) ;
}
else
{
//=== Это происходит один раз при выходе из окна
ReleaseCapture () ;
Invalidate () ;
}
}
Здесь я решил применить другой способ обрамления — с помощью функции FrameRect. Она хороша тем, что не закрашивает внутренность прямоугольника, но обладает тем недостатком, что рамка не может иметь толщину более одной логической единицы. Приведем еще один вариант обрамления, использующий толстое перо и прозрачную кисть. Здесь приведен только тот фрагмент, в котором произошли изменения:
if (rChild. PtlnRect (point) )
{
if (GetCaptureO != this)
{
SetCapture () ;
CPen pen (PS_SOLID, 4, RGB (192, 192, 255) );
CClientDC dc(this) ;
dc. SelectObject (&pen) ;
CRect r (m_Rect) ;
//====== Уменьшаем прямоугольник
r .DeflateRect (4,4) ;
//=== Выбираем прозрачную кисть для того, чтобы
//=== не закрасить его содержимое
dc. SelectObject (GetStockObject (NULL_BRUSH) ) ;
dc. Rectangle (r) ;
}
}
Вид приложения в момент, когда курсор мыши расположен над окном, отображающим данные неактивного документа. Рамка и курсор обозначают состояние готовности к произведению выбора.

 

Немодальный диалог


В предыдущем разделе мы научились редактировать данные документа, воздействуя мышью непосредственно на их представление, то есть облик документа, на экране монитора. Это довольно грубый, но быстрый и эффективный способ, позволяющий получить заготовку некоторой геометрии конструкции, которую впоследствии можно довести до желаемого состояния с помощью таблиц (элементов управления типа grid) или обычных окон редактирования. В практике проектирования геометрии устройств или описания геометрии расчетной области часто используют некоторые стандартные заготовки, которые служат отправной точкой для дальнейшей детализации и усложнения геометрии. Такие заготовки целесообразно выбирать с помощью окон диалога, работающих в немодальном режиме и зачастую называемых Toolbox-window. В них пользователь может выбрать одну из стандартных заготовок геометрии устройства или изменить атрибуты текущей. Создайте с помощью редактора диалогов Studio.Net форму диалога. Типы элементов управления, размещенных в окне диалога, и их идентификаторы сведены в табл. 5.1.
Таблица. 5.1 Идентификаторы элементов управления

Для трех кнопок (TRI, PENT и STAR) установите стиль Owner draw, так как это будут не стандартные кнопки, а кнопки с изображениями, управляемые классом CBitmapButton. Для ползунков установите следующие стили: Orientation: Horizontal, TickMarks: True, AutoTicks: True, Point: Top/Left.
Для управления диалогом необходимо создать новый класс. Для этого можно воспользоваться контекстным меню, вызванным над формой диалога.

  1. Выберите в контекстном меню команду Add Class.
  2. В левом окне диалога Add Class раскройте дерево Visual C++, сделайте выбор MFC * MFC Class и нажмите кнопку Open.
  3. В окне мастера MFC Class Wizard задайте имя класса CPolyDlg, в качестве базового класса выберите CDialog. При этом станет доступным поле Dialog ID.
  4. В это поле введите или выберите из выпадающего списка идентификатор шаблона диалога IDD_POLYCOLOR и нажмите кнопку Finish.

Просмотрите объявление класса CPolyDlg, которое должно появиться в новом окне PolyDlg.h. Как видите, мастер сделал заготовку функции DoDataExchange для обмена данными с элементами управления на форме диалога. Самих функций обмена типа DDX_ еще нет, но мы их создадим немного позже.
Нестандартные элементы управления
Рассмотрим, как создаются элементы управления, имеющие индивидуальный нестандартный облик. Сравнительно новым подходом в технологии создания таких элементов является обработка подходящего сообщения не в классе родительского окна, а в классе, связанном с элементом управления диалога. Такая возможность появилась в MFC начиная с версии 4.0, и она носит название Message Reflection. Элементы управления Windows посылают уведомляющие сообщения своим родительским (parent) окнам. Например, многие элементы, в том числе и Edit controls, посылают сообщение WM_CTLCOLOR, позволяющее родительскому окну выбрать кисть для закраски фона элемента. В версиях MFC (до 4.0), если какой-либо элемент должен выглядеть не так, как все, то эту его особенность обеспечивал класс родительского окна, обычно диалог. Теперь к старому механизму обработки уведомляющих сообщений от дочерних (child) элементов добавился новый, который позволяет произвести обработку уведомляющего сообщения в классе самого элемента. Уведомляющее сообщение как бы отражается (reflected) назад в класс дочернего окна элемента управления. Мы собираемся использовать нестандартные окна редактирования (Red, Green, Blue и Color), с тем чтобы они следили за изменением цвета, отражая текущий выбор как в числовом виде, так и в виде изменяющегося цвета фона своих окон. Эту задачу можно выполнить, создав класс (назовем его cclrEdit), производный от CEdit, и введя в него обработку отражаемого сообщения =WM CTLCOLOR.
Примечание
Обратите внимание на символ = перед идентификатором сообщения Windows. Он необходим, чтобы различить два сообщения с одним именем. Наличие символа = означает принадлежность сообщения к группе отражаемых (reflected) сообщений.
Применяя уже известный вам подход, создайте класс cclrEdit с базовым классом CEdit. В процессе определения атрибутов нового класса укажите существующие файлы (PolyDlg.h и PolyDlg.cpp) в качестве места для размещения кодов нового класса. Если возникнут окна диалогов с просьбой подтвердить необходимость погружения кодов в уже существующие файлы, то ответьте утвердительно. Введите изменения в файл PolyDlg.h, так чтобы он приобрел следующий вид:
#pragma once
//===== Класс нестандартного окна редактирования
class CClrEdit : public CEdit
{
DECLARE_DYNAMIC (CClrEdit)
public:
CClrEdit () ;
virtual -CClrEdit () ;
void ChangeColor (COLORREF clr) ; // Изменяем цвета
protected:
DECLARE_MESSAGE_MAP ()
private :
COLORREF ra_clrText; // Цвет текста
COLORREF ra_clrBk; // Цвет фона
CBrush m_brBk; // Кисть для закраски фона
};
//====== Класс для управления немодальным диалогом
class CPolyDlg : public CDialog
{
friend class CClrEdit;
DECLARE_DYNAMIC (CPolyDlg)
public : enum ( IDD = IDD_POLYCOLOR } ;
//====== Удобный для нас конструктор
CPolyDlg (CTreeDoc* p) ;
virtual -CPolyDlg ( ) ;
//====== Отслеживание цвета
void UpdateColor () ;
protected: virtual void DoDataExchange (CDataExchange* pDX) ;
DECLARE_MESSAGE_MAP ( ) private :
CTreeDoc* m_pDoc; // Обратный указатель
CBitmapButton m_cTri; // Кнопки с изображениями
CBitmapButton m_cPent;
CBitmapButton m_cStar;
bool ra_bScroll; // Флаг использования ползунка };
};
Мы изменили конструктор класса CPolyDlg так, чтобы он имел один параметр — адрес документа, который мы используем в качестве обратного указателя. Это поможет нам управлять приложением, оставаясь в рамках методов диалогового класса. Теперь воспользуемся услугами Studio.Net для создания функции-обработчика сообщения =WM_CTLCOLOR в классе нестандартного окна редактирования.

  1. Поставьте фокус на элемент CClrEdit дерева классов в окне Class View, перейдите в окно Properties и нажмите кнопку Messages.
  2. Нажмите кнопку Categorized и, нажав на маркер (-) Common, закройте список обычных сообщений.
  3. В оставшейся части списка Reflected найдите сообщение =WM_CTLCOLOR и создайте функцию для его обработки, выбрав <Add> в ячейке справа.

Найдите заготовку тела функции ctlColor в файле PolyDlg.cpp и вставьте в нее следующие коды:
HBRUSH CClrEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{
pDC->SetTextColor (m_clrText); // Цвет текста
pDC->SetBkColor (m_clrBk); // Цвет подложки текста
return m_brBk; // Возвращаем кисть
}
Создайте тело вспомогательной функции ChangeColor, которую мы будем вызывать в те моменты существования диалога, когда пользователь изменяет значения элементов управления цветом:
void CClrEdit::ChangeColor(COLORREF clr)
{
//====== Цвет текста - инвертирований цвет фона
m_clrText = ~clr & Oxffffff;
m_clrBk = clr;
//====== Создаем кисть цвета фона
m_brBk.DeleteObject();
m_brBk.CreateSolidBrush (clr);
Invalidate ();
}
Главным управляемым параметром является кисть (m_brBk), которую в ответ на отраженное сообщение =WM_CTLCOLOR надо возвратить каркасу приложения. Попутно мы изменяем цвет текста (setTextColor) и его подложки (setBkColor). Чтобы понять, что такое подложка текста, при отладке временно закомментируйте строку
pDC->SetBkColor (m_clrBk);
При изменении (инвертировании) цвета текста мы вынуждены обнулять четвертый байт переменной m_clrText. В более старых версиях Windows это действие было лишним. Теперь четвертый байт используется для задания степени прозрачности при воспризведении растровых изображений. Если он не равен нулю, то инвертирование цвета не проходит. Первые три байта, как вы помните, задают три компонента (red, green, blue).
Изменение цвета пользователем с помощью элементов управления будет мгновенно отслеживаться в четырех полях диалога (три компонента цвета и суммарный цвет в окне Color). Так как мы хотим отследить изменение цвета и в окне представления, управляемого классом CDrawView, то мы добываем адрес родительского oкna.(GetParent) и вызываем вспомогательную функцию UpdateDrawView.
  
Создание и связывание переменных с полями диалога
Для обмена данными с окнами редактирования следует в классе диалога CPolyDlg создать переменные. Это удобно делать с помощью мастера Studio.Net Add Member Variable.

  1. В окне редактора откройте форму диалога IDD_POLYCOLOR, поставьте фокус в поле IDC_RED и, вызвав контекстное меню, дайте команду Variable.
  2. В окне мастера включите флажок Control Variable, переключатель Control-Value поставьте в положение Value, а в других окнах задайте следующие опции:
    • Control ID: IDC_PEN.
    • Access: public.
    • Variable Type: UINT.
    • Variable Name: m_nPen.
  3. Нажмите кнопку Finish.

В результате работы мастера в классе CPolyDlg должна появиться новая переменная m_nPen типа UINT, которая будет хранить толщину пера текущего полигона и обмениваться числовым значением с полем IDC_PEN диалога. Обмен происходит при вызове функции:
DDX_Text(pDX, IDC_PEN, m_nPen);
который происходит в теле функции DoDataExchange. Указанная строка программы была автоматически вставлена мастером Member Variable wizard.
Примечание
Здесь я вынужден просить прощения у читателя за неполную информацию, так как моя бета-версия Studio.Net не позволяет автоматизировать процесс создания переменных и связывания их с элементами управления диалога в той степени, в которой это должно быть. Я уверен, что в той версии, с которой будете иметь дело вы, этот процесс будет более эффективным. А сейчас вынужден закрывать и вновь открывать мастер для каждой новой переменной. Мне ничего не остается, кроме как посоветовать повторить вышеописанный процесс для создания еще трех переменных (m_nRed, m_nGreen, m_nBlue) того же типа UINT, но связанных с другими окнами.
Для синхронизации положений ползунков со значениями в окнах редактирования необходимо создать еще четыре переменные (m_cColor, m_cRed, m_cGreen и m_cBiue), связанные с теми же окнами IDC_COLOR, IDC_RED, IDC_GREEN и IDC_BLUE. На сей раз переключатель Control-Value должен быть установлен в положение Control, а в поле Variable Type должен быть указан или выбран наш новый класс CClrEdit. Используя ту же технику, создайте три переменные (m_rSlider, m_gSlider и m_bSlider) типа Control и свяжите их с тремя ползунками. При этом в поле Variable Type: должен быть выбран класс CSliderCtrl.
Ограничения на числовые значения, вводимые пользователем в окна редактирования, реализуются с помощью функций динамической проверки данных. Это функции типа DDV_ (Dynamic Data Validation), которые, так же как и функции DDX_, создаются с помощью мастера Member Variable Wizard. Однако эту часть работы в бета-версии Studio.Net приходится делать вручную. Вам придется самостоятельно найти справку по использованию мастера для автоматизации создания функций проверки данных. Тема в указателе справочной системы обозначена как Dialog Data Exchange and Validation. Важной особенностью использования функций типа DDV_ является то, что для каждого элемента управления вызовы DDV_ -функций должны непосредственно следовать за вызовами оох_-функций. Нам надо задать ограничения на значения цвета (0-255) и значение толщины пера (0-100). В конечном счете функция DoDataExchange должна приобрести вид:
void CPolyDlg::DoDataExchange(CDataExchange* pDX)
{
//====== Связывание Control-переменных с ползунками
DDX_Control(pDX, IDC_BSLIDER, m_bSlider);
DDX_Control(pDX, IDCJ3SLIDER, m_gSlider);
DDX_Control(pDX, IDC_RSLIDER, m_rSlider);
//==== Связывание Control-переменных с нестандартными
//==== окнами редактирования
DDX_Control(pDX, IDC_COLOR, m_cColor) ;
DDX_Control(pDX, IDC_BLUE, m_cBlue);
DDX_Control(pDX, IDC_GREEN, m_cGreen);
DDX_Control (pDX, IDC_RED, m_cRed) ;
//==== Связывание Value-переменных с нестандартными
//==== окнами редактирования и проверка данных
DDX_Text(pDX, IDC_BLUE, m_nBlue);
DDV_MinMaxUInt(pDX, m_nBlue, 0, 255);
DDX_Text (pDX, IDC_GREEN, m_nGreen);
DDV_MinMaxUInt(pDX, m_nGreen, 0, 255);
DDX_Text(pDX, IDC_RED, m_nRed) ;
DDV_MinMaxUInt(pDX, m_nRed, 0, 255);
DDX_Text(pDX, IDC_PEN, m_nPen);
DDV_MinMaxUInt(pDX, m_nPen, 1, 100);
//==== Вызов родительской версии функции обмена CDialog::DoDataExchange(pDX);
}
  
Обработка сообщений от элементов управления
В окно диалога мы ввели четыре кнопки, при нажатии которых в класс диалогового окна посылается уведомляющее сообщение BN_CLICKED. При изменении данных в окнах редактирования посылаются другие сообщения EN_CHANGE. При воздействии на ползунки также посылаются уведомляющие сообщения, которые мы рассматривали в предыдущей главе. Однако, как было отмечено, ползунки посылают и обычные сообщения (WM_HSCROLL или WM_VSCROLL). Если в окне диалога имеется более одного ползунка, то сообщения от них удобно обработать в одной функции, которая вызывается в ответ на сообщение о прокрутке. Введите в класс CPolyDlg реакцию на WM_HSCROLL, так как наши ползунки ориентированы горизонтально:
void CPolyDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
//====== Неинтересное для нас сообщение
if (nSBCode==SB_ENDSCROLL)

return;
//====== Устанавливаем флаг сообщений от ползунков
m_bScroll = true;
//====== Узнаем идентификатор активного ползунка
switch(GetFocus()->GetDlgCtrlID())
{
case IDC_RSLIDER:
//====== Считываем текущую позицию движка
m_nRed = m_rSlider.GetPos();
//====== Синхронизируем поле, редактирования
SetDlgltemlnt(IDC_RED, m_nRed);
break;
case IDC_GSLIDER:
m_nGreen = m_gSlider.GetPos();
SetDlgltemlnt(IDC_GREEN, m_nGreen);
break; case IDC_BSLIDER:
m_nBlue = m_bSlider.GetPos() ;
SetDlgltemlnt(IDC_BLUE, m_nBlue);
break;
}
//====== Снимаем флаг сообщений от ползунков
m_bScroll = false;
}
Сообщения от всех ползунков обрабатываются в одной функции. Идентификатор активного ползунка определяется путем последовательного вызова двух функций GetFocus и GetDlgctrliD, являющихся методами класса cwnd.
Флаг сообщений от ползунков (m_bScroll) понадобился нам для того, чтобы при синхронизации элементов управления не происходили повторные вызовы функций-обработчиков. Дело в том, что при изменении позиции ползунка мы должны привести в соответствие окно редактирования, а при ручном изменении числа в окне редактирования мы должны синхронизировать позицию ползунка. Но сообщение EN_CHANGE посылается как при ручном изменении, так и при программном изменении с помощью функции SetDlgltemlnt. Проследим цепь таких событий: пользователь подвинул движок ползунка, мы вызываем SetDlgltemlnt, она провоцирует посылку сообщения EN_CHANGE, а обработчик этого сообщения корректирует положение ползунка, которое и без того верно.
Введите в класс диалога реакции на уведомления EN_CHANGE от четырех элементов IDC_PEN, IDC_RED, IDC_GREEN И IDC_BLUE. Вы помните, что это надо делать с помощью кнопки Events в окне Properties. Вставьте коды в остовы функций обработки, как показано ниже:
void CPolyDlg::OnChangePen(void)
{
BOOL bSuccess; //====== Попытка преобразовать в число
UINT nSize = GetDlgltemlnt(IDC_PEN, SbSuccess, FALSE);
if (bSuccess && nSize < 101)
{
m_nPen = nSize;
m_pDoc->m_Poly-m_nPenWidth = m_nPen;
m_pDoc->UpdateDrawView();
}
}
Отметьте, что здесь мы намеренно не пользуемся функцией UpdateData, которая провоцирует обмен данными сразу со всеми полями окна диалога, так как хотим показать более экономный способ выборочного (целевого) обмена с помощью функции GetDlgltemlnt. Правда, при таком подходе не работают функции проверки данных типа DDV_ и приходится производить проверку самостоятельно:
void CPolyDlg::OnChangeRed(void) {
//====== Если сообщение спровоцировано ползунком,
//====== то обходим коды его синхронизации
if (!m_bScroll)
{
m_nRed = GetDlgltemlnt(IDC_RED, 0, FALSE);
m_rSlider.SetPos(m_nRed);
//====== Изменяем цвет фона окна редактирования
m_cRed.ChangeColor(RGB(m_nRed, 0, 0));
//====== Корректируем интегральный цвет
UpdateColor();
}
void CPolyDlg::OnChangeGreen(void)
{
if (!m_bScroll)
{
m_nGreen = GetDlgltemlnt(IDC_GREEN, 0, FALSE), m gSlider.SetPos(m_nGreen);
m_cGreen.ChangeColor(RGB(0, m_nGreen, 0)); UpdateColor ();
}
void CPolyDlg::OnChangeBlue(void)
{
if (!m_bScroll)
{
m_nBlue = GetDlglteralnt(IDC_BLUE, 0, FALSE);
m_bSlider.SetPos(m_nBlue);
}
m_cBlue.ChangeColor(RGB(0, 0, m_nBlue));
UpdateColor ();
}
Введите тело вспомогательной функции, которая вычисляет интегральный цвет и вносит изменения, перекрашивая окно диалога IDC_COLOR, и с помощью документа текущий полигон в окне CDrawView:
void CPolyDlg::UpdateColor()
{
COLORREF clr = RGB (m_riRed,m_nGreen,m_nBlue) ;
m_cColor.ChangeColor(clr) ;
m_pDoc->m_Poly.m_BrushColor = clr;
m_pDoc->UpdateDrawView();
}
С помощью Studio.Net введите в класс диалога реакции на уведомляющие сообщения (BN_CLICKED) о нажатии кнопок выбора стандартных геометрий для полигонов (IDCJTRI, IDC_PENT и IDC_STAR). В них мы с помощью техники обратного указателя вновь обращаемся к документу и используем его данные и методы для замены координат точек текущего полигона:
void CPolyDlg::OnClickedTri(void)
{
m_pDoc->m_Poly.MakeTria() ;
m_pDoc->UpdateDrawView() ;
}
void CPolyDlg::OnClickedPent(void)
{
m_pDoc->m_Poly.MakePent() ;
m_pDoc->UpdateDrawView() ;
}
void CPolyDlg::OnClickedStar(void)
{
m_pDoc->m_Poly.MakeStar() ;
m_pDoc->UpdateDrawView();
}
Измените тело конструктора диалогового класса, с тем чтобы при открытии диалога он смог запомнить обратный указатель (адрес документа) и все его элементы были правильно инициализированы:
CPolyDlg::CPolyDlg(CTreeDoc* p)
: CDialog (CPolyDlg::IDD, 0)
{
m_pDoc = p;
m_nPen = p->m_Poly.m_nPenWidth;
//====== Расщепляем цвет фона текущего полигона
COLORREF brush = p->m_Poly.m_BrushColor;
m_nRed = GetRValue(brush); // на три компонента
m_nGreen = GetGValue(brush);
m_nBlue = GetBValue(brush) ;
m_bScroll = false; // Ползунки в покое
  
Нестандартные кнопки
Кнопкам управления, которые обычно размещаются в окне диалога, тоже можно придать нестандартный облик, пометив их bitmap-изображениями вместо традиционного текста. Для этой цели в библиотеке MFC имеется специальный класс CBitmapButton, объекту которого можно приписать до четырех изображений, соответствующих различным состояниям кнопки. Кнопка может быть в одном из следующих состояний:

  • нормальное (Up) — кнопка не нажата;
  • выбранное (Down) — кнопка не нажата;
  • в фокусе (Focused) — системный фокус расположен на кнопке;
  • недействующее (Disabled) — кнопка недоступна для пользователя.

Достаточно создать одно изображение кнопки, соответствующее первому состоянию, чтобы она функционировала. Размеры bitmap-изображений могут быть любыми, но важно, чтобы они были одинаковы. Система задает такой размер кнопке, какой имеет ее изображение в нормальном (первом) состоянии. При создании bitmap-ресурсов им следует придать идентификаторы в соответствии со следующими правилами:

  • Кнопке с заголовком, например ОК, имеющей 4 состояния, должны соответствовать 4 изображения с идентификаторами: "OKU", "OKD", "OKF", "OKX". Окончания U, D, F, X кодируют состояния: Up, Down, Focused, Disabled соответственно.
  • Идентификаторы изображений обязательно должны быть "строкового" типа, поэтому при их задании не забывайте вводить двойные кавычки.
  • Чтобы ассоциировать обычную кнопку в ресурсе диалога с этими изображениями, ей следует присвоить заголовок (caption) OK и выбрать стиль Owner draw. Это заставляет Windows посылать сообщения WM_MEASUREITEM и WM_DRAWITEM, которые обрабатывает каркас приложения, управляя обликом кнопок.
  • В классе диалога следует завести объект класса CBitmapButton и при инициализации диалога послать ему сообщение Autoload.

Заметьте, что выбор изображения происходит, опираясь на заголовок кнопки, а не на его идентификатор. Применим эту технологию для трех наших кнопок с заголовками TRI, PENT и STAR и придадим им нестандартный облик. Для этого:

  1. В окне ResourceView вызовите контекстное меню на элементе Тгее.гс и выберите команду Add > Resource.
  2. В появившемся диалоге выберите тип ресурса Bitmap и нажмите кнопку New.
  3. В окне Properties или с помощью мыши задайте желаемый размер будущей кнопки (например, 40 на 25) и создайте изображение треугольника.
  4. Присвойте новому ресурс идентификатор "TRIU".
  5. Создайте копию изображения, временно заменив язык, и присвойте копии идентификатор "TRID", возвратив язык.
  6. Повторите эти шаги для двух других кнопок, используя строковые идентификаторы: "PENTU", "PENTD", "STARU", "STARD".
  7. С помощью Studio.Net введите (переопределите) в классе CPolyDlg виртуальную функцию OnlnitDialog и измените тело этой функции так, чтобы оно было:

BOOL CPolyDlg::OnInitDialog()
{
//====== Загрузка из ресурсов изображений кнопок
m_cTri.AutoLoad (IDCJTRI, this);
m_cPent.Autoload (IDC_PENT, this);
m_cStar.AutoLoad (IDC_STAR, this);
CDialog::OnlnitDialog{);
//====== Установка диапазона ползунков
m_rSlider.SetRange (0, 255);
m_gSlider.SetRange (0, 255);
m_bSlider.SetRange (0, 255);
//====== Установка цены деления ползунков
m_rSlider.SetTicFreq (50);
m_gSlider.SetTicFreq (50);
m_bSlider.SetTicFreq (50);
//=== Вызов обработчиков для начальной
//=== закраски окон и установки ползунков OnChangeRedO ;
OnChangeGreen();
OnChangeBlue ();
return TRUE;
}
В Visual Studio 6 эта функция создавалась как обработчик сообщения WM_INITDIALOG, здесь в Studio.Net 7.0 я не обнаружил сообщения с таким именем в списке сообщений диалогового класса. Однако в списке Overrides присутствует строка с именем OnlnitDialog. В принципе рассматриваемая функция и в Visual Studio 6 имеет прототип virtual BOOL OnlnitDialog, но classWizard 6-й версии причисляет ее к функциям-обработчикам сообщений. Характерным моментом является также то, что прототип функции в Studio.Net изменился и стал BOOL OnlnitDialog (void);. Возвращаясь к диалоговому классу, заметим, что обращение к методам класса CTreeDoc требует включить традиционную строку
#include "TreeDoc.h"
в список директив препроцессора файла PolyDlg.cpp.
  
Немодальный режим работы
Особенность работы с немодальным диалогом заключается в том, что надо затратить дополнительные усилия для корректного завершения его работы. Чтобы закрыть немодальный диалог, документация требует переопределить две виртуальные функции в классе диалога: обработчик закрытия диалога OnCancel и функцию PostNcDestroy. Существуют подробные рекомендации для завершения немодального диалога. Внутри вашей версии виртуальной функции OnCancel следует уничтожить окно диалога (DestroyWindow), а внутри другой виртуальной функции PostNcDestroy рекомендуется уничтожать объект диалогового класса (delete this;). С немодальным диалогом принято работать динамически (on heap frame), а не статически (on stack frame), как с модальным. Когда уничтожается окно Windows, то последним сообщением, которое ему посылается, является WM_NCDESTROY. Обработчик по умолчанию, то есть родительская версия cwnd: :OnNcDestroy, открепляет (detach) описатель окна HWND от объекта класса C++ и вызывает виртуальную функцию PostNcDestroy.
Некоторые классы переопределяют ее для того, чтобы произвести освобождение своих объектов в области heap (динамическая память). При неудаче в процессе создания окна происходит вызов функции cwnd::PostNcDestroy, которая ничего не делает, но дает возможность виртуальным двойникам корректно освободить динамическую память. Мы тоже будем работать с диалогом динамически (on heap frame). В классе документа будет храниться адрес объекта с Pol у Dig, который должен корректно следить за стадиями создания и уничтожения диалога. Я намеренно не переопределял PostNCDestroy, чтобы показать альтернативный способ, допустимый в частных случаях. Так как наш диалог завершается командой Close с идентификатором IDOK, то, введя обработчик этой команды, мы можем с помощью родительской версии уничтожить Windows-окно, а затем освободить память, занимаемую объектом собственного класса:
void CPolyDlg::OnClickedOk(void)
{
//=== Запоминаем факт отсутствия диалога в документе
m_pDoc->m_pPolyDlg = 0;
//====== Родительская версия вызовет DestroyWindow
CDialog::OnOK();
//====== Мы освобождаем память
delete this;
}
Вызов диалога производится в ответ на команду Edit > Poly Color, которая уже присутствует в меню IDR_DrawTYPE. Введите в класс CTreeDoc обработчик этой команды и наполните его кодами, как показано ниже:
void CTreeDoc::OnEditPolycolor(void)
{
//====== Если диалог отсутствует
if (!m_pPolyDlg)
{
//====== Создаем его в две ступени
m_pPolyDlg = new CPolyDlg(this);
m_pPolyDlg->Create(IDD_POLYCOLOR) ;
}
else
//===== Иначе делаем активным его окно
m_pPolyDlg->SetActiveWindow();
}
Здесь использован указатель на объект диалогового класса, который необходимо ввести в число public-данных класса CTreeDoc (CPolyDlg *m_pPolyDlg;) и обнулить в конструкторе документа (m_pPolyDlg = 0;). Сделайте это, а также введите в файл реализации класса CTreeDoc вспомогательную функцию UpdateDrawView:
void CTreeDoc::UpdateDrawView()
{
//====== Добываем адрес нужного представления
CDrawView *pView = dynamic_cast<CDrawView*>
(GetView(SCDrawView::classCDrawView));
//====== и просим его перерисоваться с учетом изменений
if (pView)
pView->Invalidate();
}
Изменения такого рода, как вы уже догадались, влекут за собой достаточно много ошибок на стадии компиляции, если не уделить внимания проблеме видимости классов. Так, надо вставить упреждающее объявление (class CPolyDlg;) в файл с интерфейсом документа и директиву #include "PolyDlg.h" в файл с его реализацией. Кроме того, при работе с диалогом в немодалыюм режиме надо помнить о том, что для его окна свойство Visible должно быть установлено в True. По умолчанию это свойство выключено, так как при запуске диалога в модальном режиме диалог сначала невидим, но .затем функция DoModal вызывает showWindow с параметром SW_SHOW, что активизирует окно, делая его видимым. Мы тоже можем поступить так же, вставив аналогичный вызов после вызова функции Create, но проще сразу установить для диалога (в категории Behavior окна Properties) свойство Visible.
В настоящий момент приложение может быть запущено и при условии отсутствия ошибок протестировано. Команда запуска диалога должна быть доступна, только когда активно окно CDrawFrame, или, точнее, фокус ввода принадлежит представлению, управляемому классом CDrawView. Проверьте все варианты запуска диалога: с помощью команды меню или кнопки на панели инструментов. Проверьте также возможность перевода фокуса в любое из представлений документа при наличии окна диалога.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика