Работа с Visual Studio.Net

         

Реакция окна на уведомляющие сообщения


Наш анализатор кодов ошибок по сути является браузером (инструментом для просмотра) файла WinError.h с особой структурой. Мы хотим дать пользователю возможность выбрать один из двух вариантов просмотра:

  • последовательный, с помощью счетчика Spin Control или ползунка slider Control,
  • поиск по значению, задаваемому в поле элемента IDC_FIND типа Edit Control.
Отметим, что счетчик (spinner) раньше назывался Up-Down control, а ползунок (slider) — Trackbar

Control. Знание этого факта помогает понять, почему сообщения (стили) счетчика содержат аббревиатуру UD, а ползунка — тв. Все три используемых элемента (счетчик, ползунок и поле редактирования IDC_FIND) должны быть синхронизированы так, чтобы изменение позиции счетчика отслеживалось ползунком, и наоборот. Ввод пользователем кола ошибки в поле редактирования IDC_FIND должен вызывать мгновенную реакцию приложения и отражаться в показаниях счётчика и ползунка, но только в случае, если требуемый код найден.

Примечание

Напомним, что элементы управления на форме диалога являются дочерними окнами (child-windows) по отношению к окну диалога. И этот тип отношений parent-child (родство) не является отражением наследования в смысле ООП. Он существовал до того, как была создана MFC. Важно то, что дочерние окна генерируют уведомляющие сообщения об изменениях, происходящих в них а система направляет их в оконную процедуру родительского окна.

Здесь мы должны использовать способность родительских (parent) окон реагировать на уведомляющие сообщения, исходящие от их «детей». Сообщения такого типа можно условно поделить на старые (Windows 3.x) и новые (Win32). Старых много, так как каждый тип элементов имеет несколько уведомляющих сообщений. Посмотрите Help > Index, задав индекс EN_ (Edit Notifications), и вы увидите, что элемент типа окна редактирования уведомляет своего родителя о таких событиях, как: EN_CHANGE (изменился текст), EN_KILLFOCUS (окно теряет фокус ввода), EN_UPDATE (окно готово к перерисовке) и множестве других.

Наряду со старыми существует одно новое универсальное событие WM_NOTIFY. Теперь при создании новых элементов управления не надо плодить сообщения типа WM_*, которых и так очень много. Все могут обойтись одним — WM_NOTIFY. Его универсальность состоит в том, что все новые типы элементов умеют генерировать это одно сообщение. В дополнение они сопровождают его указателем на структуру NMHDR (Notify Message Header), которая способна «привести» за собой различные другие структуры. Весь трюк состоит в том, что, получив это сообщение вместе с указателем NMHDR* pNMHDR, который на самом деле показывает на другую, более сложную структуру, класс родительского окна знает тип элемента и, следовательно, знает, к какому типу надо привести этот указатель. Например, при изменении показаний счетчика система посылает родительскому окну сообщение WM_NOTIFY, в IParam которого помещен указатель типа NMHDR*:

typedef struct tagNMHDR

{

//=== Описатель окна (счетчика), пославшего сообщение

HWND hwndFrom;

//=== Идентификатор окна (счетчика)

UINT idFrora;

//=== Код сообщения

OINT code;

}

NMHDR;

Но на самом деле указатель pNMHDR содержит адрес другой структуры:

typedef struct _NM_UPDOWN

{

//====== Вложенная структура

NMHDR hdr;

//====== Текущая позиция счетчика

int iPos;

//====== Предлагаемое увеличение показаний

int iDelta;

}

NMUPDOWN, FAR *LPNMUPDOWN;

Так как структура hdr типа NMHDR стоит первой в списке полей NMUPDOWN, то все законно — присланный в iParam указатель действительно показывает на NMHDR, но в составе NMUPDOWN. Эту ситуацию легче запомнить, а может быть, и понять, если использовать аналогию. Способ запоминания замысловатых выкладок с помощью глупых аналогий известен давно. Мне приходит в голову такая: звонят в дверь (WM_NOTIFY), вы подходите к ней и видите, что пришел знакомый мальчик (NMHDR) с сообщением, но, открыв дверь, вы обнаруживаете, что за ним стоит широкоплечий мужчина (NMUPDOWN). Теперь пора ввести в класс CLookDlg реакции на уведомляющие сообщения:

  1. Откройте шаблон диалога и установите курсор мыши на счетчике (IDC_SPIN).
  2. В окне Properties нажмите кнопку с подсказкой ControlEvents.
  3. В появившемся списке уведомляющих сообщений, которые генерирует счетчик, выберите UDN_DELTAPOS, а в ячейке справа укажите действие — <Add>.
Перейдите в окно LookDlg.cpp и найдите в карте сообщений новый элемент

ON_NOTIFY(UDN_DELTAPOS, IDC_SPIN, OnDeltaposSpin)

который был вставлен инструментом ClassWizard и который означает, что если окну диалога, управляемому классом CLookDlg, придет сообщение UDN_DELTAPOS (Up-Down Notification) от элемента с идентификатором IDC_SPIN, то управление будет передано функции-обработчику OnDeltaposSpin. Теперь в конце файла найдите эту функцию:

void CLookDlg::OnDeltaposSpin(NMHDR *pNMHDR, LRESOLT *pResult)

{

NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;

// TODO: Add your control notification handler code here

*pResult = 0; }

Вот здесь происходит то, о чем было сказано выше. Указатель PNMHDR приводится к типу указателя на более сложную структуру NM_UPDOWN. Это нужно для того, чтобы достать из нее необходимую информацию. Теперь с помощью указателя pNMUpDown мы можем добыть требуемое приращение показаний счетчика (pNMUpDown->iDelta). Вместо комментария // TODO: вставьте следующий фрагмент кода:

//====== Вычисляем желаемую позицию

int nPos = m_Spin.GetPos() + pNMUpDown->iDelta;

//====== Если она вне допустимых пределов, то уходим

if (nPos < 0 || m_nltems <= nPos) return;

//====== Корректируем позицию ползунка

m_Slider.SetPos(nPos);

//====== Расшифровываем код ошибки

Getlnfo(nPos);

//====== Вызываем обмен данными с элементами окна диалога

UpdateData(FALSE);

Здесь уместно напомнить, что Studio.Net 7.0, как и Visual Studio 6, позволяет форматировать введенный текст так, как это принято в сообществе разработчиков. Выделите весь код функции и дайте команду Edit > Advanced > Format Selection или Alt+F8.

В коде мы используем данные (m_Spin, m_nltems, m_Slider) и метод (Getlnfо), которых еще нет в классе, но вы, наверное, имеете некоторый опыт программирования и знаете, что разработка часто идет в обратном порядке. Введем эти элементы в состав класса позже, а сейчас дадим оценку того, что только что сделали. С помощью ClassWizard мы ввели в класс главного окна обработку уведомляющего сообщения UDN_DELTAPOS, работающего по схеме WM_NOTIFY. Теперь введем обработку сообщения EN_CHANGE, поступающего от окна редактирования IDC_FIND каждый раз, когда в нем происходят изменения. Это сообщение работает по старой схеме и не влечет за собой необходимости преобразовывать указатели на структуры данных.

  1. Вновь откройте шаблон диалога и установите курсор мыши в окно IDC_FIND.
  2. В окне Properties нажмите кнопку с подсказкой ControlEvents.
  3. В появившемся списке уведомляющих сообщений, которые генерирует окно редактирования, выберите сообщение EN_CHANGE и его реализацию <Add>.
Проверьте результаты работы ClassWizard. Они должны быть видны в трех разных местах вашего приложения. В файле LookDlg.h должен появиться прототип функции обработки

void OnChangeFind (void) ;

в файле LookDlg.cpp должен появиться новый элемент карты сообщений

ON_EN_CHANGE(IDC_FIND, OnChangeFind)

и заготовка тела функции обработки, в которую мы должны внести свою функциональность:

void CLookDlg::OnChangeFind(void)

{

// TODO: Если это RICHEDIT control, то он не пошлет

// уведомления пока вы не дадите своей версии функции

// CDialog::OnInitDialog() и не сделаете вызов функции

// CRichEditCtrl().SetEventMask() с флагом ENM_CHANGE,

// включенным с помощью операции побитового ИЛИ.

// TODO: Здесь вставьте код обработки уведомления.

}

В комментариях CLassWizard предупреждает нас о том, что с элементом типа Rich Edit control надо работать по особым правилам. К нам это не относится, поэтому уберите комментарии и вставьте вместо них такой код:

CString s;

//==== Выбираем код ошибки, введенный пользователем

GetDlgltemText(IDC_FIND, s) ;

//==== Преобразуем к типу string, с которым мы работаем

string find = s;

//==== Ищем код в контейнере

m_Vector

for (int n=0;

n < m_nltems is find != m_Vector[n].Code;n++);

if (n < m_nltems) // Если нашли,

{

Getlnfo(n); // то расшифровываем этот код

m_Slider.SetPos(n); // и синхронизируем ползунок

UpdateData(FALSE); // Высвечиваем данные в окнах

}

Переменная s типа CString понадобилась для того, чтобы воспользоваться функцией GetDlgltemText, которая вычитывает содержимое окна редактирования. Приходится делать преобразование к типу string, так как мы хотим работать со стандартными строками (string) библиотеки STL.

Возвращаясь к элементам управления в окне диалога, отметим, что ползунок тоже посылает уведомляющие сообщения по схеме WM_NOTIFY. Их всего три и вы можете их увидеть в окне Properties после нажатия кнопки ControlEvents, если предварительно установите фокус на элементе IDC_SLIDER. Одно из них — NM_RELEASEDCAPTURE — подходит нам, так как посылается в тот момент, когда пользователь отпускает движок после его установки мышью в новое положение. Но мы не будем вводить реакцию на это уведомление, так как есть другое (старое) сообщение Windows — WM_HSCROLL (или WM_VSCROLL при вертикальном расположении ползунка), которое работает более эффективно. Дело в том, что ползунок управляется не только мышью. Если он обладает фокусом, то реагирует на все клавиши перемещения курсора (4 стрелки, Page Up, Page Down, Home, End). Это очень удобно, так как позволяет гибко управлять темпом перемещения по многочисленным кодам ошибок. Введите реакцию оконного класса на сообщение WM_HSCROLL.

  1. Вновь откройте шаблон диалога и установите фокус в его окне. Проследите, чтобы он не был ни в одном из элементов управления.
  2. В окне Properties нажмите кнопку Messages, найдите в списке сообщение WM_ HSCROLL и укажите действие <Add>.
Отыщите изменения в классе CLookDlg. Их должно быть три. Отметим, что когда ClassWizard делает вставки в карту сообщений, то он пользуется своим опознавательным знаком — знаком комментария вида //}} AFX_MSG_MAP. Напомним, что в

Visual Studio 6 эти знаки существовали парами, а вставки между элементами пар отличались цветом. Теперь все упростилось. Введите код в тело функции-обработчика так, чтобы она была:

void CLookDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{

//====== Расшифровываем новый код

Getlnfo(m_Slider.GetPos());

//====== Помещаем данные в поля диалога

UpdateData(FALSE); }

Сообщение WM_HSCROLL посылается в те моменты, когда ползунок изменяет свое положение как с помощью мыши, так и с помощью клавиш. В обработчике мы выявляем новую позицию ползунка, ищем и расшифровываем код, соответствующий этой позиции. Обратите внимание на то, что мы не пытаемся синхронизировать счетчик. Когда приложение будет работать, вы увидите, что последний, тем не менее, отслеживает изменения позиции ползунка. Попробуйте самостоятельно найти объяснение этому факту. Ответ можно найти в MSDN по теме CSpinButtonCtrl, если обратить внимание на то, что счетчик может иметь (Buddy) двойника-приятеля, в качестве которого мы уже выбрали окно редактирования IDC_CURRENT.

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