1、在WPF中,我们移动窗体,可以使用MouseDown或者MouseLeftButtonDown去触发DragMove方法
2、当我们使用UserControl的时候,它是没有DragMove方法的,这个时候怎么办
我们改为命令的形式,可以直接调出当前的窗体,或者将窗体当参数传入到ViewModel,也没问题
我写了
<i:Interaction.Triggers><i:EventTrigger EventName="MouseDown" SourceName="GridButton"><i:InvokeCommandAction Command="{Binding DragmoveCommand, UpdateSourceTrigger=PropertyChanged}" /></i:EventTrigger></i:Interaction.Triggers>
当然,它内部是可以传递参数的,我们能成功实现,
但是,注意了,注意了,
如果我们的xaml具有多个事件命令,此时就会出现问题
<Grid Background="#FF0078D7" x:Name="GridButton" ><i:Interaction.Triggers><i:EventTrigger EventName="MouseDown" SourceName="GridButton"><i:InvokeCommandAction Command="{Binding DragmoveCommand, UpdateSourceTrigger=PropertyChanged}" /></i:EventTrigger></i:Interaction.Triggers><TextBlockMargin="10,0,0,0"VerticalAlignment="Center"Foreground="White"Text="电子雷管" /><StackPanelHorizontalAlignment="Right"VerticalAlignment="Center"Orientation="Horizontal"><TextBlock Foreground="White" Text="—" x:Name="MinimizeButton"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown" SourceName="MinimizeButton"><i:InvokeCommandAction Command="{Binding MinCommand, UpdateSourceTrigger=PropertyChanged}" /></i:EventTrigger></i:Interaction.Triggers></TextBlock><TextBlockMargin="15,0,15,0"Foreground="White"Text="☐"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown" ><i:InvokeCommandAction Command="{Binding MaxCommand, Mode=OneWay}" /></i:EventTrigger></i:Interaction.Triggers></TextBlock><TextBlockMargin="0,0,15,0"Foreground="White"Text="✕"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown"><i:InvokeCommandAction Command="{Binding CloseCommand, UpdateSourceTrigger=PropertyChanged}" /></i:EventTrigger></i:Interaction.Triggers></TextBlock></StackPanel></Grid>
界面如下
上面就是我的整体全部代码,现在我移动,最大化,关闭功能都是好的,但是增加了移动的功能后,点击最小化,移动的方法出问题,说CurrentWindow是null,我打断点查看了一下,增加移动的功能后,我点击最大化,先执行ExecuteDragmove在执行最大化,在执行ExecuteDragmove,我猜想是WPF的<i:Interaction.Triggers>有路由的功能,虽然最大化的前后都执行一次ExecuteDragmove方法,但是我的界面是使用 return Application.Current.Windows.OfType
因此我使用了SourceName,但是它只能阻止最大化之前和最小化之前不会触发移动,
首先,我想到了附加属性
public static class WindowHelper{public static readonly DependencyProperty DragMoveProperty =DependencyProperty.RegisterAttached("DragMove", typeof(bool), typeof(WindowHelper), new PropertyMetadata(false, OnDragMoveChanged));public static bool GetDragMove(DependencyObject obj){return (bool)obj.GetValue(DragMoveProperty);}public static void SetDragMove(DependencyObject obj, bool value){obj.SetValue(DragMoveProperty, value);}private static void OnDragMoveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){if (d is UIElement element && (bool)e.NewValue){element.MouseDown += Element_MouseDown;}}private static void Element_MouseDown(object sender, MouseButtonEventArgs e){if (e.LeftButton == MouseButtonState.Pressed){var window = ((FrameworkElement)sender).TemplatedParent as Window;if (window != null){window.DragMove();}}}}
然后Grid使用local:WindowHelper.DragMove="True"
但是我使用的是UserControl所以他不起作用
因此我想当了行为,我将Window的DragMove方法封装成行为给Grid,
在WPF中,行为(Behavior)是一种设计模式,它允许您将可重用的功能附加到UI元素上,而无需修改元素本身的代码。行为通常用于封装复杂的交互逻辑,使其更易于管理和复用。
在WPF中,事件路由有两种主要类型:直接路由(Direct Routing)和冒泡路由(Bubble Routing)。此外,还有一种隧道路由(Tunneling Routing),但它在WPF中不如直接和冒泡路由常见。
直接路由(Direct Routing)
直接路由事件不会在元素树中传播。它们仅在触发事件的元素上触发,并且仅触发一次。Behavior通常使用直接路由,因为它们是直接附加到特定元素上的。
冒泡路由(Bubble Routing)
冒泡路由事件从触发事件的元素开始,然后向上遍历元素树,直到到达根元素。这意味着如果一个子元素触发了事件,该事件会首先在子元素上触发,然后在其父元素上触发,依此类推,直到到达根元素。
隧道路由(Tunneling Routing)
隧道路由事件从根元素开始,向下遍历元素树,直到到达触发事件的元素。这种类型的路由在WPF中主要用于处理预览事件(例如PreviewMouseDown),它们在相应的冒泡事件之前发生。
事件路由顺序
在WPF中,事件的路由顺序是:
隧道事件(从根到目标)
直接事件(在目标上)
冒泡事件(从目标到根)
您的问题分析
在我的代码中,使用了EventTrigger来处理鼠标左键按下事件。默认情况下,EventTrigger使用冒泡路由。这就是为什么当我点击最小化按钮时,首先触发了Grid上的拖动行为,然后才触发了最小化按钮上的最小化行为。
<i:EventTrigger EventName="MouseLeftButtonDown"><i:InvokeCommandAction Command="{Binding DragmoveCommand, UpdateSourceTrigger=PropertyChanged}" />
</i:EventTrigger>
由于冒泡路由的特性,当我点击最小化按钮时,事件首先在按钮上触发,然后向上冒泡到包含它的Grid,最后到达根元素。这就是为什么会看到先执行拖动行为,然后执行最小化行为的原因。
最后写上完整的成功后的代码
<Grid Background="#FF0078D7" x:Name="GridButton" ><i:Interaction.Behaviors><local:DragBehavior /></i:Interaction.Behaviors><TextBlockMargin="10,0,0,0"VerticalAlignment="Center"Foreground="White"Text="电子雷管" /><StackPanelHorizontalAlignment="Right"VerticalAlignment="Center"Orientation="Horizontal"><TextBlock Foreground="White" Text="—" x:Name="MinimizeButton"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown" SourceName="MinimizeButton"><i:InvokeCommandAction Command="{Binding MinCommand, UpdateSourceTrigger=PropertyChanged}" /></i:EventTrigger></i:Interaction.Triggers></TextBlock><TextBlockMargin="15,0,15,0"Foreground="White"Text="☐"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown" ><i:InvokeCommandAction Command="{Binding MaxCommand, Mode=OneWay}" /></i:EventTrigger></i:Interaction.Triggers></TextBlock><TextBlockMargin="0,0,15,0"Foreground="White"Text="✕"><i:Interaction.Triggers><i:EventTrigger EventName="MouseLeftButtonDown"><i:InvokeCommandAction Command="{Binding CloseCommand, UpdateSourceTrigger=PropertyChanged}" /></i:EventTrigger></i:Interaction.Triggers></TextBlock></StackPanel></Grid>
public class HeaderViewModel:ControlViewModelBase{private Window CurrentWindow=>GetCurrentWindow();public HeaderViewModel(){DragmoveCommand = MinidaoCommand.Create(ExecuteDragmove);MinCommand = MinidaoCommand.Create(ExecuteMin);MaxCommand = MinidaoCommand.Create(ExecuteMax);CloseCommand = MinidaoCommand.Create(ExecuteClose);}#region--命令--public ICommand DragmoveCommand { get; set; }public ICommand MinCommand { get; set; }public ICommand MaxCommand { get; set; }public ICommand CloseCommand { get; set;}#endregion#region--方法--private Window GetCurrentWindow(){return Application.Current.Windows.OfType<Window>().SingleOrDefault(w => w.IsActive);}private void ExecuteDragmove(){CurrentWindow.DragMove();}private void ExecuteClose(){Application.Current.Shutdown();}private void ExecuteMax(){WindowState state = CurrentWindow.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;CurrentWindow.WindowState = state;}private void ExecuteMin(){if (CurrentWindow.WindowState == WindowState.Maximized || CurrentWindow.WindowState == WindowState.Normal){CurrentWindow.WindowState = WindowState.Minimized;}}#endregion}