Функциональность перемещения элементов в окне используется в различных приложениях: от аркадных игр до самых серьезных прикладных. В статье описываются способы перемещения элементов с помощью свойств Margin, Canvas.Left и Canvas.Top в контейнерах Grid и Canvas для приложений WPF.
Программный код движения находится внутри методов событий мыши: MouseDown, MouseMove, MouseUp. Элементы, у которых MouseDown, MouseUp заменены событием Click (Button, Calendar и др.) надлежит передвигать правой кнопкой мыши или на время передвижения обрабатывать нажатие функциональной клавиши. Такие меры необходимы для сохранения стандартной функциональности элемента управления.
Изменение позиции элементов должно строиться на определенной системе координат. Наиболее часто используемая координатная система окон - это система с началом от левого верхнего края.
Для такой координатной системы используются свойства элементов 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 определяет внешнее поле элемента, на практике же это свойство определяет только дистанцию от краёв родительского контейнера и не влияет на положение относительно других одноранговых элементов. Эту особенность с успехом можно использовать для позиционирования элемента относительно родителя. Для перемещения в пределах всей клиентской области окна, родительский контейнер должен быть корневым в окне приложения 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.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#.