Библиотека Интернет Индустрии I2R.ru |
|||
|
Графика Windows и API. Создание векторного редактора. Часть 2Первым, что приходит в голову, это по координатам щелчка проверить попадает ли точка в прямоугольник фигуры и тем самым определить текущую фигуру. Это правило истинно, только для одной фигуры - прямоугольника. Посмотрим, что же неверно для других. Окружность рисуется по координатам прямоугольника. Так вот, если бы мы делали проверку, через координаты прямоугольника, то в точке щелчка мышкой произошло попадание точки, а это неправильно. Поэтому необходимо использовать специальный объект Windows - регион (region). Регион - это часть области, которую можно протестировать на наличие попадания точки, залить определенным цветом и т.д. Вся сущность региона в том, что он может иметь любую конфигурацию, что сильно облегчает обработку данных. Регион создается API функцией CreateEllipticRgn (для окружностей, существуют различные функции создания регионов), проверка попадания точки в регион функцией PtInRegion. Вообще вы можете создать несколько регионов различной конфигурации, а затем скомбинировать их в область, как показано на рисунке выше. Перемещение фигуры Определив, что точка находится в нужной области, выполняем её перемещение, путем изменения координат фигуры и перерисовке всей поверхности холста. Изменение размеров фигуры Для изменения размеров фигуры используем понятие маркеров знакомых вам по дизайнеру Visual Basic, Corel Draw и т.п. программ. После того как определено, что щелчок пришелся по нужной фигуре, необходимо по углам и серединам сторон отрисовать маркеры (размеры маркеров в программе выбраны произвольно, для более профессионального уровня необходимо определить разрешение экрана и в соответствии с ним выбрать размер маркера, т.к. при разрешении 1024X768 маркер размером в 2 точки будет слишком неудобным). Затем при наведении указателя "мыши" меняем его вид на соответствующий (диагональный, верх-низ и т.д.). Если пользователь щелкнул по маркеру, определяем его номер, чтобы корректно произвести изменение размеров в нужном направлении. При событии перемещения "мышки" изменяем размеры фигуры и перерисовываем поверхность холста. Использованные API функции В программе использованы новые API функции. Полную декларацию описания функций смотрите в исходном тексте. Где пропущено описание типа подразумевается тип Long. PtInRegion(hRGN,X,Y) --- Проверят, попадает ли точка с координатами (X,Y) в регион с дескриптором hRGN. EqualRect(lpRect1 As RECT, lpRect2 As RECT) --- Проверяет равны ли координаты прямоугольников lpRect1 и lpRect2. CreateRectRgn(X1,Y1, X2, Y2) --- Создает прямоугольный регион по координатам (X1,Y1,X2,Y2). CreateEllipticRgn(X1,Y1,X2,Y2) --- Создает регион окружности по координатам (X1,Y1,X2,Y2). Описание программы В процедуру загрузки формы добавим следующий код (Form_Load): ... ' NEW Установим координаты плоскости виртуального окна RectVW = RectForm RectVW.Left = 0 RectVW.Top = 0 RectVW.Bottom = RectVW.Bottom - 10 RectVW.Right = RectVW.Right - 10 ... ' NEW Установить начальный индекс IndexFigure = SHAPE_RECT CurrentFigure = SHAPE_RECT ... Пояснение. Переменная RectVW содержит реальные координаты окна отображения, т.е. виртуального окна. Без этой переменной происходило усечение фигур (неточность в первой статье). IndexFigure содержит индекс выбранной фигуры с помощью мышки или команды меню. На уровне модуля формы добавляем несколько переменных, их смысл будет ясен из дальнейшего объяснения. ' NEW Координаты точки в которой нажали мышку ' в событии MouseDown Private PointDown As POINTAPI ' NEW Координаты плоскости виртуального окна Private RectVW As RECT ' NEW Индекс найденной фигуры Private IndexFigure As Integer ' NEW Координаты фигуры при перемещении Private RectDown As RECT ' NEW Номер маркера с помощью которого ' производится изменение размеров Private MarkerDown As Integer ' NEW Флаг изменения размеров фигуры Private SizeFigure As Boolean ' NEW Флаг перемещения фигуры Private MoveFigure As Boolean Дополнительно применяются собственные функции: ApiPoint преобразует координаты из переменных в структуру POINTAPI. RectInRect проверяет вхождение одного прямоугольника во второй. PtInRect проверяет, попадает ли точка в заданный прямоугольник. PointConvert преобразует точку из координат формы в координаты виртуального окна. NormalizeRect если верхние координаты больше нижних координат, обменять местами. В форму добавлена новая процедура обработки события MouseDown (Form_MouseDown): ' NEW При нажатии мышки определить координаты ' для нахождения фигуры Private Sub Form_MouseDown(:) Dim i As Byte Dim FindFigure As Integer ' Дескриптор региона Dim hRGN As Long ' Проверить какая кнопка нажата If Button <> 1 Then Exit Sub ' Инициализация SizeFigure = False MoveFigure = False FindFigure = -1 ' Занесем координаты в структуру PointDown = PointConvert(CLng(X), CLng(Y)) ' Проверяем, что точка в плоскости холста If PtInRect(RectVW, PointDown) Then ' Проверяем попадание точки в одну из фигур For i = LBound(Figures) To UBound(Figures) ' Создадим регион для проверки попадания точки With Figures(i).Coord If Figures(i).Shape = SHAPE_RECT Then hRGN = CreateRectRgn(.Left, .Top, .Right, .Bottom) Else hRGN = CreateEllipticRgn(.Left, .Top, .Right, .Bottom) End If End With ' Проверим попадание в регион If PtInRegion(hRGN, PointDown.X, PointDown.Y) Then ' Точка в нужной фигуре. Выставляем индекс. IndexFigure = i FindFigure = i ' Сохранить координаты фигуры RectDown = Figures(i).Coord End If ' Удалим дескриптор региона DeleteObject hRGN Next ' Если фигура найдена установим маркеры Call mnuObjectItem_Click(IndexFigure) ' Проверить, что щелчок пришелся на маркер MarkerDown = HitTest(IndexFigure, PointDown) ' Проверяем, что происходит изменение размера ' или перемещение If MarkerDown <> -1 Then ' Режим изменения размеров SizeFigure = True Else ' Режим перемещения фигуры If FindFigure <> -1 Then MoveFigure = True End If ' Конец определения фигур End If End Sub Пояснение. После проверки того, что точка в плоскости холста в цикле перебираем фигуры (цикл для тех сделает более, чем две фигуры), на основе типа фигуры вызываем соответствующие функции создания регионов (CreateRectRgn, CreateEllipticRgn) по координатам фигуры. С помощью функции PtInRegion тестируем точку, если она в регионе, то сохраняем индекс фигуры (IndexFigure) и самое главное сохраняем координаты фигуры (RectDown), в которой щелкнули (это понадобится при перемещении фигуры). В конце цикла удаляем дескриптор региона. По завершении цикла с помощью функции HitTest проверяем, где произошел щелчок в фигуре на маркере или нет. Если в маркере - выставляем флаг изменения размера SizeFigure, иначе если есть выделенная фигура (FindFigure<>-1) выставляем флаг перемещения фигуры. После события нажатия кнопки мышки обрабатываем событие перемещения MouseMove (Form_MouseMove): ' NEW Перемещение мышки Private Sub Form_MouseMove(:) ' Координаты точки, которая перемещается Dim PointMove As POINTAPI ' Координаты курсора мыши Dim p As POINTAPI ' Временный прямоугольник фигуры Dim r As RECT ' Преобразуем перемещаемую точку в координаты холста PointMove = PointConvert(CLng(X), CLng(Y)) If PtInRect(RectVW, PointMove) Then ' Проверка, на какую кнопку нажали If (Button = 1) Then If SizeFigure Then Call MoveMarkerTo(IndexFigure, MarkerDown, PointMove) Else If MoveFigure Then ' Изменяем координаты фигуры и проверяем, чтобы ' не вышли за пределы холста r = RectDown r.Left = r.Left + (PointMove.X - PointDown.X) r.Top = r.Top + (PointMove.Y - PointDown.Y) r.Right = r.Right + (PointMove.X - PointDown.X) r.Bottom = r.Bottom + (PointMove.Y - PointDown.Y) If RectInRect(RectVW, NormalizeRect(r)) Then Figures(IndexFigure).Coord = r End If End If ' Обновление окна формы Call InvalidateCanvas End If ' Установить курсор на маркере MousePointer = GetMarkerCursor(IndexFigure, _ HitTest(IndexFigure, PointMove)) End If End Sub Пояснение. Проверяем флаги выставленные в MouseDown. Если изменение размеров - вызываем процедуру MoveMarkerTo, если перемещение, тогда сохраняем координаты из RectDown во временной переменной и изменяем размеры на разницу между координатами точки, где нажали кнопку (PointDown) и координатами точки, куда переместили указатель мышки (PointMove). Если измененные координаты фигуры находятся в области поверхности холста (RectInRect(RectVW, NormalizeRect(r))) тогда сохраняем их в структуре фигуры и перерисовываем область. В конце устанавливаем курсор маркера с помощью GetMarkerCursor. И в конце, когда пользователь отпустил кнопку обрабатываем событие MouseUp (From_MouseUp): ' NEW При отпускании мышки сбрасывание параметров Private Sub Form_MouseUp(:) ' Сбросить параметры SizeFigure = False MoveFigure = False Figures(IndexFigure).Coord = NormalizeRect(Figures(IndexFigure).Coord) End Sub Пояснение. Сбрасываем все флаги и производим нормализацию координат, если пользователь их изменял (можно оптимизировать ?). Измените процедуру CreateVirtualWindow, где была неточность: ... MBM = CreateCompatibleBitmap(CDC, RectVW.Right, RectVW.Bottom) ... PatBlt MDC, 0, 0, RectVW.Right, RectVW.Bottom, WHITENESS ... В процедуре InvalidateCanvas добавьте строчку, выводящую маркеры для выделенной фигуры: ' NEW Вывести маркеры для выделенной фигуры Call DrawTracker(IndexFigure, MDC, True) А теперь перейдем к описанию процедур и функций отвечающих за работу с маркерами. [Название подпрограммы] --- [Назначение] GetMarker --- По индексу маркера получает координаты маркера POINTAPI GetMarkerCount --- Количество маркеров у фигуры (у нас всегда 8) GetMarkerCursor --- Получает курсор для определенного маркера GetMarkerRect --- Получает по номеру маркера его координаты в виде RECT MoveMarkerTo --- Перемещает маркер в нужную позицию DrawTracker --- Отрисовывает маркеры фигуры HitTest --- Проверяет где находится точка в пределах фигуры В принципе смысл подпрограмм вы поймете из исходных текстов. Отдельного внимания заслуживают только DrawTracker и HitTest. С помощью DrawTracker отрисовываются маркеры: ' NEW Рисует маркеры на фигуре ' HDC - дескриптор контекста на котором происходит рисование ' State - статус фигуры, при истине у фигуры отрисовываются маркеры Sub DrawTracker(IndexFigure As Integer, _ HDC As Long, State As Boolean) Dim i As Integer Dim p As POINTAPI Dim sm As Integer sm = SIZE_MARKER + SIZE_MARKER If State Then ' В цикле пройтись по координатам всех маркеров For i = 1 To GetMarkerCount ' Получить координаты маркера p = GetMarker(IndexFigure, i) ' С помощью функции API шаблонная заливка PatBlt ' заполняем маркер по размеру ' инверсным цветом на котором рисуется маркер PatBlt HDC, p.X - SIZE_MARKER, _ p.Y - SIZE_MARKER, sm, sm, DSTINVERT Next End If End Sub Пояснение. В переменную (sm) устанавливаем полный размер маркера, в цикле проходимся по всем углам и серединам сторон фигуры, получаем по номеру маркера (переменная i цикла) координаты точки маркера и с помощью функции PatBlt заполняем области маркеров инверсным цветом. Вся прелесть функции PatBlt с параметром DSTINVERT, что она инвертирует цвета и поэтому при черном маркере на черном фоне, вы получите белый маркер. Таким образом мы никогда не потеряем маркер на фоне поверхности. Функция HitTest определяет положение курсора мышки в пределах фигуры: ' NEW Определение, что курсор находится в маркере ' В случае успеха вернет номер маркера, иначе (-1) Function HitTest(IndexFigure As Integer, _ p As POINTAPI) As Integer Dim i As Integer Dim r As RECT ' Установим начальное значение HitTest = -1 ' Цикл по всем маркерам For i = 1 To GetMarkerCount ' Получить координаты маркера в структуре r = GetMarkerRect(IndexFigure, i) ' Проверить попадает ли точка в ' координаты маркера If PtInRect(r, p) Then ' Если да, то установить номер маркера ' и прервать цикл HitTest = i Exit For End If Next End Function Пояснение. Передвигаясь по всем маркерам фигуры получаем координаты маркера в переменную ( r ) используя функцию GetMarkerRect. Если указатель мышки находится в маркере (PtInRect(r,p)), тогда выставляем индекс маркера, иначе (-1), т.е. указатель в фигуре. Последние замечания Вот мы и закончили разбираться с API и графикой. Рекомендую вам изучить раздел в MSDN Graphics SDK, также изучать исходные тексты и книги на Си и Паскале. В программе специально не производилась оптимизация, чтобы читателю было яснее разобраться и не запутаться в изменениях строк кода и переменных. Для тех, кто решит создавать собственный векторный редактор хочу посоветовать следующее:
Все эти возможности реализуются довольно легко, главное хорошо продумать алгоритм вашей программы. Заключение Более элегантным способом построение похожих программ является использование классов, но версия VB 6.0 обладает усеченными возможностями объектно-ориентированного программирования (например, отсутствует наследование). Этой статьей мы показали, что серьезные вещи можно писать не только на VC++ (Borland C) или Pascal (Delphi), но и на "простом" Basic (VB). Последняя статья цикла, посвященная разработке справочных систем, в принципе не связана с графикой, но также использует API функции справочной системы. К статье прилагается пример приложения. |
|
2000-2008 г. Все авторские права соблюдены. |
|