Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Catel - Part 2 of n: Using WPF Controls and Themes

0.00/5 (No votes)
25 Nov 2010 4  
This article explains some of the most important controls and windows of Catel.

Catel is a brand new framework (or enterprise library, just use whatever you like) with data handling, diagnostics, logging, WPF controls, and an MVVM Framework. So, Catel is more than "just" another MVVM Framework or some nice Extension Methods that can be used. It's more like a library that you want to include in all the (WPF) applications you are going to develop in the near future.

This article will explain the user controls and themes of the framework.

Article Browser

Table of Contents

  1. Downloads
  2. Introduction
  3. Controls
  4. Validation
  5. Windows
  6. Themes
  7. History

2. Introduction

Welcome to part 2 of the article series about Catel. This article explains some of the most important controls and windows of Catel. Not all controls are handled, simply because there are too many to mention them all.

If you haven't read the previous article(s) of Catel yet, it is recommended that you do. They are numbered, so finding them shouldn’t be too hard.

This article contains three detailed information chapters:

  • Controls: This chapter describes the most important controls of Catel.
  • Validation: This chapter describes the validation controls of Catel. They are so special that they deserve to get a separate chapter.
  • Windows: This chapter describes the windows that ship with Catel.

Each item that is described contains an image (if applicable) of the object, the reason why it is built, and finally how it is built. Sometimes, it’s so obvious how a control or window is built, that we leave out that part. If you can’t figure out how the controls work, just let us know! This article is not a reference for the user controls, but more a showcase for what Catel has to offer on various aspects. Catel ships with a reference help file that includes the actual documentation for all methods of the objects in Catel, so use that if you are looking for the actual documentation. This is an introduction article to get you interested in the UI-elements that ship with Catel.

Most of these controls need to be incorporated manually into your own controls and windows. However, it is really recommended to use the MVVM Framework together with the controls described in this article. The MVVM Framework that ships with Catel is described and explained in the next article, but fully uses the power of the controls that are explained in this article (such as the validation controls).

3. Controls

3.1. BrowseForFile

The BrowseForFile control allows you to easily add 'browse for file' features to your application. The control includes a button so you don't have to take care of this yourself.

3.1.1. Example code

<Controls:BrowseForFile FileName="{Binding SelectedFileName}" />

3.1.2. Why?

The control is very useful when the user must select a file from disk. This control saves the developer from creating a textbox with a button over and over again, and also incorporates the OpenFileDialog class.

3.2. DropDownButton

The DropDownButton is a very well-known control, so it's weird that WPF developers didn't include this control. The DropDownButton has a custom Content property, so it is fully customizable.

3.2.1. Example code

<Controls:DropDownButton ToolTip="Select a person" 
                         Style="{DynamicResource ImageDropDownButtonStyle}">
    <Controls:DropDownButton.DropDownContent>
        <ListBox ItemsSource="{Binding PersonCollection}" 
                     SelectedItem="{Binding Person}" Margin="0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <!-- Resources -->
                    <DataTemplate.Resources>
                        <Style TargetType="{x:Type LocalControls:PersonSummary}">
                            <Setter Property="Opacity" Value="0.50" />
                            <Style.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter Property="Background" 
                                       Value="{StaticResource {x:Static 
                                               SystemColors.HighlightBrushKey}}" />
                                    <Setter Property="Cursor" Value="Hand" />
                                </Trigger>
                                                
                                <!-- On MouseEnter, set opacity -->
                                <EventTrigger 
                                     RoutedEvent="LocalControls:PersonSummary.MouseEnter">
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation 
                                                Storyboard.TargetProperty="Opacity" 
                                                From="0.50" To="1" Duration="0:0:0.5" />
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>
                                                
                                <!-- On MouseLeave, set opacity -->
                                <EventTrigger 
                                     RoutedEvent="LocalControls:PersonSummary.MouseLeave">
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation 
                                               Storyboard.TargetProperty="Opacity" 
                                               From="1" To="0.50" Duration="0:0:0.5" />
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>   
                            </Style.Triggers>
                        </Style>
                    </DataTemplate.Resources>

                    <!-- Content -->
                    <StackPanel Margin="4">
                        <LocalControls:PersonSummary DataContext="{Binding}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Controls:DropDownButton.DropDownContent>

     <!-- Actual image on the dropdown button -->
    <Image Source="/Resources/Images/group.png" />
</Controls:DropDownButton>

3.2.2. Why?

This control is developed to allow the developer to use the well-known DropDownButton control. The example uses the control to show a list of users that a user can use to easily select a specific user.

3.2.3. How?

Internally, the DropDownButton control uses a Popup class that is populated with the value of the Content property. Then, when the button is clicked, it shows the popup.

3.3. MultiLineInput

The MultiLineInput control is a convenience control to easily allow users to input text.

3.3.1. Example code

<Controls:MultiLineInput Caption="Please provide correct input:" 
                         Text="{BindingProvidedText}" />

3.3.2. Why?

How many times do you need to let the user provide a multiline input with a label in front of it? Exactly, a lot! From now on, you can use this simple control which will provide you with the input and you don’t have to define separate controls any longer.

3.3.3. How?

This is a user control that combines a TextBlock and TextBox together into one control.

3.4. StackGrid

Although the example looks crappy (I am not a designer), it shows the power of the StackGrid. You don't have to specify the Grid.Row and Grid.Column attached properties. Remember the times that you want to insert a grid and you had to increase all the numbers? From now on, use the StackGrid!

3.4.1. Example code

<Controls:StackGrid>
    <!-- Row definitions -->
     <Controls:StackGrid.RowDefinitions>
           <RowDefinition Height="Auto" />
           <RowDefinition Height="Auto" MinHeight="15" />
           <RowDefinition Height="Auto" />
     </Controls:StackGrid.RowDefinitions>
     
     <!-- Column definitions -->
     <Controls:StackGrid.ColumnDefinitions>
           <ColumnDefinition Width="Auto" />
           <ColumnDefinition Width="*" />
     </Controls:StackGrid.ColumnDefinitions>
     
     <!-- Name, will be set to row 0, column 1 and 2 -->
     <Label Content="Name" />
     <TextBox Text="Geert van Horrik" />
     
     <!-- Empty row -->
     <Controls:EmptyRow />
     
     <!-- Wrappanel, will span 2 columns -->
     <WrapPanel Grid.ColumnSpan="2">
           <Button Command="ApplicationCommands.Close" />
     </WrapPanel>
</Controls:StackGrid>

3.4.2. Why?

The Grid is an excellent control to show several controls in a nice layout on the screen. However, it happens a lot that a grid consists of only 2 or 3 columns, and the first column is for all the labels, and the second one is for controls such as textboxes. You correctly implement all the windows and controls of your application based on user requirements, and then the user decides that he/she wants a row inserted into a grid containing about 20 rows. When this happens, you need to re-define all the row attributes of the grid.

With the StackGrid, it is no longer required to define the row and column definitions. The StackGrid can smartly interpret the location of the controls and therefore fill in the Grid.Row and Grid.Column attached properties for you. You need an empty row? No problem, you can use the EmptyRow class to fill up a row for you. You want a column span? No problem, just use the existing Grid.Column attached property and the StackGrid will automatically handle this for you.

3.4.3. How?

The StackGrid internally uses a Grid to measure the layout. However, it dynamically loops through its children, and then assigns the Grid.Row and Grid.Column attached properties for the user.

3.5. ToggleRadioButton

The ToggleRadioButton allows you to easily ask the user multiple choice answers that are represented by a nullable boolean.

3.5.1. Example code

<Controls:ToggleRadioButton TextCaption="Do you like this control?" 
                 GivenAnswer="{Binding UserLikesControl}" />

3.5.2. Why?

Sometimes you just want to ask the user a question, and the user can only answer with one of two options (mostly ‘yes’ or ‘no’). This requires you to create several controls, and you will need to write several converters to map the value of the radio buttons to a given answer.

With the ToggleRadioButton, you can fully customize the caption of both the question and the answers. Then, you can simply bind a nullable boolean to the GivenAnswer property. When the value is null, the user hasn’t given an answer yet. Otherwise, the value with be either true or false, depending on the given answer.

3.5.3. How?

The ToggleRadioButton combines a TextBlock and several RadioButton controls into one control. Then, it uses converters to make sure that the GivenAnswer correctly reflects the values of the radio buttons and vice versa.

3.6. TraceOutputControl

TraceOutputControl is a debugging convenience control. It shows all the trace output. This way, you can easily view all the binding errors, etc., in your app instead of the non-colored output box in Visual Studio.

3.6.1. Example code

<Controls:TraceOutputControl />

3.6.2. Why?

Many times, developers are inside an application viewing the result of what they have created. But, they also want to know what is happening in the background and view the traces they have written. The output window of Visual Studio is a solution, but it doesn’t show errors very well (black, just as the normal output). Also, it doesn’t allow run-time filtering of the results.

The TraceOutputControl allows a developer to embed a control inside a window or control in the actual application and view the information when the application is actually running. The TraceOutputControl is also available as a separate window in case it can’t be embedded into the software itself (for example, when a plug-in is being developed for a 3rd party application).

3.6.3. How?

The TraceOutputControl subscribes a custom TraceListener to the Trace.Listeners collection. Then, it filters out the messages that the user actually wants to see and stores these messages into an internal collection so the user can still filter the messages at a later time.

4. Validation

The validation controls are user controls, but deserve a special chapter since the controls should be used together.

4.1. InfoBarMessageControl

Ever wanted to show the details of error messages in WPF to your end-users? Then, the InfoBarMessageControl is the control to use! The control shows a summary of all business and field errors provided by bindings on objects that implement the IDataErrorInfo interface.

In combination with the WarningAndErrorValidator control, the InfoBarMessageControl can even show field and business warnings for objects that implement the IDataWarningInfo interface that ships with Catel.

4.1.1. Example code

<Controls:InfoBarMessageControl>
    <!-- Actual content here -->
</Controls:InfoBarMessageControl>

4.1.2. Why?

As developers of Catel, we were looking for a good way to show the user errors and warnings. WPF does provide some way of showing errors to the user by default (the red border around controls), but we didn’t think this was enough, because the user saw that something was wrong, but not exactly what was wrong.

These were the requirements for the validation we had in mind:

  1. A tooltip showing the error message of a field error (we solved this via themes that are available in Catel, more on this later);
  2. A message bar that is always at the same position (the top of the window or the user control), and shows a summary of all (business and field) errors;
  3. Have the ability to show warnings as well (because sometimes a user input is valid, but probably not what the user intended to do).

4.1.3. How?

The InfoBarMessageControl subscribes to the Validation class. This class is responsible for showing the red border around the controls that WPF shows by default. Then, it requests the actual field error property of the data item. This is added to an internal collection of error messages, and therefore the control is able to show the errors of all bindings.

When the WarningAndErrorValidator control is found as a child control, the InfoBarMessageControl also subscribes to the events exposed by the WarningAndErrorValidator. The internal working of that control is explained later in this article. When a data object is subscribed via the WarningAndErrorValidator, the InfoBarMessageControl will also handle the warnings and business errors of that data object.

4.2. WarningAndErrorValidator

The WarningAndErrorValidator control is not visible to the end user. The only thing this control takes care of is to forward business errors and warnings to controls that are interested in them. The only control that ships with Catel is the InfoBarMessageControl. Thanks to the WarningAndErrorValidator, the InfoBarMessageControl is able to show business errors and warnings to the end user.

4.2.1. Example code

<Controls:WarningAndErrorValidator Source="{Binding MyObject}" />

4.2.2. Why?

The .NET Framework lacks of a way to show warnings and business errors inside a user interface by default. Therefore, the development team of Catel developed the InfoBarMessageControl that is able to show a summary of field and business errors. However, the Validation class only provides events for field errors, not for business errors. And, by default, the .NET Framework does not provide a way to show warnings (both field and business) to the user.

The WarningAndErrorValidator takes care of these problems for the developer.

4.2.3. How?

The WarningAndErrorValidation needs to be placed inside an InfoBarMessageControl. The control then subscribes to all property changed events to make sure it receives all change notifications. Then, on every property change, the control checks whether the sender either implements the IDataErrorInfo or IDataWarningInfo interfaces.

When an error or warning is found on the changed property, the control invokes the corresponding events so the InfoBarMessageControl can show the right information. When an error or warning no longer exists in a model, a Removed event is invoked so the InfoBarMessageControl knows that the error or warning should be removed from the summary.

5. Windows

5.1. DataWindow

When developing software in WPF, I always need the following three types of windows:

  • OK / Cancel buttons for data windows
  • OK / Cancel / Apply buttons for application settings / options
  • Close button on windows for action windows

Creating these windows is just boring, and the steps are always the same:

  1. Create a WrapPanel at the bottom of the window
  2. Add the buttons with the same RoutedUICommand objects over and over again

The DataWindow class makes it much easier to create these basic windows, simply by specifying the mode of the window. By using this window, you can concentrate on the actual implementation and you don’t have to worry about the implementation of the buttons itself, which saves you time!

When the DataWindow is used, you get a lot for free. For example, you don’t have to embed the InfoBarMessageControl yourself since that is automatically added for you. The DataWindow class can do more than generate default window templates. It is also possible to specify custom buttons to the window so you can create your own button bar at the bottom of the window.

The DataWindow is fully compatible with the MVVM Framework that ships with Catel, and is used in almost all of the windows that are implemented in Catel.

5.2. MultiLineInputWindow

The MultiLineInputWindow control is a convenience control to easily allow users to input text. It is a wrapper around the MultiLineInput control.

5.2.1. Example code

// Show window
MultiLineInputWindow multiLineInputWindow = new MultiLineInputWindow();
multiLineInputWindow.ShowDialog();

5.2.2. Why?

How many times do you need to let the user provide a multiline input with a label in front of it? Exactly, a lot! From now on, you can use this simple control which will provide you with the input, and you don’t have to define separate controls any longer.

5.2.3. How?

The window simply wraps the MultiLineInput control into a window.

5.3. MultipleChoiceWindow

The MultipleChoiceWindow allows you to ask a multiple choice question (optionally with a free textual input).

5.3.1. Example code

// Create a collection of choices
List<choice> choices = new List<Choice>();
choices.Add(new Choice("Awesome", "Awesome, never seen such a great window!"));
choices.Add(new Choice("Good", "It's pretty good actually"));
choices.Add(new Choice("It's OK", "It's OK, but I've seen better"));
choices.Add(new Choice("Hmmm", "Hmmm, what shall I say?"));

// Show window
Windows.MultipleChoiceWindow multipleChoiceWindow = 
                new Windows.MultipleChoiceWindow(choices, true);
multipleChoiceWindow.Title = "What do you think of this window?";
multipleChoiceWindow.ShowDialog();

5.3.2. Why?

Sometimes, you just need to ask a simple multiple choice question where the user must choose between several answers. Instead of creating a custom control every time, you can simply create your list of choices and pass them to the constructor of the MultipleChoiceWindow.

This saves you specifying the choices in every window separately, and you don’t have to think about binding the radio buttons inside a list control.

5.4. PleaseWaitWindow

The PleaseWaitWindow is a great window to show during long operations. There is also a PleaseWaitHelper class to make it even easier to use the PleaseWaitWindow.

5.4.1. Example code

// Show window by using the PleaseWaitHelper
Catel.Windows.PleaseWaitHelper.Show(() => Thread.Sleep(2000));

5.4.2. Why?

We needed a way to show the users that the application was busy at certain times. The requirements were as follows:

  • Dim the background so the focus is on the window;
  • Show some kind of animation, even when the current thread (UI) is blocked;
  • Be able to customize the text;
  • Be able to provide a delegate that should be executed during the time that the window is visible. If the delegate is finished, the window should automatically hide;
  • Show the window for a minimum amount of time (to prevent flickering).

5.4.3. How?

The hardest thing of this window was the ability to show an animation, even when the current UI thread was being blocked by logic that was currently busy. Luckily, a smart guy called “Dwayne Need" already solved this problem for us.

Make sure to take a look at the PleaseWaitHelper class as well. It takes care of the creation of the PleaseWaitWindow, and allows you to show the window by using a delegate. This delegate will be executed, and as soon as the delegate is finished, PleaseWaitWindow is hidden again.

5.5. TipOfTheDayWindow

Sometimes, your end-users aren’t the smartest creatures on the planet. Lots of times, I am talking to an end-user because there is a “bug" or “critical issue" that needs to be fixed right away.

During the “debug" session (read: using the software as the end-user has described), you hit a lot of key strokes and other useful shortcuts that I thought the user was already familiar with. Unfortunately, most of the time, they are not.

Therefore, the Tip of the Day window is a great way of showing the end-user some handy tips when they start your software. For example, to learn about shortcuts, “hidden" features, and more.

5.5.1. Example code

// Show window
TraceOutputWindow traceOutputWindow = new TraceOutputWindow();
traceOutputWindow.Show();

5.5.2. Why?

You must be thinking: why a blog post for such an easy window? Well, because this “Tip of the Day" window has much more features than you can see on first sight. For example, have you ever thought of how to enter or manage tips? Well, this window takes care of that for you.

Simply hit CTRL + F2 when the “Tip of the Day" window is focused, and you will see the following edit mode:

http://blog.catenalogic.com/post/2010/image.axd?picture=WindowsLiveWriter/WPFTipoftheDaywindow/2CF7DEEE/tip_of_the_day_editor.png

With the included editor, you can simply create, modify, remove, and preview all the tips that are included with the window. The tips are stored in XML format in a subdirectory “Help".

5.6. TraceOutputWindow

The TraceOutputWindow is a debugging convenience window. It behaves exactly the same as the TraceOutputControl, but then is available as a separate window instead of an embedded control.

5.6.1. Example code

// Show window
TraceOutputWindow traceOutputWindow = new TraceOutputWindow();
traceOutputWindow.Show();

5.6.2. Why?

Many times, developers are inside an application viewing the result of what they have created. But, they also want to know what is happening in the background and view the traces they have written. The output window of Visual Studio is a solution, but it doesn’t show errors very good (black, just as the normal output). Also, it doesn’t allow run-time filtering of the results.

The TraceOutputWindow allows a developer to show the trace output as a separate window and view the information when the application is actually running. The TraceOutputWindow should be preferred above the TraceOutputControl in case it can’t be embedded into the software itself (for example, when a plug-in is being developed for a 3rd party application).

5.6.3. How?

The window simply wraps the TraceOutputControl into a window.

6. Themes

6.1. Shipped Themes

Catel currently ships with a single theme file. This theme file is based on the “Aero" theme that is included in the WPF libraries. The theme of Catel corrects the margins, since the default “Aero" theme sets the margin of all controls to 0, which will result in all the user controls being stuck together, as shown in the figure below:

We see too many developers fixing the margins on the control directly, while this can also be accomplished by using the Catel theme and the included StyleHelper class. See the image below for the final result:

6.2. Pixel Shaders

Catel also uses pixel shaders to apply effects to controls via themes and styles. One of the pixel shaders is, for example, the GrayscaleEffect. This effect automatically converts an image on a button to gray scale when the button is disabled. Below is an example of the shader effect:

If there are a lot of buttons used on the screen, it might be possible that the video card does not support so many shaders, and then WPF will start throwing exceptions. In that case, first try to set the shader mode of Catel to ShaderRenderMode.Software. If that doesn’t work, you can turn the shaders off by using ShaderRenderMode.Off.

// Force software rendering
StyleHelper.PixelShaderMode = PixelShaderMode.Software;

// Turn off
StyleHelper.PixelShaderMode = PixelShaderMode.Off;

6.3. StyleHelper

The StyleHelper class has a few static members that will create style forwarders. Style forwarders are styles that are defined on the application level, not on the theme level. This allows you to create forwarders with the same key as the control name, but that will forward to the DefaultxxxStyle. Since the new styles are defined at the application level, you will not get any circular references because the style defined in the theme cannot access the application level resources.

This is accomplished by simply calling StyleHelper.CreateStyleForwardersForDefaultStyles(); in the OnStartup of the WPF application.

XAML (App.xaml)
<Application x:Class="OverrideStyles.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="UI/Windows/MainWindow.xaml">
      <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <!-- Set custom theme -->
                <ResourceDictionary 
                   Source="/Catel.Windows;component/themes/generic.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
      </Application.Resources>
</Application>
Code (App.xaml.cs)
namespace OverrideStyles
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            // Create style forwarders
            Catel.Windows.Helpers.StyleHelper.
                  CreateStyleForwardersForDefaultStyles();

            // Call base
            base.OnStartup(e);
        }
    }
}

7. History

  • 25 November, 2010: Added article browser and brief introduction summary
  • 12 November, 2010: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here