Posts tagged ‘WPF’

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.

DevConnections – Make WPF Data Binding Work Against Business Objects

This talk was given by Rockford (Rocky) Lhotka.

Rocky works for Magenic and is kind of a home town hero around here. People are always talking about him and his books and his CSLA Framework and etc, but I’ve never seem him speak before, so I was looking forward to this.

Rocky talked about how data binding works with WPF and gave some examples. The real meat of the talk was centered around the gotchas, and how to work around them. I’ll mention one of them here.

If you create custom objects and a custom data provider and bind that to WPF, you need to implement INotifyPropertyChange. This will cause the UI to refresh itself with the new data, when the data changes. There is a problem here though. When re-bind the data, WPF will check if the data has changed, if not, it won’t refresh the UI. WPF uses the .Equals method to do this, rather than comparing references. If you’re using a database, most of the time you’ll have the .Equals compare something like the Id of the record, and maybe a few other fields. If you update some data that isn’t a part of that, WPF will think it’s the same object and not re-bind. The way around this is to write an identity converter that doesn’t actually do any conversions. If a value from WPF runs through a converter, it will always update the UI afterwards.

Of course Rocky was pushing his CSLA Framework as a sort of a cure for all problems, but he did get into what he was doing in the framework, and why he had to do it. I’m personally not a big fan of the framework, but it was nice to hear the reasoning behind everything he’s doing. He’s definitely a smart man, and I have a lot of respect for him after hearing him speak.

WPF seems like a very powerful tool, but it will take some time for developers to get used to a new programming model like this. I think it has great potential, and is where most, if not all Windows development will be heading in the future.

DevConnections – Building a LINQ Based Business Layer for ASP.NET Apps

This talk was given by Rick Strahl.

This talk was very upsetting. There was a person asking entry level LINQ questions though out the whole thing… and yes this is the same person as in the other sessions. I don’t know how I ended up choosing the same ones as him. Rick kept answering his questions, and in the 1.5 hour session, got to the actual topic in the last 5 minutes. I was really disappointed, ’cause this was one session I was really looking forward to.

Basically, how this is done is you would create business objects and pass up a type that can be queried using LINQ. You can pass a type from the LINQ to SQL data context, limiting it to a specific object/table. This way a consumer can still run a LINQ query on the object, and the query will be optimized and not ran until needed, just like if you were using the data context directly. You can then create all the business rules you want around these objects.

One of these days I’ll write up some examples of how this would work and post them.