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

Shuffle an IEnumerable - 1 Line of Code

by Dean 28. June 2011 11:12

At a recent contractor interview I was asked how I would shuffle the numbers 0 - 51 (representing a deck of cards).

I had a reasonable stab at it, but I wondered if I could do it on a single line of Linq syntax.

On the way to my next interview, I had my eureka moment:

var random = new Random(Guid.NewGuid().GetHashCode());

var shuffledCards = Enumerable.Range(0, 52).Select(n => Tuple.Create(n, random.Next())).OrderBy(t => t.Item2).Select(t => t.Item1);

Not bad !

Dean

Tags: , ,

C# | Linq

Linq – ‘Chunking’ Enumerable Data With New Extension Method – ‘SelectChunk’

by Dean 29. November 2010 07:58

A couple of times now I've needed to lazy-enumerate a large collection, but in ‘chunks’ – or to put it in another way, I've needed to bring data back from the collection in bite-sized batches.

There currently isn't a Linq extension to support this (afaik), so I wrote one. It preserves the lazy-enumerating characteristics of most other Linq extensions, and seems to work very well.

public static IEnumerable<IEnumerable<TResult>> SelectChunk<TSource, TResult>(
        this IEnumerable<TSource> source, Func<TSource, TResult> selector, int chunkSize)
    {
        IEnumerator<TSource> enumerator = source.GetEnumerator();
        while(true)
        {
            if (!enumerator.MoveNext())
                break;
            var resultArray = new TResult[chunkSize];
            for (int i = 0; i < chunkSize; i++)
            {
                resultArray[i] = selector(enumerator.Current);
                if (i == chunkSize-1 || !enumerator.MoveNext())
                    break;
            }
            yield return resultArray;
        } 
    }

And here is an example of its usage:

IEnumerable<string> data = new[] {"John", "Doe", "Male", "Jane", "Smith", "Female"};

var people =
    data.SelectChunk(s => s, 3)
        .Select(s => new
            {
                FirstName = s.ElementAt(0), 
                LastName = s.ElementAt(1), 
                Sex = s.ElementAt(2)
            });

Tags:

C# | Linq

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

Is It Nullable ?

by Dean 8. November 2010 16:42

Sometimes, you may have a scenario whereby you need to know if a value is a value type (say 'int'), or a nullable type (say 'int?').

Common sense suggests that we should easily be able to detect wether a type is nullable or not, but if you have a try, you'll see there are no obvious answers, or many unobvious ones either.

The problem stems from the fact that if you query the type of a nullable instance it'll cause it to be boxed, and the boxing operation means that GetType() brings back the type of the underlying valuetype - not the nullable.

However, after a bit of head-scratching, I ended up with this utility class

public static class ValueTypeHelper
{
    public static bool IsNullable<T>(T t) { return false; }
    public static bool IsNullable<T>(T? t) where T : struct { return true; }
}

and here's the test

static void Main(string[] args)
{
    int a = 123;
    int? b = null;
    object c = new object();
    object d = null;
    int? e = 456;
    var f = (int?)789;
    bool result1 = ValueTypeHelper.IsNullable(a); // false
    bool result2 = ValueTypeHelper.IsNullable(b); // true
    bool result3 = ValueTypeHelper.IsNullable(c); // false
    bool result4 = ValueTypeHelper.IsNullable(d); // false
    bool result5 = ValueTypeHelper.IsNullable(e); // true
    bool result6 = ValueTypeHelper.IsNullable(f); // true

}

Dean

Tags:

C#

LINQ - Dynamically Leverage IList<T> Functionality With VirtualList<T>

by Dean 1. November 2010 14:31

There are a lot of extension methods in the LINQ space that allow you to manipulate lists quickly and efficiently, but we may not always want to use an actual list to leverage the power of this functionality.

confused - well, consider this code:

Enumerable.Range(0, int.MaxValue).Aggregate(........)

the line above is to run an aggregation function a very large number of times.
This could be a candidate for task parallelism via the TPL, but doing the following actually has no effect

Enumerable.Range(0, int.MaxValue).AsParallel().Aggregate(.........)

This is because you cannot partition collections via an enumerator, and the 'AsParallel' method will not do anything. You need a collection that implements IList. So how about this you say

Enumerable.Range(0, int.MaxValue).ToList().AsParallel().Aggregate(.........)

Well, you'll get an 'OutOfMemoryException' because you are trying to create an enormous list, which should not be necessary as you could easily calculate the values for each item in the list.

The solution is my new VirtualList<T> class (implementation below), which allows the following to be parallelised

var result = new VirtualList<double>(int.MaxValue, i => i).AsParallel().Aggregate(........)

The underlying concept is that for a give indexer value, you run a function to retrieve your value. There is no actual list, but a class that implements the necessary interfaces and masquerades as a list.

Still not sure ? Well with list we can easily parallelise a computation that calculates the value of PI

var pi = new VirtualList<double>(int.MaxValue, i => Math.Pow(-1d, i) / (2 * i + 1) * 4).AsParallel().Aggregate(() => 0d, (tot, next) => tot + next, (maint,localt) => maint + localt, final => final);

There you go, parallelising a computation using Linq extension methods without actually having the requisite list

And here's the class (its very simple)

public class VirtualList<T> : IList<T>, IList
{
    private readonly int count;
    private readonly Func<int, T> getValueForIndex;

    public VirtualList(int count, Func<int, T> getValueForIndex)
    {
        this.getValueForIndex = getValueForIndex;
        this.count = count;
    }
    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        for (int i = 0; i < count; i++)
            yield return getValueForIndex(i);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<T>)this).GetEnumerator();
    }

    void ICollection<T>.Add(T item)
    {
        throw new NotSupportedException();
    }

    int IList.Add(object value)
    {
        throw new NotSupportedException();
    }

    bool IList.Contains(object value)
    {
        throw new NotSupportedException();
    }

    void IList.Clear()
    {
        throw new NotSupportedException();
    }

    int IList.IndexOf(object value)
    {
        throw new NotSupportedException();
    }

    void IList.Insert(int index, object value)
    {
        throw new NotSupportedException();
    }

    void IList.Remove(object value)
    {
        throw new NotSupportedException();
    }

    void IList.RemoveAt(int index)
    {
        throw new NotSupportedException();
    }

    object IList.this[int index]
    {
        get { return getValueForIndex(index); }
        set { throw new NotSupportedException(); }
    }

    bool IList.IsReadOnly
    {
        get { return true; }
    }

    bool IList.IsFixedSize
    {
        get { return true; }
    }

    void ICollection<T>.Clear()
    {
        throw new NotSupportedException();
    }

    bool ICollection<T>.Contains(T item)
    {
        throw new NotSupportedException();
    }

    void ICollection<T>.CopyTo(T[] array, int arrayIndex)
    {
        throw new NotSupportedException();
    }

    bool ICollection<T>.Remove(T item)
    {
        throw new NotSupportedException();
    }

    void ICollection.CopyTo(Array array, int index)
    {
        throw new NotSupportedException();
    }

    int ICollection.Count
    {
        get { return count; }
    }

    object ICollection.SyncRoot
    {
        get { return this; }
    }

    bool ICollection.IsSynchronized
    {
        get { return false; }
    }

    int ICollection<T>.Count
    {
        get { return count; }
    }

    bool ICollection<T>.IsReadOnly
    {
        get { return true; }
    }

    int IList<T>.IndexOf(T item)
    {
        throw new NotSupportedException();
    }

    void IList<T>.Insert(int index, T item)
    {
        throw new NotSupportedException();
    }

    void IList<T>.RemoveAt(int index)
    {
        throw new NotSupportedException();
    }

    T IList<T>.this[int index]
    {
        get { return getValueForIndex(index); }
        set { throw new NotSupportedException(); }
    }
}

Of course, not all functionality relating to lists is supported, but its a neat trick if you want to parallelise large iterative computations

Dean

Tags:

C# | Linq

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

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

C# – Creating Reliable Complex Dictionary Keys Using Generic Tuples

by Dean 8. May 2010 12:56

As .NET developers we have all implemented dictionaries. Often, its a small dictionary with strings or Guids as the key – which is simple and robust.

However, sometimes we need to do something a little more ‘serious’ and implement a dictionary that has a complex key consisting of a combination of values. In this case, it is extremely important that the type we use for the key has the following characteristics:

  1. Instances of the key’s type must be immutable, as changing their state once they are added to the dictionary will most likely mean that the value they represent cannot be found
  2. The key’s type must produce a wide range of hash codes during use, so you’ll need to override the GetHashCode() method, and implement your own algorithm
  3. You must override the Equals() method of the key’s type to implement the correct equality algorithm. Also the keys type must implement IEquatable<T> for easy lookups in generic dictionaries.
  4. The keys type should ideally be a struct rather than a class, so the key collection can be further optimised by the JIT compiler.

With all of these pre-requisites, it’s easy to see why developers steer clear of using complex keys in their dictionary, as this represents a lot of work, and must be done correctly.

Well, to solve this problem I have created a customised generic Tuple class that can be used for this situation

public struct Tuple<T, U, V> : IEquatable<Tuple<T,U, V>>
{
    public readonly T First;
    public readonly U Second;
    public readonly V Third;
 
    public Tuple(T first, U second, V third) 
    {
        First = first;
        Second = second;
        Third = third;
    }
 
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        if (this.GetType() != obj.GetType())
            return false;            
        return AreEqual(this,(Tuple<T,U,V>)obj);
    }
 
    public bool Equals(Tuple<T, U, V> other)
    {
        return AreEqual(this,other);
    }
 
    private static bool AreEqual(Tuple<T, U, V> a, Tuple<T, U, V> b)
    {
        if (!a.First.Equals(b.First))
            return false;
        if (!a.Second.Equals(b.Second))
            return false;
        if (!a.Third.Equals(b.Third))
            return false;
        return true;
    }
 
    public static bool operator == (Tuple<T,U,V> a, Tuple<T,U,V> b)
    {
        return AreEqual(a, b);
    }
 
    public static bool operator !=(Tuple<T, U, V> a, Tuple<T, U, V> b)
    {
        return !AreEqual(a, b);
    }
 
    public override int GetHashCode()
    {
        return First.GetHashCode() ^ Second.GetHashCode() ^ Third.GetHashCode();
    }
}
 
public static class Tuple
{
    public static Tuple<T, U, V> CreateNew<T,U,V>(T first, U second, V third)
    {
        return new Tuple<T, U, V>(first, second, third);
    }
}

 

The Tuple struct above is a three-value Tuple, but you could easily use the same pattern to create a Tuple of any size from 2 values upwards

To test our Tuple dictionary key, lets first create a test class for demonstration purposes

public class TestData
{
    public string Name { get; set; }
    public int Items { get; set; }
    public DateTime Start { get; set; }
    public ComplexObjectGraph OtherData { get; set; }
}

Now lets see us use this key in our code that adds values to a dictionary

var dict = new Dictionary<Tuple<string, int, DateTime>, TestData>();
var test = new TestData() { Name = "Test1", Items = 23, Start = new DateTime(2010, 01, 01) };
dict.Add(Tuple.CreateNew(test.Name, test.Items, test.Start), test);

And finally, lets lookup our value from via a key

var key = Tuple.CreateNew("Test1",23,new DateTime(2010, 01, 01));
var data = dict[key];

 

Dean

Tags: , ,

C#

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

A C# AIML Chatterbot – Artificial Intelligence In 500 Lines Of Code

by Dean 20. March 2010 21:28

I recently stumbled across an area of artificial intelligence programming called AIML ‘chatterbots’. These programs are interpreters for an XML based AI language called AIML. AIML and the code that processes it are the basis of the first and most famous chatterbot called A.L.I.C.E, where the founders and followers are infamous for promoting and winning the Loebner Prize in Artificial Intelligence.

Among the available technologies, there are the usual suspects including implementations in PHP, Perl, Python, Java and even Pascal – but nothing in .NET

On Saturday my wife (who runs her own gourmet coffee roasting business here in rural Kent – www.coffeebeanshop.co.uk) was in Germany buying a new coffee roaster, so to fill time I decided to have a go at a C# implementation, which is detailed below.

Firstly, before you do anything, you need to download some AIML files, which describe how the interpreter should handle human conversation, and sets out the rules of engagement. AIML also includes it’s own XML based mini-language, which was tough to implement. To get enough AIML to make this worthwhile, I recommend that you get files from this location - http://www.alicebot.org/aiml/alice.zip . This is the AIML created by Richard Wallace – winner of the Loebner Prize on at least one occasion.

Once you have the AIML, just use the code below, and you can chat to your own Artificial Intelligence ‘Bot’ for as long as you like :)

 

Firstly, the console program to run this thing

class Program
{
    static void Main(string[] args)
    {
        var ai = new AIMLProcessor();
        ai.Load();
        var chat = Console.ReadLine();
        AIMLProcessor.Thats.Add("*");
        while (chat != "")
        {
            AIMLProcessor.Inputs.Insert(0, chat);
            AIMLProcessor.Thats.Insert(0, ai.FindTemplate(chat, AIMLProcessor.Thats[0], "*"));
            Console.WriteLine(AIMLProcessor.Thats[0]);
            chat = Console.ReadLine();
        }
    }
}

Then you need the classes to do the work

public class AIMLProcessor
{
    public static readonly List<string> Thats = new List<string>();
    public static readonly List<string> Inputs = new List<string>();
    private readonly Dictionary<string, string> BotData = new Dictionary<string, string>();
    private readonly Dictionary<string, string> Predicates = new Dictionary<string, string>();
    private readonly AIMLData data = new AIMLData(null, string.Empty);
    private readonly Random rand = new Random(DateTime.Now.Millisecond);
 
    public void Load()
    {
        // download from http://www.alicebot.org/aiml/alice.zip and unzip locally
        // then use that location for loading, as below
        foreach (string file in Directory.GetFiles(
            @"D:\Users\Administrator\Downloads\alice", "*.aiml"))
        {
            var doc = XDocument.Load(file);
            var first = doc.Element("aiml");
            var topics = first.Elements("topic");
            var cats = first.Elements("category");
            if (topics != null && topics.Count() > 0)
            {
                foreach (var topic in topics)
                {
                    var topicName = topic.Attribute("name") == null ?
                        "*" : topic.Attribute("name").Value;
                    foreach (var cat in topic.Elements())
                        ProcessCategory(cat, topicName);
                }
            }
            if (cats == null || cats.Count() <= 0) 
                continue;
            foreach (var cat in cats)
                ProcessCategory(cat, "*");
        }
 
        LoadInitialPredicates();
    }
 
    private void ProcessCategory(XElement cat, string topic)
    {
        var patt = cat.Element("pattern");
        var temp = cat.Element("template");
        var that = cat.Element("that");
        if (patt == null || temp == null)
            return;
        var t = that != null ? that.InnerText() : "*";
        string pattern = string.Format("{0} <THAT> {1} <TOPIC> {2}", 
            patt.Nodes().First(), t.Trim(), topic.Trim());
        LoadWord(data, new List<string>(pattern.Split(' ')), temp.InnerText());
    }
 
    public string FindTemplate(string text, string that, string topic)
    {
        var pattern = string.Format("{0} <THAT> {1} <TOPIC> {2}", 
            Normalise(text).Trim(), "*", topic.Trim());
        var temp = FindTemplateRecursive(data, new List<string>(pattern.Split(' ')), 
            new PatternData(), 0);
        var result = InterpretTemplate(temp.Template, temp.WildTexts, text, that, topic);
        return result.Trim().Replace("  ", " ");
    }
 
    public string Normalise(string text)
    {
        return Regex.Replace(text, @"[^\w\ ]", "").Trim().Replace("  ", " ");
    }
 
    private string InterpretTemplate(string template, List<WildData> wilds, 
        string text, string that, string topic)
    {
        var xmltemplate = string.Format("<process>{0}</process>", template);
        var doc = XElement.Parse(xmltemplate);
        if (doc.Elements().Count() == 0)
            return doc.Value;
        string response = "";
 
        foreach (var node in doc.Nodes())
        {
            if (node.NodeType != XmlNodeType.Element)
            {
                response += " " + node;
                continue;
            }
            var element = node as XElement;
            switch (element.Name.LocalName.ToUpper())
            {
                case "INPUT":
                    response += " " + ProcessInput(element).Trim();
                    break;
                case "THAT":
                    response += " " + ProcessThat(element).Trim();
                    break;
                case "THATSTAR":
                    response += " " + ProcessThatStar(element, wilds).Trim();
                    break;
                case "TOPICSTAR":
                    response += " " + ProcessTopicStar(element, wilds).Trim();
                    break;
                case "STAR":
                    response += " " + ProcessStar(element, wilds).Trim();
                    break;
                case "PERSON":
                    response += " " + ProcessPerson(wilds).Trim();
                    break;
                case "SET":
                    response += " " + ProcessSet(element, wilds, text, that, topic).Trim();
                    break;
                case "GET":
                    response += " " + ProcessGet(element).Trim();
                    break;
                case "THINK":
                    response += " " + ProcessThink(element, wilds, text, that, topic).Trim();
                    break;
                case "RANDOM":
                    response += " " + ProcessRandom(element, wilds, text, that, topic).Trim();
                    break;
                case "BOT":
                    response += " " + ProcessBot(element).Trim();
                    break;
                case "SR":
                    response += " " + ProcessSR(element, wilds, text, that, topic).Trim();
                    break;
                case "SRAI":
                    response += " " + ProcessSRAI(element, wilds, text, that, topic).Trim();
                    break;
                default:
                    return element.ToString().Trim();
            }
        }
        return response.Trim();
    }
 
    private static string ProcessInput(XElement element)
    {
        var num = 0;
        var index = element.Attribute("index");
        if (index != null)
            num = int.Parse(index.Value.Split(',')[0]) - 1;
        if (num >= Inputs.Count)
            return string.Empty;
        return Inputs[num];
    }
 
    private static string ProcessThat(XElement element)
    {
        var num = 0;
        var index = element.Attribute("index");
        if (index != null)
            num = int.Parse(index.Value.Split(',')[0]) - 1;
        if (num >= Thats.Count)
            return string.Empty;
        return Thats[num];
    }
 
    private static string ProcessThatStar(XElement element, IEnumerable<WildData> wilds)
    {
        var w = wilds.Where(a => a.WildType == StarType.That).ToList();
        if (w.Count() == 0)
            return string.Empty;
        var num = 0;
        var index = element.Attribute("index");
        if (index != null)
            num = int.Parse(index.Value.Split(',')[0]) - 1;
        if (num >= w.Count)
            return string.Empty;
        return w[num].WildText;
    }
 
    private static string ProcessTopicStar(XElement element, IEnumerable<WildData> wilds)
    {
        var w = wilds.Where(a => a.WildType == StarType.Topic).ToList();
        if (w.Count() == 0)
            return string.Empty;
        var num = 0;
        var index = element.Attribute("index");
        if (index != null)
            num = int.Parse(index.Value.Split(',')[0]) - 1;
        if (num >= w.Count)
            return string.Empty;
        return w[num].WildText;
    }
 
    private static string ProcessStar(XElement element, IEnumerable<WildData> wilds)
    {
        var w = wilds.Where(a => a.WildType == StarType.Pattern).ToList();
        if (w.Count() == 0)
            return string.Empty;
        var num = 0;
        var index = element.Attribute("index");
        if (index != null)
            num = int.Parse(index.Value) - 1;
        if (num >= w.Count)
            return string.Empty;
        return w[num].WildText;
    }
 
    private static string ProcessPerson(IList<WildData> wilds)
    {
        if (wilds.Count == 0)
            return string.Empty;
        var words = wilds[0].WildText.Split(' ');
        for (int i = 0; i < words.Count(); i++)
        {
            if (words[i].Trim().ToUpper() == "I")
                words[i] = "you";
            if (words[i].Trim().ToUpper() == "MY")
                words[i] = "your";
        }
        var response = string.Join(" ", words);
        response = response.Replace("you am", "you are");
        return response;
    }
 
    private string ProcessSet(XElement element, List<WildData> wilds, string text, 
        string that, string topic)
    {
        if (element.Attribute("name") == null)
            return string.Empty;
        var att = element.Attribute("name").Value;
        if (!Predicates.ContainsKey(att))
            Predicates.Add(att, "");
        Predicates[att] = InterpretTemplate(element.InnerText(), wilds, text, that, topic);
        return Predicates[att];
    }
 
    private string ProcessGet(XElement element)
    {
        if (element.Attribute("name") == null)
            return string.Empty;
        var att = element.Attribute("name").Value;
        if (!Predicates.ContainsKey(att))
            return string.Empty;
        return Predicates[att];
    }
 
 
    private string ProcessThink(XElement element, List<WildData> wilds, string text, 
        string that, string topic)
    {
        InterpretTemplate(element.InnerText(), wilds, text, that, topic);
        return string.Empty;
    }
 
    private string ProcessRandom(XElement element, List<WildData> wilds, string text, 
        string that, string topic)
    {
        var num = rand.Next(0, element.Elements().Count());
        var ret = element.Elements().ToList()[num].InnerText();
        return InterpretTemplate(ret, wilds, text, that, topic);
    }
 
    private string ProcessSRAI(XElement element, List<WildData> wilds, string text, 
        string that, string topic)
    {
        var t = InterpretTemplate(element.InnerText(), wilds, text, that, topic);
        return FindTemplate(t, that, topic);
    }
 
    private string ProcessSR(XElement element, List<WildData> wilds, string text, 
        string that, string topic)
    {
        var w = wilds.Where(a => a.WildType == StarType.Pattern).ToList();
        if (w.Count() == 0)
            return string.Empty;
        var num = 0;
        var index = element.Attribute("index");
        if (index != null)
            num = int.Parse(index.Value) - 1;
        if (num >= w.Count)
            return string.Empty;
        var t = InterpretTemplate(w[num].WildText, wilds, text, that, topic);
        return FindTemplate(t, that, topic);
    }
 
    private string ProcessBot(XElement element)
    {
        return BotData[element.Attribute("name").Value.ToUpper()];
    }
 
 
    private static PatternData FindTemplateRecursive(AIMLData ai, List<string> text, 
        PatternData data, int searchPos)
    {
        var key = text[searchPos];
        if (data.IsInWildcard && searchPos < text.Count - 1 && !ai.Data.ContainsKey("_") &&
            !ai.Data.ContainsKey(key.ToUpper()) && !ai.Data.ContainsKey("*"))
        {
            data.WildTexts[data.WildTexts.Count - 1].WildText += key + " ";
            return FindTemplateRecursive(ai, text, data, searchPos + 1);
        }
        if (ai.Data.ContainsKey("_"))
        {
            if (searchPos == text.Count - 1)
            {
                data.IsAnswer = true;
                data.Template = ai.Data["_"].Template;
                return data;
            }
            data.WildTexts.Add(new WildData {WildText = key + " ", WildType = data.WildType});
            data.IsInWildcard = true;
            data = FindTemplateRecursive(ai.Data["_"], text, data, searchPos + 1);
            if (data.IsAnswer)
                return data;
            data.WildTexts.RemoveAt(data.WildTexts.Count - 1);
            data.WildType = data.WildTexts.Count == 0 ? StarType.Pattern : 
                data.WildTexts[data.WildTexts.Count - 1].WildType;
        }
        if (ai.Data.ContainsKey(key.ToUpper()))
        {
            if (searchPos == text.Count - 1)
            {
                data.IsAnswer = true;
                data.Template = ai.Data[key.ToUpper()].Template;
                return data;
            }
            if (key.ToUpper() == "<THAT>")
                data.WildType = StarType.That;
            if (key.ToUpper() == "<TOPIC>")
                data.WildType = StarType.Topic;
            data.IsInWildcard = false;
            data = FindTemplateRecursive(ai.Data[key.ToUpper()], text, data, searchPos + 1);
            if (data.IsAnswer)
                return data;
        }
        if (!data.IsAnswer && ai.Data.ContainsKey("*"))
        {
            if (searchPos == text.Count - 1)
            {
                data.IsAnswer = true;
                data.Template = ai.Data["*"].Template;
                return data;
            }
            data.WildTexts.Add(new WildData {WildText = key + " ", WildType = data.WildType});
            data.IsInWildcard = true;
            data = FindTemplateRecursive(ai.Data["*"], text, data, searchPos + 1);
            if (data.IsAnswer)
                return data;
            data.WildTexts.RemoveAt(data.WildTexts.Count - 1);
            data.WildType = data.WildTexts.Count == 0 ? StarType.Pattern : 
                data.WildTexts[data.WildTexts.Count - 1].WildType;
        }
        return data;
    }
 
    private static void LoadWord(AIMLData parent, List<string> pattern, string template)
    {
        var key = pattern[0].ToUpper().Trim();
        pattern.RemoveAt(0);
        if (!parent.Data.ContainsKey(key))
            parent.Data.Add(key, new AIMLData(parent, key));
        if (pattern.Count > 0)
            LoadWord(parent.Data[key], pattern, template);
        else
            parent.Data[key].Template = template;
    }
 
    private void LoadInitialPredicates()
    {
        BotData.Add("NAME", "Gunther");
        BotData.Add("RELIGION", "Buddha Bubba is my mesiah");
        BotData.Add("PARTY", "monster raving loony party");
        BotData.Add("GENDER", "male");
        BotData.Add("SIGN", "Capricorn");
        BotData.Add("ARCH", "Windows 7");
        BotData.Add("BIRTHDAY", "6/6/1666");
        BotData.Add("SPECIES", "chatterbot");
        BotData.Add("GENUS", "computer algorithm");
        BotData.Add("FOAVOURITEFOOD", "chicken");
        BotData.Add("BOTMASTER", "programmer");
        BotData.Add("MASTER", "The Evil Genius");
        BotData.Add("AGE", "32");
        BotData.Add("FRIEND", "Robocop");
        BotData.Add("LOCATION", "Inside a VAIO");
        BotData.Add("FAMILY", "intelligence");
        BotData.Add("KINGDOM", "In the UK");
        BotData.Add("ORDER", "program");
        BotData.Add("PHYLUM", "AI");
        BotData.Add("FORFUN", "I like fiddling with my switches");
        BotData.Add("FRIENDS", "Chico, Harpo, Groucho and Karl");
    }
}
 
public class AIMLData
{
    public AIMLData(AIMLData parent, string key)
    {
        Parent = parent;
        Key = key;
        Data = new Dictionary<string, AIMLData>();
    }
 
    public Dictionary<string, AIMLData> Data { get; private set; }
    public AIMLData Parent { get; private set; }
    public string Template { get; set; }
    public string Key { get; set; }
}
 
public class PatternData
{
    public PatternData()
    {
        WildTexts = new List<WildData>();
        WildType = StarType.Pattern;
    }
 
    public string Template { get; set; }
    public bool IsAnswer { get; set; }
    public bool IsInWildcard { get; set; }
    public StarType WildType { get; set; }
    public List<WildData> WildTexts { get; private set; }
}
 
public class WildData
{
    public string WildText { get; set; }
    public StarType WildType { get; set; }
}
 
public enum StarType
{
    Pattern,
    That,
    Topic
}
 
public static class ExtensionMethods
{
    public static string InnerText(this XElement element)
    {
        return element.Nodes().Aggregate("", 
            (current, node) => current + node.ToString());
    }
}

And there it is – its all you need.

I don’t doubt there are many refinements that could included (this WAS done in about 4 hours), and extra features that may be interesting.

Here is a screenshot of one of our first conversations:

aimlbot

As you can see, Gunther (the unfortunate name I gave it – did I mention my wife was in Germany ?), is quite a conversationalist. Not all conversations were quite as convincing, but you could extend this application and include a ‘learning mode’ where over time you can increase the Bots ability to say the right thing at the right time :)

If anyone has any suggestions or comments, the please email me (see contact details) or add a comment on this blog post

Happy Chatterbotting :)

 

Worthwhile Links:

http://alicebot.blogspot.com/
http://www.alicebot.org/aiml.html
http://www.loebner.net/Prizef/loebner-prize.html
http://en.wikipedia.org/wiki/AIML

Tags:

C# | AIML

Thread-Safe & Dispatcher-Safe Observable Collection for WPF

by Dean 1. February 2010 12:22

A common problem in WPF (& Silverlight) development is when you are working with multiple threads that need to change a collection that is a binding source and implements INotifyCollectionChanged.

Basically, the standard ObservableCollection<T> will only allow updates from the dispatcher thread, which means you need to write a lot of code for the worker threads to marshal changes onto the main message pump via the dispatcher. This can be a bit tedious, so I recently wrote a collection that performs all of the necessary marshalling internally, so users of this type do not have to be concerned about thread affinity issues.

Also, I decided to use a ReaderWriterLock to provide thread-safety during updates to the collection.

Here is my collection class:

 
public class SafeObservable<T> : IList<T>, INotifyCollectionChanged
{
    private IList<T> collection = new List<T>();
    private Dispatcher dispatcher;
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    private ReaderWriterLock sync = new ReaderWriterLock();
 
    public SafeObservable()
    {
        dispatcher = Dispatcher.CurrentDispatcher;
    }
 
    public void Add(T item)
    {
        if (Thread.CurrentThread == dispatcher.Thread)
            DoAdd(item);
        else
            dispatcher.BeginInvoke((Action)(() => { DoAdd(item); }));
    }
 
    private void DoAdd(T item)
    {
        sync.AcquireWriterLock(Timeout.Infinite);
        collection.Add(item);
        if (CollectionChanged != null)
            CollectionChanged(this,
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
        sync.ReleaseWriterLock();
    }
 
    public void Clear()
    {
        if (Thread.CurrentThread == dispatcher.Thread)
            DoClear();
        else
            dispatcher.BeginInvoke((Action)(() => { DoClear(); }));
    }
 
    private void DoClear()
    {
        sync.AcquireWriterLock(Timeout.Infinite);
        collection.Clear();
        if (CollectionChanged != null)
            CollectionChanged(this,
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        sync.ReleaseWriterLock();
    }
 
    public bool Contains(T item)
    {
        sync.AcquireReaderLock(Timeout.Infinite);
        var result = collection.Contains(item);
        sync.ReleaseReaderLock();
        return result;
    }
 
    public void CopyTo(T[] array, int arrayIndex)
    {
        sync.AcquireWriterLock(Timeout.Infinite);
        collection.CopyTo(array, arrayIndex);
        sync.ReleaseWriterLock();
    }
 
    public int Count
    {
        get
        {
            sync.AcquireReaderLock(Timeout.Infinite);
            var result = collection.Count;
            sync.ReleaseReaderLock();
            return result;
        }
    }
 
    public bool IsReadOnly
    {
        get { return collection.IsReadOnly; }
    }
 
    public bool Remove(T item)
    {
        if (Thread.CurrentThread == dispatcher.Thread)
            return DoRemove(item);
        else
        {
            var op = dispatcher.BeginInvoke(new Func<T,bool>(DoRemove), item);
            if (op == null || op.Result == null)
                return false;
            return (bool)op.Result;
        }
    }
 
    private bool DoRemove(T item)
    {
        sync.AcquireWriterLock(Timeout.Infinite);
        var index = collection.IndexOf(item);
        if (index == -1)
        {
            sync.ReleaseWriterLock();
            return false;
        }
        var result = collection.Remove(item);
        if (result && CollectionChanged != null)
            CollectionChanged(this, new
                NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        sync.ReleaseWriterLock();
        return result;
    }
 
    public IEnumerator<T> GetEnumerator()
    {
        return collection.GetEnumerator();
    }
 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return collection.GetEnumerator();
    }
 
    public int IndexOf(T item)
    {
        sync.AcquireReaderLock(Timeout.Infinite);
        var result = collection.IndexOf(item);
        sync.ReleaseReaderLock();
        return result;
    }
 
    public void Insert(int index, T item)
    {
        if (Thread.CurrentThread == dispatcher.Thread)
            DoInsert(index, item);
        else
            dispatcher.BeginInvoke((Action)(() => { DoInsert(index, item); }));
    }
 
    private void DoInsert(int index, T item)
    {
        sync.AcquireWriterLock(Timeout.Infinite);
        collection.Insert(index, item);
        if (CollectionChanged != null)
            CollectionChanged(this,
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
        sync.ReleaseWriterLock();
    }
 
    public void RemoveAt(int index)
    {
        if (Thread.CurrentThread == dispatcher.Thread)
            DoRemoveAt(index);
        else
            dispatcher.BeginInvoke((Action)(() => { DoRemoveAt(index); }));
    }
 
    private void DoRemoveAt(int index)
    {
        sync.AcquireWriterLock(Timeout.Infinite);
        if (collection.Count == 0 || collection.Count <= index)
        {
            sync.ReleaseWriterLock();
            return;
        }
        collection.RemoveAt(index);
        if (CollectionChanged != null)
            CollectionChanged(this,
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        sync.ReleaseWriterLock();
 
    }
 
    public T this[int index]
    {
        get
        {
            sync.AcquireReaderLock(Timeout.Infinite);
            var result = collection[index];
            sync.ReleaseReaderLock();
            return result;
        }
        set
        {
            sync.AcquireWriterLock(Timeout.Infinite);
            if (collection.Count == 0 || collection.Count <= index)
            {
                sync.ReleaseWriterLock();
                return;
            }
            collection[index] = value;
            sync.ReleaseWriterLock();
        }
 
    }
}
 

To test the effectiveness of this collection class, I wrote a simple WPF app, that bound to the new collection class and updated it via multiple threads:

 

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel Orientation="Vertical" VerticalAlignment="Top">
        <Button Content="Start" Click="Button_Click" />
        <ListView Name="list" ItemsSource="{Binding}" DisplayMemberPath="Text" />        
    </StackPanel>
</Window>

And the code behind is below:

 

public partial class Window1 : Window
{
    class TestData
    {
        public string Text { get; set; }
    }
 
    private Random rand = new Random(DateTime.Now.Millisecond);
    private SafeObservable<TestData> data = new SafeObservable<TestData>();
    public Window1()
    {
        InitializeComponent();
    }
 
    void Button_Click(object sender, RoutedEventArgs e)
    {
        list.DataContext = data;
        List<Action> work = new List<Action>();
        for (int i = 0; i < 100; i++)
        {
            work.Add(new Action(DoWorkAdd));
            work.Add(new Action(DoWorkClear));
            work.Add(new Action(DoWorkRemove));
            work.Add(new Action(DoWorkRemoveAt));
            work.Add(new Action(DoWorkInsert));
            work.Add(new Action(DoWorkReplace));
        }
        for (int i = 0; i < 1000; i++)
            work[rand.Next(0, work.Count)].BeginInvoke(null, null);
 
    }
 
    void DoWorkAdd()
    {
        Thread.Sleep(rand.Next(500, 30000));
        data.Add(new TestData() { Text = string.Format("Thread {0} Added", Thread.CurrentThread.ManagedThreadId) });
    }
 
    void DoWorkClear()
    {
        Thread.Sleep(rand.Next(500, 10000));
        data.Clear();
        Debug.WriteLine((string.Format("Thread {0} Clear", Thread.CurrentThread.ManagedThreadId)));
    }
 
    void DoWorkRemove()
    {
        Thread.Sleep(rand.Next(500, 10000));
        if (data.Count == 0)
            return;
        var item = data[0];
        data.Remove(item);
        Debug.WriteLine((string.Format("Thread {0} Remove", Thread.CurrentThread.ManagedThreadId)));
    }
 
    void DoWorkRemoveAt()
    {
        Thread.Sleep(rand.Next(500, 10000));
        if (data.Count == 0)
            return;
        data.RemoveAt(0);
        Debug.WriteLine((string.Format("Thread {0} RemoveAt", Thread.CurrentThread.ManagedThreadId)));
    }
 
    void DoWorkInsert()
    {
        Thread.Sleep(rand.Next(500, 10000));
        data.Insert(rand.Next(0, data.Count), new TestData() 
            { Text = string.Format("Thread {0} Insert", Thread.CurrentThread.ManagedThreadId) });
    }
 
    void DoWorkReplace()
    {
        Thread.Sleep(rand.Next(500, 10000));
        data[rand.Next(0, data.Count)] = new TestData() 
            { Text = string.Format("Thread {0} Replace", Thread.CurrentThread.ManagedThreadId) };
    }
 
}

All my WPF app does is run a number of random actions against the collection from a variety of threads.

NOTE:  When removing items from the collection I used the Refresh action of NotifyCollectionChangedAction instead of Remove. This is because the remove action doesnt work correctly in a multi-threaded scenario when used as a binding source for a list control in WPF.

If anyone has any siggestions or enhancements, please let me know

Dean

Tags:

DataBinding | C# | Threading

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