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