WPF Borderless Window issues: Aero Snap & Maximizing

前端 未结 6 1603
独厮守ぢ
独厮守ぢ 2020-12-03 03:37

I\'ve created a borderless WPF window by setting the following window properties in XAML:

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

相关标签:
6条回答
  • 2020-12-03 04:14

    I know this is a late response, but I was dealing with these exact issues a couple of years back, especially #6.

    For #6, this is an issue if you have ResizeState set to CanResize or CanResizeWithGrip, CanMinimize and NoResize fullscreens with no overscan. What I have done is I created my own titlebar with minimize, maximize, close and DragMove (with unsnapping support) functionality and I have created a thumb as the resize grip to allow resizing.

    This still have a few drawbacks like only being able to resize from the bottom right corner and having to deal with #5. I haven't found an elegant solution for this one yet.

    0 讨论(0)
  • 2020-12-03 04:20

    Easiest Full Solution

    Hello, The following solution fixes all of the issues detailed in your question in the simplest manner possible, and works on Windows 10 using WPF and the latest version of the C# language and .NET framework. This is as of 3/15/2017. Please let me know if it stops working.

    Step 1: To address issues 1, 2, and 4, within your <Window ... > </Window> tags in the XAML of your application, paste this in, at the top or bottom:

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

    CaptionHeight is the desired height of your window dragging area.

    Step 2: To address issue 3, you need to create your title bar and caption as well as the window controls. To do this, you simply need to give the desired title bar elements each a VerticalAlignment of Top, or put them into a grid with it's VerticalAlignment set to Top, which will do it for all of them, but make sure that their heights are not greater than the CaptionHeight property on the WindowChrome element declared in the XAML, from step 1. For all the buttons, you must assign them, or their container, the property WindowChrome.IsHitTestVisibleInChrome="True". Here is an example:

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

    Now, to add proper functionality to the window control buttons, within the MainWindow() constructor of your codebehind, the C# source code of your application, paste the following in, after the call to 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;
    

    Step 3: To address issues 5 and 6, you need to hook into WmGetMinMaxInfo. To do this, go to your codebehind, then copy and paste everything from this Pastebin into your Window class. Now, within your MainWindow() constructor, paste:

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

    Via Project > Add References in the file menu, be sure to have references to:

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

    The best way to check is to click on the Assemblies tab in the top left, then select Framework, then use the search box in the top right corner of the window. Now add all of these usings (namespaces) to the top of your codebehind:

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

    That should cover everything. I hope this helps!

    0 讨论(0)
  • 2020-12-03 04:21

    for all these problems, I can only recommend this:

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

    Sourcecode: https://github.com/MahApps/MahApps.Metro

    it's a nice library with a nice theme and easy to use!

    hope that helps

    0 讨论(0)
  • 2020-12-03 04:22

    I just went through this entire thing myself. It was a real chore, because you have to manually account for so much. It's funny, we take so much for granted these days, with something as simple as how a basic window operates. But a look at this sample code that I am providing is a good indication of just how much really goes into this problem.

    I hope this helps, as it took me a little time to get here myself.

    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
        }
    }
    
    0 讨论(0)
  • 2020-12-03 04:24

    For Point number 5, use this:

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

    This will ensure that the window will not overlap the taskbar when maximized.

    0 讨论(0)
  • 2020-12-03 04:27

    You can specify the maximize region by handling the WM_GETMINMAXINFO Win32 message. The code here shows how to do that. It will solve issues #5 and #6.

    Note that there are a few things that I would do differently, such as returning IntPtr.Zero instead of (System.IntPtr)0 in WindowProc and making MONITOR_DEFAULTTONEAREST a constant. But that's just coding style changes, and doesn't affect the net result.

    Also make sure to pay attention to the update where the WindowProc is hooked during the SourceInitialized event instead of OnApplyTemplate. That's the better place to do it. If you're implementing a class derived from Window, then another option is to override OnSourceInitialized to hook the WindowProc instead of attaching to the event. That's what I normally do.

    0 讨论(0)
提交回复
热议问题