Archive for February 2009

Making a Custom Window Resizable in WPF

This is a continuation of my Creating a Custom Window in WPF post. If we create a custom window, we want the window to behave like a normal window. One of those things are making the window resizable.

To make the window resizable, we need something to grab on to. We can’t use the border, because we can’t specify a side or corner of the border, so we will need to create our own border that we can grab.

Let’s start by making the top/right/bottom/left sides. Since our corner has a radius of 10, we’ll set margins on the sides so they don’t overlap the corner. We also want to put a negative margin so that the sides will overlap the border. There are some events here that we’ll use later.

<aero:SystemDropShadowChrome CornerRadius="10" Margin="10">
    <Border BorderThickness="1" BorderBrush="Black" Background="White"
        Margin="0" CornerRadius="10">
        <Grid>
            <Border Height="40" Background="#01000000" VerticalAlignment="Top"
                    CornerRadius="10,10,0,0" MouseLeftButtonDown="DragWindow" />
            <Rectangle x:Name="ResizeN" Fill="Yellow" VerticalAlignment="Top"
                       Height="4" Margin="9,-2,9,0" MouseEnter="DisplayResizeCursor"
                       MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize" />
            <Rectangle x:Name="ResizeE" Fill="Yellow" HorizontalAlignment="Right"
                       Width="4" Margin="0,9,-2,9" MouseEnter="DisplayResizeCursor"
                       MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize" />
            <Rectangle x:Name="ResizeS" Fill="Yellow" VerticalAlignment="Bottom"
                       Height="4" Margin="9,0,9,-2" MouseEnter="DisplayResizeCursor"
                       MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize" />
            <Rectangle x:Name="ResizeW" Fill="Yellow" HorizontalAlignment="Left"
                       Width="4" Margin="-2,9,0,9" MouseEnter="DisplayResizeCursor"
                       MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize" />
        </Grid>
    </Border>
</aero:SystemDropShadowChrome>

window1

Now we can create the corners with a radius of 10.

<Path x:Name="ResizeNW" VerticalAlignment="Top" HorizontalAlignment="Left"
      Stroke="Green" StrokeThickness="4" Margin="0" MouseEnter="DisplayResizeCursor"
           MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>
                    <PathFigure StartPoint="0,10">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <QuadraticBezierSegment Point1="0,0" Point2="10,0" />
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>
<Path x:Name="ResizeNE" VerticalAlignment="Top" HorizontalAlignment="Right"
      Stroke="Green" StrokeThickness="4" Margin="0,0,-2,0" MouseEnter="DisplayResizeCursor"
           MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>
                    <PathFigure StartPoint="0,0">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <QuadraticBezierSegment Point1="10,0" Point2="10,10" />
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>
<Path x:Name="ResizeSE" VerticalAlignment="Bottom" HorizontalAlignment="Right"
      Stroke="Green" StrokeThickness="4" Margin="0,0,-2,-2" MouseEnter="DisplayResizeCursor"
           MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>
                    <PathFigure StartPoint="10,0">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <QuadraticBezierSegment Point1="10,10" Point2="0,10" />
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>
<Path x:Name="ResizeSW" VerticalAlignment="Bottom" HorizontalAlignment="Left"
      Stroke="Green" StrokeThickness="4" Margin="0,0,0,-2" MouseEnter="DisplayResizeCursor"
           MouseLeave="ResetCursor" PreviewMouseLeftButtonDown="Resize">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>
                    <PathFigure StartPoint="0,0">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <QuadraticBezierSegment Point1="0,10" Point2="10,10" />
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>

window2

Now we need to make the corners and edges resizable. We also need to make the cursor change when it hovers the corners and edges. To do this, we implement the events I mentioned earlier.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
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;
using System.Windows.Interop;
using System.Runtime.InteropServices;

namespace ResizableWindow
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private const int WM_SYSCOMMAND = 0x112;
        private HwndSource hwndSource;

        private enum ResizeDirection
        {
            Left = 61441,
            Right = 61442,
            Top = 61443,
            TopLeft = 61444,
            TopRight = 61445,
            Bottom = 61446,
            BottomLeft = 61447,
            BottomRight = 61448,
        }

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

        public Window1()
        {
            SourceInitialized += Window1_SourceInitialized;

            InitializeComponent();
        }

        private void Window1_SourceInitialized( object sender, EventArgs e )
        {
            hwndSource = PresentationSource.FromVisual( (Visual)sender ) as HwndSource;
        }

        private void ResizeWindow( ResizeDirection direction )
        {
            SendMessage( hwndSource.Handle, WM_SYSCOMMAND, (IntPtr)direction, IntPtr.Zero );
        }

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

        protected void Resize( object sender, MouseButtonEventArgs e )
        {
            var clickedShape = sender as Shape;

            switch( clickedShape.Name )
            {
                case "ResizeN":
                    this.Cursor = Cursors.SizeNS;
                    ResizeWindow( ResizeDirection.Top );
                    break;
                case "ResizeE":
                    this.Cursor = Cursors.SizeWE;
                    ResizeWindow( ResizeDirection.Right );
                    break;
                case "ResizeS":
                    this.Cursor = Cursors.SizeNS;
                    ResizeWindow( ResizeDirection.Bottom );
                    break;
                case "ResizeW":
                    this.Cursor = Cursors.SizeWE;
                    ResizeWindow( ResizeDirection.Left );
                    break;
                case "ResizeNW":
                    this.Cursor = Cursors.SizeNWSE;
                    ResizeWindow( ResizeDirection.TopLeft );
                    break;
                case "ResizeNE":
                    this.Cursor = Cursors.SizeNESW;
                    ResizeWindow( ResizeDirection.TopRight );
                    break;
                case "ResizeSE":
                    this.Cursor = Cursors.SizeNWSE;
                    ResizeWindow( ResizeDirection.BottomRight );
                    break;
                case "ResizeSW":
                    this.Cursor = Cursors.SizeNESW;
                    ResizeWindow( ResizeDirection.BottomLeft );
                    break;
                default:
                    break;
            }
        }

        protected void DisplayResizeCursor( object sender, MouseEventArgs e )
        {
            var clickedShape = sender as Shape;

            switch( clickedShape.Name )
            {
                case "ResizeN":
                case "ResizeS":
                    this.Cursor = Cursors.SizeNS;
                    break;
                case "ResizeE":
                case "ResizeW":
                    this.Cursor = Cursors.SizeWE;
                    break;
                case "ResizeNW":
                case "ResizeSE":
                    this.Cursor = Cursors.SizeNWSE;
                    break;
                case "ResizeNE":
                case "ResizeSW":
                    this.Cursor = Cursors.SizeNESW;
                    break;
                default:
                    break;
            }
        }

        protected void DragWindow( object sender, MouseButtonEventArgs e )
        {
            DragMove();
        }
    }
}

Now the edges are resizable. We don’t want to keep the green and yellow coloring, so we can change all the colors to almost transparent by setting the colors to #01000000.

window3

And now our window looks like normal again and is resizable.

You can download the example project here. Note: Be sure to change the extension from .doc to .zip.

Creating a Common Window in WPF

The is a continuation of my last post Creating a Custom Window in WPF. If you have a custom window that you’re using, you’re going to want all windows to look the same and you don’t want to have to copy the code into every window.

One way to do this is to create a ControlTemplate for the window. All you have to do is set Template="{DynamicResource WindowTemplate}" on the window.

<ControlTemplate TargetType="Window" x:Key="WindowTemplate">
    <aero:SystemDropShadowCrome CornerRadius="10" Margin="10">
        <Border BorderThickness="1" BorderBrush="Black" Background="White"
                CornerRadius="10">
            <Border Height="40" Background="#01000000" VerticalAlignment="Top"
                    CornerRadius="10,10,0,0" MouseLeftButtonDown="DragWindow">
                <ContentPresenter />
            </Border>
        </Border>
    </aero:SystemDropShadowCrome>
</ControlTemplate>

There is a problem with this though. If you want multiple windows to use the template, you’ll need to put the template in a resource file and include it in the each window that you want to have the template. If you want to use any events, such as MouseLeftButtonDown, like in this example, you can’t put it in an external resource file.

The way I found to fix this, is to create an abstract base class that creates the code. You can then wire the events up right in the base class. They key to doing this is to create a ContentPresenter control, and set the window’s content to the content of the presenter. This makes it so that any code that is put in the window will show up in the presenter.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Windows.Themes;

namespace CommonWindow
{
    public abstract class WindowBase : Window
    {
        public WindowBase()
        {
            Initialized += WindowBaseInitialized;
        }

        private void WindowBaseInitialized( object sender, System.EventArgs e )
        {
            SetResourceReference( Window.StyleProperty, "Window" );
            ResourceDictionary resource = new ResourceDictionary();
            resource.Source = new Uri( "pack://application:,,,/WindowResource.xaml" );
            this.Resources.MergedDictionaries.Add( resource );

            SystemDropShadowChrome dropShadow = new SystemDropShadowChrome();
            dropShadow.SetResourceReference( Control.StyleProperty, "WindowShadow" );

            Border windowBorder = new Border();
            windowBorder.SetResourceReference( Control.StyleProperty, "WindowBorder" );
            dropShadow.Child = windowBorder;

            Grid grid = new Grid();
            grid.RowDefinitions.Add( new RowDefinition() { Height = new GridLength( 40.0 ) } );
            grid.RowDefinitions.Add( new RowDefinition() );
            windowBorder.Child = grid;

            Border headerBorder = new Border();
            headerBorder.SetResourceReference( Control.StyleProperty, "HeaderBorder" );
            headerBorder.MouseLeftButtonDown += DragWindow;
            grid.Children.Add( headerBorder );
            Grid.SetRow( headerBorder, 0 );

            ContentPresenter contentPresenter = new ContentPresenter();
            contentPresenter.Content = this.Content;
            grid.Children.Add( contentPresenter );
            Grid.SetRow( contentPresenter, 1 );

            this.Content = dropShadow;
        }

        private void DragWindow( object sender, MouseButtonEventArgs e )
        {
            DragMove();
        }
    }
}

We should put all the styling in a resource, which is the first thing included when creating the base code.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">

    <Style TargetType="Window" x:Key="Window">
        <Setter Property="AllowsTransparency" Value="True" />
        <Setter Property="WindowStyle" Value="None" />
        <Setter Property="Background" Value="Transparent" />
    </Style>

    <Style TargetType="aero:SystemDropShadowChrome" x:Key="WindowShadow">
        <Setter Property="CornerRadius" Value="10" />
        <Setter Property="Margin" Value="10" />
    </Style>

    <Style TargetType="Border" x:Key="WindowBorder">
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="BorderBrush" Value="Black" />
        <Setter Property="Background" Value="White" />
        <Setter Property="CornerRadius" Value="10" />
    </Style>

    <Style TargetType="Border" x:Key="HeaderBorder">
        <Setter Property="Grid.Row" Value="0" />
        <Setter Property="Background" Value="#01000000" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="CornerRadius" Value="10,10,0,0" />
    </Style>

</ResourceDictionary>

All that we need to do now to get a window to have our common look is inherit from the base class. All the styling, resources and controls will automatically be there.

namespace CommonWindow
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : WindowBase
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}
<local:WindowBase x:Class="CommonWindow.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CommonWindow"
    Title="Window1" Height="300" Width="300">

    <Label Content="blah" />
</local:WindowBase>

Now anything we put in our window will show up in our custom window, like our "blah" label.

window1

So all the base controls are coded but we can still abstract most of it out to xaml in resource files. Now we have a common window that we can use and all we have to do is inherit from a base class.

You can download the example project here. Note: Be sure to change the extension from .doc to .zip.

Creating a Custom Window in WPF

For an app I’m working on I wanted to create a custom window look. Here is how I did it.

The default window looks like this:

<Window x:Class="CustomWindow.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    Title="Window1" Height="300" Width="300">
    <Grid>

    </Grid>
</Window>

Default Window

To make a custom window, we need remove the default window, and make the window transparent. This will make it so there is no window that shows at all. We need to change the AllowsTransparency, WindowStyle and Background properties.

<Window x:Class="CustomWindow.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    Title="Window1" Height="300" Width="300"
    AllowsTransparency="True" WindowStyle="None" Background="Transparent">
    <Grid>

    </Grid>
</Window>

Now we need to start creating our own window. Lets add a border for the window.

<Window x:Class="CustomWindow.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    Title="Window1" Height="300" Width="300"
    AllowsTransparency="True" WindowStyle="None" Background="Transparent">

    <Border BorderThickness="1" BorderBrush="Black" Background="White"
        Margin="0" CornerRadius="10">
    </Border>

</Window>

window2

Now that we have our own window design, we should make a draggable area at the top. I made the background black so you could see the draggable area. We need the corner radius to be the same as the window for the top and left and top right so that it doesn’t go over the window. On the border’s MouseLeftButtonDown event, we want to allow the window to be draggable. To make the draggable area be invisible, set the color to #01000000. This will make the border almost completely transparent. It will look 100% transparent, but you will still be able to click on it.

<Window x:Class="CustomWindow.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    Title="Window1" Height="300" Width="300"
    AllowsTransparency="True" WindowStyle="None" Background="Transparent">

    <Border BorderThickness="1" BorderBrush="Black" Background="White"
        Margin="0" CornerRadius="10">
        <Border Height="40" Background="#01000000" VerticalAlignment="Top"
            CornerRadius="10,10,0,0" MouseLeftButtonDown="DragWindow"/>
    </Border>

</Window>
using System.Windows;
using System.Windows.Input;

namespace CustomWindow
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        protected void DragWindow( object sender, MouseButtonEventArgs e )
        {
            DragMove();
        }
    }
}

window3

Now lets add a drop shadow to the window. We need to add the PresentationFramework.Aero assembly. We can then use the SystemDropShadowChrome control.

<Window x:Class="CustomWindow.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
    Title="Window1" Height="300" Width="300"
    AllowsTransparency="True" WindowStyle="None" Background="Transparent">

    <aero:SystemDropShadowChrome CornerRadius="10" Margin="10">
        <Border BorderThickness="1" BorderBrush="Black" Background="White"
            Margin="0" CornerRadius="10">
            <Border Height="40" Background="#01000000" VerticalAlignment="Top"
                CornerRadius="10,10,0,0" MouseLeftButtonDown="DragWindow"/>
        </Border>
    </aero:SystemDropShadowChrome>

</Window>

window4

Now we have a customized window. If you want to add a maximize, minimize, and close button, you can add them to the draggable header border and hook the events up. To max/min the window, set the WindowState = WindowState.Maximized, WindowState.Minimized or WindowState.Normal. To close the window just call Close().

You can download the example project here. Note: Be sure to change the extension from .doc to .zip.