December 30, 2014

DataPipe: Pushing read-only dependency properties to view model

ActualWidth and ActualHeight are perhaps the most well-known read-only properties in WPF, but there are many other uses for read-only properties, mostly to expose various control states. XAML allows you to use read-only properties as a source for data-binding but not as a binding target. That makes sense until you consider OneWayToSource bindings, which are commonly used to push property value up into the view model. I will now present an elegant and general technique for getting these properties to view model with pure XAML code.

This technique was inspired by Dmitry Tashkinov's excellent answer on StackOverflow. I've improved it with several tweaks and packaged it as part of my opensource JungleControls library, so that people don't have to copy-n-paste it around. I myself use the technique extensively in my code.

I will present a simple example where Grid displays its own ActualWidth and ActualHeight. This is what the XAML code looks like:

<Grid Name="Page">
    <jc:DataPipe.Pipes>
        <jc:DataPipe
            Source="{Binding ActualWidth, ElementName=Page}"
            Target="{Binding MyWidth, Mode=OneWayToSource}" />
        <jc:DataPipe
            Source="{Binding ActualHeight, ElementName=Page}"
            Target="{Binding MyHeight, Mode=OneWayToSource}" />
    </jc:DataPipe.Pipes>
    <StackPanel VerticalAlignment="Center">
        <TextBlock Text="Actual page size:"
                   HorizontalAlignment="Center" />
        <StackPanel Orientation="Horizontal"
                    HorizontalAlignment="Center">
            <TextBlock Text="{Binding MyWidth}" />
            <TextBlock Text=" x " />
            <TextBlock Text="{Binding MyHeight}" />
        </StackPanel>
    </StackPanel>
</Grid>

Here, DataPipe uploads ActualWidth and ActualHeight into the view model's MyWidth and MyHeight properties. These are then referenced as binding source for TextBlock elements.

Of course, this trivial example could be also implemented by binding TextBlock.Text property directly to ActualWidth/ActualHeight. But the point is that now read-only properties show up in our view model. Once there, we can do much more interesting things with them. For example, we could reorganize the whole view depending on how much space is available to display it.

Let's take a peek at the view model now. It uses Assisticant, which I strongly recommend, but Assisticant is not required to use DataPipe.

class PageModel
{
    public readonly Observable<double> MyWidth
        = new Observable<double>();
    public readonly Observable<double> MyHeight
        = new Observable<double>();
}

When run, the XAML code will be rendered like this:



The way this works is that whenever DataPipe.Source property is changed (due to binding update or otherwise), its value is copied into DataPipe.Target property. If that property has OneWayToSource binding, the value will be further propagated to the view model. Once it's in the view model, you can do all sorts of tricks based on values of read-only properties.

You can get this control from NuGet as part of my JungleControls library.

No comments:

Post a Comment