WPF Simplified Part 15: Data Validation

Say we want to validate user input in a TextBox,

<Grid>        
    <!-- Validate this TextBox -->
    <TextBox Height="30" Width="80" Text="{Binding FirstName}" />
</Grid>

where, the TextBox is bound to an instance of the Person class,

public class Person { public string FirstName { get; set; } }

The way to do this is to associate ValidationRules with the Binding (the Text-FirstName binding). These rules validate the update of the binding source property (FirstName property) and if the update causes an error, the binding is marked as invalid.

Say we want that the TextBox’s Text (and hence the Person’s FirstName property) cannot be empty. Let’s change the Person class to throw an exception if the FirstName is null or empty,

public class Person {
    private string firstname;
    public string Firstame {
        get { return this.firstname; }
        set {
            if (string.IsNullOrEmpty(value)) {
                throw new Exception("First name cannot be null or empty");
            }
            this.firstname = value;
        }
    } 
}

Now we need a rule to associate with the binding that says, if there is an exception thrown during the update of the source property, the binding is invalid. Luckily, there is already a built-in rule for that called ExceptionValidationRule. All we need to do is associate this rule with the binding, like this,

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <ExceptionValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

The XAML above associates a ExceptionValidationRule with the binding causing the binding to be invalidated if an exception is thrown (in our case, when the FirstName is null or empty). The validation occurs when the value of a target (TextBox’s Text dependency property) is transferred to the binding source property (Person’s FirstName property). What causes a source update depends on the value of the UpdateSourceTrigger property – “PropertyChanged” means the binding engine updates the source value on every keystroke, which means it also checks every rule in the ValidationRules collection on every keystroke. UpdateSourceTrigger could also have the value “LostFocus”, which updates the source when the target has lost focus.

We can also write our own ValidationRule,

public class CustomValidationRule : ValidationRule {
    public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
        // Say the first name can't contain a space,
        if (value.ToString().Contains(' ')) {
            return new ValidationResult(false, "No spaces allowed in FirstName");
        }
    
        return ValidationResult.ValidResult;
    }
}

and apply it to the TextBox,

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <ExceptionValidationRule />
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
 

There are two type of built-in ValidationRules,

1. ExceptionValidationRule: Represents a rule that checks for exceptions that are thrown during the update of the binding source property.

2. DataValidationRule: Represents a rule that checks for errors that are raised by the IDataErrorInfo implementation of the source object.

Implementing IDataErrorInfo is easy enough, we implement the Person class like this,

public class Person : IDataErrorInfo {
    public string FirstName { get; set; }

    public string this[string propertyName] {
        get {
            string result = null;
            if (propertyName == "FirstName") {
                if (string.IsNullOrEmpty(this.FirstName))
                {
                    result = "Cannot be null or empty";
                }
            }
            return result;
        }
    }
 
    // Not using this
    public string Error {
        get { return null; }
    }

}

And the TextBox XAML is similar to above,

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <DataErrorValidationRule />
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

An alternate syntax for the above is to use the ValidatesOnDataErrors (instead of adding the DataErrorValidationRule explicitly) and ValidateOnExceptions (instead of adding the ExceptionValidationRule explicitly),

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" 
                ValidatesOnDataErrors="True" ValidatesOnExceptions="True">

                <Binding.ValidationRules>
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

We can also provide visual feedback to the user by setting the Validation.ErrorTemplate attached property of the TextBox to a custom ControlTemplate.

    <TextBox Height="30" Width="80" ToolTip="{Binding RelativeSource={RelativeSource Self}, 
                                              Path=(Validation.Errors)[0].ErrorContent}">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" 
                     ValidatesOnDataErrors="True" ValidatesOnExceptions="True">
                <Binding.ValidationRules>
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
        <Validation.ErrorTemplate>
            <ControlTemplate>
                <DockPanel>
                    <AdornedElementPlaceholder Name="MyAdorner"/>
                    <TextBlock Foreground="Red" Text="{Binding ElementName=MyAdorner, 
                        Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
                </DockPanel>
            </ControlTemplate>
        </Validation.ErrorTemplate>
    </TextBox>

 

Digg This
Advertisements

WPF Simplified Part 14: INotifyPropertyChanged and ObservableCollection

When data-binding, we want changes to the source (data bound property/collection) to be reflected in the UI. Fortunately WPF gives us some easy ways to keep the UI updated of data changes.

Consider the simple XAML,

<Grid>
    <Grid.RowDefinitions><RowDefinition /><RowDefinition /></Grid.RowDefinitions>
    <!-- Data bound text block -->
    <TextBlock x:Name="MyTextBlock" Height="30" Width="110" Text="{Binding Name}" />
    <!-- Button to change to bound property -->
    <Button Grid.Row="1" Height="30" Width="110" Content="Update" Click="MyButton_Click" />
</Grid>

And the simple class,

        public class Person { public string Name { get; set; } }

We’ll data bind like this,

        Person person;

        public Window1() {
            InitializeComponent();

            person = new Person() { Name = "John" };
            
            // Data bind Window1 to the person object
            // Or we could choose to data bind to the TextBlock directly,
            // this.MyTextBlock.DataContext = person;
            this.DataContext = person;
        }

        private void MyButton_Click(object sender, RoutedEventArgs e) {
            // Change the data bound object's data bound property
            person.Name = "Locke";
        }

When you click the button, nothing happens, the UI isn’t notified that the bound property has changed. There are two ways to fix this,

1. Implement INotifyPropertyChanged:

Implement the Person class like this,

public class Person : INotifyPropertyChanged {
    private string name;

    public string Name {
        get { return name; } 
        set {
            name = value;
            NotifyPropertyChanged("Name");
        } 
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(string name) {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}


Now clicking the button updates the UI and the TextBlock changes to “Locke”. See here for best practices for implementing INotifyPropertyChanged.

2. Change the property to a Dependency Property:

Implement the property (Name) backed by a dependency property (NameProperty) instead of backed by a field (name) as we previously did. Note that Person class will have to be derived from DependencyObject in this case.

public class Person : System.Windows.DependencyObject {
    public static DependencyProperty NameProperty 
                = DependencyProperty.Register("Name", typeof(string), typeof(Person));
 
    public string Name {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
}

We have looked at how data bound properties can update the UI, let’s also look at collections, consider that XAML,

<Grid>
    <Grid.RowDefinitions><RowDefinition /><RowDefinition /></Grid.RowDefinitions>
    <!-- Data bound list view –>
    <ListView x:Name="MyListView" DisplayMemberPath="Name" />
    <!-- Button to change the bound collection -->
    <Button Grid.Row="1" Height="30" Width="80" Content="Change" Click="Button_Click" />       
</Grid>

and the Person class,

    public class Person { public string Name { get; set; } }

Let’s data bind a collection of Person to the ListView, like this,

    List<Person> people = new List<Person>()
 
    public Window1() {
        InitializeComponent();

        people.Add(new Person() { Name = "John" });
        people.Add(new Person() { Name = "Jack" });

        // Data bind to the listview,
        this.MyListView.ItemsSource = people;
    }
    private void Button_Click(object sender, RoutedEventArgs e) {
        // Change the data bound collection
        people.Add(new Person { Name = "Jacob" });
    }

When we click the button, the UI doesn’t update. This is because the collection List<> doesn’t implement INotifyPropertyChanged and INotifyCollectionChanged. Luckily, the ObservableCollection does, so simply changing people to an ObservableCollection instance does the trick.

    ObservableCollection<Person> people = new ObservableCollection<Person>();

WPF Simplified Part 13: Value Converters

In WPF, when you data bind an element’s property to a data source, if the source property type and target property type don’t match you’ll need a value converter to hook them up correctly.

Consider the following data bound TextBlock,

    <TextBlock x:Name="MyText" Text="{Binding Name}" Foreground="{Binding DisplayColor}" />

and the code behind,

    this.MyText.DataContext = new { Name = "John", DisplayColor = "BlackSmoke" };

If you try out the code and XAML above you’ll see the following error in VisualStudio’s ‘Output’ window,

System.Windows.Data Error: 6 : ‘TargetDefaultValueConverter’ converter failed to convert value ‘BlackSmoke’ (type ‘String’); fallback value will be used, if available. BindingExpression:Path=DisplayColor; DataItem='<>f__AnonymousType0`2′ (HashCode=-850673272); target element is ‘TextBlock’ (Name=’MyText’); target property is ‘Foreground’ (type ‘Brush’) FormatException:’System.FormatException: Token is not valid.

The error is that TextBlock’s Foreground accepts a Brush type, but we bound the Foreground to a String type, hence the binding failed. What we need is a value converter that will convert a String type into a Brush type.

    [ValueConversion(typeof(string), typeof(Brush))]
    public class StringToBrushConverter : IValueConverter 
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
            if (value is string) {
                string source = value as string;
                if (source == "BlackSmoke") return new SolidColorBrush(Colors.DimGray);
            }

            // Default color
            return Colors.Black;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
            // To keep it simple, no need to convert back 
            return null;
        }
    }

As you can see, we’ve hand coded how a String will convert to a Brush type for our application – when the string is “BlackSmoke” we’d like the Brush to be “DimGray”. Applying this converter in XAML is simple enough,

<Grid>
    <Grid.Resources>
        <local:StringToBrushConverter x:Key="StringToBrushConverter" />
    </Grid.Resources>
       <TextBlock x:Name="MyText" Text="{Binding Name}" 
       Foreground="{Binding DisplayColor, Converter={StaticResource StringToBrushConverter}}" />
</Grid>

Thus, even though the TextBlock’s Foreground is still bound to a String type, our converter will provide the required type (Brush) to the Foreground dependency property.

WPF also has some built-in value converters, in fact, it has a built in String to Brush converter! To see this, try the following XAML,

    <TextBlock x:Name="MyText" Text="{Binding Name}" Foreground="{Binding DisplayColor}" />

with the following code behind,

    this.MyText.DataContext = new { Name = "John", DisplayColor = "Red" }; 

The text displays in red because of WPF’s built in String to Brush converter – it recognized the string ‘Red’ and converted it to the corresponding BrushColors.Red. In fact the built-in converter will recognize any of the colors in System.Windows.Media.Colors as strings, and will convert them to their corresponding Brushes. The code probably does the equivalent of the following,

    if (source == "Red") return new SolidColorBrush(Colors.Red);
    if (source == "Yellow") return new SolidColorBrush(Colors.Yellow);

    // etc.

Instantiating the converter in XAML as a resource can be cumbersome, so there is another way to use value converters. By deriving from MarkupExtension, you can use the converter directly in XAML, without using StaticResource.

    [ValueConversion(typeof(string), typeof(Brush))]
    public class StringToBrushConverter : MarkupExtension, IValueConverter 
    {
        public override object ProvideValue(IServiceProvider serviceProvider) {
            return this;
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
            if (value is string) {
                string source = value as string;
                if (source == "BlackSmoke") return new SolidColorBrush(Colors.DimGray);
            }

            // Default color
            return Colors.Black;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
            // To keep it simple, no need to convert back 
            return null;
        }
    }

and now you can use the converter like this,

    <TextBlock x:Name="MyText" Text="{Binding Name}" 
               Foreground="{Binding DisplayColor, Converter={local:StringToBrushConverter}}" />

WPF Simplified Part 12: Adorners

An Adorner is a new WPF construct that helps us add visual features to a UIElement. The Adorner class derives directly from FrameworkElement. So, why would you need another class to add visual effects when you can pretty much customize any WPF control using control templates and data templates? The reason is that the adorner layer lies on top of the UI element in Z-order and is helpful in some interactive scenarios to display handles and outlines.

Creating an adorner is simple, consider a simple button,

    <Button Height="30" Width="110" x:Name="MyButton" Content="Adorned Button" />

and the code behind,

    public class MyAdorner : Adorner {
        public MyAdorner(UIElement targetElement) : base(targetElement) { }

        protected override void OnRender(DrawingContext drawingContext) {
            Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
            drawingContext.DrawRectangle(null, new Pen(Brushes.Blue, 1), adornedElementRect);
        }

    }

We have created a simple adorner that will create a border around an UIElement. The adorner constructor requires the target UIElement and will draw on top of this element. To hook up the adorner we need some more code,

    void Window1_Loaded(object sender, RoutedEventArgs e) {
        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.MyTextBlock);
        MyAdorner myAdorner = new MyAdorner(this.MyTextBlock);
        adornerLayer.Add(myAdorner);
    }

Note that since the adorner layer is not available until the window is loaded, we’ve hooked up the code in the window’s Loaded event handler. GetAdornerLayer walks up the visual tree to find the first AdornerLayer which then can be used to render our custom adorner. The adorner layer’s location in the visual tree is determined by the AdornerDecorator element, the Window element provides an AdornerDecorator by default, which is what we added our custom adorner to. Note that this layer fills the entire window. If we want we can supply our own AdornerDecorator,

    <AdornerDecorator>
        <TextBlock x:Name="MyTextBlock" Text="Cancel" />
    </AdornerDecorator>

The adorner receives focus and input before the adorned UIElement. Also, just like we added the custom adorner to the AdornerLayer, we can remove it too, simply by,

    adornerLayer.Remove(myAdorner);

Another way to create Adorners is by providing a VisualCollection,

    public class MyAdorner : Adorner {
        // To store and manage the adorner's visual children
        VisualCollection visualChildren;

        // Custom UIElement to add to the target UIElement
        Button button = new Button();

        // Initialize the custom Adorner
        public MyAdorner(UIElement targetElement) : base(targetElement) {
            visualChildren = new VisualCollection(this);

            // Set the button properties
            button.Height = 20.0;
            button.Width = 20.0;
            button.Content = "?";

            visualChildren.Add(button);
        }

        protected override int VisualChildrenCount { get { return visualChildren.Count; } }
        protected override Visual GetVisualChild(int index) { return visualChildren[index]; }

        // Arrange the Adorner
        protected override Size ArrangeOverride(Size finalSize) {
            double desiredWidth = AdornedElement.DesiredSize.Width;
            double desiredHeight = AdornedElement.DesiredSize.Height;

            button.Arrange(new Rect((button.Width + desiredWidth) / 2, -desiredHeight, 
                                 desiredWidth, desiredHeight));
            return finalSize;
        }
    }

Combined with this xaml,

    <Grid>
        <Button Height="30" Width="110" x:Name="MyButton" Content="Adorned Button" />
    </Grid>

should render,

image

Digg This

WPF Simplified Part 11: XAML Tricks

The Extensible Application Markup Language (aka XAML) is a declarative programming language that was specifically built for WPF to encourage separation of front-end appearance and back-end logic.

While a full explanation of XAML is beyond the scope of this post (see here for an introduction), we’ll focus on some uncommon features of XAML,

1. Escaping curly braces: Say you want to display curly braces in XAML,

        <!-- Error! -->
        <TextBlock Text="{test string}" />

will fail because the parser thinks that the string is a markup extension. There are two ways to fix this,

    a. Escape the curly braces:

    <TextBlock Text="{}{test string}" />

    b. Use the property element syntax:

    <TextBlock>{test string}</TextBlock>

2. Procedural code inside XAML: You can embed code inside your XAML file, though its not recommended.

    <Button x:Name="MyButton" Height="100" Width="100" Click="MyButton_Click" />
    <x:Code>
        <![CDATA[ 
            private void MyButton_Click(Object sender, RoutedEventArgs e) { 
                MessageBox.Show("Button click!"); 
            } 
        ]]>
    </x:Code>

3. Preserve white space: Say you want to preserve white spaces between text,

     <TextBlock>Text     spacing</TextBlock>

this will collapse the space between ‘Text’ and ‘spacing’, to preserve the spaces try this,

     <TextBlock xml:space="preserve">Text     spacing</TextBlock>

or simply,

    <TextBlock Text="Text     spacing" /> 

4. Use null in XAML: You can specify null in XAML using the x:Null markup extension,

    <Grid Background="{x:Null}">

5. Use StringFormat in XAML: Very useful for quick formatting,

    <TextBlock Text="{Binding Name, StringFormat=Hello {0}!!}" />
    <TextBlock Text="{Binding Date, StringFormat=Today\'s date is: {0:MM/dd/yyyy}}" />

6. Use MultiBinding with StringFormat:

    <TextBlock> 
        <TextBlock.Text> 
            <MultiBinding StringFormat="Name:{0}, Age:{1}"> 
                <Binding Path="Name" /> 
                <Binding Path="Age" /> 
            </MultiBinding> 
        </TextBlock.Text>
    </TextBlock>

7. Create an array in XAML: You can create an array in XAML,

    <ListBox>
        <ListBox.ItemsSource>
            <x:Array Type="{x:Type sys:String}">
                <sys:String>John</sys:String>
                <sys:String>Paul</sys:String>
                <sys:String>Andy</sys:String>
            </x:Array>
        </ListBox.ItemsSource>
    </ListBox>

where,

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

In fact you can use other data types from the System namespace, like Int32, Boolean, and even DateTime,

    <ListBox>
        <ListBox.ItemsSource>
            <x:Array Type="{x:Type sys:DateTime}">
                <sys:DateTime>1/1/10</sys:DateTime>
                <sys:DateTime>3/3/10 03:21 PM</sys:DateTime>
            </x:Array>
        </ListBox.ItemsSource>
    </ListBox>

8. Specify an empty string in XAML:

    <ListBox>
        <ListBox.ItemsSource>
            <x:Array Type="{x:Type sys:String}">
                <sys:String>John</sys:String>
                <x:Static Member="sys:String.Empty" />
                <sys:String>Andy</sys:String>
            </x:Array>
        </ListBox.ItemsSource>
    </ListBox>
 

where,

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

9. Put constants in XAML: Say you have a constant in code,

    public static string Extension = ".xaml"  //In class Window1
 

You can use this in XAML via the x:Static markup extension, like this,

    <TextBlock Text="{x:Static local:Window1.Extension}" />

where,

    xmlns:local="clr-namespace:WpfApplication1"

In fact, you can assign any static value,

    <Label Content="{x:Static sys:DateTime.Now}" />
    <Label Foreground="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />

10. Instantiate a class in XAML: Say you have the following class in your code,

    public class Person {
        public string Name { get; set; }
        public int Age { get; set; }
    }

You can instantiate an array in XAML, populate it with Person type objects and bind to a listbox,

    <Grid.Resources>
        <!-- Create a array of Person objects -->
        <x:Array x:Key="Office" Type="{x:Type local:Person}">
            <!-- Instantiate a Person and add to the array -->
            <local:Person Name="Michael" Age="40"/>
            <local:Person Name="Jim" Age="30"/>
            <local:Person Name="Dwight" Age="30"/>
        </x:Array>
    </Grid.Resources>
    
    <!-- Bind the array to the listbox -->
    <ListBox ItemsSource="{Binding Source={StaticResource Office}}" DisplayMemberPath="Name" />

If you have a collection,

    public class Family : ObservableCollection<Person> {
        public Family() {
            Add(new Person() { Name = "Jim", Age = 30 });
            Add(new Person() { Name = "Pam", Age = 30 });
        }
    }

we can instantiate the collection in XAML and then bind to it,

    <Grid.Resources>
        <!-- Instantiate the Family class -->
        <local:Family x:Key="Family" />
    </Grid.Resources>
    <!-- Bind to the collection -->
    <ListBox ItemsSource="{Binding Source={StaticResource Family}}" DisplayMemberPath="Name" />

See more XAML tricks here.

Digg This

WPF Simplified Part 10: WPF Framework Class Hierarchy

The WPF framework contains three major components, the PresentationFramework, the PresentationCore, and the milcore (Media Integration Layer Core).

image

While PresentationFramework and PresentationCore form the managed WPF programming model, MSDN says this about the milcore,

Milcore is written in unmanaged code in order to enable tight integration with DirectX. All display in WPF is done through the DirectX engine, allowing for efficient hardware and software rendering. WPF also required fine control over memory and execution. The composition engine in milcore is extremely performance sensitive, and required giving up many advantages of the CLR to gain performance.

The WPF framework contains many classes and its useful to get an overall view of the class hierarchy. The diagrams below show some of the major class in the framework (not all classes are included) in the WPF class hierarchy.

image

The FrameworkElement contains most of the UI controls that we usually work with, and is expanded below,

image

Some notes about the class hierarchy,

1. All classes in the WPF framework derive from System.Object.

2. Deriving from System.Threading.DispatcherObject gives us a CLR object that has single thread affinity (STA) behavior. Since all the UI controls derive from DispatcherObject, all WPF UI controls can be accessed only on the thread that created it, and are thus inherently thread-unsafe. Some of the methods are of the class are,

    public Dispatcher Dispatcher { get; }
    public bool CheckAccess();
    public void VerifyAccess();
 

3. System.Windows.Dependency is the base class that supports dependency and attached properties. Some of the methods are of the class are,

    public void SetValue(DependencyProperty dp, object value);
    public object GetValue(DependencyProperty dp);
    public void ClearValue(DependencyProperty dp);
 

4. System.Windows.Media.Visual is the entry point to the WPF composition system. This is the base class for all objects that have their own visual representation. Some of the methods are of the class are,

    protected DependencyObject VisualParent { get; }
    protected void AddVisualChild(Visual child);
    protected void RemoveVisualChild(Visual child);
 

5. System.Windows.UIElement adds support for core features like, Events, Input, Layout, and CommandBindings. Some of the methods are of the class are,

    public event MouseButtonEventHandler PreviewMouseLeftButtonDown;
    public event MouseButtonEventHandler MouseLeftButtonDown;
    public static readonly DependencyProperty IsEnabledProperty;
    public bool IsMouseOver { get; }
 

6. System.Windows.FrameworkElement extends the layout features of UIElement and adds support for features like, styles, data binding, resources, data templates, tooltips, and animation. Some of the methods are of the class are,

    public double MinHeight { get; set; }
    public Style Style { get; set; }
    public ResourceDictionary Resources { get; set; }
    public object FindResource(object resourceKey);
    public object ToolTip { get; set; }
    public void BeginStoryboard(Storyboard storyboard);
 

7. System.Windows.Controls.Control adds support for templating and is the base class for the familiar UI controls like Button, Grid, and TextBox. Some of the methods are of the class are,

    public ControlTemplate Template { get; set; }
    public Brush Background { get; set; }
    public FontFamily FontFamily { get; set; }
Digg This

WPF Simplified Part 9: Logical Resources

There are two types of resources in WPF – binary and logical. Binary resources are plain old .NET resources like bitmaps, fonts, and strings that can either be embedded inside the assembly or packaged in a resource file. We’ll discuss this in another post.

Logical resources, henceforth called resources are new to WPF. In XAML, resources are simply objects stored in an element’s Resource property so that they can be shared by child elements. Just about any object in WPF can be a resource, like a brush, a color, some text, some style etc.

Consider the simple XAML,

<Grid>
    <Button Height="100" Width="100" Content="Some Text" FontStyle="Italic" />
    <RadioButton Content="Some Text" FontStyle="Italic" />

</Grid>

Since we want both the child elements of Grid to have the same FontStyle we could define a FontStyle resource, put it in the Grid’s Resource property and point the FontStyles of the Button and RadioButton to the resource, like below,

<Grid>
     <Grid.Resources>
         <!-- Define a FontStyle resource -->
         <FontStyle x:Key="GridFontStyle">Italic</FontStyle>
     </Grid.Resources>

     <Button Height="100" Width="100" Content="Some Text" 
             FontStyle="{StaticResource GridFontStyle}" />
     <RadioButton Content="Some Text" FontStyle="{StaticResource GridFontStyle}" />     
 </Grid>

Doing this in code is simple enough,

<Grid x:Name="MyGrid">
    <Button x:Name="MyButton"  Height="100" Width="100" Content="Some Text" />
    <RadioButton x:Name="MyRadioButton" Content="Some Text" />    
</Grid>

// And in code...
this.MyGrid.Resources.Add("GridFontStyle", FontStyles.Italic);
this.MyButton.FontStyle = (FontStyle)this.MyGrid.FindResource("GridFontStyle");
this.MyRadioButton.FontStyle = (FontStyle)this.MyGrid.FindResource("GridFontStyle");

Every resource in the element’s Resource property must be unique, this is because internally WPF uses a ResourceDictionary to implement the resources. Thus, the x:Key that you specify in XAML, or equivalently the first parameter to Add, becomes the key in the dictionary for that resource. Note that although we have used a String object as the key any object can be used as a key. However, we don’t have to explicitly set the x:Key on all resources, for example in the case of Styles setting the TargetType is sufficient (for an example see here), and WPF implicitly creates an x:Key for the resource.

Resources are scoped hierarchically, like this,imageSo when we use FindResource to get a resource like in the above example, FindResource will start looking at the parent’s resources, then the parent’s parent’s resources etc., traversing the logical tree upward all the way to Window’s resources, then the entire Application’s resources and eventually the System’s resources! FindResource is quite a hard worker,

[In App.xaml]

<Application.Resources>
    <FontStyle x:Key="ApplicationFontStyle">Italic</FontStyle>
</Application.Resources>

[In Window1.xaml]

<Window.Resources>
    <FontStyle x:Key="WindowFontStyle">Italic</FontStyle>
</Window.Resources>
    
<Grid x:Name="MyGrid">
    <Button Height="100" Width="100" Content="Some Text" FontStyle="{StaticResource WindowFontStyle}" />
    <RadioButton Content="Some Text" FontStyle="{StaticResource ApplicationFontStyle}" />
</Grid>

We can use FrameworkElement.Resources to search the element’s resource dictionary directly, which will return null if the resource isn’t found.

// Looks up the Grid's ResourceDictionary directly using a key and returns null
this.MyButton.FontStyle = (FontStyle)this.MyGrid.Resources["WindowFontStyle"];

// Looks up the logical tree and finds the resource in Window's resources
this.MyButton.FontStyle = (FontStyle)this.MyGrid.FindResource("WindowFontStyle");

In addtion to using StaticResource in XAML, we could also use DynamicResource, the differences being,

                                                  StaticResource                                                DynamicResource
The reference must appear after the resource to which it refers. Forward references are allowed.
The resource is applied only once – the first time it is needed. The resource is reapplied every time the resource changes.
Can be used on regular .NET properties and almost anywhere. Can only be used to set dependency property values.
Resources are loaded when the Window or Page loads. Not loaded until the resource is actually used.
Code equivalent: FindReference Code equivalent: SetResourceReference

As an example, consider the XAML,

<Window.Resources>
    <RadioButton x:Key="MyRadioButton" x:Shared="False" Content="Some Text" />
</Window.Resources>
    
<Grid>
    <!-- The button’s background is set to a System resource -->
    <Button x:Name="MyButton" Height="100" Width="100" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" >

        <!-- The static resource can be used anywhere... --> 
        <StaticResource ResourceKey="MyRadioButton" />
    </Button>

    <!-- We can use the same resource here because we set x:Shared to False -->
    <StaticResource ResourceKey="MyRadioButton" />        
</Grid>

A few notes about the XAML above,

1. The button’s Background dependency property is set to a system resource. Setting the property like this,

    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"

is interpreted as a dynamic reference to a resource named by the string SystemColors.ControlBrushKey. Setting x:Static means the text should be treated as the name of a static property not a string. The equivalent in code is,

    this.MyButton.SetResourceReference(Button.BackgroundProperty, SystemColors.ControlBrushKey);

2. The RadioButton resource is used to instantiate a radio button inside the button and also inside the grid. By default, when a resource is applied in multiple places, the same object instance is used, so a resource can only be applied once in an element tree because each resource application is the same instance. This can be overridden by setting x:Shared to false. Note that you’ll get a VisualStudio designer error if you’re trying to use x:Shared and instantiate a new copy of the resource, x:Shared will work fine in a compiled XAML.

image

Digg This