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

我的问题有两个方面,我希望 WPF提供的解决方案比 WinForms 提供的标准解决方案更容易解决(这是 Christophe Geers 提供的,在我做出这个澄清之前)。

首先,是否有一种方法可以让 Window 变得可拖动,而不需要捕获和处理鼠标单击 + 拖动事件?我的意思是窗口可以被标题栏拖动,但是如果我设置一个窗口没有标题栏并且仍然想要拖动它,有没有一种方法可以将事件以某种方式重定向到处理标题栏拖动的任何东西?

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

110712 次浏览

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

CodeProject 上的这篇文章展示了一个可能的解决方案:

Http://www.codeproject.com/kb/cs/draggableform.aspx

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

  • 鼠标向下: 记住位置
  • 鼠标移动: 存储新的位置
  • 鼠标向上: 位置形式到新的位置

下面是视频教程中解释的一个类似的解决方案:

Http://www.youtube.com/watch?v=tjly9ax73vs

当用户单击上述窗体中的控件时,我不允许拖动窗体。当用户单击不同的控件时,会看到不同的结果。当我的表单突然开始移动,因为我点击了一个列表框,按钮,标签等,这将是令人困惑的。

当然,应用以下 WindowMouseDown事件

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

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

您可以使用 PreviewMouseDown而不是 MouseDown,但是拖动事件会吃掉 Click事件,因此您的窗口停止响应鼠标左键单击事件。如果您真的希望能够从任何控件中单击并拖动窗体,那么您可以使用 PreviewMouseDown,启动一个计时器来开始拖动操作,如果 MouseUp事件在 X 毫秒内触发,则取消操作。

如果 wpf 表单无论在哪里被点击都需要可拖动的话,最简单的方法就是使用一个委托在 windows onload 事件或者 grid load 事件上触发 DragMove ()方法

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

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

所以你确定左边的按钮在那一刻被按下了。

有时,我们不能访问 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。这种技术适用于任何用户元素,包括 标准 WPF特勒里克(另一个优秀的 WPF 库提供者)。

<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
<![CDATA[
private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
]]>
</x:Code>

来源

这些都是必须的!

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

正如 @ 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>

添加到您的窗口样式(我认为属性是不言而喻的)

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