Tag Archive: xaml

IValueConverter Interface (System.Windows.Data.IValueConverter)

Probably the most common question I get from people new to WPF is how to bind the Visibility of a control based on a System.Boolean value. WPF provides IValueConverter in System.Windows.Data specifically for converting one type to another and back when binding. Let’s setup a very simple example.

/// <summary>
/// ViewModel for MainWindow
/// Assigned to MainWindow.DataContext in its class constructor
/// </summary>
internal class MainWindowDataContext : INotifyPropertyChanged
{
    private bool _visible;

    public bool Visible {
        get {
            return _visible;
        }
        set {
            _visible = value;

            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null) {
                handler(this, new PropertyChangedEventArgs("Visible"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Visibility="{Binding Visible}" />
    </Grid>
</Window>

You should already know that line 7 doesn’t work. There isn’t a conversion from System.Boolean to System.Windows.Visibility. This is where IValueConverter comes in, so we will make a new class that implements the interface and call it BoolToVisibilityConverter.

internal class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        if (!(value is bool)) {
            throw new System.ArgumentException("value is not of type System.Boolean", "value");
        }
        return (bool)value ? Visibility.Visible : Visibility.Hidden;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        if (!(value is Visibility)) {
            throw new System.ArgumentException("value is not of type System.Windows.Visibility", "value");
        }
        switch ((Visibility)value) {
            case Visibility.Visible:
                return true;
            default:
                return false;
        }
    }
}

The implementation is simple. When the binding gets the value from MainWindow.DataContext (eg. MainWindowDataContext.Visible) it is passed to the value parameter of object Convert. Then it’s up to the convert method to return the appropriate value based on the parameter. In this case, we do a simple check to make sure the parameter is in fact a System.Boolean value, and if it is we return the appropriate Visibility enumeration value. Obviously, ConvertBack does exactly the opposite when the value of the binding is set, and is propagated down to the data context. It takes a Visibility value and converts it to a System.Boolean value, which is assigned to MainWindowDataContext.Visible.

Now we need to update our Xaml to use the converter. This is done by including it as a resource and specifying the Converter attribute on the binding.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverterResourceKey"/>
        </Grid.Resources>
        <Button Visibility="{Binding Visible, Converter={StaticResource BoolToVisibilityConverterResourceKey}}" />
    </Grid>
</Window>

You can implement IValueConverter any time you need to convert one type to another and back in a binding.

How Auto width Grid columns work with column spanning.

A friend of mine was wondering if he had encountered a bug in WPF today. What he was trying to do is have a Grid with four columns, each with a width of Auto, and have a TextBox fill the entire width of his Grid. Here was his Xaml.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TextBox Grid.ColumnSpan="4" Grid.Row="0"
                Margin="5,6"
                HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" />

    <Button Grid.Row="1" Grid.Column="0" Margin="3" Content="Hello" />
    <Button Grid.Row="1" Grid.Column="1" Margin="3" Content="Hello" />
    <Button Grid.Row="1" Grid.Column="2" Margin="3" Content="Hello" />
    <Button Grid.Row="1" Grid.Column="3" Margin="3" Content="Hello" />
</Grid>

What he wants is for his TextBox to take up the remaining width in the Grid. The way Auto works for a GridColumn‘s width, is that it sizes itself to the maximum width of its content. So in this case, the width of each column is that of the width of each button. Now because there are only four columns defined in the Grid, the TextBox can only span a maximum of four columns. The remainder of the Grid is undefined space that fills the Window, because the default behavior of a Grid is to fill its parent container.

So how do we get the TextBox to fill the undefined space? You define it. Add a fifth column with a * width, which means it will fill the remainder of the Grid space, and then we can set the TextBox to span all five columns, instead of the first four.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TextBox Grid.ColumnSpan="5" Grid.Row="0"
                Margin="5,6"
                HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" VerticalContentAlignment="Center" />

    <Button Grid.Row="1" Grid.Column="0" Margin="3" Content="Hello" />
    <Button Grid.Row="1" Grid.Column="1" Margin="3" Content="Hello" />
    <Button Grid.Row="1" Grid.Column="2" Margin="3" Content="Hello" />
    <Button Grid.Row="1" Grid.Column="3" Margin="3" Content="Hello" />
</Grid>

Working with the WPF TextBlock control.

Introduction
Setting a TextBlock’s Content
Formatting Text
Using Controls with Formatted Text

Introduction

In WPF there are two very similar controls Label and TextBlock, and I’m going to cover TextBlock and why its so useful. First, what is the TextBlock control? A first look at the Msdn documentation.

Provides a lightweight control for displaying small amounts of flow content.

Well, that doesn’t tell that much about the control. A more accurate description would be that its a lightweight control for displaying various forms of formatted text and controls in a flow layout. Specifically, the TextBlock control can contain other controls, and arbitrary text that you can format with different font styles, colors, and brushes. Because it can contain other controls, it is actually much more than a `Text`Block, and I’m not sure why they named it TextBlock considering.

Setting a TextBlock’s Content

For demonstration, I will start with a small WPF Windows Application that contains just a TextBlock control containing the text Hello World!.

<Window x:Class="TextBlockDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Top"> 
            Hello World!
        </TextBlock>
    </Grid>
</Window>
TextBlockDemo_HelloWorld

An empty window with a TextBlock

There are two ways you can set text for a TextBlock. The first is the most obvious, and that is to set the Text property.

<TextBlock Text="Hello World!" HorizontalAlignment="Stretch" VerticalAlignment="Top">

The second is by setting the inner value of the control like I did in the demo application. Both methods give the same result for displaying Hello World!, but only setting the inner value of the TextBlock control gives us access to the true potential of the control. The Text property itself can only be used to display an arbitrary System.String. Shown below is the source code from the TextBlock metadata of its Text property.

//
// Summary:
//     Gets or sets the text contents of a System.Windows.Controls.TextBlock.
//
// Returns:
//     The text contents of this System.Windows.Controls.TextBlock. Note that all
//     non-text content is stripped out, resulting in a plain text representation
//     of the System.Windows.Controls.TextBlock contents. The default is System.String.Empty.
[Localizability(LocalizationCategory.Text)]
public string Text { get; set; }

Because the TextBlock can contain multiple controls by using its inner value, we can add things like buttons or other controls.

<Window x:Class="TextBlockDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Top">
            Hello World!
            <Button Content="Click me!" />
        </TextBlock>
    </Grid>
</Window>

A TextBlock containing text along with a Button control.

One of the most common questions from those just entering the WPF world is why doesn’t the button display on a new line even though in the Xaml the Button element is on its own line? Placing elements on a new line doesn’t necessarily do anything, and for a TextBlock control, adding a new line is actually done using the LineBreak element.

<Window x:Class="TextBlockDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Top">
            Hello World!
            <LineBreak />
            <Button Content="Click me!" />
        </TextBlock>
    </Grid>
</Window>

A TextBlock control with text, a button, and a line break.

I won’t cover any more about nested controls in the TextBlock. Once you understand that a control can contain other controls, the rest depends on your understanding of understanding the layout and styling mechanisms of WPF. The usefulness of truly comes from the ability to format strings of text without using multiple controls.

Formatting Text

Formatting text is done using an element called a Run which is defined as a inline-level flow content element intended to contain a run of formatted or unformatted text. Using a Run, you can style seperate segments of text using colors, brushes, and styles.

<Window x:Class="TextBlockDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Top">
            <Run Foreground="Red">Hello</Run>
            <Run Foreground="Blue">World!</Run>
        </TextBlock>
    </Grid>
</Window>

A TextBlock containing the text 'Hello World!' split into two runs, one red and one blue.

You can see that it was very easy to style the text to be red and blue. Now let’s add a LinearGradientBrush to the second run and give it a larger font with a bold style.

<Window x:Class="TextBlockDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Top">
            <Run Foreground="Red">Hello</Run>
            
            <LineBreak />
            
            <Run Text="World!" FontSize="24pt" FontWeight="Bold">
                <Run.Foreground>
                    <LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
                        <GradientStop Offset="0" Color="Navy" />
                        <GradientStop Offset="1" Color="DodgerBlue" />
                    </LinearGradientBrush>
                </Run.Foreground>
            </Run>
        </TextBlock>
    </Grid>
</Window>

A TextBlock run with its Foreground set to a LinearGradientBrush

Just like the TextBlock, a Run can contain its text in either its Text property, or its inner value. Since we want to use its inner value to set some text effects, it was moved from the inner value to the Text property. The FontSize and FontWeight properties were also added, along with our Run.Foreground element that contains our linear gradient effect.

Using Controls with Formatted Text

Often times you may want to do something like have a piece of text actually contain a link to some resource. I had a need for this myself in one of my dialogs in my licensing modules; I need it to display a link to a product page. A TextBlock can contain both controls and runs, which makes for great flexibility. A Run however, cannot contain a control so its important to make note of that.

<Window x:Class="TextBlockDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Top">
            <Run Text="This is some text containing a " />
            <Hyperlink NavigateUri="http://danderson.io/">link to a page.</Hyperlink>
        </TextBlock>
    </Grid>
</Window>

A TextBlock containing a HyperLink.

The cool thing about this is that because a HyperLink is its own control, it can contain its own styling and behaviors.

There’s not much else to say about the TextBlock because it really is a simple control. It’s just really flexible and when used correctly can be a very easy way to display lots of formatted text to a user. The ability to contain controls that can also have their own styles and behaviors is really cool, but I still think the WPF team should have chosen a better name than TextBlock, because its much more than just arbitrary text.

Showing a splash screen in WPF from an external assembly or class library.

This isn’t anything special, but the documentation on Msdn for System.Windows.SplashScreen is lacking. Basically I am writing a licensing system and during debugging the WPF stack can take many seconds to load. When making changes to this system sometimes it may result in a failure, but I may not see it for a few seconds. I wanted something to display during this time period so I know at least something is going on.

I knew that WPF has built-in support for splash screens by setting the Build Action of an image in my project to Splash Screen in the properties window, but the problem is that this only works for the project the image is embedded into, and only if its a WPF Application project. My requirements are that I can display the splash screen from my licensing dll (class library) when my licensing service is invoked. This means that the licensing dll would contain the image to display, and contain the logic to show the splash.

Luckily, SplashScreen exposes a SplashScreen(Assembly, String) constructor which allows you to do just this. To accomplish this task, all you need to do is add your image to your class library project, set its build action to Splash Screen, and then write three lines of code.

Assembly asm = Assembly.GetAssembly(GetType());
SplashScreen splash = new SplashScreen(asm, "SplashScreen.png");
splash.Show(true);

The first line is simple; we are just getting the assembly that contains the splash resource (in this case my licensing library). The second line takes the assembly as an argument, and we pass in the name of the image. Next we simply call splash.Show(true); which actually displays the image, and because we passed in true as the argument, it will automatically be closed by the WPF stack once the first window is constructed, loaded, and displayed.

Make sure that you add your image to the root of your project, so that the WPF stack can find it.

I haven’t looked into it because I don’t want to spend a lot of time on a task that doesn’t mean much, but placing the image at other hierarchical locations in your project structure can cause the embedded resource path string to change, and it may not be found by the WPF stack internally. It’s probably possible to specify a qualified path using the second constructor parameter, but I was not able to get it to work using a fully qualified resource path string.