Free!  FoxPro to WPF screen converter
   Almost free!  One day of FREE VFP to WPF evaluation of your FoxPro app ONSITE
   Free!  Read the foreword to Les' new book (available here)...
Skip Navigation Links

Build a Base Class for your WPF windows

Get all your windows to work the same way




Login or register to download source code

Coding time: 20 minutes.   

Jump to: 

 Introduction   Window Behavior   Visual Styles   Conclusion 

 


Introduction

Web programming has Master Pages; WinForms has form classes; so where do you put your base form in WPF in order to have visual inheritance? You don't. That is, if you expect to build a base form class and visually inherit from it. WPF doesn't work that way.

However, there are ways to get the same effect as form inheritance or master pages. They're just not located where you expect them, and they aren't used the same way. But here are places to put code and styles so that they will be inherited by various elements in your application.

The purpose of this article is to demonstrate how to achieve a common appearance and behavior in your WPF-MVVM applications by using inheritance and styling in their various forms. Together, these techniques lend consistency to your applications.

There are two technologies that I'll focus on in this article:

  • A BaseWindow class built entirely in code and then referenced in both the XAML and the codebehind of each window that should inherit from the window; you can have several such window classes, none of which has a .xaml file. This is where common behaviors reside;
  • App.xaml Styles that apply to all XAML objects, including Grids, StackPanels, DataGrids, Labels, etc. in all of your Windows;

The combination of these elements should give you what you're looking for.


Standardizing Window Behavior

Any Window can inherit from any other Window class. The first tag in any WPF Window tells the form to inherit from System.Windows.Window:

<Window
  x:Class="Inheritance_in_WPF.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:c="clr-namespace:Inheritance_in_WPF"
  WindowStartupLocation="CenterScreen"
  Title="Prospects"
  Height="260" Width="500" MaxWidth="500" MaxHeight="260" >

The class described in the CodeBehind tersely inherits from the same Window class:

  public partial class MainWindow : Window

But you don't have to use the Window class; you can write your own, and I have. It's called BaseWin. In Visual Studio 2010, Select New, Project, WPF Application from the menu, and name it Inheritance_in_WPF. Right click on the project name and select Add, Class, and name it BaseWin.cs. At the end of the class declaration, specify that it inherits from Window":

BaseWin.cs

using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;

namespace Inheritance_in_WPF
{
  public class BaseWin : Window
  {
  }
}

I've chosen four behaviors that I want to see in all of my forms:

  • I want all forms centered on the screen;
  • I want to specify the name of the first control to get focus in the window's xaml;
  • When the user resizes the form, I want all of the controls on the form to resize automatically, and I want the height and width to preserve their original aspect (proportionality); and
  • When the Escape key is pressed, I want to close the form.

You'll undoubtedly think of others, but the techniques shown in this section can be used to add just about any behavior to all of your forms.

Centering all forms

I used to add WindowStartupLocation="CenterScreen" to the Window attributes for all of my Windows, as I did above. If I can add that specification to my base form class, it will appear in all of my forms. But there's no default .xaml file to inherit from in WPF apps.

However, anything you can do in xaml you can do in code. Centering can be done in the BaseWin class constructor, as seen here:

    public BaseWin()   /* The constructor for the BaseWin class */
     {WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;}

The constructor fires when the object based on the class is created. Once I've done this, I no longer have to add the WindowStartupLocation attribute in each window's xaml.

Specifying which control gets focus first

You'd think that the first input control on each form should get the focus every time, but you'd be wrong. If your forms start up with most input controls disabled (as mine do when I use the Add/Edit/Delete paradigm shown in Article 610), then you probably want to start with the Add or Edit button enabled instead. It's different on each form. So I decided to add a property at the top of the xaml for each window where you can specify the name of the control that gets the focus first.

To do this, I started by adding a public property called FocusedControlName to the Window Base Class:

    public string FocusedControlName { get; set; }

It now appears as an available choice while you're specifying the xaml for the BaseWin, as shown in Fig. 1:



and here's the code that uses the property to set the focus:

    private void BaseWin_Loaded(object sender, RoutedEventArgs e)
    { if (FocusedControlName != null)
      { object  c  = this.FindName(FocusedControlName);
        Control cn = c as Control;
        cn.Focus(); }
    }

The BaseWin_Loaded event fires when the form is loaded - not because of its name, but because I told it to in the constructor of the base window class, as shown in Listing 2.

Maintaining Window aspect ratio

The third feature is available almost for free in WPF, if you wrap the contents of a Window with this:

<Window...>
  <Viewbox VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="Uniform">
...
  </Viewbox>
</Window...>

However, if you expand the window downward, the width of the window doesn't change, and if you drag the right border right or left, the height doesn't change. I want them to remain proportional. So I wanted to store the original height and width, and then increase both of those dimensions by the amount that the height or width had changed.

I added a little code to check whether my windows included a ViewBox control. I won't include the listing here, but you can download it by logging in at the top of this article; you'll have to register and get a password, but it's free.

      /* Display a message if the form doesn't have a ViewBox */

      // in the declarations at the top of the BaseWin class:
      const bool CheckForViewBoxes = true;  // set to false if you don't want to see messages

      // in BaseWin_Loaded:
      if (CheckForViewBoxes)
      { Window window = this;
        bool FoundVB = false;
        foreach (Viewbox vb in window.FindChildren<Viewbox>()) { FoundVB = true; }  /* See Util.cs */
        string WindowName = this.Name;
        if (!FoundVB) MessageBox.Show("If you want a scalable window, add a Viewbox as the outermost element in " + this.Title);
      }

The code in the Window_SizeChanged event handler shown in Listing 2 does just that. Note that the method signature is the same one that the corresponding codebehind stub would have if you used the Events tab in the Properties Window of a form to generate one. And that's exactly what I did to get this event handler name and parameters. StartingWidth and StartingHeight will be private to each window that inherits from this class.

Resize event handler code to maintain the window's aspect ration

    public void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    { if (StartingWidth  == 0) { StartingWidth  = ActualWidth; }
      if (StartingHeight == 0) { StartingHeight = ActualHeight; }
      double pct1   = this.ActualWidth  / StartingWidth;
      double pct2   = this.ActualHeight / StartingHeight;
      double maxpct = pct1 > pct2 ? pct1 : pct2;
      this.Height   = StartingHeight * maxpct;
      this.Width    = StartingWidth  * maxpct;
    }

Finally, the KeyDown handler traps the Escape key and closes the form. Sweet and simple.

   public void Window_KeyDown(object sender, KeyEventArgs e)
     { if (e.Key == Key.Escape) Close(); }

The complete BaseWin class code is shown below in Listing 1. Note the highlighted code where the three event handlers are wired up:


To use it, you have to do several things. First, change your Window's CodeBehind class declaration to inherit from BaseWin, like this:

   public partial class MainWindow : BaseWin

Next, add a new XML NameSpace ("xmlns:") attribute within the Window declaration at the top of MainWindow.xaml; this provides an internal name for an external assembly.

   xmlns:local=

As soon as you enter the "=", the IDE will offer to let you select an executable or dll; you want the project that you're working with. In my case, it's xmlns:local="clr-namespace:Inheritance_in_WPF".

Once you've added it, you can change the base class of the form. Go up to the top and change <Window to <local:BaseWin. Change the tag at the end of the file to </local:BaseWin> as well. Recompile, and you're using a base form for your window.

I hope that the next version of Visual Studio will automate the use of base form classes, but it only takes a couple of seconds to implement them right now.

We'll need to add a couple of controls in order to test our base form, so change MainWindow.xaml to look like Listing 2:


Press F5 to run the app.



Note that the window is indeed centered. Drag any margin to resize it, and note that the aspect ration is maintained. Finally, press the Escape key to close it. Check, check, check.

There are lots of other form-level behaviors that you may want to add to your Windows. And of course, you may have several types of windows, and of course you can have a base class for each type. By using base classes for all of them, you can add behaviors once, and then enjoy them in all of your forms.


Standardizing Visual Styles

Half of the reason for using inheritable forms in both Web and WinForms applications is to get a consistent "look and feel." The behaviors implemented above are part of the user experience, but visual elements are more likely to get their attention when you're demonstrating a product that you want them to buy. In fact, few people would say that the "wow factor" lies in what we just did. Colors, graphics and fonts are more likely to get their attention.

Visual Studio's answer to visual styles is Styles. I'm not being cute: Styles are instructions that you embed either in the .xaml file for each window, or in a single repository that applies the same styles to all of the controls on all of the forms in your application. That repository is called App.xaml. It's right up there below the name of your UI project in Solution Explorer. Double click to open the XAML Editor (there's no visual representation of App.xaml). You'll see... this:

<Application
  x:Class="Inheritance_in_WPF.App"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  StartupUri="MainWindow.xaml">

  <Application.Resources>
  </Application.Resources>

</Application>

Styles go in the Application Resources section. This is where you create styles to be applied to every visual element in your UI. I've seen resource dictionaries (xaml files) thousands of lines long, and at first glance, they look complicated. However, each style consists of just a few elements. Here's a sample style for a textbox that changes from black on white to yellow on blue when it gets the focus:

  <Style TargetType="TextBox">
      <Setter Property="FontFamily" Value="Courier New" />
      <Setter Property="FontSize"   Value="10" />
      <Setter Property="Foreground" Value="Black" />
      <Setter Property="Background" Value="White" />
      <Style.Triggers>
        <Trigger Property="IsFocused"   Value="True" >
          <Setter Property="Foreground" Value="Yellow" />
          <Setter Property="Background" Value="Blue" />
        </Trigger>
      </Style.Triggers>
    </Style>

Any Styles in App.xaml will be applied to the designated TargetType - Window, Grid, Label, TextBox or any other xaml visual component. In addition, you can specify key names for styles and apply them selectively. You can use the BasedOn property to inherit from a previously-defined Style, and then add a different foreground or background color, a different font, or any other attribute. You can use Triggers to add behaviors like color changing when the focus changes, or when the mouse cursor passes over a control. Finally, you can build multiple ResourceDictionaries (essentially, the contents of an App.xaml file), and let users swap one out for another during program execution. If you're selling a product that needs to demo well, implementing Themes is easy; see Article 608: Theme Support in WPF for a quick tutorial.

A sample App.xaml file is shown in Listing 3:


The styles that have a TargetType will be applied to all targets of the named type. However, if they have a Key (the "x:Key" prefix means that an identifier of the same name will be created and can be applied to controls of the same TargetType in your xaml using the syntax Style="{StaticResource KeyName}".

x:Key is the primary identifier in a ResourceDictionary. When you create a Key, the style will only be applied if a control contains an explicit assignment, using "Style="{StaticResource KeyName}".

You can also use the x:Key identifier to create local variables, which you can then refer to in your XAML. Here I've used a few Brushes to be applied to a number of different types of controls so as to impart a consistent color scheme.

You don't have to put all your styles in one basket. You can include several xaml files in app.xaml, or in any other dictionary file. You use the MergedDictionaries attribute to combine them.

<Window...>
  <ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="Brushes.xaml"/>
      <ResourceDictionary Source="Colors.xaml"/>
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
  ...
</Window>

Article 608 shows how to implement Themes. However, only Google and a lot of free time will get you up to speed on styling in xaml. You can use Expression Blend, but the xaml editor in Visual Studio does a pretty good job. And whether you use Expression Blend or the XAML Editor, you still have to have a pretty good idea of what you want your UI to look like. I expect to see a generation of xaml designers who can take your application and turn it into a work of art, now that we can separate the implementation from the interface. And that, after all, is what WPF and MVVM are all about.


Conclusion

In this article, we've built a base window that can be inherited in all of your Window forms. and which reduces coding and standardizes the look and feel of your WPF/MVVM applications. We've also looked at using Styles in App.xaml to standardize UI elements. The combination of these two elements gives your software a consistent and professional look, and should be the starting point for your applications.

See 'ya :)

qqq


Copyright(C) Pinter Consulting, 2012Tel: +1 (650) 464-6924
Automated conversion between C# and VB by Visible C#/Visible VB from Tangible Software Solutions