WPF在UserControl使用MVVM模式实现窗体移动,最大化,最小化,关闭
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>
<TextBlock
Margin="10,0,0,0"
VerticalAlignment="Center"
Foreground="White"
Text="电子雷管" />
<StackPanel
HorizontalAlignment="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>
<TextBlock
Margin="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>
<TextBlock
Margin="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,最后到达根元素。这就是为什么会看到先执行拖动行为,然后执行最小化行为的原因。
最后写上完整的成功后的代码
View
<Grid Background="#FF0078D7" x:Name="GridButton" >
<i:Interaction.Behaviors>
<local:DragBehavior />
</i:Interaction.Behaviors>
<TextBlock
Margin="10,0,0,0"
VerticalAlignment="Center"
Foreground="White"
Text="电子雷管" />
<StackPanel
HorizontalAlignment="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>
<TextBlock
Margin="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>
<TextBlock
Margin="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>
ViewModel
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
}
行为
public class DragBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
}
private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var window = Window.GetWindow(AssociatedObject);
if (window != null)
{
window.DragMove();
}
}
}