WPF / MVVM - Extending ViewModel Functionality Via ValueConverters

by Dean 28. September 2011 18:54

In a Model-View-ViewModel (MVVM) project, the ViewModel can easily become a large and unwieldy piece of code. You can break this down by creating a hierarchy of ViewModels that exist in a parent-child relationship, or you can extend the functionality of an existing ViewModel via the use of ValueConverters.

Here is a simple example of what I mean

First, a simple ViewModel that doesn’t contain any functionality, just properties and an implementation of INotifyPropertyChanged for change notifications

public class DemoViewModel1 : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private bool someBoolValue = true;
    public bool SomeBoolValue
    {
        get { return someBoolValue; }
        set
        {
            someBoolValue = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, 
                    new PropertyChangedEventArgs("SomeBoolValue"));
                PropertyChanged(this,
                    new PropertyChangedEventArgs("SomeText"));
            }
        }
    }

    private string someText;
    public string SomeText
    {
        get { return someText; }
        set
        {
            someText = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs("SomeText"));
            }
        }
    }
}

Now we’d like to add functionality to this ViewModel so that the string ‘SomeText’ has its characters reversed if the bool ‘SomeBoolValue’ is true. Rather than include this functionality in the ViewModel, lets include it via a ValueConverter.

Here's the ValueConverter code:

public class Demo1ValueConverter : IValueConverter
{
    public DemoViewModel1 ViewDataContext { get; set; }

    public object Convert(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        var bindingData = value as string;
        if (bindingData == null)
        {
            return null;
        }
        return ViewDataContext.SomeBoolValue ? new string(bindingData.Reverse()
            .ToArray()): bindingData;
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
And now we just need to use our new ValueConverter in our XAML
<Window.Resources>
    <ViewModel:DemoViewModel1 x:Key="viewModel" />
    <Converters:Demo1ValueConverter x:Key="demoConverter" ViewDataContext="{StaticResource viewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource viewModel}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <TextBlock Text="Enter String" />
    <TextBox Width="70" Grid.Column="1" Text="{Binding SomeText}" />
    <TextBlock Grid.Column="2" Text="{Binding SomeText, 
        Converter={StaticResource demoConverter}}" />
    <TextBlock Text="Reverse?" Grid.Row="1" />
    <CheckBox IsChecked="{Binding SomeBoolValue}" Grid.Column="1" Grid.Row="1" />
    <Button Content="Process" Grid.Row="2" />
</Grid>

As you can see, the ValueConverter has supplemented the function of the ViewModel by extending its functionality.

Tags: , ,

C# | WPF | MVVM | DataBinding

WPF MVVM – Simple Property Rules Engine

by Dean 9. September 2011 19:47

I'm currently working on a large legacy WPF project where my ViewModels often have a large number of properties, and those properties have some complex inter-relationships that need to be reflected in the behaviour of the app,  via the ViewModels and XAML bindings.

Specifically, some properties may need to support some of the following behaviours:

1) A property may need to invoke a change notification in the UI if a related property changes

2) A property may be a calculated value, and the calculation may rely on other property values which may change.

3) A property may need to support validation

With these requirements in mind, I wrote a simple POC (proof of concept) of a ‘rules engine’ that may provide those features and can be utilised in the ViewModel in a straightforward way.

Below is the code for the rules engine class:

using System;
using System.Collections.Generic;
using System.ComponentModel;

public class PropertyRuleEngine
{
    private readonly Dictionary<string, List<string>> dependencies = 
        new Dictionary<string, List<string>>();
    private readonly Dictionary<string, Action> evaluations = 
        new Dictionary<string, Action>();
    private readonly Dictionary<string, List<Func<string>>> validations = 
        new Dictionary<string, List<Func<string>>>();
    private readonly object source;

    public PropertyRuleEngine(object source)
    {
        this.source = source;
    }

    public PropertyRuleEngine AddDependency(string dependentPropertyName, 
        string propertyName)
    {
        if (!this.dependencies.ContainsKey(propertyName))
        {
            this.dependencies.Add(propertyName, new List<string>());
        }

        if (!this.dependencies[propertyName].Contains(dependentPropertyName))
        {
            this.dependencies[propertyName].Add(dependentPropertyName);
        }

        return this;
    }

    public PropertyRuleEngine AddEvaluatedProperty(string propertyName, 
        Action calculateAction)
    {
        this.evaluations.Add(propertyName, calculateAction);
        return this;
    }

    public PropertyRuleEngine AddValidationProperty(string propertyName, 
        Func<string> validationFunction)
    {
        if (!this.validations.ContainsKey(propertyName))
        {
            this.validations.Add(propertyName, new List<Func<string>>());
        }

        this.validations[propertyName].Add(validationFunction);
        return this;
    }

    public void Notify(string property, PropertyChangedEventHandler handler)
    {
        this.InvokePropertyChangedHandler(property, handler);
        if (!this.dependencies.ContainsKey(property))
        {
            return;
        }

        foreach (string tmp in this.dependencies[property])
        {
            if (this.evaluations.ContainsKey(tmp))
            {
                this.evaluations[tmp]();
            }
            else
            {
                this.InvokePropertyChangedHandler(tmp, handler);
            }
        }
    }

    public IEnumerable<string> GetErrors(string property)
    {
        List<string> result = null;
        if (!this.validations.ContainsKey(property))
        {
            return result;
        }

        foreach (var validation in validations[property])
        {
            var tmp = validation();
            if (!string.IsNullOrEmpty(tmp))
            {
                if (result == null)
                {
                    result = new List<string>();
                }
                result.Add(tmp);
            }
        }
        return result;
    }

    private void InvokePropertyChangedHandler(string propertyName, 
        PropertyChangedEventHandler originalHandler)
    {
        PropertyChangedEventHandler handler = originalHandler;
        if (handler == null)
        {
            return;
        }

        Delegate[] delegates = handler.GetInvocationList();
        foreach (Delegate d in delegates)
        {
            d.DynamicInvoke(new[] { this.source, 
                new PropertyChangedEventArgs(propertyName) });
        }
    }
}

 

And here is the (rather simplistic) test ViewModel that utilises the rules engine. It demonstrates how it handles inter-property dependencies, calculated properties and validation

using System;
using System.ComponentModel;

public class TestDomainObjectViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    private readonly PropertyRuleEngine rulesEngine;
    public event PropertyChangedEventHandler PropertyChanged;

    public TestDomainObjectViewModel()
    {
        this.rulesEngine = new PropertyRuleEngine(this);
        this.rulesEngine
            .AddDependency("TestName", "TestId")
            .AddEvaluatedProperty("TestName", () => 
                this.TestName = string.Format("Name : {0}", this.TestId.ToString()))
            .AddValidationProperty("TestId", () => 
                    this.TestId > 50 ? "Number too big" : string.Empty);
    }

    private string testName;
    public string TestName
    {
        get
        {
            return testName;
        }
        set
        {
            testName = value;
            this.rulesEngine.Notify("TestName", this.PropertyChanged);
        }
    }

    private int testId;
    public int TestId
    {
        get
        {
            return this.testId;
        }
        set
        {
            this.testId = value;
            this.rulesEngine.Notify("TestId", this.PropertyChanged);
        }
    }
        
    public string this[string columnName]
    {
        get
        {
            var result = rulesEngine.GetErrors(columnName);
            if (result == null)
            {
                return null;
            }
            return string.Join(Environment.NewLine, result);
        }
    }

    public string Error
    {
        get
        {
            return null;
        }
    }
}

 

And finally, here is the XAML and (if your new to MVVM) the code-behind file for completeness

 

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <TextBlock Text="ID" />
    <TextBox Text="{Binding TestId, Mode=TwoWay, ValidatesOnDataErrors=True,
        UpdateSourceTrigger=LostFocus}" Grid.Column="1" Width="100">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="True">
                        <Setter Property="ToolTip" 
                                Value="{Binding RelativeSource={RelativeSource Mode=Self}, 
                            Path=(Validation.Errors)[0].ErrorContent}" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
    <TextBlock Text="Name" Grid.Row="1" />
    <TextBox Text="{Binding TestName, Mode=TwoWay}" Grid.Row="1" Grid.Column="1" 
                Width="100" IsReadOnly="True" />
    <Button Content="Update" Grid.Row="2" />
</Grid>
public MainWindow()
{
    InitializeComponent();
    DataContext = new TestDomainObjectViewModel();
}

Tags: ,

C# | MVVM | WPF | XAML

WPF TreeView – SelectedItem Two Way Binding

by Dean 1. September 2011 07:30

Because the standard WPF TreeView implementation supports Virtualization it is unable to support two way bindings on its SelectedItem property as standard. This makes sense, because with Virtualization you may not have a container (TreeViewItem) available for any particular bound data item at the time you need it (to set its IsSelected property of the TreeViewItem  to ‘True’).

The solution is to use attached properties to force the ItemContainerGenerator to create the necessary containers for each data item. This will break the virtualization support, but that is a price you need to pay for a solution to this issue, so you should only use it on TreeView controls where the lack of Virtualization wont be a significant drawback.

Here is the attached property implementation:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
 
public class DemoAttachedProps
{
    public static DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached(
        "SelectedItem", typeof(object), typeof(DemoAttachedProps), 
        new PropertyMetadata(new object(), OnSelectedItemChanged));
 
    public static object GetSelectedItem(TreeView treeView)
    {
        return treeView.GetValue(SelectedItemProperty);
    }
 
    public static void SetSelectedItem(TreeView treeView, object value)
    {
        treeView.SetValue(SelectedItemProperty, value);
    }
 
    private static void OnSelectedItemChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs args)
    {
        var treeView = d as TreeView;
        if (treeView == null)
        {
            return;
        }
        treeView.SelectedItemChanged -= TreeViewItemChanged;
        var treeViewItem = SelectTreeViewItemForBinding(args.NewValue, treeView);
        if (treeViewItem != null)
        {
            treeViewItem.IsSelected = true;
        }
        treeView.SelectedItemChanged += TreeViewItemChanged;
    }
 
    private static void TreeViewItemChanged(object sender, 
        RoutedPropertyChangedEventArgs<object> e)
    {
        ((TreeView)sender).SetValue(SelectedItemProperty, e.NewValue);
    }
 
    private static TreeViewItem SelectTreeViewItemForBinding(
        object dataItem, ItemsControl ic)
    {
        if (ic == null || dataItem == null)
        {
            return null;
        }
        IItemContainerGenerator generator = ic.ItemContainerGenerator;
        using (generator.StartAt(generator.GeneratorPositionFromIndex(-1), 
            GeneratorDirection.Forward))
        {
            foreach (var t in ic.Items)
            {
                bool isNewlyRealized;
                var tvi = generator.GenerateNext(out isNewlyRealized);
                if (isNewlyRealized)
                {
                    generator.PrepareItemContainer(tvi);
                }
                if (t == dataItem)
                {
                    return tvi as TreeViewItem;
                }
 
                var tmp = SelectTreeViewItemForBinding(dataItem, 
                    tvi as ItemsControl);
                if (tmp != null)
                {
                    return tmp;
                }
            }
        }
        return null;
    }
}

And here is the attached property in action:

public class TestDataObjViewModel
{
    public List<TestDataObj> TestList { get; private set; }
    public List<TestDataObj> TestHierarchy { get; private set; }
 
    public TestDataObjViewModel()
    {
        TestList = new List<TestDataObj>();
        var top = new TestDataObj { TestText = "top" };
        TestList.Add(top);
        var second1 = new TestDataObj { TestText = "second 1" };
        TestList.Add(second1);
        top.Children.Add(second1);
        var second2 = new TestDataObj { TestText = "second 2" };
        TestList.Add(second2);
        top.Children.Add(second2);
        var third = new TestDataObj { TestText = "third" };
        TestList.Add(third);
        second1.Children.Add(third);
        TestHierarchy = new List<TestDataObj> { top };
    }
}
 
public class TestDataObj
{
    private readonly List<TestDataObj> children = new List<TestDataObj>();
 
    public string TestText { get; set; }
 
    public List<TestDataObj> Children
    {
        get
        {
            return children;
        }
    }
}

Here's the code-behind for the demo XAML

public MainWindow()
{
    InitializeComponent();
    DataContext = new TestDataObjViewModel();
}

And here’s the XAML

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <TreeView ItemsSource="{Binding TestHierarchy}" x:Name="treeView" 
              Margin="0,10" DemoApp1:DemoAttachedProps.SelectedItem="{x:Null}" >
        <TreeView.ItemContainerStyle>
            <Style TargetType="TreeViewItem">
                <Setter Property="IsExpanded" Value="True" />
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding TestText}" />
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    <ComboBox ItemsSource="{Binding TestList}" DisplayMemberPath="TestText" 
              Grid.Row="1" HorizontalAlignment="Left" x:Name="comboBox" 
              SelectedItem="{Binding ElementName=treeView, 
                Path=(DemoApp1:DemoAttachedProps.SelectedItem), Mode=TwoWay}" />
</Grid>

Tags: , , ,

Attached Properties | MVVM | WPF | XAML

WPF – Colour Picker Widget With Attached Properties

by Dean 21. November 2010 08:47

We can set the colour of any WPF control that supports it, but what about being able to specifically sett individual colour channels (RED, GREEN, BLUE, ALPHA)

I've seen many solutions that contain complex custom controls, but one of the key philosophies of WPF is to reuse what you already have, and change the look or extend the functionality via control templates and the dependency system.

Therefore, I have created a colour picker widget that uses only standard controls, and doesn't even change their control templates. Everything is accomplished via the use of attached properties, which are a very underrated tool in the WPF tool-box.

Here’s the attached property class

public static class BrushExtender
{
    public readonly static DependencyProperty BrushProperty =
        DependencyProperty.RegisterAttached("Brush", typeof(Brush), 
        typeof(BrushExtender), new PropertyMetadata(Brushes.Black, DoBrushChanged));

    public readonly static DependencyProperty RedChannelProperty = 
        DependencyProperty.RegisterAttached("RedChannel", typeof(int), 
        typeof(BrushExtender), new PropertyMetadata(DoColorChangedRed));

    public readonly static DependencyProperty GreenChannelProperty = 
        DependencyProperty.RegisterAttached("GreenChannel", typeof(int), 
        typeof(BrushExtender), new PropertyMetadata(DoColorChangedGreen));

    public readonly static DependencyProperty BlueChannelProperty =
        DependencyProperty.RegisterAttached("BlueChannel", typeof(int),
        typeof(BrushExtender), new PropertyMetadata(DoColorChangedBlue));

    public readonly static DependencyProperty AlphaChannelProperty = 
        DependencyProperty.RegisterAttached("AlphaChannel", typeof(int),
        typeof(BrushExtender), new PropertyMetadata(DoColorChangedAlpha));

    public readonly static DependencyProperty ColourValueProperty =
        DependencyProperty.RegisterAttached("ColourValue", typeof(string),
        typeof(BrushExtender), new PropertyMetadata(DoValueChanged));

    public static void SetRedChannel(DependencyObject o, int value)
    {
        o.SetValue(RedChannelProperty, value);
    }

    public static void SetGreenChannel(DependencyObject o, int value)
    {
        o.SetValue(GreenChannelProperty, value);
    }

    public static void SetBlueChannel(DependencyObject o, int value)
    {
        o.SetValue(BlueChannelProperty, value);
    }

    public static void SetAlphaChannel(DependencyObject o, int value)
    {
        o.SetValue(AlphaChannelProperty, value);
    }

    public static void SetBrush(DependencyObject o, SolidColorBrush brush)
    {
        o.SetValue(BrushProperty, brush);
    }

    public static void SetColourValue(DependencyObject o, string value)
    {
        o.SetValue(ColourValueProperty, value);
    }

    private static void DoColorChangedRed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
        DoColorChange(d, (int)e.NewValue, c => c.R, () => 
            Color.FromArgb(color.A, ((byte)(int)e.NewValue), color.G, color.B));
    }

    private static void DoColorChangedGreen(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
        DoColorChange(d, (int)e.NewValue, c => c.G, () => 
            Color.FromArgb(color.A, color.R, ((byte)(int)e.NewValue), color.B));
    }

    private static void DoColorChangedBlue(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
        DoColorChange(d, (int)e.NewValue, c => c.B, () => 
            Color.FromArgb(color.A, color.R, color.G, (byte)(int)e.NewValue));
    }

    private static void DoColorChangedAlpha(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
        DoColorChange(d, (int)e.NewValue, c => c.A, () => 
            Color.FromArgb((byte)(int)e.NewValue, color.R, color.G, color.B));
    }

    private static void DoColorChange(DependencyObject d, int newValue, Func<Color, int> colorCompare, 
        Func<Color> getColor)
    {
        var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
        if (colorCompare(color) == newValue)
            return;
        var newBrush = new SolidColorBrush(getColor());
        d.SetValue(BrushProperty, newBrush);
        d.SetValue(ColourValueProperty, newBrush.Color.ToString());
    }

    private static void DoValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var color = ((SolidColorBrush)d.GetValue(BrushProperty)).Color;
        if (color.ToString() == (string)e.NewValue)
            return;
        Color? newColour = null;
        try
        {
            newColour = (Color) ColorConverter.ConvertFromString((string)e.NewValue);
        }
        catch { }
        if (newColour == null)
            return;
        var newBrush = new SolidColorBrush(newColour.Value);
        d.SetValue(BrushProperty, newBrush);
    }


    private static void DoBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue == e.OldValue)
            return;
        var colour = ((SolidColorBrush)e.NewValue).Color;
        d.SetValue(RedChannelProperty, (int)colour.R);
        d.SetValue(GreenChannelProperty, (int)colour.G);
        d.SetValue(BlueChannelProperty, (int)colour.B);
        d.SetValue(AlphaChannelProperty, (int)colour.A);
        d.SetValue(ColourValueProperty, colour.ToString());
    }
}

And here is the simple WPF widget that makes use of this new functionality

<Window x:Class="ColourBlender.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:ColourBlender="clr-namespace:ColourBlender" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TextBlock Text="Red" />
        <TextBlock Text="Green" Grid.Row="1" />
        <TextBlock Text="Blue" Grid.Row="2" />
        <TextBlock Text="Alpha" Grid.Row="3" />

        <Slider Name="redSlider" Grid.Column="1" Minimum="0" Maximum="255" Width="200" Height="20" 
                Grid.ColumnSpan="2" Value="{Binding ElementName=rect, 
                Path=(ColourBlender:BrushExtender.RedChannel), Mode=TwoWay}" />
        <Slider Name="greenSlider" Grid.Column="1" Grid.Row="1" Minimum="0" Maximum="255" Width="200" Height="20" 
                Grid.ColumnSpan="2" Value="{Binding ElementName=rect, 
                Path=(ColourBlender:BrushExtender.GreenChannel), Mode=TwoWay}"  />
        <Slider Name="blueSlider" Grid.Column="1" Grid.Row="2" Minimum="0" Maximum="255" Width="200" Height="20" 
                Grid.ColumnSpan="2" Value="{Binding ElementName=rect, 
                Path=(ColourBlender:BrushExtender.BlueChannel), Mode=TwoWay}"  />
        <Slider Name="alphaSlider" Grid.Column="1" Grid.Row="3" Minimum="0" Maximum="255" Width="200" Height="20" 
                Grid.ColumnSpan="2" Value="{Binding ElementName=rect, 
                Path=(ColourBlender:BrushExtender.AlphaChannel), Mode=TwoWay}"  />

        <Rectangle Fill="SandyBrown" Name="rect" Width="200" Height="50" Grid.Row="4" Grid.ColumnSpan="3" 
                Margin="0,20,0,10" ColourBlender:BrushExtender.Brush="{Binding RelativeSource={RelativeSource Self}, 
                Path=Fill, Mode=TwoWay}"/>
        
        <TextBlock Text="Colour Value" Margin="5,0,5,0" Grid.Row="5" HorizontalAlignment="Center"  />
        <TextBox Text="{Binding ElementName=rect, Path=(ColourBlender:BrushExtender.ColourValue), Mode=TwoWay}" 
                Margin="0,0,0,0" Grid.Row="5" Grid.Column="1" Width="100" HorizontalAlignment="Center" />
        
        <Button Content="Update" Grid.Row="5" Grid.Column="3" />
    </Grid>
</Window>

This widget allows you to alter the Fill colour of a rectangle via red, green, blue and alpha sliders. There is also a textbox that shows the colour code (which you can also edit and the change is reflected in the slider positions)

Here are some screen shots

 

colour1

colour2

Tags: ,

Attached Properties | DataBinding | WPF | XAML

WPF – Loading Spinner Via MVVM and Attached Properties

by Dean 13. November 2010 14:47

When I used to write a lot of Ajax for ASP.NET (many years ago) I quite like the feature whereby you could disable the page and show a ‘loading spinner’ while waiting for an update in the page.

This is a great feature to have in WPF, so I have created a ViewModel with a ‘loading’ switch (bool), and an attached property that creates and displays the animated spinner (for the duration of the switch flag being true)

Firstly, we need a control template for a content control that will be our animated spinner.

<Style x:Key="LoadingSpinner" TargetType="ContentControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ContentControl">
                <Grid>
                    <Rectangle Width="160" Height="160">
                        <Rectangle.Fill>
                            <VisualBrush Stretch="None">
                                <VisualBrush.Visual>
                                    <Canvas RenderTransformOrigin="0.5,0.5">
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="71.1667" 
                                                Canvas.Top="3.00002" Stretch="Fill" Fill="#FF000000"/>
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="71.1667" 
                                                Canvas.Top="139.833" Stretch="Fill" Fill="#85000000"/>
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="139.583" 
                                                Canvas.Top="71.4167" Stretch="Fill" Fill="#C2000000"/>
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="2.75" 
                                                Canvas.Top="71.4167" Stretch="Fill" Fill="#48000000"/>
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="22.7888" 
                                                Canvas.Top="23.0388" Stretch="Fill" Fill="#29000000"/>
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="119.545" 
                                                Canvas.Top="119.795" Stretch="Fill" Fill="#A4000000"/>
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="119.545" 
                                                Canvas.Top="23.0388" Stretch="Fill" Fill="#E1000000"/>
                                        <Ellipse Width="14.8333" Height="14.8333" Canvas.Left="22.7888" 
                                                Canvas.Top="119.795" Stretch="Fill" Fill="#67000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="44.9828" 
                                                Canvas.Top="8.20598" Stretch="Fill" Fill="#1A000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="97.3466" 
                                                Canvas.Top="134.623" Stretch="Fill" Fill="#94000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="134.373" 
                                                Canvas.Top="45.2328" Stretch="Fill" Fill="#D2000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="7.95596" 
                                                Canvas.Top="97.5967" Stretch="Fill" Fill="#57000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="7.95596" 
                                                Canvas.Top="45.2328" Stretch="Fill" Fill="#39000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="134.373" 
                                                Canvas.Top="97.5966" Stretch="Fill" Fill="#B3000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="97.3466" 
                                                Canvas.Top="8.20599" Stretch="Fill" Fill="#F0000000"/>
                                        <Ellipse Width="14.8372" Height="14.8372" Canvas.Left="44.9828" 
                                                Canvas.Top="134.623" Stretch="Fill" Fill="#76000000"/>
                                        <Canvas.RenderTransform>
                                            <RotateTransform Angle="0" />
                                        </Canvas.RenderTransform>
                                        <Canvas.LayoutTransform>
                                            <ScaleTransform ScaleX="0.6" ScaleY="0.6" />
                                        </Canvas.LayoutTransform>
                                        <Canvas.Triggers>
                                            <EventTrigger RoutedEvent="ContentControl.Loaded">
                                                <BeginStoryboard>
                                                    <Storyboard>
                                                        <DoubleAnimation 
                                                            Storyboard.TargetProperty=
                                                                "(Canvas.RenderTransform).Angle" 
                                                            From="0" To="360" Duration="0:0:02" 
                                                            RepeatBehavior="Forever" />
                                                    </Storyboard>
                                                </BeginStoryboard>
                                            </EventTrigger>
                                        </Canvas.Triggers>
                                    </Canvas>
                                </VisualBrush.Visual>
                            </VisualBrush>
                        </Rectangle.Fill>
                    </Rectangle>
                    <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The Content property of out content presenter will include our text message that will be inside the spinner

Next we need our attached property class

    public class AsyncNotifier
    {
        public static readonly DependencyProperty TriggerProperty = 
            DependencyProperty.RegisterAttached("Trigger", typeof(bool), typeof(AsyncNotifier), 
            new PropertyMetadata(false, TriggerCallback));

        public static readonly DependencyProperty SpinnerTextProperty = 
            DependencyProperty.RegisterAttached("SpinnerText", typeof(string), typeof(AsyncNotifier));

        private static readonly DependencyProperty SpinnerProperty = 
            DependencyProperty.RegisterAttached("Spinner", typeof(Grid), typeof(AsyncNotifier));

        public static void SetTrigger(DependencyObject d, bool trigger)
        {
            d.SetValue(TriggerProperty, trigger);
        }

        public static void SetSpinnerText(DependencyObject d, string text)
        {
            d.SetValue(SpinnerTextProperty, text);
        }

        private static void TriggerCallback(DependencyObject d,
DependencyPropertyChangedEventArgs e)
        {
            var parentGrid = d as Grid;
            if (parentGrid == null)
                return;
            string spinnerText = (string)parentGrid.GetValue(SpinnerTextProperty);
            bool trigger = (bool)parentGrid.GetValue(TriggerProperty);
            Grid grid = parentGrid.GetValue(SpinnerProperty) as Grid;
            if (grid == null)
            {
                grid = new Grid();
                parentGrid.SetValue(SpinnerProperty, grid);
                if (parentGrid.ColumnDefinitions.Count > 0)
                    Grid.SetColumnSpan(grid, parentGrid.ColumnDefinitions.Count);
                if (parentGrid.RowDefinitions.Count > 0)
                    Grid.SetRowSpan(grid, parentGrid.RowDefinitions.Count);
            }
            grid.Background = new SolidColorBrush(Colors.White) { Opacity = 0.6 };
            grid.Children.Clear();
            ContentControl cont = new ContentControl();
            cont.Content = new TextBlock() { Text = spinnerText };
            cont.Style = (Style)parentGrid.FindResource("LoadingSpinner");
            grid.Children.Add(cont);
            if (!parentGrid.Children.Contains(grid))
                parentGrid.Children.Add(grid);
            grid.Visibility = trigger ? Visibility.Visible : Visibility.Hidden;
        }
    }

Now our ViewModel

public class MainViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public bool IsDataLoading { get; private set; }
    public List<string> TestData { get; private set; }
    public ICommand LoadData { get; private set; }

    public MainViewModel()
    {
        IsDataLoading = false;
        LoadData = new ActionCommand(LoadTestData);
    }

    private void LoadTestData()
    {
        TestData = new List<string>();
        ThreadPool.QueueUserWorkItem((o) => 
            {
                IsDataLoading = true;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("IsDataLoading"));
                for (int i = 0; i < 20; i++)
                    Dispatcher.CurrentDispatcher.Invoke((Action)(() => TestData.Add("Test Data Item " + i)));
                Thread.Sleep(2000);
                IsDataLoading = false;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("IsDataLoading"));
                    PropertyChanged(this, new PropertyChangedEventArgs("TestData"));
                }
            });
    }
}

public class ActionCommand : ICommand
{
    private readonly Action action;
    public ActionCommand(Action action)
    {
        this.action = action;
    }
    public void Execute(object parameter)
    {
        action();
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;
}

Heres out Window that uses the attached property to include a spinner

    <Window.Resources>
        <CollectionViewSource x:Key="DataList" Source="{Binding TestData}"  />
    </Window.Resources>
    <Grid Background="AliceBlue" app:AsyncNotifier.Trigger="{Binding IsDataLoading}" 
          app:AsyncNotifier.SpinnerText="Loading...">
        <TabControl Grid.RowSpan="2">
            <TabItem Header="TabItem">
                <Grid Background="#FFE5E5E5">
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <ListBox ItemsSource="{Binding 
                        Source={StaticResource DataList}}" />
                    <Button Content="Do Update" HorizontalAlignment="Left" Command="{Binding LoadData}"
                            VerticalAlignment="Top" Width="75" Grid.Row="1" Margin="0,5" />
                </Grid>
            </TabItem>
            <TabItem Header="TabItem">
                <Grid Background="#FFE5E5E5"/>
            </TabItem>
        </TabControl>
    </Grid>

And finally our code behind

public MainWindow()
{
    InitializeComponent();
    DataContext = new MainViewModel();
}

And here are some screen shots

Before:

before

During Loading:

during

When Done:

after

Dean

Tags: ,

WPF | XAML | Animation

WPF - Modal Controls Via DispatcherFrame (Nested Message Pumps)

by Dean 12. November 2010 09:55

Lets say your new WPF gui has a button that loads confidential data. Lets then say that during the button's click handler you need to get some credentials from the user BEFORE continuing code execution within the handler. The usual approach would be to popup a modal window via Window.ShowDialog(), because the gui thread will block until the popup is closed (for getting credentials in this scenario) which is the desired behaviour.

But what if you wanted to do something a bit more complex, using controls in the existing logical tree ? You could have a hidden control with a high z-index that you can make visible (emulating a popup), but the gui won't block at that point, because if it does you wont be able to interact with that control (its a deadlock).

The solution is to use a little-known feature of the WPF Dispatcher, called DispatcherFrame - with which you can create nested message pumps, that allow the user to interact with controls while the main Dispatcher thread is blocked.

Here’s some sample code to illustrate the approach

 

<Window x:Class="Dispatcher.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="500" Width="500">
    <Grid>
        <TabControl>
            <TabItem Header="TabItem">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <ListBox ItemsSource="{Binding}"/>
                    <Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" 
                            Content="Get Data" Grid.Row="1" Margin="0,5" Click="GetData"/>
                </Grid>
            </TabItem>
            <TabItem Header="TabItem">
                <Grid/>
            </TabItem>
        </TabControl>
        <Grid Name="popupGrid" Visibility="Hidden">
            <Grid.Background>
                <SolidColorBrush Opacity="0.4" Color="#FFD8CFCF"/>
            </Grid.Background>
            <Border HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="100" 
                    BorderBrush="Black" BorderThickness="1" Background="White" Padding="5">
                <StackPanel>
                    <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Enter Number Of Items"/>
                    <TextBox HorizontalAlignment="Left" Text="10" TextWrapping="Wrap" Margin="0,3" 
                             Width="100" Name="countText"/>
                    <Button HorizontalAlignment="Left" Width="75" Content="Do Data Add" Click="DataCountEntered"/>
                </StackPanel>
            </Border>
        </Grid>
    </Grid>
</Window>

 

public partial class MainWindow : Window
{
    private DispatcherFrame frame;
    private readonly ObservableCollection<string> collection = new ObservableCollection<string>();
    public MainWindow()
    {
        InitializeComponent();
        DataContext = collection;
    }

    private void GetData(object sender, RoutedEventArgs e)
    {
        collection.Clear();
        frame = new DispatcherFrame();
        popupGrid.Visibility = Visibility.Visible;
        System.Windows.Threading.Dispatcher.PushFrame(frame); // blocks gui message pump & creates nested pump
        var count = int.Parse(countText.Text); // after DispatcherFrame is cancelled, it continues
        for (int i = 0; i < count; i++)
            collection.Add("Test Data " + i);
        popupGrid.Visibility = Visibility.Hidden;
    }

    private void DataCountEntered(object sender, RoutedEventArgs e)
    {
        frame.Continue = false; // un-blocks gui message pump
    }
}

I don't think there are many scenarios where this would be the best approach, but its a neat trick if you need to use it.

Dean

Tags: ,

C# | WPF | Dispatcher

MVVM – Simple Generic<T> Event Commands With Attached Properties

by Dean 10. November 2010 07:08

One of the challenges of MVVM is to favour command bindings over handling routed events, as currently all event handling must happen in the ‘code behind’ file rather than the ViewModel – which breaks the pattern.

With WPF4 we get the new InputBindings feature, that allows us to hook-up mouse and keyboard gestures to commands, but this still doesn't solve all scenarios where event handling seems to be our only option.

There are many elaborate and highly engineered strategies that I have seen to solve this, but maybe we could use a simple attached property implementation to achieve the desired effect.

Also, WPF bindings (including command bindings) don't support generics, but maybe we could use type inference to create a generic implementation.

In the example below, I have created an attached property that hooks up the ‘Selector’ (base control for ListBox and ListView) ‘SelectionChanged’ routed event to a custom ICommand that can be utilised in our ViewModel

Here's the attached property and command:

 

public class SelectionChangedCommand<T> : ICommand
{
    private readonly Action<List<T>, List<T>> action;

    public SelectionChangedCommand(Action<List<T>, List<T>> action)
    {
        this.action = action;
    }

    public void Execute(object parameter)
    {
        var args = parameter as SelectionChangedEventArgs;
        if (args == null)
            return;
        List<T> added = (
                from object item
                in args.AddedItems
                select (T)item
                ).ToList();
        List<T> removed = (
                from object item
                in args.RemovedItems
                select (T)item
                ).ToList();
        action(added, removed);
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;
}

public static class SelectionChangedCommand
{
    public static DependencyProperty BindProperty = DependencyProperty.RegisterAttached("Bind",
                    typeof(ICommand),
                    typeof(SelectionChangedCommand),
                    new PropertyMetadata(AttachCommand));

    public static void SetBind(DependencyObject d, ICommand command)
    {
        d.SetValue(BindProperty, command);
    }

    private static void AttachCommand(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Selector selector = d as Selector;
        if (selector == null)
            return;
        ICommand oldCommand = e.OldValue as ICommand;
        ICommand newCommand = e.NewValue as ICommand;
        if (oldCommand != null || newCommand == null)
            return;
        selector.SelectionChanged += (o, a) => newCommand.Execute(a);
    }
}

Here’s a test ViewModel where we supply data to the View and well as our command binding:

public class MainViewModel
{
    public List<string> MainList 
    { get; set; }
    public SelectionChangedCommand<string> MainListChanged
    { get; set; }

    public MainViewModel()
    {
        MainList = new List<string> { 
            "testitem1", "testitem2", "testitem3", "testitem4", "testitem5" };
        MainListChanged = new SelectionChangedCommand<string>(DoChange);
    }

    private void DoChange(List<string> addedItems, List<string> removedItems)
    {
        // do viewmodel update here
    }
}

Heres our View

<Window x:Class="MVVMEvents.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:e="clr-namespace:MVVMEvents" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox ItemsSource="{Binding MainList}" SelectionMode="Multiple"
               e:SelectionChangedCommand.Bind="{Binding MainListChanged}"/>
    </Grid>
</Window>

And here’s where we hook up our ViewModel to the View (usual code):

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
}

And there it is, we’ve hooked up a non-generic Routed Event (SelectionChanged) to a generic command command that fits nicely into our MVVM pattern implementation.

NOTE: This simple implementation doesn't support Weak Events, which would be a worthwhile improvement.

Dean

Tags: , ,

WPF | Silverlight | MVVM | DataBinding | XAML | Events

WPF – Using Attached Properties For Animated Data Change Notifications

by Dean 29. October 2010 09:49

Working in the investment banking industry, we often need to feed live (changing) market data into our WPF controls, and often there is a requirement that certain movements (in value) are highlighted briefly so as to give a strong visual representation of data changes.

The best way to do this (in my opinion) is to have the TextBlock representing the value ‘blink’ a different colour when the value changes. You would normally include rules that for example stipulate that a movement of more than 3% is required before a notification.
You would also like movements ‘up’ to be shown in a different colour to movements ‘down’.

One key characteristic is that these background changes are very short lived – giving a quick ‘blink’ as a notification.

In fact, to add an extra feature, I have effectively removed the acceleration and deceleration phases of the animation to create a ‘toggle’ style blink (rather than pulse), so it suits our scenario better.

There are several ways that you could achieve this, but my favourite is with attached properties and animations.

Here’s the full code

public class ToggleEase : EasingFunctionBase
{
    protected override Freezable CreateInstanceCore()
    {
        return new ToggleEase();
    }

    protected override double EaseInCore(double normalizedTime)
    {
        if (normalizedTime == 1d)
            return 1d;
        return 0d;
    }
}

public class CellHighlight : DependencyObject
{
    public static readonly DependencyProperty ChangeSourceProperty =
        DependencyProperty.RegisterAttached("ChangeSource", typeof(double),
                                            typeof (CellHighlight),
                                            new PropertyMetadata(double.MinValue, DoPropertyChanged));

    public static readonly DependencyProperty MinimumPercentageMovementProperty =
        DependencyProperty.RegisterAttached("MinimumPercentageMovement", typeof(double),
                                            typeof(CellHighlight),
                                            new PropertyMetadata(0d));

    public static readonly DependencyProperty MovementUpColourProperty =
        DependencyProperty.RegisterAttached("MovementUpColour", typeof(Color),
                                            typeof(CellHighlight),
                                            new PropertyMetadata(Colors.LightGreen));

    public static readonly DependencyProperty MovementDownColourProperty =
        DependencyProperty.RegisterAttached("MovementDownColour", typeof(Color),
                                            typeof(CellHighlight),
                                            new PropertyMetadata(Colors.LightSalmon));

    public static void SetChangeSource(DependencyObject d, double useVal)
    {
        d.SetValue(ChangeSourceProperty, useVal);
    }

    public static void SetMinimumPercentageMovement(DependencyObject d, double perc)
    {
        d.SetValue(MinimumPercentageMovementProperty, perc);
    }

    public static void SetMovementUpColour(DependencyObject d, Color color)
    {
        d.SetValue(MovementUpColourProperty, color);
    }

    public static void SetMovementDownColour(DependencyObject d, Color color)
    {
        d.SetValue(MovementDownColourProperty, color);
    }

    private static void DoPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var oldv = (double) e.OldValue;
        var newv = (double) e.NewValue;

        if (oldv == double.MinValue)
            return;

        var percChange = (double) d.GetValue(MinimumPercentageMovementProperty);
        var upColour = (Color) d.GetValue(MovementUpColourProperty);
        var downColour = (Color) d.GetValue(MovementDownColourProperty);

        var diffPerc = Math.Abs((newv - oldv)/oldv*100);

        if (diffPerc <= percChange)
            return;

        var textblock = (TextBlock)d;

        SolidColorBrush brush = new SolidColorBrush();
        textblock.Background = brush;
        ColorAnimation anim = new ColorAnimation
                    {
                        To = oldv > newv ? downColour : upColour,
                        Duration = TimeSpan.FromMilliseconds(125),
                        AutoReverse = true,
                        EasingFunction = new ToggleEase()
                    };
        brush.BeginAnimation(SolidColorBrush.ColorProperty, anim);

    }

}
<Window x:Class="AnimatedCell.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:AnimatedCell="clr-namespace:AnimatedCell" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListView ItemsSource="{Binding}">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
                        <GridViewColumn Header="Product Name" DisplayMemberBinding="{Binding ProductName}" />
                        <GridViewColumn Header="Price">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding MktPrice}" 
                                        AnimatedCell:CellHighlight.ChangeSource="{Binding MktPrice}" 
                                        AnimatedCell:CellHighlight.MinimumPercentageMovement="3"
                                        AnimatedCell:CellHighlight.MovementDownColour="Red" 
                                        AnimatedCell:CellHighlight.MovementUpColour="Green"   />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>
internal class Trade : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private double mktPrice;
    public double MktPrice
    {
        get { return mktPrice; }
        set
        {
            if (mktPrice == value)
                return;
            mktPrice = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("MktPrice"));
        }
    }

    public string ID { get; set; }
    public string ProductName { get; set; }

    internal static List<Trade> GetTestDataCollection()
    {
        var rand = new Random(Guid.NewGuid().GetHashCode());
        var data = new List<Trade>();
        Enumerable.Range(0, 10).ToList().ForEach((i) =>
            data.Add(new Trade { ID = rand.Next(1000, 9999).ToString(), 
                mktPrice = rand.NextDouble(), ProductName = "Prod" + i.ToString() }));
        return data;
    }

}
public partial class MainWindow : Window
{
    private readonly DispatcherTimer timer = new DispatcherTimer();
    public MainWindow()
    {
        InitializeComponent();

        var rand = new Random(Guid.NewGuid().GetHashCode());
        var data = Trade.GetTestDataCollection();
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Tick += (o, e) => data.ForEach((t) => t.MktPrice = rand.NextDouble());

        DataContext = data;

        timer.Start();
    }

}

And heres a quick screen show (obviously you cant see the animation as such)

celanim

 

Dean

Tags: , ,

C# | DataBinding | Events | WPF | Animation

WPF – Easy INotifyPropertyChanged Via DynamicObject Proxy

by Dean 15. October 2010 07:53

There has been a big debate at the bank about how best to implement INotifyPropertyChanged on Model classes that need to support change notifications.

None of the approaches seem entirely satisfactory, and here is a quick rundown of the main contenders:

  1. Default approach – hard-code string representations of the property name that’s changing –> this is the easiest approach, and the one that’s included in most documentation, but it can quickly introduce subtle binding errors in WPF if you forget to re-write the strings when you refactor your model classes
  2. Derive Model classes from DependencyObject and use the WPF dependency system for change notifications –> This is an effective solution, but breaks down if you want to do good unit testing. Also architecturally its not good, as your model classes now depend on systems designed to support WPF controls.
  3. Wrap your model classes in their own ViewModel that implements the interface –> this is a sound solution, but requires the coding of additional decorator classes, and can lead to a lot of code-bloat.
  4. Use Lambda Expressions –> this is refactor-proof, but is a poor performer as deciphering the lambda expressions in order to get at the property name uses reflection. Even though lambda expressions are lazy-compiled, there’s still a significant performance hit and the coding style seems counter-intuitive, which may confuse junior developers.
  5. Get the name of the current method inside the property setter, and then strip out the ‘set_’ part of the method name –> this is a really ugly solution, and may not work if you obfuscate the code.
  6. Look at the method call stack inside a helper class –> really ugly code and fragile if refactored.
  7. Derive your model classes from ContextBoundObject so they can be automatically remoted, and then inject AOP code into the message sink-chain –> are you serious !!!

None of the approaches are great, and every one requires the original Model class to be modified in order to support the approach.

So I thought – how about a generic class that derives from DynamicObject, and implements INotifyPropertyChanged. With such a class you’d achieve the following:

  1. Automatic property changed notifications without changing a line of code on your model classes
  2. The solution is completely refactor-proof
  3. It performs reasonably well
  4. The additional memory consumption and strain on the GC is negligible.
  5. It a '’one size fits all’ solution

So, here’s my implementation:

public class DynamicBindingProxy<T> : DynamicObject, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private static readonly Dictionary<string, Dictionary<string, PropertyInfo>> properties =
        new Dictionary<string, Dictionary<string, PropertyInfo>>();
    private readonly T instance;
    private readonly string typeName;

    public DynamicBindingProxy(T instance)
    {
        this.instance = instance;
        var type = typeof(T);
        typeName = type.FullName;
        if (!properties.ContainsKey(typeName))
            SetProperties(type, typeName);
    }

    private static void SetProperties(Type type, string typeName)
    {
        var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        var dict = props.ToDictionary(prop => prop.Name);
        properties.Add(typeName, dict);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (properties[typeName].ContainsKey(binder.Name))
        {
            result = properties[typeName][binder.Name].GetValue(instance, null);
            return true;
        }
        result = null;
        return false;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (properties[typeName].ContainsKey(binder.Name))
        {
            properties[typeName][binder.Name].SetValue(instance, value,null);
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(binder.Name));
            return true;
        }
        return false;
    }
}

 

As you can see, the only minor initial performance hit is when reflecting the classes type for the first time, which is then cached in a static dictionary.

And here is it being used:

public class TestObj
{
    public string Name { get; set; }
    public int ID { get; set;  }
    public DateTime SomeDate { get; set; }
    public double Amount { get; set; }
}

 

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        mc:Ignorable="d" x:Class="WpfApplication1.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Border Padding="10">
        <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <UniformGrid Rows="4" Columns="2">
                <TextBlock TextWrapping="Wrap"><Run Language="en-gb" Text="Name"/></TextBlock>
                <TextBox TextWrapping="Wrap" Text="{Binding Name}"/>
                <TextBlock TextWrapping="Wrap"><Run Language="en-gb" Text="ID"/></TextBlock>
                <TextBox TextWrapping="Wrap" Text="{Binding ID}"/>
                <TextBlock TextWrapping="Wrap"><Run Language="en-gb" Text="Amount"/></TextBlock>
                <TextBox TextWrapping="Wrap" Text="{Binding Amount}"/>
                <TextBlock TextWrapping="Wrap"><Run Language="en-gb" Text="Some Date"/></TextBlock>
                <TextBox TextWrapping="Wrap" Text="{Binding SomeDate}"/>
            </UniformGrid>
            <StackPanel HorizontalAlignment="Left" Orientation="Horizontal" Grid.Row="1" 
                        d:LayoutOverrides="Height" Margin="0,10,0,0">
                <TextBox TextWrapping="Wrap" Margin="0,0,10,0" Width="150" Name="newText"/>
                <Button Content="Change Name" Click="UpdateName" d:LayoutOverrides="Width"/>
            </StackPanel>
        </Grid>
    </Border>
</Window>
public partial class MainWindow : Window
{
    private readonly TestObj tObj;
    private DynamicBindingProxy<TestObj> dynObj;
    public MainWindow()
    {
        InitializeComponent();
        tObj = new TestObj() { Name = "test", Amount = 123.45, ID = 44, SomeDate = DateTime.Now };
        dynObj = new DynamicBindingProxy<TestObj>(tObj);
        DataContext = dynObj;
    }

    private void UpdateName(object sender, RoutedEventArgs e)
    {
        ((dynamic)dynObj).Name = newText.Text;
    }
}

As you can see if you run this code, clicking on the update button changes the value of the name property via the dynamic proxy, which publishes the change notification.

 

Dean

Tags: , , ,

C# | DataBinding | MVVM | WPF | XAML

F# - Interacting With WPF Dispatcher Via F# Interactive Window

by Dean 8. October 2010 08:44

Heres an interesting scenario that was discussed with me by one of the quants in the front-office team yesterday.

"Can I invoke a WPF Window/Control to popup from the FSI window in Visual Studio ?" - "Yes, thats easy I said"....

"and then interact with it ASYNCHRONOUSLY via F# commands in the FSI window ?" - "hmmmm, I said"

So the first bit is easy and has been done many times before with WPF and Winforms, and that is mainly to instantiate UI controls and popups from within Fsharp Interactive, and update these controls when running F# commands..... but....... you're doing your updates synchronously on the main UI thread !

What if the update your running is a long-running task, that WPF popup is going to freeze on you, and to solve it you are facing the classic issue of STA thread affinity with either WPF or Winforms.

The common solution in standard WPF/Winforms apps is to marshall messages to the UI pump via delegates, but how would that work in F# interactive ?

After a little head-scratching, I figured it out, and here is a sample of how its done below:

 

#light

#r "WindowsBase"
#r "PresentationCore"
#r "PresentationFramework"

open System
open System.Windows
open System.Windows.Controls
open System.Threading
open System.Windows.Threading

// create a reference to a WPF control in interactive window
let mutable (wp : TextBlock) = null

// create a new WPF gui thread with a running dispatcher and message pump
let thread = new System.Threading.Thread(fun() ->
    let window = new System.Windows.Window(Name="Test",Width=500.0,Height=500.0)
    wp <- new TextBlock()
    wp.Text <- "test1"
    window.Content <- wp
    window.Visibility <- Visibility.Visible
    window.Show()
    window.Closed.Add(fun e ->
        Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background)
        Thread.CurrentThread.Abort())
    Dispatcher.Run()
    )
thread.SetApartmentState(ApartmentState.STA)
thread.IsBackground <- true

// start the thread, which will invoke the popup ui
thread.Start()

// once WPF window is up, you can marshall updates via its running dispatcher
wp.Dispatcher.BeginInvoke(Action(fun _ -> wp.Text <- "test2")) |> ignore

To test this, run all but the last line in the interactive window, and you should see a popup like this:

 

 

Then run the final line, which marshalls the update onto the new gui thread, and you get this

 

Proving the solution:

So what this means now is that we can create sophosticated WPF controls, invoke them in the FSI window, and then apply long-running upddates via FSI without the WPF window freezing on us.

That should keep the trading desk guys happy :)

Dean

Tags: , , ,

F# | Threading | WPF

WPF - Simple Error Notifications For Data Binding Expressions

by Dean 7. October 2010 14:19

One of the frustrating things about a large and complex XAML view in WPF, is that you are not often aware of errors in your data binding expressions.

You may not even know there's an error, and if you do then you have to trawl through debugger outputs to try and find it. You could always use snoop, but surely theres a simpler way

Here it is, in just a few lines of code:

 

public partial class MainWindow : Window
{
    public MainWindow()
    {
        BindingErrorListener.Listen(m => MessageBox.Show(m));
        InitializeComponent();
        DataContext = new string[] { "hello" };
    }
}

public class BindingErrorListener : TraceListener
{
    private Action<string> logAction;
    public static void Listen(Action<string> logAction)
    {
        PresentationTraceSources.DataBindingSource.Listeners
            .Add(new BindingErrorListener() { logAction = logAction });
    }
    public override void Write(string message) { }
    public override void WriteLine(string message)
    {
        logAction(message);
    }
}

so I tried it with an incorrect binding:

    <Grid>
        <TextBlock Text="{Binding BadBinding}" />
    </Grid>

and I got this

 

 

Its simple, easy to implement and can be easily removed for production builds

Dean

Tags: , ,

DataBinding | WPF

C# - Fast Parallel ConcurrentList<T> Implementation

by Dean 7. October 2010 09:59

The new Task Parallel Library (TPL) introduced in the .NET 4 framework simplifies many aspects of writing parallel code, without much of the necessary thread synchronisation logic that is usually required.

One of the really nice aspects of TPL is the System.Collections.Concurrent namespace, which includes a number of collection classes that fully support multithreaded access and fast parallel access. The only glaring omission (in my opinion) is the absence of a ConcurrentList<T> class.

So, I thought Id create one, and the code is below:

public class ConcurrentList<T> : IList<T>, IList
{
    private readonly List<T> underlyingList = new List<T>();
    private readonly object syncRoot = new object();
    private readonly ConcurrentQueue<T> underlyingQueue;
    private bool requiresSync;
    private bool isDirty;

    public ConcurrentList()
    {
        underlyingQueue = new ConcurrentQueue<T>();
    }

    public ConcurrentList(IEnumerable<T> items)
    {
        underlyingQueue = new ConcurrentQueue<T>(items);
    }

    private void UpdateLists()
    {
        if (!isDirty)
            return;
        lock (syncRoot)
        {
            requiresSync = true;
            T temp;
            while (underlyingQueue.TryDequeue(out temp))
                underlyingList.Add(temp);
            requiresSync = false;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        lock (syncRoot)
        {
            UpdateLists();
            return underlyingList.GetEnumerator();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Add(T item)
    {
        if (requiresSync)
            lock (syncRoot)
                underlyingQueue.Enqueue(item);
        else
            underlyingQueue.Enqueue(item);
        isDirty = true;
    }

    public int Add(object value)
    {
        if (requiresSync)
            lock (syncRoot)
                underlyingQueue.Enqueue((T)value);
        else
            underlyingQueue.Enqueue((T)value);
        isDirty = true;
        lock (syncRoot)
        {
            UpdateLists();
            return underlyingList.IndexOf((T)value);
        }
    }

    public bool Contains(object value)
    {
        lock (syncRoot)
        {
            UpdateLists();
            return underlyingList.Contains((T)value);
        }
    }

    public int IndexOf(object value)
    {
        lock (syncRoot)
        {
            UpdateLists();
            return underlyingList.IndexOf((T)value);
        }
    }

    public void Insert(int index, object value)
    {
        lock (syncRoot)
        {
            UpdateLists();
            underlyingList.Insert(index, (T)value);
        }
    }

    public void Remove(object value)
    {
        lock (syncRoot)
        {
            UpdateLists();
            underlyingList.Remove((T)value);
        }
    }

    public void RemoveAt(int index)
    {
        lock (syncRoot)
        {
            UpdateLists();
            underlyingList.RemoveAt(index);
        }
    }

    T IList<T>.this[int index]
    {
        get
        {
            lock (syncRoot)
            {
                UpdateLists();
                return underlyingList[index];
            }
        }
        set
        {
            lock (syncRoot)
            {
                UpdateLists();
                underlyingList[index] = value;
            }
        }
    }

    object IList.this[int index]
    {
        get { return ((IList<T>)this)[index]; }
        set { ((IList<T>)this)[index] = (T)value; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool IsFixedSize
    {
        get { return false; }
    }

    public void Clear()
    {
        lock (syncRoot)
        {
            UpdateLists();
            underlyingList.Clear();
        }
    }

    public bool Contains(T item)
    {
        lock (syncRoot)
        {
            UpdateLists();
            return underlyingList.Contains(item);
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        lock (syncRoot)
        {
            UpdateLists();
            underlyingList.CopyTo(array, arrayIndex);
        }
    }

    public bool Remove(T item)
    {
        lock (syncRoot)
        {
            UpdateLists();
            return underlyingList.Remove(item);
        }
    }

    public void CopyTo(Array array, int index)
    {
        lock (syncRoot)
        {
            UpdateLists();
            underlyingList.CopyTo((T[])array, index);
        }
    }

    public int Count
    {
        get
        {
            lock (syncRoot)
            {
                UpdateLists();
                return underlyingList.Count;
            }
        }
    }

    public object SyncRoot
    {
        get { return syncRoot; }
    }

    public bool IsSynchronized
    {
        get { return true; }
    }

    public int IndexOf(T item)
    {
        lock (syncRoot)
        {
            UpdateLists();
            return underlyingList.IndexOf(item);
        }
    }

    public void Insert(int index, T item)
    {
        lock (syncRoot)
        {
            UpdateLists();
            underlyingList.Insert(index, item);
        }
    }
}

The design of this class is simple. Adding items to the list does in fact add items to an underlying ConcurrentQueue<T> object, which enables the list to be added to in a high performance and thread-safe maner, that can take full advantage of task paralellism. Tests have shown this to be equally as fast as the other concurrent collection classes when adding items.

Then, when any of the other public members of the ConcurrentList<T> class are accessed, it freezes thread access to its underlying ConcurrentQueue<T> store and copies items from the ConcurrentQueue<T> to an underlying List<T> (if the underlying ConcurrentQueue<T> object hasnt been added-to since the last update, it skips this process, increasing performance once the list is fully populated), and then accesses the same IList<T> or IList interface implementation of the underlying list (effectively decorating the underlying List<T>). Thread synchronisation is implemented here on all members, so that access is serialized, thus making the class completely thread-safe.

This implementation has the following characteristics:

  1. In a single threaded scenario, it has a very similar performance to List<T> in all operations.
  2. In a multi-threaded (task parallel) item adding scenario, it was much faster at adding items than List<T> (by several magnitudes)
  3. In a multithreaded access scenario (except when adding items), it has very similar performance to List<T>

In order to test these scenarios, I wrote the following test code:

class Program
    {
        static void DoWork(ICollection<int> list, int count)
        {
            for (var i = 0; i < count; i++)
            {
                list.Add(i);
                // use spinwait to emulate work but avoiding context switching
                Thread.SpinWait(100000);
            }

        }
        static void Main(string[] args)
        {
            Console.WriteLine("standard List<T> - 10000 work items");
            var list1 = new List<int>();
            var start1 = DateTime.Now.Ticks;
            DoWork(list1, 10000);
            var end1 = DateTime.Now.Ticks;
            var c1 = list1.Count; // accesses list
            var cend1 = DateTime.Now.Ticks;
            Console.WriteLine();
            Console.WriteLine("Work Time: {0} - milliseconds", (end1 - start1) / TimeSpan.TicksPerMillisecond);
            Console.WriteLine("Get Count Time: {0} - milliseconds", (cend1 - end1) / TimeSpan.TicksPerMillisecond);
            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("ConcurrentList<T> - 10000 work items on single thread");
            var list2 = new ConcurrentList<int>();
            var start2 = DateTime.Now.Ticks;
            DoWork(list2, 10000);
            var end2 = DateTime.Now.Ticks;
            var c2 = list2.Count; // accesses list, update performed
            var cend2 = DateTime.Now.Ticks;
            Console.WriteLine();
            Console.WriteLine("Work Time: {0} - milliseconds", (end2 - start2) / TimeSpan.TicksPerMillisecond);
            Console.WriteLine("Get Count Time: {0} - milliseconds", (cend2 - end2) / TimeSpan.TicksPerMillisecond);
            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("ConcurrentList<T> - 10000 work items on 4 parallel tasks");
            var list3 = new ConcurrentList<int>();
            var start3 = DateTime.Now.Ticks;
            var tasks3 = new Task[4]
              {
                  Task.Factory.StartNew(() => DoWork(list3,2500)),
                  Task.Factory.StartNew(() => DoWork(list3,2500)),
                  Task.Factory.StartNew(() => DoWork(list3,2500)),
                  Task.Factory.StartNew(() => DoWork(list3,2500))
              };
            Task.WaitAll(tasks3);
            var end3 = DateTime.Now.Ticks;
            var c3 = list3.Count; // accesses list, update performed
            var cend3 = DateTime.Now.Ticks;
            Console.WriteLine();
            Console.WriteLine("Work Time: {0} - milliseconds", (end3 - start3) / TimeSpan.TicksPerMillisecond);
            Console.WriteLine("Get Count Time: {0} - milliseconds", (cend3 - end3) / TimeSpan.TicksPerMillisecond);
            Console.WriteLine();

            Console.ReadLine();
        }
    }

And here is a screenshot of the result

 

 

Tags: , ,

C# | Threading | WPF

WPF MVVM – Simple ‘MessageBox.Show’ With Action & Func

by Dean 6. May 2010 08:08

In the MVVM world, things like message boxes (MessageBox.Show) and Dialogs (open file, save file etc), don't naturally fit.

These popups are closely tied to the ‘View’ part of MVVM, but they can only really be invoked from the ‘ViewModel’ which will break the clean separation in MVVM.

If you google this issue, you will find a wide range of elaborate solutions, many of which are significant engineering projects in their own right.

I am a huge fan of implementing simple solutions wherever possible, as verbose code is the number one culprit in un-maintainable projects, so I was keen to find a solution that is simple, robust, elegant and doesnt break the MVVM pattern

The solution I came up with, is to use generic Action and Func Delegates.

OK, to illustrate my solution, I have created a new project using the ‘WPF Model-View-ViewModel Toolkit’, (http://wpf.codeplex.com/wikipage?title=WPF%20Model-View-ViewModel%20Toolkit), which installs a project template in VS2008

Here is my altered ‘MainViewModel'.cs’ class

public class MainViewModel : ViewModelBase
{
    private DelegateCommand exitCommand;
    private Action<string> popup;
    private Func<string, string, bool> confirm;
 
    public MainViewModel(Action<string> popup, Func<string, string, bool> confirm)
    {
        this.popup = popup;
        this.confirm = confirm;
    }
 
    public ICommand ExitCommand
    {
        get
        {
            if (exitCommand == null)
                exitCommand = new DelegateCommand(Exit);
            return exitCommand;
        }
    }
 
    private void Exit()
    {
        if (confirm("Are you sure you want to exit", "confirm exit"))
            Application.Current.Shutdown();
    }
}

As you can see, the MainViewModel’s constructor takes 2 delegates, 1 for popup and 1 for confirm

Now take a look at App.xaml.cs, where the View and the ViewModel get instantiated

private void OnStartup(object sender, StartupEventArgs args)
{
    // messagebox
    var popup = (Action<string>)(msg => MessageBox.Show(msg));
 
    // confirm box
    var confirm = (Func<string, string, bool>)((msg, capt) => 
        MessageBox.Show(msg, capt, MessageBoxButton.YesNo) == MessageBoxResult.Yes);
 
    Views.MainView view = new Views.MainView();
    view.DataContext = new ViewModels.MainViewModel(popup,confirm);
    view.Show();
}

If you look closely, you’ll see that my delegates actually map to methods in the static class ‘MessageBox’, which will give us the popups we need. The popup delegate will instantiate a simple message popup, and the confirm delegate will instantiate a message popup with confirm buttons.

And this is what happens when we click on the Exit menu item (note: this menu is created by default when you create a new project using the toolkit)

popup

Now, when we want to run unit tests on our ViewModel, we can just pass in dummy delegates

[TestMethod()]
public void MainViewModelConstructorTest()
{
    var dummyPopup = (Action<string>)((a) => {return;});
    var dummyConfirm = (Func<string,string,bool>)((a,b) => {return true;});
    ViewModels.MainViewModel target = new ViewModels.MainViewModel(dummyPopup, dummyConfirm);
    Assert.Inconclusive("TODO: Implement code to verify target");
}

Dean

Tags: ,

MVVM | C# | WPF | Unit Tests

WPF – DataContext Virtualization With Paged Services

by Dean 27. April 2010 21:51

Many WPF applications need to handle a very large data collections – maybe the users really need a million rows in their GridView control. The way we cope with this is to ‘virtualize’ the data, and have it available to your control on an ‘as needed’ basis.

Most list controls in WPF (including the standard ListView/GridView) include the concept of a ‘viewport’ under the hood. A viewport is a virtual ‘window’ on the underlying data collection, which only requires the data currently being displayed – so if your collection is a million rows, and your ‘viewport’ is only 100 rows high, then you only need 100 rows (although they must be the ‘right’ rows).

In addition, if the data collection that is your DataContext implements the non-generic ‘IList’ interface, the viewport will optimize it access to the collection calling the ‘IList.this[index]’ for row enumeration rather than GetEnumerator().

This means that if we create a custom collection that implements the non-generic ‘IList’ interface, we can ‘virtualize’ access to the data – giving us an opportunity to put a million rows in our grid in the blink of an eye.

Also, this ‘million row’ scenario may need to retrieve data from a WCF service (for example), and the service method to retrieve data will most likely be paged, requiring page number and page size parameters to retrieve the correct page.

OK, that’s the intro over, so lets discuss a REALLY easy solution to this requirement

Firstly, I am assuming that you have a service reference to a WCF service that has 2 service methods

  1. A method that gets the total collection count
  2. A method that takes page number and page size parameters and returns a strongly-typed collection representing a single ‘page’ of data

As an example of what I mean, here is a sample service contract for a service I have created for this post

namespace WCFDataPaging.WCF
{
    [ServiceContract]
    public interface IService
    {
 
        [OperationContract]
        List<TestDataObject> GetPageData(int page, int pageSize);
 
        [OperationContract]
        int GetDataCount();
    }
}

and here is my service implementation

public class MainService : IService
{
    public List<TestDataObject> GetPageData(int page, int pageSize)
    {
        return Utility.TestData.Skip(page*pageSize).Take(pageSize).ToList();
    }
 
    public int GetDataCount()
    {
        return Utility.TestData.Count;
    }
}

Now the utility class simple generates a test collection of about a million rows. In real life, these services will be retrieving real data from some kind of data store.

Now I need a collection class on the WPF side, that can perform all of the necessary virtualization:

public sealed class VirtualServiceCollection<T> : IList<T>, IList
{
    private readonly Func<int, int, T[]> dataFunction;
    private readonly Func<int> countFunction;
    private readonly int pageSize;
    private readonly List<T> data;
    private int currentPage;
 
    public VirtualServiceCollection(Func<int,int,T[]> dataFunction, Func<int> countFunction, int pageSize)
    {
        this.dataFunction = dataFunction;
        this.countFunction = countFunction;
        this.pageSize = pageSize;
        data = new List<T>(dataFunction(0, pageSize));
    }
 
    public IEnumerator<T> GetEnumerator()
    {
        var count = countFunction();
        for (var i = 0; i < count; i++)
            yield return this[i];
    }
 
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
 
    public void Add(T item)
    {
        throw new NotImplementedException();
    }
 
    public int Add(object value)
    {
        throw new NotImplementedException();
    }
 
    public bool Contains(object value)
    {
        throw new NotImplementedException();
    }
 
    void IList.Clear()
    {
        DoClear();
    }
 
    public int IndexOf(object value)
    {
        return data.IndexOf((T)value);
    }
 
    public void Insert(int index, object value)
    {
        throw new NotImplementedException();
    }
 
    public void Remove(object value)
    {
        throw new NotImplementedException();
    }
 
    void IList.RemoveAt(int index)
    {
        throw new NotImplementedException();
    }
 
    private T GetItem(int index)
    {
        var bot = currentPage * pageSize;
        var top = Math.Min(bot + pageSize, countFunction());
        if (index >= bot && index < top)
            return data[index - bot];
        currentPage = (int)Math.Floor(index / (double)pageSize);
        data.Clear();
        data.AddRange(dataFunction(currentPage, pageSize));
        return data[index - (currentPage * pageSize)];
    }
 
    object IList.this[int index]
    {
        get { return GetItem(index); }
        set { throw new NotImplementedException(); }
    }
 
    bool IList.IsReadOnly
    {
        get { return false; }
    }
 
    public bool IsFixedSize
    {
        get { return false; }
    }
 
    private void DoClear()
    {
        currentPage = 0;
        data.Clear();
    }
 
    void ICollection<T>.Clear()
    {
        DoClear();
    }
 
    public bool Contains(T item)
    {
        throw new NotImplementedException();
    }
 
    public void CopyTo(T[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }
 
    public bool Remove(T item)
    {
        throw new NotImplementedException();
    }
 
    public void CopyTo(Array array, int index)
    {
        throw new NotImplementedException();
    }
 
    int ICollection.Count
    {
        get { return countFunction(); }
    }
 
    public object SyncRoot
    {
        get { return this; }
    }
 
    public bool IsSynchronized
    {
        get { return false; }
    }
 
    int ICollection<T>.Count
    {
        get { return countFunction(); }
    }
 
    bool ICollection<T>.IsReadOnly
    {
        get { return false; }
    }
 
    public int IndexOf(T item)
    {
        return data.IndexOf(item);
    }
 
    public void Insert(int index, T item)
    {
        throw new NotImplementedException();
    }
 
    void IList<T>.RemoveAt(int index)
    {
        throw new NotImplementedException();
    }
 
    public T this[int index]
    {
        get { return GetItem(index); }
        set { throw new NotImplementedException(); }
    }
}

One of the things that’s interesting about this is that it takes two Func delegates in its constructor – one to get the collection count, and one to retrieve a page

now, we need to wire it all up

First here’s my WPF code-behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += DoLoaded;
    }
 
    private void DoLoaded(object sender, RoutedEventArgs e)
    {
        var s = new ServiceClient();
        var coll = new VirtualServiceCollection<TestDataObject>(s.GetPageData, s.GetDataCount, 100);
        DataContext = coll;
    }
}

As you can see, we pass into our collection the to service methods that get the data we need

And finally – here’s the XAML

<Window x:Class="WCFDataPaging.WPF.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>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Count : " />
            <TextBlock Text="{Binding Path=Count}" />
        </StackPanel>
        <ListView ItemsSource="{Binding}" Grid.Row="1">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
                        <GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
                        <GridViewColumn Header="Start Date" DisplayMemberBinding="{Binding StartDate}" />
                        <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

We’ve got a textbox at the top for the count, and the columns for our strongly-typed object

And here it is in action:

virtualdata

This took about half a second to load, and scrolls through the million rows pretty smoothly. FYI the first part of the name column data in my test collection is the row numer, so I can check it’s working :)

 

Dean

Tags: , ,

DataBinding | WCF | XAML | WPF

RecentComments

Comment RSS
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012 Dean Chalk's Blog