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