Форум программистов, компьютерный форум, киберфорум
Наши страницы
ashsvis
Войти
Регистрация
Восстановить пароль
"Орешек знаний тверд, Но все же мы не привыкли отступать..." (с)
Оценить эту запись

Шаг четвёртый (графический векторный редактор). Перемещение фигур

Запись от ashsvis размещена 10.11.2018 в 17:14

Итак, мои уважаемые читатели, я продолжаю рассматривать животрепещущую тему строительства самолепного,
простого векторного графического редактора.

2. Перемещение фигур

Для перемещения фигур у нас пока задействована только мышка и мы будем рассматривать взаимодействие пользователя
с фигурами через события _container.MouseDown, _container.MouseMove и _container.MouseUp. Я об этом не упоминал ранее,
а теперь скажу, что имеется конструктор класса Picture, в котором и производится привязка событий контейнера к нашим
обработчикам событий:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public Picture(Control container)
{
    _container = container;
    // подключаемся к необходимым событиям на контейнере
    _container.MouseDown += ContainerMouseDown;
    _container.MouseMove += ContainerMouseMove;
    _container.MouseUp += ContainerMouseUp;
    _container.Paint += ContainerPaint;
    // подключаем объект выбора рамкой
    _ribbonSelector = new RectangleRibbonSelector(_container);
    _ribbonSelector.OnSelected += RibbonSelectorOnSelected;
    // а здесь пробуем найти ссылку на форму, на которой расположен PaintBox
    var parent = _container.Parent;
    // пока не найдём форму или пустой Parent
    while (!(parent is Form))
    {
        if (parent == null) break;
        parent = parent.Parent;
    }
    _form = parent as Form;
    // если найдена форма
    if (_form != null)
    {
        // то подключим к ней обработчик нажатия клавиш
        _form.KeyDown += FormKeyDown;
        _form.KeyUp += FormKeyUp;
        // включим признак предварительного просмотра нажатия клавиш
        _form.KeyPreview = true;
    }
    // при изменении выбора выключаем режим изменения узлов
    _selected.CollectionChanged += (sender, args) =>
        {
            NodeChanging = false;
        };
}
В строках с 5 по 8 подключаем в цепочку вызовов собственные обработчики событий на нажатие кнопок мышки и
её перемещение, а также обработчик события рисования на канве контейнера.
В строках 10 и 11 создаём объект для выбора рамкой и подключаем ему обработчик события:
Обработчик события завершение выбора прямоугольником
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
/// Обработчик события завершение выбора прямоугольником
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void RibbonSelectorOnSelected(object sender, RibbonSelectedEventArgs e)
{
    if (EditorMode != EditorMode.Selection) return;
    // нормализация параметров для прямоугольника выбора
    var rect = e.RectangleSelected;
    // добавляем все фигуры, которые оказались охваченными прямоугольником выбора
    // в список выбранных фигур
    foreach (var fig in _figures.Where(fig => rect.Contains(Rectangle.Ceiling(fig.GetBounds))))
        _selected.Add(fig);
}

В строках с 13 по 20 пробуем среди череды предков (Parent) найти компонент типа Form, у которого имеются
события нажатия клавиш на клавиатуре _form.KeyDown и _form.KeyUp, к которым мы также подключаем
собственные обработчики событий в строках 25 и 26 для сохранения состояния управляющей клавиши Ctrl,
и которое используется в алгоритме множественного выбора фигур.
В строке 28 мы включаем у контейнерной формы режим пред-просмотра нажатия клавиш на контролах формы.
Вот эти обработчики:
Обработчики событий нажатия клавиш клавиатуры
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// Во внешней форме нажата клавиша
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FormKeyDown(object sender, KeyEventArgs e)
{
    // проверяем нажатие Ctrl
    if (e.Control) _controlPressed = true;
}
 
/// <summary>
/// Во внешней форме отпущена клавиша
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FormKeyUp(object sender, KeyEventArgs e)
{
    // проверяем отпускание Ctrl
    if (!e.Control) _controlPressed = false;
}

В строках с 31 по 34 мы программируем событие изменения списка выбора, где сбрасывается режим изменения узлов
NodeChanging.

При выборе фигур мышкой они попадают в список выбранных, точка нажатия запоминается в переменной _mouseDownLocation,
а переменная смещения координат мышки _mouseOffset относительно _mouseDownLocation - обнуляется.
Если была нажата левая кнопка мышки, то производится захват мышки контейнером _container.Capture = true;
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// <summary>
/// Нажатие кнопки "мышки" на PaintBox
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ContainerMouseDown(object sender, MouseEventArgs e)
{
    // запоминаем точку первую точку для прямоугольника выбора
    _mouseDownLocation = e.Location;
    _mouseOffset = Point.Empty;
    // если установлен другой режим, кроме выбора прямоугольником
    if (EditorMode != EditorMode.Selection)
        _selected.Clear();   // очищаем список выбранных
 
    Figure fig;
    _markerIndex = PointInMarker(e.Location, out fig);
 
    // ищем фигуру в точке нажатия
    if (fig == null) fig = PointInFigure(e.Location);
 
    if (e.Button == MouseButtons.Left)
        _container.Capture = true;
 
    ...
}
Когда пользователь "тащит" фигуру мышкой, левая кнопка удерживается нажатой и при этом пересчитывается величина
смещения курсора мышки относительно точки первоначального нажатия в переменной _mouseOffset.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// Перемещение мышки над PaintBox
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ContainerMouseMove(object sender, MouseEventArgs e)
{
    if (_mouseDownLocation == e.Location) return;
    // если удерживается левая кнопка и мышка захвачена
    if (e.Button == MouseButtons.Left && _container.Capture)
    {
        // пересчитываем смещение мышки
        _mouseOffset.X = e.X - _mouseDownLocation.X;
        _mouseOffset.Y = e.Y - _mouseDownLocation.Y;
        // просим перерисовать
        _container.Invalidate();
    }
    if (e.Button != MouseButtons.None) return;
 
    ...
}
Когда мы просим _container.Invalidate(), то в событии ContainerPaint для всех выбранных фигур
вызывается метод DrawFocusFigure():
C#
1
2
3
4
5
6
7
8
private void ContainerPaint(object sender, PaintEventArgs e)
{
    ...
    // при перетаскивании
    if (EditorMode != EditorMode.Dragging) return;
    foreach (var fig in _selected)
        DrawFocusFigure(fig, e.Graphics, _mouseOffset, _markerIndex, NodeChanging);
}
В методе DrawFocusFigure() при перетаскивании фигуры происходит следующее:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
....
using (var gp = new GraphicsPath())
{
    if (figure.Kind == DrawingKind.Polygon)
        // добавляем в полигон все точки фигуры
        gp.AddPolygon(figure.GetPoints());
    else
        gp.AddLines(figure.GetPoints());
    // получаем графический путь
    var ps = gp.PathPoints;
    // для всех точек пути
    for (var i = 0; i < ps.Length; i++)
    {
        // делаем смещение
        ps[i].X += offset.X;
        ps[i].Y += offset.Y;
    }
    DrawCustomFigure(graphics, ps, figure.Kind);
}
....
По точкам фигуры строится графический путь (строки 4-8), из этого пути берётся реальный массив точек для рисования (строка 10) и
в цикле всем точкам передаётся текущее смещение мышки на данный момент (строки 12-17), ну а метод DrawCustomFigure()
просто рисует фигуру по точкам, пунктиром, на новом месте.

То есть реального перемещения фигур в событии ContainerMouseMove не делается, а только показывается их будущее положение.

Фиксация фигур на новых местах происходит при обработке события ContainerMouseUp().
Когда перетаскивание фигур заканчивается, то для всех выбранных фигур (из списка _selected) выполняется метод UpdateLocation(fig, _mouseOffset),
который фиксирует положение фигур на новом месте:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// <summary>
/// Перемещение фигуры
/// </summary>
/// <param name="fig">объект фигуры</param>
/// <param name="offset">смещение</param>
public void UpdateLocation(Figure fig, PointF offset)
{
    // перемещение фигуры
    var pts = fig.GetPoints();
    var oldrect = CalcFocusRect(PointF.Empty, fig, 0);
    var newrect = CalcFocusRect(offset, fig, 0);
    for (var i = 0; i < pts.Length; i++)
    {
        pts[i].X = newrect.Left + (pts[i].X - oldrect.Left);
        pts[i].Y = newrect.Top + (pts[i].Y - oldrect.Top);
    }
    fig.SetPoints(pts);
}
В стоке 9 все точки фигуры собираются в массив, затем при помощи метода CalcFocusRect() по этому массиву точек
строятся два охватывающего прямоугольника, и в цикле для всех точек производится коррекция положения.
В строке 17 массив точек заливается обратно в фигуру.

Так фигура у нас перемещается на новое место.

Я всё думаю о том, что такая модель хранения абсолютных координат точек в фигуре не совсем хороша.
Как-то не по феншую... Было бы лучше хранить не самые координаты точек, а их смешения относительно некой одной
точки (Origin) и алгоритм рисования делать исходя из этого. Тогда достаточно было бы смещать координаты только одной
этой точки, не трогая остальной массив.
С другой стороны, при изменении размеров фигуры пересчёт координат всех точек производить бы пришлось...

С перемещением фигур также закончили.
Но, чем дальше, тем интереснее: нас ждут изменение размеров и изменение узловых точек.

До встречи!
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru