【问题标题】:Make WPF window draggable, no matter what element is clicked使 WPF 窗口可拖动,无论单击什么元素
【发布时间】:2011-11-17 01:54:37
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# wpf user-interface drag-and-drop


    【解决方案1】:

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

    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。让窗口管理器处理移动而不是通过记住位置和移动窗口来伪造它要好得多。 (无论如何,后一种方法在某些极端情况下也容易出错)
    • 为什么不直接设置MouseLeftButtonDown 事件,而不是签入.cs?
    • @Drowin 您可能会使用该事件,但请务必先测试它,因为MouseLeftButtonDown 具有直接路由策略,而MouseDown 具有冒泡路由策略。有关更多信息,请参阅MSDN page for MouseLeftButtonDown 的备注部分,以及如果您要使用MouseLeftButtonDown 而不是MouseDown,请注意一些额外的事情。
    • @Rachel 是的,我正在使用它并且它有效,但感谢您的解释!
    • @Rahul 拖动用户控件要困难得多...您需要将其放置在像 Canvas 这样的父面板中并手动设置 X/Y(或 Canvas.Top 和 Canvas.Left)用户移动鼠标时的属性。我上次这样做时使用了鼠标事件,因此 OnMouseDown 捕获位置并注册移动事件,OnMouseMove 更改 X/Y 和 OnMouseUp 删除移动事件。这就是它的基本思想:)
    【解决方案2】:

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

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

    【讨论】:

    • 我将它添加到构造函数中。很有魅力。
    • 如果您右键单击表单上的任意位置,这将引发异常,因为DragMove 只能在鼠标主按钮按下时调用。
    • 最好检查 ChangedButton this.MouseDown += delegate (object sender, MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left) DragMove(); };
    【解决方案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 来专门使用与事件参数关联的按钮,即使它可能永远不会重要。
    【解决方案4】:

    有时,我们无法访问Window,例如如果我们使用DevExpress,所有可用的都是UIElement

    第 1 步:添加附加属性

    解决办法是:

    1. 挂钩MouseMove 事件;
    2. 向上搜索可视化树,直到找到第一个父级Window
    3. 拨打我们新发现的Window.DragMove()

    代码:

    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。此技术适用于任何用户元素,包括 standard WPFTelerik(另一个优秀的 WPF 库提供程序)。

    【讨论】:

    • 这正是我想要的。恕我直言,后面的所有 WPF 代码都应编写为附加行为。
    【解决方案5】:

    正如@fjch1997 已经提到的,实现一个行为很方便。到这里,核心逻辑和@loi.efy的answer中的一样:

    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>
    

    【讨论】:

      【解决方案6】:

      这一切都需要!

      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();
              }
          }
      

      【讨论】:

        【解决方案7】:

        可以通过单击表单上的任意位置来拖放表单,而不仅仅是标题栏。如果你有一个无边框的表单,这很方便。

        CodeProject 上的这篇文章演示了一种可能的解决方案来实现这一点:

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

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

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

        这是视频教程中解释的类似解决方案:

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

        当用户单击所述表单中的控件时,我不允许拖动表单。当用户点击不同的控件时,他们会得到不同的结果。当我的表单突然开始移动时,因为我点击了一个列表框、按钮、标签……等等。那会令人困惑。

        【讨论】:

        • 当然它不会通过单击任何控件来移动,但是如果您单击并拖动,您不会期望表单移动。我的意思是,您不会期望按钮或列表框会移动,例如,如果您单击并拖动它,那么如果您确实尝试单击并拖动表单中的按钮,则表单的运动是一种自然的期望,我认为。跨度>
        • 猜猜,这只是个人口味。无论如何......控件将需要处理相同的鼠标事件。您必须通知这些事件的父表单,因为它们不会冒泡。
        • 另外,虽然我知道 WinForms 对此的解决方案,但我希望在 WPF 中存在一种更简单的方法,我想我应该在问题中更清楚地说明这一点(现在它只是一个标签)。
        • 对不起,我的错。没有注意到 WPF 标签。原问题中没有提到。我只是默认使用 WinForms,查看了标签。
        【解决方案8】:
        <Window
        ...
        WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
        <x:Code>
            <![CDATA[            
                private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
                {
                    DragMove();
                }
            ]]>
        </x:Code>
        

        source

        【讨论】:

          【解决方案9】:

          最有用的方法,无论是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);
              }
          

          【讨论】:

            【解决方案10】:

            将此添加到您的窗口样式(我认为属性是不言自明的)

            <Setter Property="WindowChrome.WindowChrome">
              <Setter.Value>
                <WindowChrome GlassFrameThickness="0" ResizeBorderThickness="3" CornerRadius="0" CaptionHeight="40" />
              </Setter.Value>
            </Setter>
            

            【讨论】:

              猜你喜欢
              • 2014-05-23
              • 1970-01-01
              • 2011-03-17
              • 1970-01-01
              • 2016-10-09
              • 1970-01-01
              • 2016-04-07
              • 1970-01-01
              • 2014-03-04
              相关资源
              最近更新 更多