WPF перемещение окон мышью

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

Перемещение окон окна мышью

Все мы привыкли к стандартной функциональности перемещения окна приложения по экрану монитора. Перемещение окна происходит захватыванием мышью его заголовка. Но окна приложений, созданные со стилем WindowStyle.None не имеют заголовка. Или есть необходимость разработки приложения с возможностью перетаскивания окна за клиентскую часть. Для таких случаев в операционной системе не предусмотрена возможность перемещать окна с помощью мыши.

Для устранения этого недостатка необходимо писать специальный программный код, ответственный за эту функциональность. Для этой операции задействуются события мыши, доставшиеся классу Window от его предка: UIElement.MouseDown, UIElement.MouseMove, UIElement.MouseUp.

Ниже описывается пример программного кода перемещения главного окна по экрану монитора. Программный код на языке C# написан для платформы WPF, но с незначительными изменениями применим и для окон Windows Forms. Практически данный программный код применён в приложениях описываемых в Фигурные окна приложений.

Особенности окон Windows и WPF

Рамки главного окна Windows

Работая с позиционированием окон Windows необходимо учитывать следующие особенности: размеры окна Width, Height включают неклиентскую область, координаты Left,Top выражаются в экранных координатах, курсор мыши в событиях указывает координаты относительно клиентской части окна. Программный код позиционирования окна с заголовком и свойством Window. ResizeMode разрешающим изменять размеры окна должен вычислять поправки на размеры рамок и заголовка главного окна приложения.

В исходники описаны два способа учёта размеров неклиентских частей:
- на разнице преобразованных в экранные координаты курсора мыши и позиции окна на экране: Window.Left, Windows.Top.
- на свойстве главного дочернего контейнера окна с выравниванием HorizontalAligment, VerticalAligment со значением Stretch повторять размеры клиентской области.

Примечание. Как видно на изображении полупрозрачная рамка изменения размеров выходит за пределы окна. Координата Window.Left главного окна содержит логические единицы, установив Window.Left=0, окно фактически от левого края будет на расстоянии рамки окна. При необходимости расположения окна на нулевой левой координате необходимо смещать в отрицательную область координату Left на толщину рамки. Аналогичный эффект будет наблюдаться также на крайних правых и нижних координатах. Top-координата проходит точно по верхнему краю окна, не выходя за его пределы.

Примечание. Окна WPF без заголовка с WindowsStyle=None имеют не только клиентскую область, но рамку изменения размеров. Чтобы получить окно только с клиентской частью необходимо назначить свойствам значения: Window. ResizeMode=None или AllowsTransparency="True".

Окно без заголовка

Тестовое окно без заголовка создано подобно описанным на странице Создание фигурных окон приложений. Окно фигурное, напоминающее цветок, вокруг круга равномерно расположены меньшие кружочки. Окно не имеет заголовка и обычным способом его перетащить невозможно.

Программный код для прозрачных окон не требует поправок на рамки и заголовок, и отладочный код в данном случае выведет нулевые значения переменных универсальной корректировки.

Программный код создания геометрии для фигурного окна:

void GeometryFormWindow(Panel panel)
{
    // Создание комплексной фигуры.
    GeometryGroup geometryGroup = new();
    // Вокруг центра будет 12 лепестков
    int count = 12;
    double angle = 360.0 / count;
    // Лепестки расположены на расстоянии 210 единиц от центра,
    // равномерно по окружности.
    double radius = 210;
    for (int i = 0; i < count; i++)
    {  
        // Расчет угла поворота лепестка в радианах.
        double tmp = angle * i;
        double rad = tmp * Math.PI / 180.0;

        // Координаты центров лепестков.
        double x = radius * Math.Cos(rad) + Width / 2;
        double y = radius * Math.Sin(rad) + Height / 2;

        // Создание лепестка-кружочка с установленными размерами и
        // координатами на плоскости окна. 
        geometryGroup.Children.Add
        (
            new EllipseGeometry()
            {
                Center = new Point(x, y),
                RadiusX = 50,
                RadiusY = 50,
            }
        );
    }
    // Центральный круг, лепестки вокруг него.
    EllipseGeometry eCenter = new()
    {
        Center = new Point(400, 400),
        RadiusX = 150,
        RadiusY = 150
    };
    geometryGroup.Children.Add((eCenter));

    // Собираем полностью геометрическую фигуру окна без заголовка.
    Path figureWindow = new()
    {
        // Состав фигуры
        Data = geometryGroup,
        // Цвет фигуры
        Fill = Brushes.DarkGoldenrod,
        // Без окантовки
        StrokeThickness = 0,
        // Ориентация фигуры
        HorizontalAlignment = HorizontalAlignment.Left,
        VerticalAlignment = VerticalAlignment.Top
    };

    // Добавляем фигуру в состав родительской панели.
    panel.Children.Add(figureWindow);
}

Функциональность перемещения окна

Координаты позиции окна приложения на экране монитора

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

Главная определяющая позицию окна является координата активной точка курсора. Начальную позицию окна перед перемещением можно получить имея экранные координаты указателя мыши и положения окна на экране Window.Left, Window.Top. Начальная позиция фиксируется в событии MouseDown, далее, в MouseMove разность между текущими координатами курсора и начальной позицией будет определять новое положение окна на экране.

Положение окна отсчитывается от двух крайних точек: по горизонтали - левая, по вертикали верхняя:

 --- Данные получаемые в MouseDown ---
1 способ определения смещения указателя (начальная позиция окна)
offsetPoint.X =  PointToScreen(p).X - Window.Left
offsetPoint.Y =  PointToScreen(p).Y - Window.Top
2 способ определения смещения указателя  (начальная позиция окна)
offsetPoint.X = mousePoint.X + толщина_рамки
offsetPoint.Y = mousePoint.Y + толщина_рамки + высота_заголовка
* толщина_рамки и высота_заголовка вычисляются используя размеры дочернего контейнера

--- Данные получаемые в MouseMove ---
Window.Left = PointToScreen(p).X - offsetPoint.X
Window.Top = PointToScreen(p).Y - offsetPoint.Y

* p - текущие координаты курсора мыши

Программный код перемещения окна приложения указателем мыши:

#region Функциональность перемещения окна

private bool _canMove = false;
private Point _offsetPoint = new(0, 0);
private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    Point p = e.MouseDevice.GetPosition(this);
    _canMove = true;


    // --- Первый способ вычисления смещения указателя мыши ---

    _offsetPoint.X = PointToScreen(p).X - this.Left;
    _offsetPoint.Y = PointToScreen(p).Y - this.Top;

    // --- / ---


    // --- Второй способ вычисления смещения указателя мыши ---

    /* // Значения корректировки смещения при различных стилях окна.
     // Вычисление толщины рамки вокруг окна:
     // граница + рамка изменения размеров.
     double frameThickness = (this.ActualWidth - myGrid.ActualWidth) / 2;
     // Вычисление размера заголовка + верхние рамки.
     double captionHeight = (this.ActualHeight - myGrid.ActualHeight) - frameThickness;


     // Позиция курсора относительно краёв окна.
     _offsetPoint.Y = p.Y + captionHeight;
     _offsetPoint.X = p.X + frameThickness;

     // Диагностика
     System.Diagnostics.Debug.WriteLine("frameThickness=" + frameThickness);
     System.Diagnostics.Debug.WriteLine("captionHeight=" + captionHeight);
     // --- / ---
    */

    e.MouseDevice.Capture(this);
}

private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    if (_canMove == true)
    {
        // Координаты курсора относительно окна.
        Point p = e.MouseDevice.GetPosition(this);
        
        // Новые координаты окна относительно области экрана дисплея
        // при изменениях положения указателя мыши.
        Left = PointToScreen(p).X - _offsetPoint.X;
        Top = PointToScreen(p).Y - _offsetPoint.Y;
    }
}

private void Window_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    // Запрет перемещения окна и освобождение 
    // устройства мышь после отпускания кнопки мыши.
    _canMove = false;
    e.MouseDevice.Capture(null);
}

#endregion

Исходник примера перемещения окна мышью

Исходный код написан на языке C# в среде программирования MS Visual Studio 2022, технология WPF платформа .NET6.

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