Contents

1.0 - How to make a Window with a custom border
2.0 - Summary
3.0 - Notes/Links


1] Download source code (Visual Studio 2008, C#): Solution_WinBorderImage_01.zip [30 kB].
2] Download source code (Visual Studio 2008, C#): Solution_WinBorderless_02.zip [27 kB].

Environment: WPF, Framework 3.5.


1.0 - How to make a Window with a custom border

When, for some reason, a standard Window border is undesirable you can replace it by your own. The original border is made invisible and then replaced with a custom border. All sorts of non-rectangular borders are now possible. The strategy is simple:
  1. Remove the original Window border.
  2. Add a Canvas or another Panel to the Window as a container for the other controls.
  3. Add a border to the Canvas as Border or Shape, as first element.
Removing the original border also removes the top-bar with Minimize, Maximize and Close buttons. Resizing and moving the window is not possible anymore. These functions must be re-added with extra code.


1.1 - Border types

Most obvious candidate for a custom border is the Border (System.Windows.Controls). This element can have rounded corners with property CornerRadius. Line-width and color can be chosen with properties BorderThickness and BorderBrush.
More flexible are the shapes from System.Windows.Shapes. All sorts of forms are possible with a Rectangle, Ellipse, Polygon or Path.




Fig. 1.1 - Window with a border made from a transparent PNG.
(Download 1).


Another possibility is to define the border with a transparent image, as is done in Fig. 1.1. Images, however, have some disadvantages compared to a Shape. On the outside of the border white artifacts can show up when the window is shown on a dark background, because of antialiasing. Another problem is resizing. While resizing a Shape from code is easy with ScaleTransform, resizing an image is much more difficult or unfeasible, because of interpolation artifacts. A (large) image also adds weight to a solution.


1.2 - Removing the standard border

The standard Window border is removed by setting the WindowStyle and AllowsTransparency properties:


  C# - A Window without border.

  Window Window1 = new Window();
  [...]
  Window1.WindowStyle = WindowStyle.None;
  Window1.AllowsTransparency = true;
  Window1.Background = Brushes.Transparent;


Setting property AllowsTransparency = true is essential. Without it the window has still a small border, even with WindowStyle.None.
The area outside our new border is made invisible by setting the Window Background to Transparent.

Removing the border leaves us with an unmanageable window because it has no top-bar. It cannot be resized or moved and it has no Minimize, Maximize and Close buttons. So, we must add these features ourselves.


1.3 - Adding a custom border

A custom border, and all the rest, is simply made with UIElement's placed on the Window. A Window can contain only one element (with property Content) and a Panel is usually placed on the window as a container for the other elements. The first element on the panel is the new border, for example a Rectangle or another Shape.

When a Border Control is used as new border it is the first element in the window because it can have only one child. A Panel is added to it for the other elements.

In Fig. 1.2. the border is made with a Rectangle with rounded corners, set with properties RadiusX and RadiusY.





Fig. 1.2a - Window with custom border.

A window with rounded corners and a new top-bar with buttons. (Download 2)



Fig. 1.2b - Window with custom border.

The window is extendable with button 'More/Less'. All sorts of exotic borders are possible.


  C# - A Window with a Rectangle as border.

  using System;
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Media;
  using System.Windows.Shapes;
  [...]

  private Canvas canvasMain = new Canvas();     // Main Canvas in Window.
  private Rectangle border1 = new Rectangle();  // Border.
  [...]

  //
  // border1
  //
  border1.Width = 160;
  border1.Height = 160;
  border1.StrokeThickness = 1.0;
  border1.Margin = new Thickness(0);
  border1.Stroke = Brushes.DarkGray;
  border1.Fill = Brushes.LightYellow; 
  border1.RadiusX = 10.0;
  border1.RadiusY = 10.0;
  Canvas.SetLeft(border1, 0);
  Canvas.SetTop(border1, 0);
  //
  // canvasMain
  //
  canvasMain.Background = Brushes.Transparent;
  canvasMain.Children.Add(border1);
  //
  // this Window
  //
  this.Width = 160;
  this.Height = 160;
  this.AllowsTransparency = true;
  this.WindowStyle = WindowStyle.None;
  this.Background = Brushes.Transparent;
  this.Content = canvasMain;


Along with the border, new buttons are made for the Minimize, Maximize and Close functions. When the original border is removed you can design any user-interface as usually, only the contour is different. In this example a window is made with a More/Less button for making it expandable.

When the new border does not cover the complete area of the original Window, here outside the rounded corners, the background of the Window must be made transparent with property Background = Brushes.Transparent. The same applies to the panel on which the border is placed.


1.4 - Moving the Window

A Window is usually moved with the left mouse button down on the top-bar. So, a top-bar replacement (caption) is made with a Rectangle and event-handlers are added for MouseLeftButtonDown, MouseMove and MouseLeftButtonUp.


  C# - Moving the window.

  using System;
  using System.Windows;
  using System.Windows.Media;
  using System.Windows.Controls;
  using System.Windows.Shapes;
  [...]

  private Rectangle oRec1;            // Caption for subwindow 1.
  oRec1 = new Rectangle();
  [...]

  private Point MousePos;             // Position of the mouse.
  private Point WindowPos;            // Position of Window.
  private bool bDragging;             // =true: the Window is dragged.
  [...]

  //
  // oRec1
  //
  oRec1.Name = "REC_1";
  oRec1.Width = 140;
  oRec1.Height = 22;
  oRec1.StrokeThickness = 1.0;
  oRec1.Margin = new Thickness(0);
  oRec1.Stroke = Brushes.DarkGray;
  oRec1.Fill = br;
  oRec1.MouseLeftButtonDown += 
        new MouseButtonEventHandler(oRec1_MouseLeftButtonDown);
  oRec1.MouseMove += 
        new MouseEventHandler(oRec1_MouseMove);
  oRec1.MouseLeftButtonUp += 
        new MouseButtonEventHandler(oRec1_MouseLeftButtonUp);
  Canvas.SetLeft(oRec1, 10);
  Canvas.SetTop(oRec1, 0);
  [...]

  // Start dragging Window.
  private void oRec1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  {
      bDragging = true;
      MousePos = oRec1.PointToScreen(e.GetPosition(this));
      WindowPos = new Point(this.Left, this.Top);
  }

  // Drag Window.
  private void oRec1_MouseMove(object sender, MouseEventArgs e)
  {
      if (!bDragging) return;
      Point pos = oRec1.PointToScreen(e.GetPosition(this));
      this.Left = WindowPos.X + pos.X - MousePos.X;
      this.Top = WindowPos.Y + pos.Y - MousePos.Y;
  }

  // Stop dragging Window.
  private void oRec1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
  {
      bDragging = false;
  } 


The window caption is made with a Rectangle (oRec1). The Minimize, Maximize and Close buttons are also placed on this element (Fig. 1.2).

With the PointToScreen method a Point on the Rectangle is converted to a Point in screen coordinates. The new position of the Window (this) is set with the X- and Y-values from this Point.
The PointToScreen method is available for Visual (System.Windows.Media), from which Control and Shape are derived.


1.5 - Resizing the Window

Resizing is not implemented in this example, the window has a fixed size.

This part needs quite some code. A small padding must be defined inside the border where the mouse cursor is changed to Cursors.SizeNS .. Cursors.SizeWE and where the displacement of the mouse is measured when the mouse button is down. With the measured distance the window is resized and in some cases moved (top and left border). Maximum and minimum values of the window size must be checked and if the window is already maximized.

A quick alternative is to set the Window ResizeMode property to ResizeMode.CanResizeWithGrip. This will add a resize-grip in the bottom-right corner:




Fig. 1.3 - Borderless window with resize grip.


1.6 - Minimize, Maximize and Close buttons

Implementing the Minimize, Maximize and Close buttons is easy with Window.WindowState and with the Close method.
The buttons can be made from real Button controls, with a Shape or a Panel. In this example a Canvas is used with an Image on it (System.Windows.Controls).


  C# - Functions for the buttons on the top-bar.

  Window Window1 = new Window();
  [...]

  // Minimize.
  Window1.WindowState = WindowState.Minimized;

  // Maximize.
  Window1.WindowState = WindowState.Maximized;

  // Close.
  Window1.Close();


These methods can be called from the button MouseUp eventhandlers.


  C# - Button 'Minimize'.

  using System;
  using System.Windows;
  using System.Windows.Media;
  using System.Windows.Media.Imaging;
  using System.Windows.Shapes;
  [...]

  private Canvas Button16;
  [...]

  // A button with an image from \Resource.
  Button16 = NewButton("Minimize_16.png");
  Button16.MouseUp += new MouseButtonEventHandler(Button16_MouseUp);
  [...]

  // Minimize the window.
  private void Button16_MouseUp(object sender, MouseButtonEventArgs e)
  {
     this.WindowState = WindowState.Minimized;
  }
  [...]

  // ---------------------------------------------------------------
  // Date      290110
  // Purpose   Make a button from a resource image, as Canvas.
  // Entry     sFileName - The filename of the image (no path).
  // Return    A Button 16x16 px as Canvas.
  // Comments  Use the images added to \Resources in the 
  //           Solution Explorer.
  // ---------------------------------------------------------------
  private Canvas NewButton(string sFileName)
  {
      Image oI = new Image();
      oI.Width = 16;

      BitmapImage bitmap = new BitmapImage();
      bitmap.BeginInit();
      bitmap.UriSource =
          new Uri("pack://application:,,,/Resources/" + 
          sFileName, UriKind.RelativeOrAbsolute);
      bitmap.DecodePixelWidth = 16;
      bitmap.EndInit();
      oI.Source = bitmap;
      Canvas.SetLeft(oI, 0);
      Canvas.SetTop(oI, 0);   
   
      Canvas oC = new Canvas();
      oC.Width = 16.0;
      oC.Height = 16.0;
      oC.Background = Brushes.Transparent;
      oC.Children.Add(oI);

      return oC;
  }


With a Canvas + Image instead of a Button we can design our button as we like it, with a custom border and in any color. The images are made as 16x16 px (transparent) PNG's and added as resource to the project at design time. They are retrieved from the resource with a pack-Uri.


2.0 - Summary

Making a custom border for a window is not as straightforward as it seems at first sight. It is easy to make a border, for example with a Path, but the window also needs a new top-bar with Minimize, Maximize and Close buttons and functions for moving and resizing the window.


3.0 - Notes/Links


[1] MSDN - Window Class.
Provides the ability to create, configure, show, and manage the lifetime of windows and dialog boxes.
http://msdn.microsoft.com/en-us/library/system.windows.window.aspx