Сегодня вы узнаете:
Если вы выберете в режакторе диалоговое окно приложения и во вкладке
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 | Колесо мыши прокручивается |
Вверх
Вот как эта функция объявлена в классе CDC
Контекст устройства принадлежит классу CDC,потомком которого является класс
CClientDC. В классе CDC собраны средства рисования линий,заливки а так же
некоторые другие возможности. В классе CClientDC имеются некоторые
вспомогательные средства, используемые перед началом сеанса рисования, а так
же средства очистки, которые используются после сеанса рисования с помощью
средств класса CDC.
Но чтобы начать сеанс рисования недостаточно просто объявить объект класса
CClientDC или CDC. Еще необходимо получить контекст устройства, на которое
будет осуществляться вывод. В листинге 4.1 конструктору класса CClientDC
в качестве параметра передается this.
Вверх
и нарисовав линию приведенным ниже способом:
Вот как эти функции объявлены в классе CDC :
Вверх
Вы увидите что приложение теперь работает нормально.
Добавлю что обе эти функции являются функциями-членами класса CWnd.
public:
Кроме того в карте сообщений содержатся сообщения,которые контролируют
эти события мыши :
BEGIN_MESSAGE_MAP(CMouseDlg, CDialog)
АВ.Толщина линии и прибор которым рисуют в данной программе
никак нами не контролируется и используется по умолчанию.
Программа контролирующая толщину линии и выбирающая прибор
для рисования будет написана нами далее.
Но мы можем написать на основании этого материала программу
в которой определим цвет рисования.
Рисование с помощью мыши
Листинг 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);
Данный класс инкапсулирует контекст устройства и большинство методов,
предназначенных для работы с ним, включая все методы,позволяющие
рисовать на экране монитора. Контекст устройства можно представить как
холст для рисования, пока нет холста, нельзя ничего нарисовать.
Создав однажды контекст устройства, затем можно вызывать его функцию
SetPixel().
dc.SetPixel(point.x,point.y,RGB(0,0,0));
Эта функция раскрашивает пиксель, координаты которого заданы первыми
двумя аргументами функции, в цвет заданный третьим аргументом.
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 переведет фокус на какой либо элемент управления в наружном окне. То есть при нажатии кнопки мыши функция рисования уже не сможет
быть вызвана.
При выполнении программы происходит следующее: если курсор мыши движется,
то через малые промежутся времени программа периодически (можно сказать
циклически) вызывает эту функцию, передавая ей при каждом вызове флаг
и координаты. И наша функция при каждом вызове рисует точку в этой координате,
если левая кнопка нажата. Если левая кнопка не была нажата, то рисуется
стандартный курсор мыши в данной координате экрана.
O цвете рисования (пропущен)
Вверх
Библиотека MFC. Контекст устройства
В Windows вы никогда не взаимодействуете напрямую с устройством (монитором
или др). На самом деле взаимодействие происходит с так называемым контекстом
устройства. Контекст устройства - это абстракция монитора или другого
устройства вывода, которое используется в данные момент. Контекст утсройства
позволяет использовать один и тот же код для рисования на экране монитора
или печати на принтере.
CClientDC dc(this);
В данной ситуации переменная this ссылается на текущее диалоговое окно.
Конструкторы и деструкторы
(Материал пропущен)
Динамически создаваемые и уничтожаемые объекты
(Материал пропущен)
Улучшение графического приложения
В нашем предыдущем приложении приходилось двигать мышь очень
медленно,чтобы получалась сплошная линия. Потому, что мы рисуем
ставя отдельные точки на экран.
В других графических программах рисуются линии между двумя соседними
точками. Попробуем и мы составить такую программу.
Это значит что в приложение требуется добавить координаты X и Y
предыдущего положения указателя мыши.
Значит нам надо добавить две новых переменных.
После того как мы добавили переменные-члены к классу,
можно внести соответствующие изменения в функцию 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 не сможет
определить где начинается линия.
Теперь с новыми изменениями приложение работает лучше, но все же есть
неприятный момент. В то время как вы начинаете новую линию,
она автоматически соединяется с концом предыдущей.
Совет:
m_pPrevPoint = point;
dc.MoveTo(m_pPrevPoint);
dc.LineTo(point);
// Line-Output Functions
CPoint MoveTo(int x, int y);
CPoint MoveTo(POINT point);
BOOL LineTo(int x, int y);
BOOL LineTo(POINT point);
Последние штрихи
Указанную выше ситуацию можно исправить, если инициализировать переменные,
хранящие координаты предыдущего положения указателя мыши,координатами
точки, в которой находился указатель в момент нажатия левой кнопки мыши.
Листинг 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 которая будет рисовать
линию от начальной точки.
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONDOWN()
...
END_MESSAGE_MAP()
Упражнения