Начало урока | Вперед
Содержание

День 4

Взаимодействие пользователя с приложением при помощи мыши и клавиатуры

Сегодня вы узнаете:

События мыши
Как мы узнали ранее для большинства элементов управления имеется ограниченное число событий. Когда дело доходит до мыши, вы ограничены главным образом событиями щелчка и двойного щелчка кнопкой мыши. Но простого взгляда на мышь достаточно, чтобы утверждать,что событий связанных с мышью должно быть гораздо больше. А как же правая кнопка мыши? И как узнать нажата ли она? А как обстоят дела с графическими приложениями? Как в графическом приложении отслеживается перетаскивание объекта с помощью мыши?

Если вы выберете в режакторе диалоговое окно приложения и во вкладке Properties щелкнете на кнопке Message, а затем пользуясь полосой прокрутки, просмотрите список имеющихся сообщений, вы обнаружите ряд событий, связанных с мышью.
Эти события так же перечислены в таб.4.1 Пользуясь указанными сообщениями о событиях, можно решать практически любые задачи, возникающие в процессе создания приложения.

Таблица 4.1 Сообщения о событиях мыши.
WM_LBUTTONDOWN Левая кнопка мыши нажата
WM_LBUTTONUP Левая кнопка мыши отпущена
WM_LBUTTONDBLCLK Левая кнопка мыши (двойной щелчок)
WM_RBUTTONDOWN Правая кнопка мыши нажата
WM_RBUTTONUP Правая кнопка мыши отпущена
WM_RBUTTONDBCLK Правая кнопка мыши (двойной щелчок)
WM_MLBUTTONDOWN Средняя кнопка мыши нажата
WM_MLBUTTONUP Средняя кнопка мыши отпущена
WM_MLBUTTONDBCLK Средняя кнопка мыши (двойной щелчок)
WM_XBUTTONDOWN Дополнительная кнопка мыши нажата
WM_XLBUTTONUP Дополнительная кнопка мыши отпущена
WM_XLBUTTONDBCLK Дополнительная кнопка мыши (двойной щелчок)
WM_MOUSEMOVE Перемещение указателя мыши в окне приложения
WM_MOUSEWHEEL Колесо мыши прокручивается

Вверх

Рисование с помощью мыши
  1. Само приложение назовем Mous.
  2. Посредством обычных операций создадим диалоговое окно, уберем из него все элементы
    созданные по умолчанию такие как - кнопки, надпись.
  3. В заголовке окна напишем Mous and Keyboard .

    Убрав все элементы с поверхности окна, вы тем самым всю поверхность окна отведете для рисования. Данный шаг необходим еще для того, чтобы любое событие клавиатуры перехватывалось приложением.

    Если в диалоговом окне приложения имеются какие-либо элементы
    управления, то все события клавиатуры будут адресованы тому элементу
    управления, который имеет текущий фокус ввода, то есть элементу
    управления выделенному каким-либо образом, или в котором находится
    курсор. Чтобы любое событие клавиатуры перехватывалось окном,
    из него необходимо удалить все элементы управления.


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



  4. Выберите диалоговое окно приложения и затем на вкладке Properties щелкните на кнопке Messages. Из списка выберите сообщение WM_MOUSEMOVE и добавьте функцию OnMouseMove() обрабатывающую данное событие выбрав из комбинированного списка.


    АВ:Здесь очень важно то, что надо "выделить" целиком окно, и тогда сообщение WM_MOUSEMOVE будет относиться именно к этому окну. И функция OnMouseMove() попадет именно в класс CMouseDlg этого окна. (Смотри файл MouseDlg.h) А это означает что как только курсор мыши окажется над окном, то моментально Windows вызовет функцию ассоциированную нами с этим событием(эта функция приведена ниже):



  5. Добавьте в эту функцию нижеследующий код :
Листинг 4.1

void CMouseDlg::OnMouseMove(UINT nFlags, CPoint point)
{
//выяснить нажата ли левая кнопка мыши
if((nFlags & MK_LBUTTON) == MK_LBUTTON)
{
//получить контекст устройства
CClientDC dc(this);
//нарисовать пиксель
dc.SetPixel(point.x,point.y,RGB(0,0,0));
}
CDialog::OnMouseMove(nFlags,point);
}



Вы видите, что в функцию передается два аргумента.
В качестве первого аргумента UINT nFlags выступает набор флагов. С его помощью можно определить, удерживалась ли при перемещении указателя кнопка мыши нажатой, и если удерживалась, то какая именно.

С этой целью в первой строке кода используется условный оператор if.

В этом коде собственно интересно условие, позволяющее выяснить нажата левая кнопка мыши или нет.

if((nFlags & MK_LBUTTON) == MK_LBUTTON)

Первая часть данного вычисляемого выражения является фильтром, с помощью которого извлекается флаг, отражающий текущее состояние левой кнопки мыши. (nFlags & MK_LBUTTON)
Во второй части логического выражения извлеченный флаг сравнивается с флагом, соответствующим нажатой левой кнопке мыши. Если они совпадают, значит левая кнопка нажата.


В результате применения оператора & к переменной nFlags и флагу MK_LBUTTON положительное значение будет получено лишь в том случае, если флаг отражающий текущее состояние кнопки мыши, установлен. В противном случае будет получен 0.



Можно было бы написать даже так :
if(nFlags & MK_LBUTTON)

Вторым аргументом данной функции (CPoint point) является положение указателя мыши. Этот аргумент содержит текущие координаты указателя мыши на экране.

Вы можете использовать эти данные, чтобы нарисовать точку в диалоговом окне. Для рисовани точки в окне, вызывается функция SetPixel контекста рисования данного устройства (данного окна).

Но прежде чем рисовать, необходимо получить контекст устройства для данного диалогового окна. С этой целью нужно объявить новый экземпляр класса CClientDC :

CClientDC dc(this);

(АВ:здесь важно то, что контекст устройства вызывается именно для данного диалогового окна! Благодаря аргументу - this. Значит и процесс рисования будет происходить в данном диалоговом окне).

Данный класс инкапсулирует контекст устройства и большинство методов, предназначенных для работы с ним, включая все методы,позволяющие рисовать на экране монитора. Контекст устройства можно представить как холст для рисования, пока нет холста, нельзя ничего нарисовать. Создав однажды контекст устройства, затем можно вызывать его функцию SetPixel().

dc.SetPixel(point.x,point.y,RGB(0,0,0));

Эта функция раскрашивает пиксель, координаты которого заданы первыми двумя аргументами функции, в цвет заданный третьим аргументом.

Вот как эта функция объявлена в классе CDC

COLORREF SetPixel(int x, int y, COLORREF crColor);
COLORREF SetPixel(POINT point, COLORREF crColor);


Скомпилируйте и запустите.Теперь вы можете рисовать.



Еще замечание:

АВ:Здесь очень важно то,что надо "выделить" целиком окно, и тогда сообщение WM_MOUSEMOVE будет относиться именно к этому окну. А это означает что как только курсор мыши окажется над окном, то моментально Windows вызовет функцию ассоциированную нами с этим событием(эта функция приведена ниже):
void CMouseDlg::OnMouseMove(UINT nFlags, CPoint point)
При этом внутри этой функции если нажата левая кнопка, то рисуется линия, а если не нажата то вызывается аналогичная функция родительского окна:
CDialog::OnMouseMove(nFlags,point);
при которой не происходит рисования, а курсор просто движется по окну.
Как только курсор мыши уйдет с этого окна для рисования, то Windows переведет фокус на какой либо элемент управления в наружном окне. То есть при нажатии кнопки мыши функция рисования уже не сможет быть вызвана.



АВ:Поскольку откомпилировав и выполнив это приложение замечаю ,что когда веду мышь по экрану быстро, то точки рисуются далеко друг от друга. Если же веду мышь медленно, то точки рисуются близко друг к другу и почти сливаются в одну линию. Это говорит о том, что Windows вызывает данную функция рисования в окне с некоторой частотой, и частота вызова этой функции не слишком велика. Спрашивается как часто,(сколько раз в секунду) вызывается эта функция? Хотя что собственно даст мне эта информация?

АВ:Еще о функции OnMouseMove. В этой функции первый аргумент позволяет определить нажата ли кнопка, второй аргумент показывает координаты курсора на экране. Если кнопка не нажата, то будет вызвана одноименная функция базового класса, если кнопка нажата, то будет вызвана наша собственная функция. Координаты курсора,содержащиеся во втором аргументе тоже могут быть использованы в нашей функции. (в данном случае координаты курсора использованы).
При выполнении программы происходит следующее: если курсор мыши движется, то через малые промежутся времени программа периодически (можно сказать циклически) вызывает эту функцию, передавая ей при каждом вызове флаг и координаты. И наша функция при каждом вызове рисует точку в этой координате, если левая кнопка нажата. Если левая кнопка не была нажата, то рисуется стандартный курсор мыши в данной координате экрана.



O цвете рисования (пропущен)


Вверх
Библиотека MFC. Контекст устройства
В Windows вы никогда не взаимодействуете напрямую с устройством (монитором или др). На самом деле взаимодействие происходит с так называемым контекстом устройства. Контекст устройства - это абстракция монитора или другого устройства вывода, которое используется в данные момент. Контекст утсройства позволяет использовать один и тот же код для рисования на экране монитора или печати на принтере.

Контекст устройства принадлежит классу CDC,потомком которого является класс CClientDC. В классе CDC собраны средства рисования линий,заливки а так же некоторые другие возможности. В классе CClientDC имеются некоторые вспомогательные средства, используемые перед началом сеанса рисования, а так же средства очистки, которые используются после сеанса рисования с помощью средств класса CDC.

Но чтобы начать сеанс рисования недостаточно просто объявить объект класса CClientDC или CDC. Еще необходимо получить контекст устройства, на которое будет осуществляться вывод. В листинге 4.1 конструктору класса CClientDC в качестве параметра передается this.

CClientDC dc(this);

В данной ситуации переменная this ссылается на текущее диалоговое окно.

(АВ: Вот почему и рисование в данном случае будет происходить в текущем диалоговом окне!)
На самом деле вы передаете указатель на текущее диалоговое окно конструктору класса CClientDC, который инициализирует экземпляр класса CClientDC текущим контекстом диалогового окна. В дальнейшем вы узнаете более о контексте устройства.

Конструкторы и деструкторы
(Материал пропущен)
Динамически создаваемые и уничтожаемые объекты
(Материал пропущен)


Вверх

Улучшение графического приложения
В нашем предыдущем приложении приходилось двигать мышь очень медленно,чтобы получалась сплошная линия. Потому, что мы рисуем ставя отдельные точки на экран. В других графических программах рисуются линии между двумя соседними точками. Попробуем и мы составить такую программу. Это значит что в приложение требуется добавить координаты X и Y предыдущего положения указателя мыши. Значит нам надо добавить две новых переменных.
  1. Перейдите на вкладку Class View
  2. Выберите класс CMouseDlg
  3. Щелкните правой кнопкой и выберите Add Variable
  4. В диалоговом окне задайте сначала одну переменную ее имя m_iPrevX.Укажите в качестве типа int и еще private. Затем повторите это для второй переменной - m_iPrevY
После того как мы добавили переменные-члены к классу, можно внести соответствующие изменения в функцию OnMouseMove() :

Листинг 4.2

void CMouseDlg::OnMouseMove(UINT nFlags, CPoint point)
{
//выяснить нажата ли левая кнопка мыши
if((nFlags & MK_LBUTTON) == MK_LBUTTON)
{
//получить контекст устройства
CClientDC dc(this);
//провести линию от предыдущей точки до текущей точки
dc.MoveTo(m_iPrevX,m_iPrevY);
dc.LineTo(point.x,point.y);
//сохранить текущую точку в качестве предыдущей точки
m_iPrevX = point.x;
m_iPrevY = point.y;
}
CDialog::OnMouseMove(nFlags,point);
}

Взгляните на код, рисующий линию между предыдущим и текущим положением указателя мыши:

//провести линию от предыдущей точки до текущей точки
dc.MoveTo(m_iPrevX,m_iPrevY);
dc.LineTo(point.x,point.y);

Вы видите что сначала необходимо перейти к предыдущему положению указателя мыши, а затем от него провести линию к текущему положению. Первый шаг очень важен, если его не сделать то Windows не сможет определить где начинается линия.

Теперь с новыми изменениями приложение работает лучше, но все же есть неприятный момент. В то время как вы начинаете новую линию, она автоматически соединяется с концом предыдущей.

Совет:

Вы можете использовать вместо двух целочисленных переменных одну переменную типа CPoint. В таком случае вы несколько упростите код, присваивая значение этой переменной следующим образом:

m_pPrevPoint = point;

и нарисовав линию приведенным ниже способом:

dc.MoveTo(m_pPrevPoint);
dc.LineTo(point);

Вот как эти функции объявлены в классе CDC :

// Line-Output Functions
CPoint MoveTo(int x, int y);
CPoint MoveTo(POINT point);
BOOL LineTo(int x, int y);
BOOL LineTo(POINT point);

Вверх

Последние штрихи

Указанную выше ситуацию можно исправить, если инициализировать переменные, хранящие координаты предыдущего положения указателя мыши,координатами точки, в которой находился указатель в момент нажатия левой кнопки мыши.
  1. На вкладке Properties воспользуйтесь режимом Messages и добавите к объекту диалогового окна функцию,обрабатывающую сообщение WM_LBUTTONDOUN
  2. Отредактируйте только что созданную функцию OnLButtonDown() ниже следуюшим кодом :
Листинг 4.3
void CMouseDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
//установить текущую точку в качестве исходной
m_iPrevX = point.x;
m_iPrevY = point.y;
CDialog::OnLButtonDown(nFlags, point);
}

Вы увидите что приложение теперь работает нормально.


Кажется с рисованием при помощи мыши все понятно.
АВ: Полагаю что когда пользователь нажимает на левую кнопку мыши, затем проводит мышь по экрану,затем отпускает кнопку. То Windows сначала вызывает функцию OnLButtonDown затем вызывает функцию OnMouseMove И затем вызывает функцию OnLButtonUp
Благодаря этому свойству Windows мы и встроили новый код в функцию OnLButtonDown - ведь эта функция будет вызвана первой и присвоит этим двум переменным новые координаты начальной точки рисования, а затем уже будет вызвана функция OnMouseMove которая будет рисовать линию от начальной точки.

Добавлю что обе эти функции являются функциями-членами класса CWnd.

public:
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

Кроме того в карте сообщений содержатся сообщения,которые контролируют эти события мыши :

BEGIN_MESSAGE_MAP(CMouseDlg, CDialog)

...
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONDOWN()
...

END_MESSAGE_MAP()

АВ.Толщина линии и прибор которым рисуют в данной программе никак нами не контролируется и используется по умолчанию. Программа контролирующая толщину линии и выбирающая прибор для рисования будет написана нами далее.

Но мы можем написать на основании этого материала программу в которой определим цвет рисования.

Упражнения
  1. Измените графическое приложение так,чтобы при нажатой левой кнопке мыши цвет рисуемого объекта был красным, а при нажатой правой - голубым.

    Ответы

    Вверх | Вперед
    Содержание

    Hosted by uCoz