My blog about application development on the .NET platform and Windows®.

How to create a custom window in WPF

This introductory post will provide a walkthrough on how to create your own custom looking window control with resize, drag, minimize, restore and close functionality and how to use it in your WPF applications.

The solution includes two Visual Studio projects, the first one a class library where the control itself is implemented and the second one a sample WPF application which will have a reference to the class library and use the custom window control as its main window.

Get started

Start by creating a new C# class library in Visual Studio (File->New Project) and give it a suitable name like Mm.Wpf.Controls.
Create project

Add references

After removing the automatically generated Class1.cs from the project the first thing to do is to add some references. We will need a reference to the PresentationFramework.dll where the out-of-the-box System.Windows.Window class, which our custom window class will inherit from, is implemented. We also need to add a reference to PresentationCore.dll where the RoutedEventArgs class lives, System.Xaml.dll and WindowsBase.dll. You add a reference in Visual Studio by choosing “Add reference” from the Project menu or by right-clicking the project in the solution explorer and the required DLLs should be found under the .NET tab.
addreference

Implement the control

The next step is to add a new class called CustomWindow.cs to the project by right-clicking the project name in the solution explorer and choose Add->Class. The class will be public and inherit from the default WPF window class (Sytem.Windows.Window). By extending the default Window class we can use its built-in functionality for minimizing, restoring and closing the window. We add the three click event handlers shown below to take care of this.

using System.Windows;

namespace Mm.Wpf.Controls
{
    public class CustomWindow : Window
    {
        #region Click events
        protected void MinimizeClick(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Minimized;
        }

        protected void RestoreClick(object sender, RoutedEventArgs e)
        {
            WindowState = (WindowState == WindowState.Normal) ? WindowState.Maximized : WindowState.Normal;
        }

        protected void CloseClick(object sender, RoutedEventArgs e)
        {
            Close();
        }
        #endregion
    }
}

When you create a custom control in WPF you define a template for it in XAML. The default style must be located in a folder called “Themes” at the root of the class library project and for our custom window control to be able to find its default style we add a static constructor where the default value of the DefaultStyleKey property is set to the type object of our class.

static CustomWindow()
{    
    DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow),
        new FrameworkPropertyMetadata(typeof(CustomWindow)));
}

To define the style for the window we then add a “Themes” folder to the project. Inside this folder you can add different templates for each Windows theme but since theming is out of the scope of this article we will only add a so called fallback style. This style must be located in (or defined in a file referenced from) a file called “Generic.xaml” in the “Themes” folder.

Since the project template is a generic class library you probably won’t find the option to add a WPF Resource dictionary when right-clicking the newly created folder and choosing Add. Instead, choose to add a simple text file, name it “Generic.xaml” and give it the below content.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Mm.Wpf.Controls">
    <!--  Button style -->
    <Style TargetType="{x:Type Button}" x:Key="WindowButtonStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ButtonBase}">
                    <Border
                            x:Name="Chrome"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            Margin="0"
                            Background="{TemplateBinding Background}"
                            SnapsToDevicePixels="True">
                        <ContentPresenter
                                ContentTemplate="{TemplateBinding ContentTemplate}"
                                Content="{TemplateBinding Content}"
                                ContentStringFormat="{TemplateBinding ContentStringFormat}"
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                Margin="{TemplateBinding Padding}"
                                RecognizesAccessKey="True"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="FontFamily" Value="Webdings"/>
        <Setter Property="FontSize" Value="13.333" />
        <Setter Property="Foreground" Value="Black" />
        <Setter Property="Margin" Value="0,2,3,0"/>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Foreground" Value="Gray" />
            </Trigger>
        </Style.Triggers>
    </Style>
    
    <!-- Window style -->
    <Style TargetType="{x:Type local:CustomWindow}">
        <Setter Property="WindowStyle" Value="None"/>
        <Setter Property="ResizeMode" Value="NoResize"/>
        <Setter Property="Background" Value="White"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="BorderBrush" Value="Silver"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomWindow}">
                    <Border BorderThickness="{TemplateBinding BorderThickness}" 
                            BorderBrush="{TemplateBinding BorderBrush}">
                        <Grid>
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition />
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition />
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <Rectangle x:Name="moveRectangle" Fill="Transparent"
                                           Grid.Row="0" Grid.Column="0"/>
                                <StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
                                    <Button x:Name="minimizeButton" Style="{StaticResource WindowButtonStyle}"
                                            Content="0" />
                                    <Button x:Name="restoreButton" Style="{StaticResource WindowButtonStyle}"
                                            Content="1" />
                                    <Button x:Name="closeButton" Style="{StaticResource WindowButtonStyle}"
                                            Content="r" />
                                </StackPanel>
                                <Grid Background="{TemplateBinding Background}"
                                           Grid.Row="1" Grid.ColumnSpan="2" Margin="5,5,5,5">
                                    <AdornerDecorator>
                                        <ContentPresenter/>
                                    </AdornerDecorator>
                                </Grid>
                            </Grid>
		        <! -- Resize grid to be inserted here -->
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Note that the buttons for minimizing, restoring and closing the window have their own template defined above the actual custom window template. Also these buttons have been assigned a name by the x:Name attribute for us to be able to find them in code and hook up their click event handlers. This is done by overriding the OnApplyTemplate in our CustomWindow class.

public override void OnApplyTemplate()
{
    Button minimizeButton = GetTemplateChild("minimizeButton") as Button;
    if (minimizeButton != null)
    	minimizeButton.Click += MinimizeClick;

    Button restoreButton = GetTemplateChild("restoreButton") as Button;
    if (restoreButton != null)
    	restoreButton.Click += RestoreClick;

    Button closeButton = GetTemplateChild("closeButton") as Button;
    if (closeButton != null)
    	closeButton.Click += CloseClick;

    base.OnApplyTemplate();
}

Make it draggable

At this stage our custom window is almost (the ThemeAttribute is missing) ready to be used in a WPF application but it cannot yet be dragged around on the screen nor resized. You may have noticed that the template above contains a rectangle named “moveRectangle” and this one will serve as a ‘handle’ to drag our window. We add a PreviewMouseDown event handler for the Rectangle in the OnApplyTemplate method and implement it as below.

private void moveRectangle_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
   if (Mouse.LeftButton == MouseButtonState.Pressed)
      DragMove();
}

Make it resizable

To make the window resizable requires a bit more effort. We start by adding a bunch of invisible (Fill=”Transparent”) rectangles, one for each side of the window and another one for each corner of the window, to our XAML defined template. These rectangles will serve as the window’s edges and we will show the resizing cursors when the mouse moves over these. It is accomplished by implementing the MouseMove event for each of the rectangles. Note that the rectangle’s name decides which type of cursor to display.

<Grid x:Name="resizeGrid">
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            VerticalAlignment="Top"
            Height="5"
            x:Name="top"
            Margin="5,0,5,0" />
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            x:Name="bottom"
            Height="5"
            VerticalAlignment="Bottom"
            Margin="5,0,5,0" />
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            HorizontalAlignment="Left"
            Margin="0,5,0,5"
            Width="5"
            x:Name="left"/>
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            Margin="0,5,0,5"
            Width="5"
            HorizontalAlignment="Right"
            x:Name="right" />
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            HorizontalAlignment="Left"
            VerticalAlignment="Bottom"
            Width="5"
            Height="5"
            x:Name="bottomLeft" />
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            VerticalAlignment="Bottom"
            Height="5"
            Width="5"
            HorizontalAlignment="Right"
            x:Name="bottomRight" />
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            HorizontalAlignment="Right"
            Width="5"
            Height="5"
            VerticalAlignment="Top"
            x:Name="topRight" />
        <Rectangle
            Stroke="{x:Null}"
            Fill="Transparent"
            HorizontalAlignment="Left"
            Width="6"
            VerticalAlignment="Top"
            Height="5"
            x:Name="topLeft" />
    </Grid>
public override void OnApplyTemplate()
{
      /* omitted */

      Grid resizeGrid = GetTemplateChild("resizeGrid") as Grid;
      if (resizeGrid != null)
      {
           foreach (UIElement element in resizeGrid.Children)
           {
                 Rectangle resizeRectangle = element as Rectangle;
                 if (resizeRectangle != null)
                 {
                      resizeRectangle.PreviewMouseDown += ResizeRectangle_PreviewMouseDown;
                      resizeRectangle.MouseMove += ResizeRectangle_MouseMove;
                 }
           }
       }

       base.OnApplyTemplate();
}

protected void ResizeRectangle_MouseMove(Object sender, MouseEventArgs e)
{
    Rectangle rectangle = sender as Rectangle;
    switch (rectangle.Name)
    {
        case "top":
            Cursor = Cursors.SizeNS;
            break;
        case "bottom":
            Cursor = Cursors.SizeNS;
            break;
        case "left":
            Cursor = Cursors.SizeWE;
            break;
        case "right":
            Cursor = Cursors.SizeWE;
            break;
        case "topLeft":
            Cursor = Cursors.SizeNWSE;
            break;
        case "topRight":
            Cursor = Cursors.SizeNESW;
            break;
        case "bottomLeft":
            Cursor = Cursors.SizeNESW;
            break;
        case "bottomRight":
            Cursor = Cursors.SizeNWSE;
            break;
        default:
            break;
        }
}

We also need to remember to reset the cursor to its default state when the mouse moves from one of the edges into the content area and for this we add a constructor to our custom window class and an event handler to the Window’s PreviewMouseMove event.

public CustomWindow()
    : base()
{
    PreviewMouseMove += OnPreviewMouseMove;
}

protected void OnPreviewMouseMove(object sender, MouseEventArgs e)
{
    if (Mouse.LeftButton != MouseButtonState.Pressed)
        Cursor = Cursors.Arrow;
}

To implement the actual resizing behavior in the PreviewMouseDown event handler for each of the edge rectangles we then make use of the platform invocation services (PInvoke) to call the unmanaged Windows API SendMessage method.

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam);

protected void ResizeRectangle_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    Rectangle rectangle = sender as Rectangle;
    switch (rectangle.Name)
    {
        case "top":
            Cursor = Cursors.SizeNS;
            ResizeWindow(ResizeDirection.Top);
            break;
        case "bottom":
            Cursor = Cursors.SizeNS;
            ResizeWindow(ResizeDirection.Bottom);
            break;
        case "left":
            Cursor = Cursors.SizeWE;
            ResizeWindow(ResizeDirection.Left);
            break;
        case "right":
            Cursor = Cursors.SizeWE;
            ResizeWindow(ResizeDirection.Right);
            break;
        case "topLeft":
            Cursor = Cursors.SizeNWSE;
            ResizeWindow(ResizeDirection.TopLeft);
            break;
        case "topRight":
            Cursor = Cursors.SizeNESW;
            ResizeWindow(ResizeDirection.TopRight);
            break;
        case "bottomLeft":
            Cursor = Cursors.SizeNESW;
            ResizeWindow(ResizeDirection.BottomLeft);
            break;
        case "bottomRight":
            Cursor = Cursors.SizeNWSE;
            ResizeWindow(ResizeDirection.BottomRight);
            break;
        default:
            break;
     }
}

private void ResizeWindow(ResizeDirection direction)
{
    SendMessage(_hwndSource.Handle, 0x112, (IntPtr)(61440 + direction), IntPtr.Zero);
}

private enum ResizeDirection
{
    Left = 1,
    Right = 2,
    Top = 3,
    TopLeft = 4,
    TopRight = 5,
    Bottom = 6,
    BottomLeft = 7,
    BottomRight = 8,
}

For the above ResizeWindow wrapper method to compile we need to add a member variable of type HwndSource to represent our window and provide access to its window handle. The variable is initialized in the Window.SourceInitialized event.

private HwndSource _hwndSource;

protected override void OnInitialized(EventArgs e)
{
    SourceInitialized += OnSourceInitialized;
    base.OnInitialized(e);
}

private void OnSourceInitialized(object sender, EventArgs e)
{
    _hwndSource = (HwndSource)PresentationSource.FromVisual(this);
} 

At this point the class library should compile just fine if you have included the following required using statements at the top of the CustomWindow.cs file.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Shapes;
using System;
using System.Windows.Interop;
using System.Runtime.InteropServices;

The last thing to do is to add a ThemeInfo attribute to our assembly to specify where WPF should look for the possible theme dictionaries and the generic dictionary. As we don’t have any specific theme styles and our Generic.xaml is located in the current assembly we add the following to the automatically generated AssemblyInfo.cs located under the Properties folder at the root of the project.

[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]

Use the control in a WPF application

The CustomWindow control is now ready be used. We add a new WPF application to our solution by choosing File->Add->New project on the menu bar.

We then set the newly added project to the startup project by right-clicking on it in the solution explorer and choosing “Set as StartUp project”. If you run the solution at this point you should see an ordinary empty window popup on your screen.
default
To be able to replace this window with our custom style window, we first add a reference to the Mm.Wpf.Controls class library by right-clicking the WPF application project and choosing “Add reference” and the “Projects” tab.

Then we simply edit the MainWindow class, both the code-behind C# and the XAML, to inherit from our custom CustomWindow class instead of the default Window class as shown below.

namespace WpfApplication
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : CustomWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
<control:CustomWindow x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:control="clr-namespace:Mm.Wpf.Controls;assembly=Mm.Wpf.Controls"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Text="Here goes the content!"/>
    </Grid>
</control:CustomWindow>

The application’s main window should now show up with our custom style and you should be able to drag it around on the screen, resize it, minimize it, restore it and close it just like you would with the default window.
Custom window

Advertisements

85 Comments on “How to create a custom window in WPF”

  1. Pavithra says:

    Hi Swarnava,

    Are you using VB6 or VB.net?

  2. CJohnson says:

    There is a bug:

    The window doesn’t move. It resizes, but does not move.

  3. Vinicius Souza says:

    example move:
    window_1.DragMove();

  4. Joel says:

    Hey, I’m in search for a solution that when going maximized not covering taskbar.. tried your example but still no luck :/ found some solutions but placing taskbar in top of screen always gets covered.

  5. Hi Joel,

    Please refer to my reply in the following thread for an example of how you could handle the SourceInitialized event of the window and call some interop code to hopefully make it work as expected: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/3a5266e0-c555-425e-a979-0d74061540e8/my-window-overlaps-taskbar-when-windowsstyle-none-and-transparency-true-wpf?forum=wpf

  6. Joel says:

    Works like a dream, many thanks!!

    Got a hint on Windows resize feature when mouse touches screen edges too? Some way to tap in to it or is it manual work all the way?

    Thanks in advance!

  7. Mudassir says:

    That is a great solution. one thing missing is the toggle between restore and minimize when clicking on task bar. I am still unable to find any good solution to that. any idea?

  8. webfraggle says:

    Hi, it works also for me.

    But how can I add the Window Title to the xaml File?

    Thanks Christoph

  9. webfraggle says:

    Ok, in OnApplyTemplate you can add the folowing:

    Label WindowTitle = GetTemplateChild(“WindowTitle”) as Label;
    WindowTitle.Content = Title;

    And you have to add a label names WindowTitle to your xaml

  10. Bharat says:

    When I tried to create this project using .NET 4.5, I was getting a type “x:Type” not found. Are you missing a reference.

    Adding System.Xaml fixed this issue.

  11. Goldcing says:

    Very nice. There is another toolkit, LinsUIWPF, which is very useful for developers who will like their application is customizable. If you are interested in it, the link is here, http://goldcing.blogspot.com/2012/07/introduction-to-linsuiwpf-suite.html.

  12. “CustomWindow” does not exist in the namespace “clr-namespace:Mm.Wpf.Controls”
    i m getting this error Pls Help me

  13. Hi Ashwanikumar singh,

    Please take a look at the sample markup again. You need to specify the name of the assembly where the CustomWindow class is defined:

    xmlns:control=”clr-namespace:Mm.Wpf.Controls;assembly=Mm.Wpf.Controls”

  14. Ashwani Singh says:

    Thanks for the reply Sir ….

    Dear sir i am using .net 4.5 I tried all the way but i am still getting 5 errors.. Sir, I am beginner in WPF please help me I am sending my code in notepad I have followed all the steps you mentioned on the site Of “How to create Custom window ” I am not able to solve my errors please Help me out

  15. Chris L says:

    Thanks for the tutorial Magnus.
    I’m getting the same error as Ashwanikumar singh posted.
    The Wpf.Controls assembly is showing in the references.
    Still I get “CustomWindow” does not exist in the namespace “clr-namespace:Wpf.Controls;assembly=Wpf.Controls”
    (I chose to name mine differently)

  16. Sarah says:

    Thanks for this tutorial!

    @CJohnson (dragging issue):
    If moving the window doesn’t work, add the following code to the OnApplyTemplate method of the CustomWindow class:
    Rectangle moveRect = GetTemplateChild(“moveRectangle”) as Rectangle;
    if(moveRect != null)
    moveRect.PreviewMouseDown += moveRectangle_PreviewMouseDown;

  17. grimDazzle says:

    where is the source code?

  18. Chris L says:

    Thought I’d just update on my own problem. My issue was related to changing the name of the assembly and I missed a name change.

  19. vikas kumar says:

    Hi, I tried the same thing, i could successfully build and run the application. But I’ve got some problem with the visual. The visual is proper in visualstudio designer, but when i run it in my system, it is totally different. Please suggest what I should do

  20. Ruchika Jain says:

    Hi, Everything is working fine, except for black borders. Does anyone have a solution as to how to get rid of them?

  21. Abdul Rahman says:

    Hey I face the same problem just like Ashwani and Chris. Any luck on that?

  22. Thank you so much for this tutorial. I am facing a problem with this, please help me.
    This tutorial work fine bot the Form (main window) come with Black form border. For that when i move my cursor at the cross button, then only button show otherwise main form border style property is totally black.

    Please help me to resolve.

    Thanks in advance.

  23. Arthur says:

    Nice tutorial :)
    If you have black rectangle problems as I do, you should change the following in the “Window Style”:

    I think that as everything is transparent, Windows puts black as default background.

  24. sanyam says:

    Hi i am getting the Black screen, Help me

  25. Claire says:

    I was able to get this to work in VS2013, Net 4.5. I did however, as some suggested, create the project as a WPF Custom Control Library.

    The “black title bar” issue I fixed by adding Background=”White” to the Border style of the window template:
    … Border BorderThickness=”{TemplateBinding BorderThickness}” BorderBrush=”{TemplateBinding BorderBrush}” Background=”White”

    To prevent the window from being resized below a given value, just set the MinWidth and MinHeight properties in the constructor.

  26. sanyam says:

    Hi Claire, Are u able to see ur screen the way it is described in this tutorial?
    Could u please share ur code by email me at sanyamra5@gmail.com

  27. Claire says:

    @sanyam:
    Done.

  28. alex says:

    Claire, sanyam. Could you please send source code to chertykto@gmail.com

    Thanks

  29. Claire says:

    @alex:
    I put a copy of the solution on my OneDrive account:

    https://onedrive.live.com/?id=B7DC65465093599B!105&cid=B7DC65465093599B&group=0

    Anybody should be able to download it from there.

    This being said, people can also take advantage of the WindowChrome class which allows you to customize a window without having to re-implement the standard functionalities:

    https://msdn.microsoft.com/en-us/library/system.windows.shell.windowchrome(v=vs.110).aspx

    This class is available only in .NET 4.5+ though (for prior versions you may still be able to download it as a separate DLL).

  30. Cherty Kto says:

    Thank you very much

  31. psykrest says:

    Thanks a lot, works like a charm!!

  32. EficazCS says:

    On .NET 4.5 there’s no need to write code to move, neither resize. There’s a WindowChrome extension that’s do all de job. Use properties “CaptionHeight” (Height from top that’s accepts moving) and “BorderThickness” to resize operations.

  33. Dawie Lombaard says:

    For everyone who was having the issue with the black border, Claire’s solution works fine, however if you want the border to change along with the background color of the window, the Background property of the Border control should be set to

    Background=”{TemplateBinding Background}”

    instead of

    Background=”White”

    this way when you change the Background of your window the border will change along with it.

  34. Ashraz says:

    can we set button background as icon or image instead of color?

  35. jack says:

    Followed to the T. Still shows a normal window when run


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s