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>

界面如下
image
上面就是我的整体全部代码,现在我移动,最大化,关闭功能都是好的,但是增加了移动的功能后,点击最小化,移动的方法出问题,说CurrentWindow是null,我打断点查看了一下,增加移动的功能后,我点击最大化,先执行ExecuteDragmove在执行最大化,在执行ExecuteDragmove,我猜想是WPF的<i:Interaction.Triggers>有路由的功能,虽然最大化的前后都执行一次ExecuteDragmove方法,但是我的界面是使用 return Application.Current.Windows.OfType().SingleOrDefault(w => w.IsActive);获取的,只有在电脑屏幕活动,哪怕在最大化前后各执行一次ExecuteDragmove方法也不会出错,但是最小化功能不同,触发最小化的时候,它肯定前后也会走路由,也就是点击最小化前后都会执行一次ExecuteDragmove方法,但似乎最小化的同时,屏幕上没有活动的窗体,所以ExecuteDragmove方法中CurrentWindow将会是null,所以解决的方法应该关闭路由
因此我使用了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();
          }
      }
  }
posted @ 2024-09-07 02:28  孤沉  阅读(291)  评论(0编辑  收藏  举报