Урок 2. Сообщения, события, пункты меню Обработка сообщений Создайте новый проект с именем test, как в уроке 0.
Посмотрим внимательно на код. Wizard автоматически создал три класса: CChildView, CMainFrame, CtestApp. На самом деле есть еще четвертый – CAboutDlg – про него будет речь в следующем уроке (класс описывает диалоговое окошко, доступное по Help -> About test…).
CtestApp – класс приложения, CMainFrame и CChildView – классы окон, первый – класс всего окна целиком, второй – класс клиентской области (где проиходит рисование). Оба являются наследниками класса CWnd. Каждый класс имеет механизм для обработки сообщений, поступающих окну, который он представляет. Работать с классами удобнее через вкладку Class View (если ее нет, то ее можно найти по меню View -> Class View)
Обработчик одного из сообщений (WM_PAINT) уже реализован. Посмотрите на код декларации класса (ChildView.h):
class CChildView : public CWnd
{
…
…
…
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
}; В файле описания класса (ChildView.cpp) можно увидеть такие строчки:
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
END_MESSAGE_MAP() Это – макроопределения (директивы #define), которые некоторым образом описывают таблицу (массив, поле класса CChildView) соответствий
<идентификатор сообщения> → <обработчик>. На сам код макроопределений, если интересно, можно посмотреть, нажав правую кнопку мыши (см. выше), и выбрав пункты Go To Definition или Go To Declaration. Вообще это удобный инструмент, можно посмотреть код почти любой функции, если что-то неясно в Help.
Графическое представление этой таблицы находится в окошке Properties для данного класса. Окошко можно вызвать по нажатию правой кнопкой мыши на классе CChildView. Появится окошко Properties, в котором нужно выбрать вкладку Messages
Это и есть таблица обработчиков соощений. При выборе каждого сообщения внизу подсвечивается его описание. Давайте создадим обработчик нажатия на левую кнопку мыши (WM_LBUTTON_DOWN). После выбора пункта Add в классе CChildView добавится метод OnLButtonDown. Также добавится строчка в блоке BEGIN_MESSAGE_MAP(…)…END_MESSAGE_MAP(). Чтобы удалить обработчик сообщения, нужно удалить метод и соответствующую строчку в блоке таблицы.
Методу приходит в параметре объект типа CPoint – точка нажатия, в системе координат клиентского окна. Давайте ее сохраним и в месте нажатия будем рисовать окружность с радиусом 50. Для этого добавим поле в класс CChildView.
Это можно сделать вручную, а можно нажав правой кнопкой мыши в окне Class View и выбрав Add Variable. Переменные внутри классов MFC принято начинать с m_
Будем сохранятьпозицию точки.
Кроме этого, нужно как-то сообщить окну о том, что нужно перерисовать себя. Для этого можно использовать метод Invalidate().
void CChildView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_press_point = point;
Invalidate(); CWnd::OnLButtonDown(nFlags, point);
}
В методе OnPaint добавим код рисования окружности:
void CChildView::OnPaint()
{
CPaintDC dc(this);
CPen bpen, *oldpen;
bpen.CreatePen(PS_DASH, 5, RGB(0, 0, 0));
oldpen = dc.SelectObject(&bpen);
dc.Ellipse(m_press_point.x - 50, m_press_point.y - 50,
m_press_point.x + 50, m_press_point.y + 50);
dc.SelectObject(oldpen);
}
Ура, программа работает.
Теперь сделаем, чтобы при наведении на окружность, она становилась красной. Добавим в класс поле m_mouse_over типа bool. И немного изменим строчку в OnPaint: bpen.CreatePen(PS_DASH, 5, RGB(m_mouse_over ? 255 : 0, 0, 0));.
Добавим обработчик сообщения WM_MOUSE_MOVE:
void CChildView::OnMouseMove(UINT nFlags, CPoint point)
{
m_mouse_over =
(point.x - m_press_point.x) * (point.x - m_press_point.x) +
(point.y - m_press_point.y) * (point.y - m_press_point.y) <= 2500;
Invalidate(); CWnd::OnMouseMove(nFlags, point);
}
Программа работает, но изображение мерцает. Этого можно избежать, если обновлять окно, только если изменилось «состояние».
bool new_state = (point.x - m_press_point.x) * (point.x - m_press_point.x) +
(point.y - m_press_point.y) * (point.y - m_press_point.y) <= 2500; if (m_mouse_move != new_state) {
m_mouse_move = new_state;
Invalidate();
}
События и пункты меню Добавим в программу возможность выбора ширины линии с помощью меню. Описание меню (а также некоторых других элементов UI) находится в файле ресурсов, включенном в проект: (в нашем проекте это test.rc). Найдите его в окошке Solution Explorer и двойным щелчком вы попадете в окошко Resource View. Вы увидите список доступных ресурсов по категориям, у каждого ресурса есть идентификатор.
Меню будет иметь идентификатор IDR_MAINFRAME, он подключается меню в методе InitInstance() класса CtestApp (в этом методе инициализируется программа).
// create and load the frame with its resources
pFrame->LoadFrame(IDR_MAINFRAME,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
NULL);
В принципе, можно создать другое меню, и подключать его, или менять меню по ходу выполнения программы.
В меню уже есть кое-какие пункты, и на них есть даже уже определенная реакция (можете запустить и поэкспериментировать с подпунктами View). У конечных пунктов (которые не имеют сами подпунктов) есть идентификатор (его можно найти во вкладке Properties).
Добавлять пункты можно в места Type Here. Создадим пункт меню верхнего уровня Draw, и у него подпункт Thick Line. Также можно поставить амперсанд внутри названия пункта меню, например, если написать D&raw, то этот пункт меню можно будет вызвать по нажатию клавиш Alt+R.
Между пунктами меню еще можно вставлять разделители (separator). Попробуйте для эксперимента добавить один (для этого нужно воспользоваться правой кнопкой мыши).
Пункту Thick line по умолчанию будет присвоен идентификатор ID_DRAW_THICKLINE. Пункты меню также называются «событиями». Они так называются, в частности потому, что они относятся не только к меню. Но об этом чуть позже.
Создадим обработчик событий. Опять же, выберем класс, который будет обрабатывать сообщение. Пусть это CChildView. В его Properties выберем вкладку Events (значок ). Там в списке команд меню можно найти наш пункт меню и добавить обработчик события (для типа сообщения COMMAND).
Существует альтернативный способ: в редакторе ресурсов нажать правой кнопкой мыши на пункте меню, выбрать Add Event Handler.
У класса CChildView появился метод OnDrawThickLine. Напишем там что-нибудь:
void CChildView::OnDrawThickline()
{
MessageBox("Hello");
}
Еще добавилась строчка в MESSAGE_MAP:
ON_COMMAND(ID_DRAW_THICKLINE, &CChildView::OnDrawThickline)
END_MESSAGE_MAP()
Она ставит в соответствие событию ID_DRAW_THICKLINE соответствующий обработчик. Кстати, как вы думаете, где находится список идентификаторов? Ответ: в файле resource.h содержатся константы, описанные с помощью макроопределений.
// Used by test.rc
//
#define IDD_ABOUTBOX 100
#define IDR_MAINFRAME 128
#define IDR_testTYPE 129
#define ID_DRAW_THICKLINE 32771
Запустим программу, она реагирует на пункт меню. Теперь осуществим задуманное: сделаем возможность управлять шириной лини с помощью этого пункта меню. Добавим переменную m_draw_thickline типа bool. Будем рисовать жирную линию, если она имеет значение true и тонкую, если false. (Ниже – измененный код функции OnPaint()).
bpen.CreatePen(PS_DASH, 5 * (m_draw_thickline ? 2 : 1),
RGB(m_mouse_over ? 255 : 0, 0, 0));
В обработчике события (OnDrawThickLine) будем изменять значение переменной на обратное, и, естественно, перерисовывать.
void CChildView::OnDrawThickline()
{
m_draw_thickline = !m_draw_thickline;
Invalidate();
}
Можно запустить программу, и убедиться, что все работает. Заметим, что наш пункт меню работает как переключатель с двумя состояниями (On/Off). Логично это отразить и в том, как он выглядит. В Windows такое поведение пунта меню можно отразить графически с помощью галочки (см. например пункт меню View с двумя подпунктами Status Bar и Toolbar). Для этого нужно создать обработчик специального сообщения об обновлении UI (то есть графического представления) команды (заданной идентификатором).
В этот обработчик приходит объект типа (CmdUI *), c помощью которого можно управлять видом пункта меню. Можно этот пункт меню пометить галочкой (SetCheck), можно его делать доступным/недоступным (Enable), можно изменить текст (SetText).
void CChildView::OnUpdateDrawThickline(CCmdUI *pCmdUI)
{
pCmdUI->SetCheck(m_draw_thickline);
}
Эта строчка означает, что пункт меню будет помеченным тогда и только тогда, когда рисуется жирная линия. За тем, чтобы эта функция вызывалась в нужное время, следит система.
Пункты меню можно enable/disable-ить и помечать также в редакторе меню во вкладке Properties.
Помимо пунктов меню, с командами можно связать кнопки из Toolbar – панели кнопочек, которую видно сверху окна. Toolbar тоже является
ресурсом и находится в файле ресурсов. Кнопочку можно дорисовать на свободное место. Еще нужно ей назначить идентификатор, либо придумать новый, либо выбрать из списка уже существующий.
Запустите программу – о чудо, появилась работающая кнопка! Более того, она «западает», если на нее нажать. Это волшебство происходит за счет того, что метод OnUpdateDrawThickline вызывается не только при обновлении пунктов меню, но и при обновлении toolbar. То есть объект CCmdUI *pCmdUI, который приходит в этот метод, стоит воспринимать именно как графическое представление команды целиком, неважно какое – пункт меню, кнопка в тулбаре или что-то еще.
|