How can I create a basic custom window chrome for a WPF window, that doesn't include the close button and still a moveable and resizeable window?
- 1blogs.msdn.com/b/wpfsdk/archive/2008/09/08/…BoltClock– BoltClock2011-07-22 15:27:34 +00:00Commented Jul 22, 2011 at 15:27
- I checked that out...but was looking for more of a tutorial...Carl Weis– Carl Weis2011-07-22 15:32:48 +00:00Commented Jul 22, 2011 at 15:32
- An oldish, but still relevant question here: Creating Bordless Windows with custom visual.DK.– DK.2011-07-22 21:46:34 +00:00Commented Jul 22, 2011 at 21:46
5 Answers
You set your Window's WindowStyle="None", then build your own window interface. You need to build in your own Min/Max/Close/Drag event handlers, but Resizing is still maintained.
For example:
<Window WindowState="Maximized" WindowStyle="None" WindowStartupLocation="CenterScreen" MaxWidth="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Width}" MaxHeight="{Binding Source={x:Static SystemParameters.WorkArea}, Path=Height}" > <DockPanel x:Name="RootWindow"> <DockPanel x:Name="TitleBar" DockPanel.Dock="Top"> <Button x:Name="CloseButton" Content="X" Click="CloseButton_Click" DockPanel.Dock="Right" /> <Button x:Name="MaxButton" Content="Restore" Click="MaximizeButton_Click" DockPanel.Dock="Right" /> <Button x:Name="MinButton" Content="Min" Click="MinimizeButton_Click" DockPanel.Dock="Right" /> <TextBlock HorizontalAlignment="Center">Application Name</TextBlock> </DockPanel> <ContentControl Content="{Binding CurrentPage}" /> </DockPanel> </Window> And here's some example code-behind for common window functionality
/// <summary> /// TitleBar_MouseDown - Drag if single-click, resize if double-click /// </summary> private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e) { if(e.ChangedButton == MouseButton.Left) if (e.ClickCount == 2) { AdjustWindowSize(); } else { Application.Current.MainWindow.DragMove(); } } /// <summary> /// CloseButton_Clicked /// </summary> private void CloseButton_Click(object sender, RoutedEventArgs e) { Application.Current.Shutdown(); } /// <summary> /// MaximizedButton_Clicked /// </summary> private void MaximizeButton_Click(object sender, RoutedEventArgs e) { AdjustWindowSize(); } /// <summary> /// Minimized Button_Clicked /// </summary> private void MinimizeButton_Click(object sender, RoutedEventArgs e) { this.WindowState = WindowState.Minimized; } /// <summary> /// Adjusts the WindowSize to correct parameters when Maximize button is clicked /// </summary> private void AdjustWindowSize() { if (this.WindowState == WindowState.Maximized) { this.WindowState = WindowState.Normal; MaxButton.Content = "1"; } else { this.WindowState = WindowState.Maximized; MaxButton.Content = "2"; } } 6 Comments
DockPanel.Dock="Top" on the Titlebar dockpanel.Shell:WindowChrome.CaptionHeight and you dont need any of that titlebar click drag nonsense. No reason to re-invent the wheel. Also keeps other default windows behaviors like double click to maximize, shake to solo.WindowChrome.CaptionHeight to have all the titlebar features. @RandomEngy the combination with WindowChrome.IsHitTestVisibleInChrome="True" at the buttons will have the result that they are clickable in the titlebar area..NET 4.5 added a new class that greatly simplifies this.
The WindowChrome class enables you to extend Windows Presentation Foundation (WPF) content into the non-client area of a window that is typically reserved for the operating system’s window manager.
You can find a tutorial here.
And here's a short example usage.
4 Comments
I've just used the example below for .net 4.5 and it works very well. Interestingly, it uses a code behind for a resource dictionary for the click events. All you have to do is reference the resource dictionary in your app.xaml file and then assign the Window the Style CustomWindowStyle. This was shamelessly stolen from http://www.eidias.com/blog/2014/1/27/restyle-your-window.
<ResourceDictionary x:Class="WpfApp7.WindowStyle" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="CustomWindowStyle" TargetType="{x:Type Window}"> <Setter Property="WindowChrome.WindowChrome"> <Setter.Value> <WindowChrome CaptionHeight="30" CornerRadius="4" GlassFrameThickness="0" NonClientFrameEdges="None" ResizeBorderThickness="5" UseAeroCaptionButtons="False" /> </Setter.Value> </Setter> <Setter Property="BorderBrush" Value="Black" /> <Setter Property="Background" Value="Gray" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Window}"> <Grid> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="5,30,5,5"> <AdornerDecorator> <ContentPresenter /> </AdornerDecorator> </Border> <Grid Height="30" VerticalAlignment="Top"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <StackPanel Orientation="Horizontal" Margin="5,0"> <Button Content="A" Margin="0,0,5,0" VerticalAlignment="Center" Click="Button_Click" WindowChrome.IsHitTestVisibleInChrome="True"/> <Button Content="B" Margin="0,0,5,0" VerticalAlignment="Center" Click="Button_Click" WindowChrome.IsHitTestVisibleInChrome="True"/> <Button Content="C" Margin="0,0,5,0" VerticalAlignment="Center" Click="Button_Click" WindowChrome.IsHitTestVisibleInChrome="True"/> <Button Content="D" Margin="0,0,5,0" VerticalAlignment="Center" Click="Button_Click" WindowChrome.IsHitTestVisibleInChrome="True"/> </StackPanel> <TextBlock Margin="5,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16" Foreground="White" Text="{TemplateBinding Title}" Grid.Column="1"/> <StackPanel Orientation="Horizontal" Grid.Column="2"> <Button x:Name="btnClose" Width="15" Margin="5" Click="CloseClick" Content="X" WindowChrome.IsHitTestVisibleInChrome="True" /> <Button x:Name="btnRestore" Width="15" Margin="5" Click="MaximizeRestoreClick" Content="#" WindowChrome.IsHitTestVisibleInChrome="True" /> <Button x:Name="btnMinimize" Width="15" Margin="5" VerticalContentAlignment="Bottom" Click="MinimizeClick" Content="_" WindowChrome.IsHitTestVisibleInChrome="True" /> </StackPanel> </Grid> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> And for the code behind:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace WpfApp7 { public partial class WindowStyle : ResourceDictionary { public WindowStyle() { InitializeComponent(); } private void CloseClick(object sender, RoutedEventArgs e) { var window = (Window)((FrameworkElement)sender).TemplatedParent; window.Close(); } private void MaximizeRestoreClick(object sender, RoutedEventArgs e) { var window = (Window)((FrameworkElement)sender).TemplatedParent; if (window.WindowState == System.Windows.WindowState.Normal) { window.WindowState = System.Windows.WindowState.Maximized; } else { window.WindowState = System.Windows.WindowState.Normal; } } private void MinimizeClick(object sender, RoutedEventArgs e) { var window = (Window)((FrameworkElement)sender).TemplatedParent; window.WindowState = System.Windows.WindowState.Minimized; } private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Hello!"); } } } Comments
Here is an easy solution which looks very similar to the default Windows 10 buttons, it simply uses the same Font for the symbols:
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" WindowChrome.IsHitTestVisibleInChrome="True"> <Button Click="Minimize_Click" Content="" FontFamily="Segoe MDL2 Assets" FontSize="10" Padding="15,15,15,5" Background="Transparent" BorderBrush="Transparent" /> <Button Click="Maximize_Click" FontFamily="Segoe MDL2 Assets" FontSize="10" Padding="15,10" Background="Transparent" BorderBrush="Transparent"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="Button.Content" Value="" /> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized"> <Setter Property="Button.Content" Value="" /> </DataTrigger> </Style.Triggers> </Style> </Button.Style> </Button> <Button Click="Close_Click" Content="" FontFamily="Segoe MDL2 Assets" FontSize="10" Padding="15,10" Background="Transparent" BorderBrush="Transparent" /> </StackPanel> If you want Support for older Windows Versions (7 and 8) have a look here: https://stackoverflow.com/a/27911618/9758687
Comments
Here's an overview of the approach you'll need to take:
- Set
WindowStyle="None"to do your own UI. - Use
WindowChrome.CaptionHeightto get standard title bar drag/double click/shake behavior and setWindowChrome.IsHitTestVisibleInChrome="True"on your buttons to make them clickable. - Implement click handlers for your buttons.
- Hook into the Window.StateChanged event to handle maximize/restore changes and update your UI. You can't assume everyone is using your title bar buttons to maximize and restore. This can happen via keyboard shortcuts (Win+Up/Win+Down) or double-clicking the title bar.
- 7px of your window gets cut off from all sides when you maximize. To fix we need to hook into WM_GETMINMAXINFO to supply the correct maximized position.
- You'll need to re-implement a window border to provide contrast over different backgrounds.
- Use <Path> to render the title bar icons so they look good at different DPIs.
- Change the look of the title bar depending on whether the window is active, to give the user an indication of which window has focus.
The full writeup is a little long; I go over them in detail with code examples in this blog post.