【问题标题】:WPF Borderless Window issues: Aero Snap & MaximizingWPF 无边框窗口问题:Aero Snap 和最大化
【发布时间】:2012-11-05 12:53:24
【问题描述】:

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

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

这会导致许多问题:

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

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

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

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

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

6) 通过 aero snap 最大化或设置 WindowState 似乎包括 -7 边距,使窗口在超出窗口边缘的每一侧都有 7 个像素。

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

我找到了#4 here的答案

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

真正奇怪的是,将窗口大小从顶部边框调整到屏幕顶部可以很好地触发 aero 全高捕捉,完全没有问题。

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

【问题讨论】:

  • 如果您在答案中添加解决方案 1-4,那就太好了..
  • 附议,我很想知道#4的答案
  • “4”的解决方案我认为这个很好。 stackoverflow.com/a/11432873/11912872

标签: c# wpf aero wndproc


【解决方案1】:

最简单的完整解决方案

您好,以下解决方案以最简单的方式解决了您问题中详述的所有问题,并且适用于使用 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,或者将它们放入一个将 VerticalAlignment 设置为 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>

现在,要向窗口控制按钮添加适当的功能,请在代码隐藏的 MainWindow() 构造函数中,即应用程序的 C# 源代码中,将以下内容粘贴到 调用 @ 987654330@:

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 &gt; 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;

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

【讨论】:

  • 虽然我个人认为应该避免引用Microsoft.Win32(出于便携性原因,如果没有别的原因),我很欣赏这个答案。我知道WindowChrome,但只有你的帖子让我意识到它比我想象的要简单得多。
  • 在具有不同分辨率设置的多显示器中并不完全完美,但迄今为止找到了更好的解决方案!我特别喜欢 WindowChrome 的东西
【解决方案2】:

我自己刚刚经历了整个事情。这是一件真正的苦差事,因为您必须手动考虑这么多。有趣的是,这些天我们把很多事情都视为理所当然,就像基本窗口的操作一样简单。但是看看我提供的这个示例代码可以很好地表明这个问题到底有多少。

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

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
    }
}

【讨论】:

  • 出色的工作,对我来说很好。 -- 当我双击 TitleBar 时,这里发生了一个小错误,窗口最大化并恢复到正常大小。我试图修复,但失败了。 (我使用的是带有 2 个屏幕监视器(显示器)的 Windows 10)
  • 非常好,因为它很容易实现。我只需要安装一些像这样的引用 stackoverflow.com/questions/6639468/…
【解决方案3】:

对于第 5 点,使用这个:

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

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

【讨论】:

  • 这仅适用于 (a) 您只有一台显示器,并且 (b) 任务栏位于显示器底部边缘的情况。任何其他配置都会失败。
【解决方案4】:

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

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

还要注意在SourceInitialized 事件期间挂钩WindowProc 而不是OnApplyTemplate 的更新。那是最好的地方。如果您正在实现从 Window 派生的类,那么另一种选择是覆盖 OnSourceInitialized 以挂钩 WindowProc 而不是附加到事件。这就是我通常所做的。

【讨论】:

    【解决方案5】:

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

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

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

    这是一个不错的库,主题不错且易于使用!

    希望对你有帮助

    【讨论】:

    • 我现在没有时间,但有机会我会看看源代码。感谢您的回复。
    【解决方案6】:

    我知道这是一个迟到的回应,但几年前我正在处理这些确切的问题,尤其是 #6。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-18
      相关资源
      最近更新 更多