WPF在UserControl使用MVVM模式實現窗體移動,最大化,最小化,關閉

孤沉發表於2024-09-07

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

相關文章