Разработка полноценных Windows-приложений
2. Параллельный перенос и масштабирование плоскости
Создадим пункт меню Tools (инструменты). В нем создадим подпункт Parallel Shift (параллельный перенос) и связанную с ним кнопку на панели инструментов, которые переводят программу в режим ожидания параллельного переноса или выводят ее из него. Если программа находится в режиме ожидания параллельного переноса, то нажатие левой кнопки активирует его и программа начинает обрабатывать событие WM_MOUSEMOVE (движение мыши). В соответствии с перемещением курсора, перемещается точка наблюдения. Отпускание левой кнопки мыши деактивирует перенос и возвращает программу в режим ожидания. В данном пункте необходимо добавить обработчики движения мыши, нажатия левой кнопки мыши и ее отпускания. Обработчик движения мыши (важные для этого пункта строки выделены жирным шрифтом):
void CGpView::OnMouseMove(UINT nFlags, CPoint point) { CRect cr; //Переменная для хранения размеров клиентского прямоугольника CString str; //Строка для записи и вывода координат курсора GetClientRect(&cr); //Получение размеров клиентского прямоугольника CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Указатель на основную рамку (необходимо для обращения к строке состояния) CStatusBar *pStatus = &pFrame->m_wndStatusBar; //Указатель на строку состояния (будет отображать координаты курсора) if(cr.PtInRect(point)) //Если курсор в клиентском прямоугольнике то... { SetCapture(); //Захватываем мышь if(parallel_shift) //Если в режиме ожидания параллельного переноса то... if(move_camera) //Если перенос актвирован { //Сдвигается точка наблюдения на разность между текущим и предыдущим положениями курсора CameraPoint.x += (static_cast<double>(point.x) - static_cast<double>(MousePosPoint.x))/ScaleXY; CameraPoint.y -= (static_cast<double>(point.y) - static_cast<double>(MousePosPoint.y))/ScaleXY; Invalidate(); //Обновление клиентсой области MousePosPoint = point; //Сохранение положения курсора } else SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Если перенос не активирован то устанавливается нужный курсор if(mark_points) SetCursor(LoadCursor(NULL,IDC_CROSS)); //Если в режиме визуального редактирования то устанавливается соответствующий курсор } else //Если курсор вне клиентского прямоугольника то... { ReleaseCapture(); //Освобождается мышь move_camera = false; //Деактивируется перенос } if(pStatus) //Если указатель на строку состояния верный то... { str.Format("x = %f", SysToLog(point).x); //Запись во вспомогательную строку координаты x pStatus->SetPaneText(0,str); //Вывод в строку состояния str.Format("y = %f", SysToLog(point).y); //Запись во вспомогательную строку координаты y pStatus->SetPaneText(1,str); //Вывод в строку соостояния } CView::OnMouseMove(nFlags, point); }Листинг 5.2.
MousePosPoint - это переменная типа CPoint, которая служит для хранения координат курсора и объявлена в прототипе класса CGpView с модификатором private. С тем же модификатором объявлены переменные parallel_shift и move_camera типа bool. Первая переменная сигнализирует, нажата ли кнопка параллельного переноса, а вторая - активирован ли перенос (нажата или отпущена левая кнопка мыши). Функция SetCapture() перенаправляет все сообщения мыши на обработку окну, которое ее вызвало. Функция ReleaseCapture() восстанавливает стандартный путь сообщений мыши в операционной системе. Функция SetCursor(...) меняет изображение курсора. Функция LoadCursorA(...), член класса приложения, загружает курсор из ресурсов приложения, вызвавшего ее, и возвращает его описатель, в качестве входного параметра требует его идентификатор. Функция AfxGetApp() возвращает указатель на экземпляр приложения.
Обработчик нажатия левой кнопки мыши:
void CGpView::OnLButtonDown(UINT nFlags, CPoint point) //Обработчик нажатия левой кнопки мыши { CRect cr; //Переменная для хранения размеров клиентского прямоугольника GetClientRect(&cr); //Получение размеров клиентского прямоугольника if(parallel_shift && cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник... { MousePosPoint = point; //Сохранение координат нажатия move_camera = true; //Активация переноса SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_FIST)); //Устанавливается соответствующий курсор } CView::OnLButtonDown(nFlags, point); }
Обработчик отпускания левой кнопки мыши:
void CGpView::OnLButtonUp(UINT nFlags, CPoint point) //Обработчик отпускания левой кнопки мыши { CRect cr; //Переменная для хранения размеров клиентского прямоугольника GetClientRect(&cr); //Получение размеров клиентского прямоугольника if(parallel_shift && cr.PtInRect(point)) //Если в режиме ожидания переноса и курсор в клиетском прямоугольник ... { move_camera = false; //Деактивация переноса SetCursor(AfxGetApp()->LoadCursorA(IDC_CURSOR_PALM)); //Устанавливается соответствующий курсор } CView::OnLButtonUp(nFlags, point); }
Для корректной работы программы создадим и отредактируем соответствующие обработчики для кнопки Parallel Shift. В рамках данного пункта важны строки, выделенные жирным шрифтом. Обработчик пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов:
void CGpView::OnToolsParallelshift() { mark_points = false; //Если в режиме визуального редактирования узлов то выход из него parallel_shift = !parallel_shift; //Вход или выход в режим ожидания переноса }
Обработчик изменения внешнего вида пункта меню Parallel Shift и связанной с ним кнопки на панели инструментов:
void CGpView::OnUpdateToolsParallelshift(CCmdUI *pCmdUI) //Обрабатывает изменение внешнего вида кнопки (пункта меню) { pCmdUI->SetCheck(parallel_shift); //Смена статуса (активна или не активна) в зависимости от флага }
Для масштабирования следует обрабатывать прокрутку колеса мыши. В зависимости от направления вращения будет меняться масштабный коэффициент. Обработчик колесика мыши:
BOOL CGpView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { CString str("Updating coordinates. Please move cursor."); //Информативная строка CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd; //Получение указатель на основную рамку CStatusBar *pStatus = &pFrame->m_wndStatusBar; //Получение указатель на строку состояния if(zDelta > 0) {if(ScaleXY < 200) ScaleXY += 5;} //Если вращение вперед, то увеличивается массштабный коэффициент else if(ScaleXY > 40) ScaleXY -= 5; //Если вращение назад, то уменьшается масштабный коэффициент Invalidate(); //Перерисовываем if(pStatus) //Если указатель на строку состояния истина, то { //Вывод информативной строки в строку состояния pStatus->SetPaneText(0,str); pStatus->SetPaneText(1,str); } return CView::OnMouseWheel(nFlags, zDelta, pt); }