Book Image

Mastering Windows Presentation Foundation - Second Edition

By : Sheridan Yuen
Book Image

Mastering Windows Presentation Foundation - Second Edition

By: Sheridan Yuen

Overview of this book

Microsoft Windows Presentation Foundation (WPF) provides a rich set of libraries and APIs for developers to create engaging user experiences. This book features a wide range of examples, from simple to complex, to demonstrate how to develop enterprise-grade applications with WPF. This updated second edition of Mastering Windows Presentation Foundation starts by introducing the benefits of using the Model-View-View Model (MVVM) software architectural pattern with WPF, then moves on, to explain how best to debug our WPF applications. It explores application architecture, and we learn how to build the foundation layer of our applications. It then demonstrates data binding in detail, and examines the various built-in WPF controls and a variety of ways in which we can customize them to suit our requirements. We then investigate how to create custom controls, for when the built-in functionality in WPF cannot be adapted for our needs. The latter half of the book deals with polishing our applications, using practical animations, stunning visuals and responsive data validation. It then moves on, to look at improving application performance, and ends with tutorials on several methods of deploying our applications.
Table of Contents (15 chapters)

Debugging data bound values

So far, we have seen that we can utilize a number of sources of information to help with tracking down the causes of our problems. However, what about actual debugging? In other GUI languages, we can add breakpoints at various locations in our code and watch our values changing as we step through our code. While we can also do this with WPF applications, it is not always so obvious where to put our breakpoints to ensure that program execution will hit them.

If you remember from the previous chapter, the CommandManager.RequerySuggested event is raised when the CommandManager class detects a change in the UI that could reflect on whether a command could execute or not. Well, it turns out that two of the conditions that the CommandManager looks out for is when the application window is either activated or deactivated and we can take advantage of this to help us when debugging. Note that the application window is deactivated when the user moves focus from it and is reactivated when the user returns focus to it.

Therefore, while running the application side by side with Visual Studio, we can put a breakpoint in any method that is being used as a canExecute handler for our ActionCommand class, thereby removing focus from the application. Now, when we click back on the WPF application, the focus will be returned to it.

This will cause the CommandManager.RequerySuggested event to be raised and as a result, the canExecute handler will be called and our breakpoint will be hit. This basically means that we are able to get the program execution into our View Models to debug parameter values any and every time that we need to. Let's see what else we can do to help fix our data binding errors.

Outputting values to UI controls

One of the simplest ways of working out what values our data bound properties have is to just data bind them to other UI controls that have a textual output. For example, if we have a collection of items and we want to do something with the selected item, but whatever that is isn't working, we need to verify that our binding to that selected item is correct.

To visualize the result of the binding, we can simply copy and paste the binding path to the Text property of a TextBox and run the application. If our binding path is correct, we'll see something output in the TextBox control and if not, we'll know that the problem that we're having is in fact, down to the binding path. We can therefore, use this method to verify that objects that don't normally have a textual output are at least correctly data bound or not.

This simple technique can help in any situation where the faulty data binding is not already rendered in a text-based UI control. For example, we might need to debug a data bound value because a particular visual effect that is created with a DataTrigger instance is not working and we need to determine whether the problem is related to the UI control or the data binding path.

Catching changing Dependency Property values

As we saw at the beginning of this chapter, the WPF Framework won't call the CLR property wrappers of our Dependency Properties when the property values are changing. However, there is a way to accomplish this using callback handlers. In fact, we've already seen an example of this when we were looking at the creation of the OnEnterKeyDown Attached Property. Let's remind ourselves what that looked like:

public static readonly DependencyProperty OnEnterKeyDownProperty =  
  DependencyProperty.RegisterAttached("OnEnterKeyDown", 
typeof(ICommand), typeof(TextBoxProperties),
new PropertyMetadata(OnOnEnterKeyDownChanged));

...
public static void OnOnEnterKeyDownChanged(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { TextBox textBox = (TextBox)dependencyObject; if (e.OldValue == null && e.NewValue != null) textBox.PreviewKeyDown += TextBox_OnEnterKeyDown; else if (e.OldValue != null && e.NewValue == null) textBox.PreviewKeyDown -= TextBox_OnEnterKeyDown; }

For this Attached Property, we used a particular overload of the DependencyProperty.RegisterAttached method that accepts a PropertyMetadata object, which enabled us to assign a PropertyChangedCallback handler to the property. Note that there is an identical overload for the DependencyProperty.Register method for declaring Dependency Properties.

Program execution will enter these PropertyChangedCallback handlers each time their related Dependency Property changes and so, that makes them perfect for debugging their values. While we don't often need to attach these handlers, it only takes a moment to add one when we need to and they enable us to find out what's going on with the Dependency Property values at runtime.

Exploiting converters

If we're having a problem with a data binding that uses an IValueConverter to convert the data bound value from one type to another, then we can place a breakpoint into the Convert method of the converter. As long as we have correctly set up the converter, we can be sure that the breakpoint will be hit when the binding is evaluated at runtime. If it doesn't get hit, that will mean that we have not set it up correctly.

However, even when we are not already using a converter on a binding that is not displaying the value that we are expecting, we can still add one just for this purpose. We can either add an existing converter to the binding, if we have one of the relevant type, or we can create a simple converter specifically for the purpose of debugging and use that instead. Let's take a look at how we might do this:

[ValueConversion(typeof(object), typeof(object))] 
public class DebugConverter : IValueConverter 
{ 
  public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) { if (Debugger.IsAttached) Debugger.Break(); return value; } public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture) { if (Debugger.IsAttached) Debugger.Break(); return value; } }

As you can see from the preceding code snippet, it's a very simple implementation of the IValueConverter interface. We start by specifying that we are converting from object to object in the ValueConversion attribute, thereby outlining that we are not actually converting any data bound values in this converter. The rest of the class represents a typical converter class, but without any conversion code.

The only real point of interest here are the two calls to the Debugger.Break method from the System.Diagnostics assembly. When the program execution reaches either of these method calls, it will automatically break, just as if there were breakpoints set on these lines. Therefore, when using this converter, we don't even need to set a breakpoint; we can just plug it into the binding, run the program, and investigate the value of the value input parameter when the data bound value is changed.

It can be attached like any other converter:

xmlns:Converters="clr-namespace:CompanyName.ApplicationName.Converters; 
  assembly=CompanyName.ApplicationName.Converters" 
... 
<UserControl.Resources> 
  <Converters:DebugConverter x:Key="Debug" /> 
</UserControl.Resources> 
... 
<ListBox ItemsSource="{Binding Items, Converter={StaticResource Debug}}" />

However, this method can be unsafe to use in a production environment and the converter should be removed when debugging is finished. If it is left connected in release code, an exception will be thrown at runtime, complaining that Windows has encountered a user-defined breakpoint. Although I wouldn't recommend leaving a converter that is just used for debugging data bound values connected in a production environment, we can make a slight alteration to it to completely eliminate the danger of this occurring:

[ValueConversion(typeof(object), typeof(object))] 
public class DebugConverter : IValueConverter 
{ 
  public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) { Break(value); return value; } public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture) { Break(value); return value; }

[Conditional("DEBUG")]
private void Break(object value)
{
Debugger.Break();
}
}

Now, the Debugger.Break method and the data bound value have been moved into a separate Break method, where the value of the value input parameter can be inspected. Note the use of the ConditionalAttribute attribute on this new method. It provides a way to include or exclude methods that it has been set on, depending on the current solution configuration. If the configuration is set to debug, this method can be called, but otherwise, all calls to it are removed from the compiled code. In this way, we can be assured that we will not run into problems with our release code.