WPF 无边框窗口问题:Aero Snap & Maximing(英语:Aero Snap & Maxima)



我通过在XAML中设置以下窗口属性创建了一个无边界WPF窗口:

... WindowStyle="None" AllowsTransparency="True" ...

这会导致许多问题:

1)已解决:它不再具有任何内置的调整大小功能

2)已解决:它不再具有任何内置的拖动功能

3)已解决:如果没有顶部工具栏,它将不再具有最小化/最大化/恢复/关闭按钮

4)已解决:通过空气捕捉或设置WindowState最大化可防止其取消捕捉。

5)通过空气捕捉或设置WindowState最大化将使用整个屏幕作为边界,与窗口工具栏重叠。

6)通过空气捕捉或设置WindowState实现最大化似乎包括-7的裕度,使窗口每侧超过窗口边缘的7个像素。

1-3通过制作xaml窗口模板来解决。我使用了不可见的矩形作为句柄区域,并通过重写OnApplyTemplate()应用了后面的一些代码,通过user32.dll SendMessage(…)附加功能,以调整大小/移动/最小化/最大化/恢复/关闭。

我在这里找到了#4的答案

我尝试通过WndProc拦截最大化消息并手动设置大小/位置来解决5-6问题,但这存在将RestoreRegion覆盖到最大大小/位置的问题,从而无法恢复窗口。

真正奇怪的是,将窗口从顶部边界调整到屏幕顶部会很好地触发aero全高捕捉,一点问题都没有。

所以,我已经走了很长的路,但5-6仍然是一个问题。。。有没有办法手动指定最大化区域?或者,有没有一种方法可以在不影响restoreregion属性的情况下设置窗口大小?

最简单的完整解决方案

你好,以下解决方案以最简单的方式修复了问题中详细介绍的所有问题,并使用WPF和最新版本的C#语言和.NET框架在Windows 10上运行。截至2017年3月15日。如果它停止工作,请告诉我。

步骤1:要解决应用程序XAML中<Window ... > </Window>标记中的问题1、2和4,请将其粘贴到顶部或底部:

<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="35"/>
<WindowChrome.WindowChrome>

CaptionHeight是窗口拖动区域所需的高度。

步骤2:要解决问题3,您需要创建标题栏和标题以及窗口控件。要做到这一点,只需为每个所需的标题栏元素指定一个VerticalAlignment为Top,或者将它们放入一个将其VerticalAlign设置为Top的网格中,这将对所有元素执行此操作,但请确保它们的高度不大于步骤1中XAML中声明的WindowChrome元素上的CaptionHeight属性。对于所有按钮,必须为它们或它们的容器分配属性WindowChrome.IsHitTestVisibleInChrome="True"。这里有一个例子:

<Grid VerticalAlignment="Top" Background="White" Name="TitleBar" Height="35">
<Label Content="Borderless Window Test" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<StackPanel WindowChrome.IsHitTestVisibleInChrome="True" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal" Name="WindowControls">
<Button Height="35" Width="35" Content="-" Padding="0" Name="MinimizeButton"/>
<Button Height="35" Width="35" Content="+" Padding="0" Name="MaximizeButton"/>
<Button Height="35" Width="35" Content="x" Padding="0" Name="CloseButton"/>
</StackPanel>
</Grid>

现在,要为窗口控制按钮添加适当的功能,请在您的应用程序的C#源代码codeehind的MainWindow()构造函数中,在调用InitializeComponent();:之后粘贴以下内容

CloseButton.Click += (s, e) => Close();
MaximizeButton.Click += (s, e) => WindowState = WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;
MinimizeButton.Click += (s, e) => WindowState = WindowState.Minimized;

步骤3:要解决问题5和6,您需要挂接WmGetMinMaxInfo。要做到这一点,请转到代码绑定,然后将此Pastebin中的所有内容复制并粘贴到Window类中。现在,在MainWindow()构造函数中,粘贴:

SourceInitialized += (s, e) =>
{
IntPtr handle = (new WindowInteropHelper(this)).Handle;
HwndSource.FromHwnd(handle).AddHook(new HwndSourceHook(WindowProc));
};

通过文件菜单中的Project > Add References,确保参考:

System.Management
System.Windows.Interop
System.Security.Principal
System.Runtime.InteropServices
Microsoft.Win32

最好的检查方法是单击左上角的Assemblies选项卡,然后选择Framework,然后使用窗口右上角的搜索框。现在,将所有这些using(名称空间)添加到代码的顶部

using System.Management;
using System.Windows.Interop;
using System.Security.Principal;
using System.Runtime.InteropServices;
using Microsoft.Win32;

这应该涵盖所有内容。我希望这能有所帮助!

我只是自己经历了整个过程。这真是一件烦人的事,因为你必须手动解释这么多。有趣的是,这些天我们认为很多事情都是理所当然的,就像一个基本的窗口是如何运行的一样简单。但是,看看我提供的这个示例代码,就可以很好地表明这个问题到底有多严重。

我希望这能有所帮助,因为我自己花了一点时间才来到这里。

主窗口.Xaml

<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
Background="Transparent"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip"
AllowsTransparency="True"
WindowStyle="None"
mc:Ignorable="d"
Title="Test Window Behavior" Height="768" Width="1024" StateChanged="Window_StateChanged" PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown">
<Grid>
<DockPanel Grid.Column="1" Grid.Row="1">
<DockPanel x:Name="titleBar" Background="White" DockPanel.Dock="Top">
<Rectangle Width="32" Height="32" DockPanel.Dock="Left" Fill="Red" Margin="2"/>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" Margin="2">
<!-- Minimize Button -->
<Border Width="24" Height="24" Margin="2" HorizontalAlignment="Right" MouseLeftButtonUp="OnMinimizeWindow" Grid.Column="2">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FFD0D0D0" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" Text="0" FontFamily="Webdings" />
</Border>
<!-- Maximize Button -->
<Border Width="24" Height="24" Margin="2" HorizontalAlignment="Right" MouseLeftButtonUp="OnMaximizeWindow" Grid.Column="3">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FFD0D0D0" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock x:Name="IsMaximized" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Webdings">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InternalWindowState, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" Value="Maximized">
<Setter Property="Text" Value="2" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<!-- Close Button -->
<Border Width="24" Height="24" Margin="2" HorizontalAlignment="Right" MouseLeftButtonUp="OnCloseWindow" Grid.Column="4">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" Text="r" FontFamily="Webdings" />
</Border>
</StackPanel>
<Label MouseLeftButtonDown="OnDragMoveWindow" MouseDoubleClick="OnMaximizeWindow" Margin="8 0 0 0" FontSize="12" VerticalContentAlignment="Center" Content="{Binding Path=Title, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, FallbackValue='Main Window'}" />
</DockPanel>
<Grid Background="White" DockPanel.Dock="Bottom" Height="32">
<Label VerticalContentAlignment="Center" Content="Statusbar Text Goes Here ..." />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<!-- Top 3 -->
<Border Background="Gray" Grid.Row="0" Grid.Column="0" />
<Border Background="Gray" Grid.Row="0" Grid.Column="1" BorderBrush="Black" BorderThickness="0 0 0 1" />
<Border Background="Gray" Grid.Row="0" Grid.Column="2" />
<!-- Middle 2 -->
<Border Background="Gray" Grid.Row="1" Grid.Column="0" BorderBrush="Black" BorderThickness="0 0 1 0" />
<Border Background="Gray" Grid.Row="1" Grid.Column="2" BorderBrush="Black" BorderThickness="1 0 0 0" />
<!-- Bottom 3 -->
<Border Background="Gray" Grid.Row="2" Grid.Column="0" />
<Border Background="Gray" Grid.Row="2" Grid.Column="1" BorderBrush="Black" BorderThickness="0 1 0 0" />
<Border Background="Gray" Grid.Row="2" Grid.Column="2" />
</Grid>
</DockPanel>
<Grid>
<Grid.Resources>
<Style TargetType="Thumb">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="25" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="*" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<!-- Top/Left -->
<DockPanel LastChildFill="False" Grid.Row="0" Grid.Column="0">
<Thumb DockPanel.Dock="Left" Width="4" Cursor="SizeNWSE" Tag="0" DragDelta="Thumb_DragDelta" />
<Thumb DockPanel.Dock="Top" Height="4" Cursor="SizeNWSE" Tag="0" DragDelta="Thumb_DragDelta" />
</DockPanel>
<!-- Top/Right -->
<DockPanel LastChildFill="False" Grid.Row="0" Grid.Column="2">
<Thumb DockPanel.Dock="Right" Width="4" Cursor="SizeNESW" Tag="0" DragDelta="Thumb_DragDelta" />
<Thumb DockPanel.Dock="Top" Height="4" Cursor="SizeNESW" Tag="0" DragDelta="Thumb_DragDelta" />
</DockPanel>
<!-- Bottom/Left -->
<DockPanel LastChildFill="False" Grid.Row="2" Grid.Column="0">
<Thumb DockPanel.Dock="Left" Width="4" Cursor="SizeNESW" Tag="1" DragDelta="Thumb_DragDelta" />
<Thumb DockPanel.Dock="Bottom" Height="4" Cursor="SizeNESW" Tag="1" DragDelta="Thumb_DragDelta" />
</DockPanel>
<!-- Bottom/Right -->
<DockPanel LastChildFill="False" Grid.Row="2" Grid.Column="2">
<Thumb DockPanel.Dock="Right" Width="4" Cursor="SizeNWSE" Tag="1" DragDelta="Thumb_DragDelta" />
<Thumb DockPanel.Dock="Bottom" Height="4" Cursor="SizeNWSE" Tag="1" DragDelta="Thumb_DragDelta" />
</DockPanel>
<!-- Left -->
<Thumb Grid.Row="1" Grid.Column="0" Width="4" Cursor="SizeWE" Tag="0" HorizontalAlignment="Left" DragDelta="Thumb_DragDelta" />
<!-- Top -->
<Thumb Grid.Row="0" Grid.Column="1" Height="4" Cursor="SizeNS" Tag="0" VerticalAlignment="Top" DragDelta="Thumb_DragDelta" />
<!-- Right -->
<Thumb Grid.Row="1" Grid.Column="2" Width="4" Cursor="SizeWE" Tag="1" HorizontalAlignment="Right" DragDelta="Thumb_DragDelta" />
<!-- Bottom -->
<Thumb Grid.Row="2" Grid.Column="1" Height="4" Cursor="SizeNS" Tag="1" VerticalAlignment="Bottom" DragDelta="Thumb_DragDelta" />
</Grid>
</Grid>

主窗口.xaml.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp1
{
public partial class MainWindow : Window
{
#region --- Declarations ---
private Rect _location { get; set; }
#endregion
#region --- Constructors ---
public MainWindow()
{
InitializeComponent();
}
#endregion
#region --- Properties ---
private Rect DesktopArea
{
get
{
var c = System.Windows.Forms.Cursor.Position;
var s = System.Windows.Forms.Screen.FromPoint(c);
var a = s.WorkingArea;
return new Rect(a.Left, a.Top, a.Width, a.Height);
}
}
#endregion
#region --- Dependency Properties ---
public static readonly DependencyProperty InternalWindowStateProperty = DependencyProperty.Register("InternalWindowState", typeof(WindowState), typeof(MainWindow), new PropertyMetadata(WindowState.Normal, new PropertyChangedCallback(OnInternalWindowStateChanged)));
public WindowState InternalWindowState
{
get { return (WindowState)GetValue(InternalWindowStateProperty); }
set { SetValue(InternalWindowStateProperty, value); }
}
private static void OnInternalWindowStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainWindow instance = (MainWindow)d;
instance.SetInternalWindowState((WindowState)e.NewValue);
}
#endregion
#region --- Private Methods ---
private void StoreLocation()
{
_location = new Rect(this.Left, this.Top, this.Width, this.Height);
}
private void RestoreLocation()
{
this.Width = _location.Width;
this.Height = _location.Height;
this.Top = _location.Top >= 0 ? _location.Top : 0;
this.Left = _location.Left;
}
private void SetMaximizedState()
{
this.Width = DesktopArea.Width;
this.Height = DesktopArea.Height;
this.Top = DesktopArea.Top;
this.Left = DesktopArea.Left;
}
private void SetInternalWindowState(WindowState state)
{
InternalWindowState = state;
switch (InternalWindowState)
{
case WindowState.Normal:
this.WindowState = WindowState.Normal;
RestoreLocation();
break;
case WindowState.Maximized:
this.WindowState = WindowState.Normal;
SetMaximizedState();
break;
case WindowState.Minimized:
this.WindowState = WindowState.Minimized;
break;
}
}
#endregion
#region --- Sizing Routines --- 
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Thumb thumb = (Thumb)sender;
int tag = Convert.ToInt32(thumb.Tag);
if (thumb.Cursor == Cursors.SizeWE) HandleSizeWE(tag, e);
if (thumb.Cursor == Cursors.SizeNS) HandleSizeNS(tag, e);
if (thumb.Cursor == Cursors.SizeNESW) HandleSizeNESW(tag, e);
if (thumb.Cursor == Cursors.SizeNWSE) HandleSizeNWSE(tag, e);
}
private void HandleSizeNWSE(int tag, DragDeltaEventArgs e)
{
if (tag == 0)
{
this.Top += e.VerticalChange;
this.Height -= e.VerticalChange;
this.Left += e.HorizontalChange;
this.Width -= e.HorizontalChange;
}
else
{
this.Width += e.HorizontalChange;
this.Height += e.VerticalChange;
}
}
private void HandleSizeNESW(int tag, DragDeltaEventArgs e)
{
if (tag == 0)
{
this.Top += e.VerticalChange;
this.Height -= e.VerticalChange;
this.Width += e.HorizontalChange;
}
else
{
this.Left += e.HorizontalChange;
this.Width -= e.HorizontalChange;
this.Height += e.VerticalChange;
}
}
private void HandleSizeNS(int tag, DragDeltaEventArgs e)
{
if (tag == 0)
{
this.Top += e.VerticalChange;
this.Height -= e.VerticalChange;
}
else
this.Height += e.VerticalChange;
}
private void HandleSizeWE(int tag, DragDeltaEventArgs e)
{
if (tag == 0)
{
this.Left += e.HorizontalChange;
this.Width -= e.HorizontalChange;
}
else
this.Width += e.HorizontalChange;
}
#endregion
#region --- Event Handlers ---
private void OnDragMoveWindow(Object sender, MouseButtonEventArgs e)
{
if (this.InternalWindowState == WindowState.Maximized)
{
var c = System.Windows.Forms.Cursor.Position;
this.InternalWindowState = WindowState.Normal;
this.Height = _location.Height;
this.Width = _location.Width;
this.Top = c.Y - (titleBar.ActualHeight / 2);
this.Left = c.X - (_location.Width / 2);
}
this.DragMove();
}
private void OnMaximizeWindow(Object sender, MouseButtonEventArgs e)
{
if (this.InternalWindowState == WindowState.Maximized)
this.InternalWindowState = WindowState.Normal;
else
this.InternalWindowState = WindowState.Maximized;
}
private void OnMinimizeWindow(Object sender, MouseButtonEventArgs e)
{
this.InternalWindowState = WindowState.Minimized;
}
private void OnCloseWindow(Object sender, MouseButtonEventArgs e)
{
Application.Current.Shutdown();
}
private void Window_StateChanged(object sender, EventArgs e)
{
if (this.WindowState == WindowState.Maximized)
{
this.InternalWindowState = WindowState.Maximized;
}
}
private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (this.InternalWindowState != WindowState.Maximized)
StoreLocation();
}
#endregion
}
}

对于点号5,使用以下内容:

public WindowName() // Constructor for your window
{
this.MaxHeight = SystemParameters.WorkArea.Height;
this.MaxWidth = SystemParameters.WorkArea.Width;
}

这将确保窗口在最大化时不会与任务栏重叠。

您可以通过处理WM_GETMINMAXINFOWin32消息来指定最大化区域。这里的代码展示了如何做到这一点。它将解决问题#5和#6。

请注意,我会做一些不同的事情,例如在WindowProc中返回IntPtr.Zero而不是(System.IntPtr)0,以及将MONITOR_DEFAULTTONEAREST设为常数。但这只是编码风格的改变,不会影响最终结果。

还要确保注意在SourceInitialized事件期间挂接WindowProc而不是OnApplyTemplate的更新。这是一个更好的地方。如果你正在实现一个从Window派生的类,那么另一个选项是重写OnSourceInitialized来挂接WindowProc,而不是附加到事件。这是我通常做的事。

对于所有这些问题,我只能推荐:

MahApps.Metro:http://mahapps.com/MahApps.Metro/

源代码:https://github.com/MahApps/MahApps.Metro

这是一个很好的图书馆,主题很好,很容易使用!

希望能帮助

我知道这是一个延迟的响应,但几年前我就在处理这些问题,尤其是#6。

对于#6,如果您将ResizeState设置为CanResize或CanResizeWithGrip、CanMinimize和NoResize全屏且没有过扫描,则这是一个问题。我所做的是创建了自己的标题栏,具有最小化、最大化、关闭和拖动移动(支持取消抓取)功能,并创建了一个拇指作为调整大小的夹点,以允许调整大小。

这仍然有一些缺点,比如只能从右下角调整大小,并且必须处理#5。我还没有找到一个优雅的解决方案。

相关内容

  • 没有找到相关文章

最新更新