Introduction
I’ve recently been trying to spruce up my Boggle application on codeplex, aka Bogglex. The current 0.2 release is, to put it mildly, a little on the plain side.
I quite fancied getting rid of the default chrome which Windows dresses its forms in and creating my own window design. I usually loath UIs which do this (iTunes, any number of programs shipped with pre-built PCs, but especially iTunes). However, I really like MetroTwit‘s simple and clean UI, so I thought I’d attempt to imitate it.
The starting point for this is getting a window that:
- Has none of the Windows chrome surrounding it.
- Retains a drop shadow. (This really helps the aesthetics when your window is on top of another).
- Can be dragged around the screen.
- Can be resized.
That’s what this blog post covers. This post starts off with a brand new empty WPF/C# project in Visual Studio 2010.
Getting Rid of the Windows Chrome
Getting rid of the chrome is pretty simple and done in the XAML declaration of the Window. The key properties are WindowStyle and ResizeMode:
-
<Window x:Class="WpfApplication1.MainWindow"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
Title="MainWindow" Height="350" Width="525"
-
WindowStyle="None" ResizeMode="NoResize">
-
</Window>
This combination of values will give you a white rectangle of a Window that can’t be moved or resized. Pretty useless really, but a reasonable starting point.
A Drop Shadow
That white rectangle looks horrible, especially on top of other windows. However, we can get it to look a little nicer by adding a drop shadow. One way to do this is to make the window transparent, and use a border with a reasonable margin which has a drop shadow.
The drawback to this technique is that the window will appear 20 pixels smaller both vertically and horizontally than it actually is. This means that if you do Alt+PrintScreen you’ll capture a 20 pixel border of whatever’s underneath the window. You will also lose cleartype support, and may have to tweak the border if you want to allow the window to be maximized.
I have yet to investigate other methods of drawing a drop shadow (I doubt MetroTwit uses this technique), but will post again if I find a better way. However, I fear it will involve quite a bit of P/Invoke. Anyway, here’s the border technique:
-
<Window x:Class="WpfApplication1.MainWindow"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
Title="MainWindow" Height="350" Width="525"
-
WindowStyle="None" ResizeMode="NoResize"
-
AllowsTransparency="True" Background="Transparent">
-
<Border Margin="10">
-
<Border.Effect>
-
<DropShadowEffect Color="Black"
-
Direction="270"
-
BlurRadius="10"
-
ShadowDepth="3" />
-
</Border.Effect>
-
<Grid Background="White" />
-
</Border>
-
</Window>
Here we’ve added a couple of properties to make the Window transparent, and added a border with drop shadow and decent margin. Running this, you should be able to see how the Window now looks like it’s on top of other windows.
Supporting Dragging
It’s easy to support dragging a window in WPF. The framework handily provides us with the method DragMove() which simply needs to be called when the left mouse button is pressed over a certain element.
We’ll make ourselves a fairly basic lime green title bar, and hook up its MouseLeftButtonDown event to an event handler which calls DragMove().
-
<Window x:Class="WpfApplication1.MainWindow"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
Title="MainWindow" Height="350" Width="525"
-
WindowStyle="None" ResizeMode="NoResize"
-
AllowsTransparency="True" Background="Transparent">
-
<Border Margin="10">
-
<Border.Effect>
-
<DropShadowEffect Color="Black"
-
Direction="270"
-
BlurRadius="10"
-
ShadowDepth="3" />
-
</Border.Effect>
-
<Grid Background="White">
-
<DockPanel>
-
<DockPanel Name="titleBar"
-
DockPanel.Dock="Top"
-
Height="32"
-
Background="LimeGreen">
-
<TextBlock Padding="8"
-
VerticalAlignment="Center"
-
Text="My Special Window"
-
Foreground="White"
-
FontWeight="999"
-
FontSize="16" />
-
</DockPanel>
-
<ContentControl Name="content" />
-
</DockPanel>
-
</Grid>
-
</Border>
-
</Window>
-
namespace WpfApplication1
-
{
-
/// <summary>
-
/// Interaction logic for MainWindow.xaml
-
/// </summary>
-
public partial class MainWindow : Window
-
{
-
public MainWindow()
-
{
-
InitializeComponent();
-
titleBar.MouseLeftButtonDown += (o, e) => DragMove();
-
}
-
}
-
}
This looks a little something like:
Holding the left mouse button down over the lime green title bar and dragging is now enough to move the window.
Resizing
Now for resizing. Implementing resizing is at least a hundred times harder than the rest (barring a decent drop shadow implementation). With the amazing support for dragging windows with DragMove, you’d have thought that there’d be similar support for resizing a window. Unfortunately, there’s not.
However, it is possible to get half decent resizing functionality, but you’ll have to P/Invoke your way to victory. Kirupa Chinnathambi provides an excellent example of how to accomplish borderless window resizing which I took and refactored into some reusable classes that are hopefully fairly simple to use.
The entire classes would be a little cumbersome pasted into this blog, but you can view or obtain the code as it’s used in the Bogglex source:
Simply add those classes to the project. Once that’s been done, we need to add elements to represent the borders of our window.
-
<Window x:Class="WpfApplication1.MainWindow"
-
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-
Title="MainWindow" Height="350" Width="525"
-
WindowStyle="None" ResizeMode="NoResize"
-
AllowsTransparency="True" Background="Transparent">
-
<Border Margin="10">
-
<Border.Effect>
-
<DropShadowEffect Color="Black"
-
Direction="270"
-
BlurRadius="10"
-
ShadowDepth="3" />
-
</Border.Effect>
-
<Grid Background="White">
-
<Grid.RowDefinitions>
-
<RowDefinition Height="4" />
-
<RowDefinition Height="*" />
-
<RowDefinition Height="4" />
-
</Grid.RowDefinitions>
-
<Grid.ColumnDefinitions>
-
<ColumnDefinition Width="4" />
-
<ColumnDefinition Width="*" />
-
<ColumnDefinition Width="4" />
-
</Grid.ColumnDefinitions>
-
<DockPanel Grid.RowSpan="3" Grid.ColumnSpan="3">
-
<DockPanel Name="titleBar"
-
DockPanel.Dock="Top"
-
Height="32"
-
Background="LimeGreen">
-
<TextBlock Padding="8"
-
VerticalAlignment="Center"
-
Text="My Special Window"
-
Foreground="White"
-
FontWeight="999"
-
FontSize="16" />
-
</DockPanel>
-
<ContentControl Name="content" />
-
</DockPanel>
-
<Rectangle Name="topLeft" Fill="YellowGreen" />
-
<Rectangle Name="top" Fill="YellowGreen" Grid.Column="1" />
-
<Rectangle Name="topRight" Fill="YellowGreen" Grid.Column="2" />
-
<Rectangle Name="right" Fill="YellowGreen" Grid.Row="1" Grid.Column="2" />
-
<Rectangle Name="bottomRight" Fill="YellowGreen" Grid.Row="2" Grid.Column="2" />
-
<Rectangle Name="bottom" Fill="YellowGreen" Grid.Row="2" Grid.Column="1" />
-
<Rectangle Name="bottomLeft" Fill="YellowGreen" Grid.Row="2" />
-
<Rectangle Name="left" Fill="YellowGreen" Grid.Row="1" />
-
</Grid>
-
</Border>
-
</Window>
To use the WindowResizer, we simply call the constructor like so:
-
namespace WpfApplication1
-
{
-
/// <summary>
-
/// Interaction logic for MainWindow.xaml
-
/// </summary>
-
public partial class MainWindow : Window
-
{
-
public MainWindow()
-
{
-
InitializeComponent();
-
titleBar.MouseLeftButtonDown += (o, e) => DragMove();
-
new WindowResizer(this,
-
new WindowBorder(BorderPosition.TopLeft, topLeft),
-
new WindowBorder(BorderPosition.Top, top),
-
new WindowBorder(BorderPosition.TopRight, topRight),
-
new WindowBorder(BorderPosition.Right, right),
-
new WindowBorder(BorderPosition.BottomRight, bottomRight),
-
new WindowBorder(BorderPosition.Bottom, bottom),
-
new WindowBorder(BorderPosition.BottomLeft, bottomLeft),
-
new WindowBorder(BorderPosition.Left, left));
-
}
-
}
-
}
There, now we have a borderless, draggable, resizable window with a drop shadow. It’s far from a finished product, but it’s a decent starting point we can build on.
Further Development
This is exactly how I implemented Bogglex‘s borderless window as of changeset 9106. With a little bit of thought I managed to get something with a MetroTwit-ish style, albeit with a strange border and no cleartype support.
I am going to research a better way of drawing the drop shadow for Bogglex before releasing version 1.0. I don’t like the loss of cleartype, and the margin is annoying. You can decide for yourself whether the drop shadow is worth it.
It’s suprising how Microsoft make it so easy to switch to a borderless look for a window, and provide great support for dragging a window, but almost no support for resizing it or for a drop shadow. Perhaps we’ll see better support in a future version of WPF. I’m sure we will if Fix WPF has anything to do with it.