[英]WPF Borderless Window issues: Aero Snap & Maximizing
我通過在XAML中設置以下窗口屬性創建了一個無邊框WPF窗口:
... WindowStyle="None" AllowsTransparency="True" ...
這會導致許多問題:
1)已解決:它不再具有任何內置的調整大小功能
2)已解決:它不再具有任何內置拖動功能
3)已解決:沒有頂部工具欄,它不再具有最小化/最大化/恢復/關閉按鈕
4)已解決:通過aero snap最大化或設置WindowState可防止它被取消。
5)通過aero快照最大化或設置WindowState將使用整個屏幕作為邊界,與窗口工具欄重疊。
6)通過aero快照最大化或設置WindowState似乎包含-7邊距,使窗口的每邊7個像素超出窗口邊緣。
通過制作xaml窗口模板來解決1-3。 我使用了不可見的矩形作為句柄區域,后面的一些代碼通過覆蓋OnApplyTemplate()來應用,通過user32.dll SendMessage(...)附加功能,用於調整大小/移動/最小化/最大化/恢復/關閉。
我在這里找到了#4的答案
我嘗試通過WndProc攔截最大化消息並手動設置大小/位置來解決5-6,但是這有一個問題,即將RestoreRegion覆蓋到最大化的大小/位置,從而無法恢復窗口。
真正奇怪的是,從頂部邊框調整窗口大小到屏幕頂部會觸發aero full height snap,沒有任何問題。
所以,我已經走了很長的路,但5-6仍然是一個問題......有沒有辦法手動指定最大化區域? 或者,有沒有辦法設置窗口大小而不影響restoreregion屬性?
最簡單的完整解決方案
您好,以下解決方案以最簡單的方式修復了您的問題中詳述的所有問題,並使用WPF和最新版本的C#語言和.NET框架在Windows 10上運行。 這是截至2017年3月15日。 如果它停止工作,請告訴我。
步驟1:要解決問題1,2和4,在應用程序的XAML中的<Window ... > </Window>
標記內,將其粘貼到頂部或底部:
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="35"/>
<WindowChrome.WindowChrome>
CaptionHeight
是窗口拖動區域的所需高度。
第2步:要解決問題3,您需要創建標題欄和標題以及窗口控件。 要做到這一點,你只需要給每個VerticalAlignment of Top提供所需的標題欄元素,或者將它們放入一個網格,其VerticalAlignment設置為Top,這將為所有這些元素執行,但要確保它們的高度不是大於在XAML中聲明的WindowChrome
元素上的CaptionHeight
屬性,從步驟1.對於所有按鈕,您必須為它們或其容器分配屬性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>
現在,要向窗口控件按鈕添加適當的功能,在代碼隱藏的MainWindow()
構造函數中,應用程序的C#源代碼, 在調用InitializeComponent();
之后粘貼以下內容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 System.Management;
using System.Windows.Interop;
using System.Security.Principal;
using System.Runtime.InteropServices;
using Microsoft.Win32;
這應該涵蓋一切。 我希望這有幫助!
對於第5點,使用此:
public WindowName() // Constructor for your window
{
this.MaxHeight = SystemParameters.WorkArea.Height;
this.MaxWidth = SystemParameters.WorkArea.Width;
}
這將確保窗口在最大化時不會與任務欄重疊。
您可以通過處理WM_GETMINMAXINFO
Win32消息來指定最大化區域。 這里的代碼顯示了如何做到這一點。 它將解決問題#5和#6。
請注意,我會做一些不同的事情,例如在WindowProc中返回IntPtr.Zero
而不是(System.IntPtr)0並使MONITOR_DEFAULTTONEAREST
為常量。 但這只是編碼風格的變化,並不影響凈結果。
另外,請務必注意在SourceInitialized
事件期間SourceInitialized
WindowProc
而不是OnApplyTemplate
。 那是更好的地方。 如果您正在實現從Window派生的類,那么另一個選項是覆蓋OnSourceInitialized
以掛鈎WindowProc
而不是附加到事件。 這就是我通常做的事情。
對於所有這些問題,我只能推薦這個:
MahApps.Metro: http ://mahapps.com/MahApps.Metro/
源代碼: https : //github.com/MahApps/MahApps.Metro
這是一個很好的圖書館,主題很好,易於使用!
希望有所幫助
我自己就完成了這件事。 這是一項真正的苦差事,因為你必須手動解釋這么多。 有趣的是,這些天我們非常理所當然地使用基本窗口如何操作這么簡單。 但是看看我提供的這個示例代碼可以很好地說明這個問題到底有多少。
我希望這會有所幫助,因為我花了一點時間來到這里。
MainWindow.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>
MainWindow.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
}
}
我知道這是一個遲到的回應,但幾年前我正在處理這些確切的問題,特別是#6。
對於#6,如果您將ResizeState設置為CanResize或CanResizeWithGrip,CanMinimize和NoResize全屏而沒有過掃描,則會出現問題。 我所做的是我創建了自己的標題欄,包括最小化,最大化,關閉和DragMove(具有無遮擋支持)功能,我創建了一個拇指作為調整大小的手柄以允許調整大小。
這仍然有一些缺點,例如只能從右下角調整大小並且必須處理#5。 我還沒有找到一個優雅的解決方案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.