使WPF窗口可拖动,无论单击什么元素


111

我的问题是2折,我希望WPF提供的解决方案比WinForms的标准解决方案(在我澄清之前,Christophe Geers提供的)更简单。

首先,有没有一种方法可以使Window可拖动而不捕获和处理鼠标单击+拖动事件?我的意思是窗口可以通过标题栏拖动,但是如果我将一个窗口设置为没有窗口并且仍然希望能够拖动它,是否可以通过某种方式将事件重新定向到处理标题栏拖动的任何方式?

其次,是否有一种方法可以将事件处理程序应用于窗口中的所有元素?如图所示,无论用户单击并拖动哪个元素,都使其可拖动。显然,无需手动将处理程序添加到每个元素。只是在某个地方做一次?

Answers:


284

当然,MouseDown对您的应用以下事件Window

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ChangedButton == MouseButton.Left)
        this.DragMove();
}

当用户单击/拖动任何控件时,这将允许用户拖动窗口,但吃掉MouseDown事件(e.Handled = true)的控件除外

您可以使用PreviewMouseDown代替MouseDown,但是拖动事件将吞噬该Click事件,因此您的窗口将停止响应鼠标左键单击事件。如果您确实希望能够从任何控件中单击并拖动表单,则可以使用PreviewMouseDown,启动计时器以开始拖动操作,如果MouseUp事件在X毫秒内触发,则取消操作。


+1。最好让窗口管理器来处理移动,而不是通过记住位置和移动窗口来伪装它。(无论如何,后一种方法在某些情况下也有可能出错)
Joey

为什么不只是设置MouseLeftButtonDown事件,而不是签入.cs?

1
@Drowin您可能可以改用该事件,但请确保先进行测试,因为MouseLeftButtonDown它具有直接路由策略,而MouseDown具有冒泡路由策略。有关更多信息,以及要使用over的注意事项,请参见MSDN页面上的MouseLeftButtonDown的备注部分。MouseLeftButtonDownMouseDown
雷切尔

@Rachel是的,我正在使用它并且可以使用,但是感谢您的解释!

2
@Rahul拖动UserControl困难得多……您需要将其放置在Canvas之类的父面板中,并在用户移动鼠标时手动设置X / Y(或Canvas.Top和Canvas.Left)属性。我上次这样做是使用鼠标事件,因此OnMouseDown捕获位置并注册移动事件,OnMouseMove更改X / Y和OnMouseUp删除移动事件。那是它的基本思想:)
Rachel

9

如果wpf表单无论在何处单击都需要可拖动,则简单的解决方法是使用委托在Windows onload事件或网格加载事件上触发DragMove()方法

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}

2
我将此添加到构造函数。很有魅力。
乔·约翰斯顿

1
如果右键单击表单上的任何位置,这将引发异常,因为DragMove只有在按下主鼠标按钮时才能调用该异常。
Stjepan Bakrac 2014年

4

有时,我们无权访问Window(例如,如果我们正在使用)DevExpress,则所有可用的只是一个UIElement

步骤1:添加附加属性

解决方案是:

  1. 挂接到MouseMove事件;
  2. 搜索视觉树直到找到第一个父母Window;
  3. 呼吁.DragMove()我们新发现的Window

码:

using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace DXApplication1.AttachedProperty
{
    public class EnableDragHelper
    {
        public static readonly DependencyProperty EnableDragProperty = DependencyProperty.RegisterAttached(
            "EnableDrag",
            typeof (bool),
            typeof (EnableDragHelper),
            new PropertyMetadata(default(bool), OnLoaded));

        private static void OnLoaded(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var uiElement = dependencyObject as UIElement;
            if (uiElement == null || (dependencyPropertyChangedEventArgs.NewValue is bool) == false)
            {
                return;
            }
            if ((bool)dependencyPropertyChangedEventArgs.NewValue  == true)
            {
                uiElement.MouseMove += UIElementOnMouseMove;
            }
            else
            {
                uiElement.MouseMove -= UIElementOnMouseMove;
            }

        }

        private static void UIElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            var uiElement = sender as UIElement;
            if (uiElement != null)
            {
                if (mouseEventArgs.LeftButton == MouseButtonState.Pressed)
                {
                    DependencyObject parent = uiElement;
                    int avoidInfiniteLoop = 0;
                    // Search up the visual tree to find the first parent window.
                    while ((parent is Window) == false)
                    {
                        parent = VisualTreeHelper.GetParent(parent);
                        avoidInfiniteLoop++;
                        if (avoidInfiniteLoop == 1000)
                        {
                            // Something is wrong - we could not find the parent window.
                            return;
                        }
                    }
                    var window = parent as Window;
                    window.DragMove();
                }
            }
        }

        public static void SetEnableDrag(DependencyObject element, bool value)
        {
            element.SetValue(EnableDragProperty, value);
        }

        public static bool GetEnableDrag(DependencyObject element)
        {
            return (bool)element.GetValue(EnableDragProperty);
        }
    }
}

步骤2:将附加属性添加到任何元素以使其拖动窗口

如果我们添加以下附加属性,则用户可以通过单击特定元素来拖动整个窗口:

<Border local:EnableDragHelper.EnableDrag="True">
    <TextBlock Text="Click me to drag this entire window"/>
</Border>

附录A:可选的高级示例

DevExpress的此示例中,我们将停靠窗口的标题栏替换为我们自己的灰色矩形,然后确保如果用户单击并拖动所述灰色矩形,则窗口将正常拖动:

<dx:DXWindow x:Class="DXApplication1.MainWindow" Title="MainWindow" Height="464" Width="765" 
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking" 
    xmlns:local="clr-namespace:DXApplication1.AttachedProperty"
    xmlns:dxdove="http://schemas.devexpress.com/winfx/2008/xaml/docking/visualelements"
    xmlns:themeKeys="http://schemas.devexpress.com/winfx/2008/xaml/docking/themekeys">

    <dxdo:DockLayoutManager FloatingMode="Desktop">
        <dxdo:DockLayoutManager.FloatGroups>
            <dxdo:FloatGroup FloatLocation="0, 0" FloatSize="179,204" MaxHeight="300" MaxWidth="400" 
                             local:TopmostFloatingGroupHelper.IsTopmostFloatingGroup="True"                             
                             >
                <dxdo:LayoutPanel ShowBorder="True" ShowMaximizeButton="False" ShowCaption="False" ShowCaptionImage="True" 
                                  ShowControlBox="True" ShowExpandButton="True" ShowInDocumentSelector="True" Caption="TradePad General" 
                                  AllowDock="False" AllowHide="False" AllowDrag="True" AllowClose="False"
                                  >
                    <Grid Margin="0">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Border Grid.Row="0" MinHeight="15" Background="#FF515151" Margin="0 0 0 0"
                                                                  local:EnableDragHelper.EnableDrag="True">
                            <TextBlock Margin="4" Text="General" FontWeight="Bold"/>
                        </Border>
                        <TextBlock Margin="5" Grid.Row="1" Text="Hello, world!" />
                    </Grid>
                </dxdo:LayoutPanel>
            </dxdo:FloatGroup>
        </dxdo:DockLayoutManager.FloatGroups>
    </dxdo:DockLayoutManager>
</dx:DXWindow>

免责声明:我隶属于DevExpress。该技术可与任何用户元素一起使用,包括标准WPFTelerik(另一个优秀的WPF库提供程序)。


1
这正是我想要的。恕我直言,所有后面的WPF代码都应作为附加行为编写。
fjch1997 '17

3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

在某些情况下会引发异常(即,如果在窗口上还有一个可单击的图像,则在单击该图像时会打开一个消息框。当您从消息框退出时,您会得到错误提示),使用起来更安全

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
            this.DragMove();
}

因此,您可以确定此时已按下向左按钮。


我使用e.LeftButton而不是Mouse.LeftButton专门使用与事件args关联的按钮,即使它可能永远都没有关系。
Fls'Zen 2015年

2

可以通过单击表单上的任意位置(而不仅仅是标题栏)来拖放表单。如果您具有无边界格式,这将很方便。

本文在CodeProject上演示了实现此目的的一种可能的解决方案:

http://www.codeproject.com/KB/cs/DraggableForm.aspx

基本上,将创建Form类型的后代,在其中处理鼠标的下移,上移和移动事件。

  • 鼠标向下:记住位置
  • 鼠标移动:存储新位置
  • 鼠标向上:将表格定位到新位置

这是一个视频教程中介绍的类似解决方案:

http://www.youtube.com/watch?v=tJlY9aX73Vs

当用户单击所说表单中的控件时,我将不允许拖动表单。用户单击不同的控件时会得到不同的结果。当我单击列表框,按钮,标签等导致表格突然开始移动时。那会令人困惑。


当然,单击任何控件都不会移动它,但是如果单击并拖动它,就不会期望表单移动。我的意思是,我希望您不希望按钮或列表框发生移动,例如,单击并拖动按钮,如果您确实尝试单击并拖动表单中的按钮,则窗体的运动是自然的期望。
亚历克斯K

猜猜,那只是个人喜好。无论如何...控件将需要处理相同的鼠标事件。您将不得不将这些事件通知父表单,因为它们不会冒泡。
Christophe Geers 2011年

另外,虽然我知道WinForms解决方案,但我希望在WPF中存在一种更简单的方法,但我想我应该在问题中更清楚地说明(现在它只是一个标记)。
亚历克斯K

对不起这是我的错。没有注意到WPF标签。原始问题中未提及。我只是假定WinForms默认情况下,查看了标签。
Christophe Geers 2011年

2

@ fjch1997所述,实现行为很方便。在这里,核心逻辑与@ loi.efy的答案相同

public class DragMoveBehavior : Behavior<Window>
{
    protected override void OnAttached()
    {
        AssociatedObject.MouseMove += AssociatedObject_MouseMove;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
    }

    private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed && sender is Window window)
        {
            // In maximum window state case, window will return normal state and
            // continue moving follow cursor
            if (window.WindowState == WindowState.Maximized)
            {
                window.WindowState = WindowState.Normal;

                // 3 or any where you want to set window location after
                // return from maximum state
                Application.Current.MainWindow.Top = 3;
            }

            window.DragMove();
        }
    }
}

用法:

<Window ...
        xmlns:h="clr-namespace:A.Namespace.Of.DragMoveBehavior"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <i:Interaction.Behaviors>
        <h:DragMoveBehavior />
    </i:Interaction.Behaviors>
    ...
</Window>

1

这都是需要的!

private void UiElement_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            if (this.WindowState == WindowState.Maximized) // In maximum window state case, window will return normal state and continue moving follow cursor
            {
                this.WindowState = WindowState.Normal;
                Application.Current.MainWindow.Top = 3;// 3 or any where you want to set window location affter return from maximum state
            }
            this.DragMove();
        }
    }

0

对于WPF和Windows窗体,最有用的方法是WPF示例:

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);

    public static void StartDrag(Window window)
    {
        WindowInteropHelper helper = new WindowInteropHelper(window);
        SendMessage(helper.Handle, 161, 2, 0);
    }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.