Перемещение элементов мышью

Все исходники /  Язык программирования C# /  OS Windows /  Desktop /  WPF программирование / Перемещение элементов мышью

Перемещение элементов

Функциональность перемещения элементов в окне используется в различных приложениях: от аркадных игр до самых серьезных прикладных. В статье описываются способы перемещения элементов с помощью свойств Margin, Canvas.Left и Canvas.Top в контейнерах Grid и Canvas для приложений WPF.

Программный код движения находится внутри методов событий мыши: MouseDown, MouseMove, MouseUp. Элементы, у которых MouseDown, MouseUp заменены событием Click (Button, Calendar и др.) надлежит передвигать правой кнопкой мыши или на время передвижения обрабатывать нажатие функциональной клавиши. Такие меры необходимы для сохранения стандартной функциональности элемента управления.

Вычисление позиции элемента окна

Перемещение фигуры в окне Windows

Изменение позиции элементов должно строиться на определенной системе координат. Наиболее часто используемая координатная система окон - это система с началом от левого верхнего края.

Для такой координатной системы используются свойства элементов Margin.Left, Margin.Top или свойства Canvas.Left, Canvas.Top в зависимости от типа родительского контейнера.

Перед началом движения происходит фиксация позиции курсора относительно границ элемента - приклеивание курсора к элементу. Фиксация позиции курсора и разрешение перемещения в событии MouseDown, вычисление следующих позиций элемента при перемещении мыши в - MouseMove, запрет движения в - MouseUp.

 --- Данные получаемые в MouseDown ---
Вычисление смещения курсора перед перемещением элемента
offsetPoint.X =  p.X - Element.Left
offsetPoint.Y =  p.Y - Element.Top

 --- Данные получаемые в MouseMove ---
Вычисление следующих позиций элемента при движении курсора в событии MouseMove
Element.Left = p.X - offsetPoint.X
Element.Top = p.Y - offsetPoint.Y

* p - текущие координаты курсора мыши получаемые в событиях Mouse*

Перемещение по координатам Margin

По смыслу Margin определяет внешнее поле элемента, на практике же это свойство определяет только дистанцию от краёв родительского контейнера и не влияет на положение относительно других одноранговых элементов. Эту особенность с успехом можно использовать для позиционирования элемента относительно родителя. Для перемещения в пределах всей клиентской области окна, родительский контейнер должен быть корневым в окне приложения WPF.

Свойство Margin принадлежит классу FrameworkElement, поэтому для перемещения таким способом подходят только его наследники. Margin представляет собой структуру Thickness, имеющую значения толщины границы вокруг четырех сторон элемента: Left, Top, Right и Bottom.

Способ перемещения элементов окна с помощью Margin универсальный, позиционирование возможно контейнерах Grid и Canvas. Необходимое условие для корректного перемещения - установка свойств элементов в значения: HorizontalAlignment="Left" и VerticalAlignment="Top". Тогда начало координатной системы позиций будет в левом верхнем углу.

#region Функция перемещения элементов

// Счётчик z-Index позиции 
int countZ = 0;
bool _canMove = false;
Point _offsetPoint = new(0, 0);
private void FF_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{ 
    // Разрешаем перемещение.
    _canMove = true;
    // Каждое перемещение будет увеличивать zIndex элемента.
    // Размера типа Int достаточно для очень длительной работы приложения.
    countZ++;

    // Объект вызывающий событие приводим к универсальному для всех элементов типу.
    //  Таким образом можно тестирован многие элементы не корректируя код.
    FrameworkElement ffElement = (FrameworkElement)sender;
    // Поднимаем над всеми активный элемент.
    Grid.SetZIndex(ffElement, countZ);
    
    //  Позиция курсора в начале движения.
    Point posCursor = e.MouseDevice.GetPosition(this);
    // Значения смещения позиции курсора мыши относительно 
    // левого и верхнего края элемента.
    _offsetPoint = 
        new Point(posCursor.X - ffElement.Margin.Left, posCursor.Y - ffElement.Margin.Top);

    // Захват устройства мышь предотвращает отрыв
    // курсора от элемента при резком движении мыши.
    e.MouseDevice.Capture(ffElement);
}

private void FF_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    if (_canMove == true)
    {
        FrameworkElement ffElement = (FrameworkElement)sender;
        // Среди множества элементов перемещаться будет только выбранный.
        if (e.MouseDevice.Captured == ffElement)
        {
            Point p = e.MouseDevice.GetPosition(this);

            Thickness margin = new(p.X - _offsetPoint.X, p.Y - _offsetPoint.Y, 0, 0);
            ffElement.Margin = margin;
        }
    }
}

private void FF_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    _canMove = false;
    // Освобождаем устройство мышь
    e.MouseDevice.Capture(null);
}

#endregion

Перемещение элементов в Canvas

Перемещения элементов внутри Canvas базируется на присоединённых к элементам свойствах Canvas.Left и Canvas.Top. В таком виде данные свойства определяются только в файлах XAML, а программно устанавливаются методами Canvas.SetLeft(UIElement, Double) и Canvas.SetTop(UIElement, Double). Программный код (см. ниже) незначительно отличается от кода способа перемещения на свойстве Margin.

Примечание. Для свободного перемещения элементов в пределах поля Canvas необходимо инициализировать начальные свойства Canvas.Left и Canvas.Top. По умолчанию свойства имеют значение auto и перемещение элементов курсором мыши невозможно.

Программный код методов перемещения элементов в контейнере Canvas, отличительный код для Margin закомментирован:
#region Функция перемещения элементов

int countZ = 0;
bool _canMove = false;
Point _offsetPoint = new(0, 0);
private void FF_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    _canMove = true;
    // Передвигаемый элемент будет всегда сверху.
    countZ++;
    // Универсальное приведение для всех тестируемых элементов.
    FrameworkElement ffElement = (FrameworkElement)sender;
    Grid.SetZIndex(ffElement, countZ);

    Point posCursor = e.MouseDevice.GetPosition(this);
    /*_offsetPoint = 
       new Point(posCursor.X - ffElement.Margin.Left, posCursor.Y - ffElement.Margin.Top);*/
    _offsetPoint = new Point(
            posCursor.X - Canvas.GetLeft(ffElement), 
            posCursor.Y - Canvas.GetTop(ffElement)
    );

    // Чтобы курсор не отрывался от фигуры
    // при резком движении мышью.
    //  Захват устройства мышь. 
    e.MouseDevice.Capture(ffElement);
}

private void FF_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    if (_canMove == true)
    {
        FrameworkElement ffElement = (FrameworkElement)sender;

        if (e.MouseDevice.Captured == ffElement)
        {
            Point p = e.MouseDevice.GetPosition(this);

            /*Thickness margin = new(p.X - _offsetPoint.X, p.Y - _offsetPoint.Y, 0, 0);
            ffElement.Margin = margin;*/
            Canvas.SetLeft(ffElement, p.X - _offsetPoint.X);
            Canvas.SetTop(ffElement, p.Y - _offsetPoint.Y);
        }
    }
}

private void FF_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    _canMove = false;
    e.MouseDevice.Capture(null);
}

#endregion

Исходники перемещения элементов в окне

В архивном файле находятся исходники WPF приложений: в одном элементы перемещаются внутри контейнера Grid, в другом - внутри Canvas. Среда программирования MS Visual Studio 2022, платформа .NET6, язык программирования C#.

Скачать исходник