XAML 拖动窗口时,应用程序界面停止响应

9avjhtql  于 2023-04-18  发布在  其他
关注(0)|答案(1)|浏览(125)

我需要为应用程序窗口实现自己的标题栏。为此,我创建了一个UserControl。为了实现移动窗口,我使用了SendMessage(_windowInteropHelper.Handle, 0x112, 0xf012, 0)。但在我移动窗口后,整个应用程序的界面停止响应我的操作(我无法在文本字段中输入数据,当我将鼠标悬停在按钮上时效果不起作用,等等)。我尝试添加Mouse.Capture(null),但它不起作用。我如何解决这个问题?
WindowToolBarControl.xaml.cs代码:

namespace Calendar.Views.Controls
{
/// <summary>
/// Interaction logic for WindowToolBarControl.xaml
/// </summary>
public partial class WindowToolBarControl : UserControl
{
    #region Dependency Properties
    #region CornerRadius
    public CornerRadius CornerRadius
    {
        get => (CornerRadius)GetValue(CornerRadiusProperty);
        set => SetValue(CornerRadiusProperty, value);
    }

    public static readonly DependencyProperty CornerRadiusProperty =
        DependencyProperty.Register(
            nameof(CornerRadius),
            typeof(CornerRadius),
            typeof(WindowToolBarControl),
            new PropertyMetadata(new CornerRadius(0))
        );
    #endregion

    #region Title
    public string Title
    {
        get => (string)GetValue(TitleProperty);
        set => SetValue(TitleProperty, value);
    }

    public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
        nameof(Title),
        typeof(string),
        typeof(WindowToolBarControl),
        new PropertyMetadata(string.Empty)
    );
    #endregion

    #region Parent Window
    public Window ParentWindow
    {
        get => (Window)GetValue(ParentWindowProperty);
        set => SetValue(ParentWindowProperty, value);
    }

    public static readonly DependencyProperty ParentWindowProperty =
        DependencyProperty.Register(
            nameof(ParentWindow),
            typeof(Window),
            typeof(WindowToolBarControl),
            new PropertyMetadata(null)
        );
    #endregion
    #endregion

    #region Properties and fields
    private WindowToolBarViewModel _viewModel;
    private WindowInteropHelper _windowInteropHelper;
    #endregion

    #region Mouse Capture
    [DllImport("user32.DLL", EntryPoint = "ReleaseCapture")]
    private static extern void ReleaseCapture();

    [DllImport("user32.DLL", EntryPoint = "SendMessage")]
    private static extern void SendMessage(System.IntPtr hwnd, int wmsg, int wparam, int lparam);
    #endregion

    #region Constructor
    public WindowToolBarControl()
    {
        InitializeComponent();
    }
    #endregion

    #region Methods of Events
    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        _windowInteropHelper = new WindowInteropHelper(ParentWindow);

        _viewModel = DataContext as WindowToolBarViewModel;
        if (_viewModel == null) return;
        _viewModel.OnRequestClose += (o, args) => { ParentWindow?.Close(); };
        _viewModel.OnRequestCollapse += (o, args) => { ParentWindow.WindowState = WindowState.Minimized; };
    }

    private void OnMouseDown(object sender, MouseButtonEventArgs e)
    {
        if (ParentWindow == null) return;
        e.Handled = false;

        ReleaseCapture();
        SendMessage(_windowInteropHelper.Handle, 0x112, 0xf012, 0);

        Mouse.Capture(null);
    }
    #endregion
   }
}

WindowToolBarViewModel.cs代码:

internal class WindowToolBarViewModel : ViewModelBase
{
    #region Events
    public event EventHandler OnRequestClose;
    public event EventHandler OnRequestCollapse;
    #endregion

    #region Commands
    public DelegateCommand CloseCommand { get; }
    public DelegateCommand CollapseCommand { get; }
    #endregion

    #region Constructor
    public WindowToolBarViewModel()
    {
        CloseCommand = new DelegateCommand(Close);
        CollapseCommand = new DelegateCommand(Collapse);
    }
    #endregion

    #region Private Methods
    private void Close() => OnRequestClose?.Invoke(this, EventArgs.Empty);

    private void Collapse() => OnRequestCollapse?.Invoke(this, EventArgs.Empty);
    #endregion
}

xaml代码

<UserControl x:Class="Calendar.Views.Controls.WindowToolBarControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:viewModels="clr-namespace:Calendar.ViewModels"
         mc:Ignorable="d"
         d:DesignWidth="300"
         x:Name="UserControl"
         HorizontalAlignment="Stretch"
         Loaded="OnLoaded">
<UserControl.DataContext>
    <viewModels:WindowToolBarViewModel />
</UserControl.DataContext>

<Border CornerRadius="{Binding ElementName=UserControl, Path=CornerRadius}"
        Background="{DynamicResource Secondary}"
        ClipToBounds="True">
    <Grid Margin="0"
          ClipToBounds="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Border Grid.Column="0"
                Background="Transparent"
                PreviewMouseDown="OnMouseDown">
            <TextBlock Text="{Binding ElementName=UserControl, Path=Title, UpdateSourceTrigger=PropertyChanged}"
                       VerticalAlignment="Center"
                       Margin="10 0"
                       FontFamily="{DynamicResource Inter}"
                       Foreground="{DynamicResource TextColor}" />
        </Border>

        <StackPanel Grid.Column="1"
                    Orientation="Horizontal">
            <Button Command="{Binding CollapseCommand}"
                    Style="{DynamicResource DefaultControlButton}"
                    Padding="5"
                    Margin="0 0 0 0">
                <Image Source="{DynamicResource MinusIcon}"
                       Width="15"
                       Height="15" />
            </Button>

            <Button Command="{Binding CloseCommand}"
                    Style="{DynamicResource CloseControlButton}"
                    Padding="5"
                    Margin="0 0 15 0">
                <Image Source="{DynamicResource XMarkIcon}"
                       Width="15"
                       Height="15" />
            </Button>
        </StackPanel>
    </Grid>
</Border>

我就是这么用的:

<controls:WindowToolBarControl Grid.Row="0"
                               ParentWindow="{Binding ElementName=Window}"
                               Title="{Binding ElementName=Window, Path=Title}"
                               CornerRadius="10 10 0 0" />
mwkjh3gx

mwkjh3gx1#

如果你想提供一个自定义标题栏(窗口chrome或非客户区),那么你应该覆盖WindowChromeControlTemplate
这样就不必重新实现标题栏的所有默认功能。
例如,窗口拖动和调整大小将工作的权利开箱即用。
只是一些关于你的代码的一般说明:你不应该在视图模型中处理Window状态。而是使用路由命令或事件处理程序,并在Window的代码隐藏中处理它们(或一般的控件)。
每个控件一个视图模型类在类设计方面并不好用,当这个视图模型类被用来容纳视图相关的代码,比如UI逻辑,这样的设计甚至违反了MVVM模式。
MVVM要求所有与UI相关的代码都在视图中。例如,在WPF中,这将使用XAML或代码隐藏在类或分部类中。
糟糕的设计是通过你笨拙的实现来证明的,你把UI相关的动作路由到视图模型,只是为了引发一个事件,视图本质上是这个事件的触发器,然后通过观察视图模型来处理这个事件。
一个与UI相关的动作(例如close Window)被路由到视图模型,这证明你违反了MVVM。
视图模型类用于将数据从模型类呈现给视图类。它们不应该保存视图状态或视图逻辑。
下面的示例创建一个完整的自定义窗口镶边。
该示例显示如何正确覆盖Window的模板,以及如何使用系统常量(SystemParameters)创建遵循当前Windows主题化约束的布局。
该示例还使用了预定义的WPF路由命令及其助手API,两者都在SystemComands中定义。

主窗口.xaml

<Window Style="{StaticResource WindowStyle}">
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private WindowState OldWindowState { get; set; }

  public MainWindow()
  {
    InitializeComponent();

    var minimizeWindowCommandBinding = new CommandBinding(SystemCommands.MinimizeWindowCommand, ExecuteMinimizeWindowCommand, CanExecuteWindowCommands);
    _ = this.CommandBindings.Add(minimizeWindowCommandBinding);

    var maximizeWindowCommandBinding = new CommandBinding(SystemCommands.MaximizeWindowCommand, ExecuteMaximizeWindowCommand, CanExecuteWindowCommands);
    _ = this.CommandBindings.Add(maximizeWindowCommandBinding);

    var restoreWindowCommandBinding = new CommandBinding(SystemCommands.RestoreWindowCommand, ExecuteRestoreWindowCommand, CanExecuteWindowCommands);
    _ = this.CommandBindings.Add(restoreWindowCommandBinding);

    var closeWindowCommandBinding = new CommandBinding(SystemCommands.CloseWindowCommand, ExecuteCloseWindowCommand, CanExecuteWindowCommands);
    _ = this.CommandBindings.Add(closeWindowCommandBinding);
  }

  private void CanExecuteWindowCommands(object sender, CanExecuteRoutedEventArgs e)
    => e.CanExecute = true;

  private void ExecuteMinimizeWindowCommand(object sender, ExecutedRoutedEventArgs e)
    => SystemCommands.MinimizeWindow(this);

  private void ExecuteMaximizeWindowCommand(object sender, ExecutedRoutedEventArgs e)
    => SystemCommands.MaximizeWindow(this);

  private void ExecuteRestoreWindowCommand(object sender, ExecutedRoutedEventArgs e)
    => SystemCommands.RestoreWindow(this);

  private void ExecuteCloseWindowCommand(object sender, ExecutedRoutedEventArgs e)
    => SystemCommands.CloseWindow(this);
}

App.xaml

<Application xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework">
  <Application.Resources>
    <Style x:Key="WindowStyle"
           TargetType="Window">
      <Setter Property="SnapsToDevicePixels"
              Value="True" />
      <Setter Property="WindowStyle"
              Value="None" />
      <Setter Property="WindowChrome.WindowChrome">
        <Setter.Value>
          <WindowChrome NonClientFrameEdges="Right"
                        ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
        </Setter.Value>
      </Setter>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Window">
            <Border x:Name="ResizeBorder">
              <Border Background="{TemplateBinding Background}"
                      BorderBrush="{TemplateBinding BorderBrush}"
                      BorderThickness="{TemplateBinding BorderThickness}">
                <Grid Background="Transparent">
                  <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" /> <!-- Title bar row -->
                    <RowDefinition /> <!-- Window content row -->
                    <RowDefinition Height="Auto" /> <!-- Resize gripper row -->
                  </Grid.RowDefinitions>

                  <!-- Window.Content host -->
                  <AdornerDecorator Grid.Row="1">
                    <Border Background="Transparent"
                            Margin="{x:Static SystemParameters.WindowNonClientFrameThickness}">
                      <ContentPresenter />
                    </Border>
                  </AdornerDecorator>

                  <ResizeGrip x:Name="WindowResizeGrip"
                              Grid.Row="2"
                              HorizontalAlignment="Right"
                              VerticalAlignment="Bottom"
                              Visibility="Collapsed"
                              IsTabStop="false" />

                  <!-- Custom window chrome host -->
                  <Grid Grid.Row="0"
                        Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
                        Background="#FF3F3F3F"
                        VerticalAlignment="Top">
                    <Grid.ColumnDefinitions>
                      <ColumnDefinition />
                      <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>

                    <!-- Custom window chrome -->
                    <StackPanel Grid.Column="0"
                                Orientation="Horizontal"
                                Margin="{x:Static SystemParameters.WindowResizeBorderThickness}">
                      <Image Source="{TemplateBinding Icon}" />
                      <TextBlock Text="{TemplateBinding Title}"
                                  TextTrimming="CharacterEllipsis"
                                  VerticalAlignment="Center"
                                  Margin="16,0" />

                      <Menu shell:WindowChrome.IsHitTestVisibleInChrome="True">
                        <MenuItem Header="CustomChromeMenu">
                          <MenuItem Header="Action" />
                        </MenuItem>
                      </Menu>
                    </StackPanel>

                    <StackPanel Grid.Column="1"
                                Orientation="Horizontal"
                                HorizontalAlignment="Right">
                      <Button Command="{x:Static SystemCommands.MinimizeWindowCommand}"
                              Width="45"
                              Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
                              ToolTip="Minimize window"
                              ToolTipService.ShowDuration="5000"
                              shell:WindowChrome.IsHitTestVisibleInChrome="True">
                        <TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
                                    FontFamily="Segoe MDL2 Assets"
                                    FontSize="11"
                                    Text="&#xE921;" />
                      </Button>
                      <Button x:Name="MaximizeAndRestoreButton"
                              Command="{x:Static SystemCommands.MaximizeWindowCommand}"
                              ToolTip="Maximize window"
                              Width="45"
                              Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
                              ToolTipService.ShowDuration="5000"
                              shell:WindowChrome.IsHitTestVisibleInChrome="True">
                        <TextBlock x:Name="MaximizeAndRestoreIcon"
                                    Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
                                    FontFamily="Segoe MDL2 Assets"
                                    FontSize="11"
                                    Text="&#xE922;" />
                      </Button>
                      <Button Command="{x:Static SystemCommands.CloseWindowCommand}"
                              ToolTip="Close application"
                              ToolTipService.ShowDuration="5000"
                              Width="50"
                              Height="{Binding Source={x:Static SystemParameters.WindowNonClientFrameThickness}, Path=Top}"
                              shell:WindowChrome.IsHitTestVisibleInChrome="True">
                        <TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=Control}, Path=Foreground}"
                                    FontFamily="Segoe MDL2 Assets"
                                    FontSize="11"
                                    Text="&#xE8BB;" />
                      </Button>
                    </StackPanel>
                  </Grid>
                </Grid>
              </Border>
            </Border>

            <ControlTemplate.Triggers>
              <Trigger Property="ResizeMode"
                       Value="CanResizeWithGrip">
                <Setter TargetName="WindowResizeGrip"
                        Property="Visibility"
                        Value="Visible" />
              </Trigger>
              <Trigger Property="WindowState"
                       Value="Maximized">
                <Setter TargetName="ResizeBorder"
                        Property="BorderThickness"
                        Value="{x:Static SystemParameters.WindowResizeBorderThickness}" />
                <Setter TargetName="MaximizeAndRestoreButton"
                        Property="Command"
                        Value="{x:Static SystemCommands.RestoreWindowCommand}" />
                <Setter TargetName="MaximizeAndRestoreIcon"
                        Property="Text"
                        Value="&#xE923;" />
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Application.Resources>
</Application>

相关问题