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

Lightweight ‘O/R Mapping’ in F# Interactive

by Dean 26. April 2010 21:15

I’ve been playing a lot with F# lately, particularly in the area of financial option modelling, which requires quite a lot of number crunching – a perfect scenario for tinkering around in F# interactive.

However, I need to get data out of my data store, and use it to create collections of records, that represent the data that I need.

This was becoming a little cumbersome, so I thought I’d create a little ORM function to do the trick

open System.Data.SqlClient
open Microsoft.FSharp.Reflection
 
let BuildData<'T> (connection:string, command:string) = 
    let conn = new SqlConnection(connection)
    let comm = new SqlCommand(command,conn)
    let recordType = typeof<'T>
    let fieldCount = FSharpType.GetRecordFields(recordType).Length
    conn.Open()
    let db = comm.ExecuteReader()
    let rec populate (reader:SqlDataReader) (l:'T list) = 
        match reader.Read() with
        | false -> l
        | _ -> 
            let vals = Array.create<obj> fieldCount null
            ignore(reader.GetValues(vals))
            let dataObj = FSharpValue.MakeRecord(recordType,vals) :?> 'T 
            let x = dataObj::l
            populate reader x
    let  data = populate db []
    conn.Close()
    data

run the above in interactive, and you’ll get

val BuildData : string * string -> 'T list

now lets give it a spin

 

type pricedata = { Price : Decimal; Symbol : String; PriceDate : DateTime }
let conn = "Data Source=DEAN-PC\SQLEXPRESS;Initial Catalog=StockData;Integrated Security=SSPI"
let comm = "select price, symbol, pricedate from symboldata where symbol = 'AA'"
let data = BuildData<pricedata>(conn,comm)

so we now have a strongly-typed collection, ready for pumping into our financial modelling functions

or alternatively, you could display the data in a grid :-

open System.Windows.Forms
 
let grid data =
    let form = new System.Windows.Forms.Form(Visible=true,TopMost=true)
    let g = new System.Windows.Forms.DataGrid(Dock = DockStyle.Fill, Visible=true)
    g.DataSource <- List.toArray data
    form.Controls.Add(g) 
 
grid data

and the result is like this

stockdata

Tags: , , ,

F# | DataBinding

F# And MVVM – A Simple ViewModel

by Dean 12. April 2010 20:51

For many years, OOP abstractions and design patterns have been the cornerstones of my development methodology as a senior C# developer in investment banking.

However, over the last year or so I have taken quite a shine to Microsoft’s new FP language (F#), not just because purely functional program code is concise powerful and elegant, but because the eclectic mix of functional and OOP paradigms in F# enable me to develop better, faster, stronger and more maintainable applications.

In investment banking, the business has been changing fast, and being able to get production quality WPF apps onto the trader desks has been a big priority, and with F# I find myself more able to meet that challenge.

The WPF design pattern ‘du jour’ is MVVM. Anyone who’s serious about enterprise-strength development in WPF would have come across this pattern and probably have used it at some point in the recent past.

Detailed below is a very basic implementation where :-

  1. There is a WPF project that contains only Views
  2. There is a C# ‘model’ project that contains only the generated classes from SqlMetal (for Linq to SQL)
  3. The is an F# code library project that contains my ViewModel

The F# ViewModel is a very simple one, but it covers all the main bases:-

  1. It has a generic ICommand implementation for command binding
  2. It implements INotifyPropertyChanged for change notifications
  3. It has Data for binding

So here it is

#light
module FSharpMVVM
 
open System
open System.Windows.Input
open System.Data.SqlClient
open System.ComponentModel
open FsMVVM.DAL
 
// below is our Linq-SQL data context, created via SqlMetal and in a separate
// C# project - as SqlMetal doesnt generate F# yet *)
 
let ctx = new DataClassesDataContext()
 
// an implemnentation of a generic ICommand, that takes two functions
// one to see if the command can be executed, and one to execute the command 
 
type FuncCommand (canExec:(obj -> bool),doExec:(obj -> unit)) =
    let cecEvent = new DelegateEvent<EventHandler>()
    interface ICommand with
        [<CLIEvent>]
        member x.CanExecuteChanged = cecEvent.Publish
        member x.CanExecute arg = canExec(arg)
        member x.Execute arg = doExec(arg)
 
// Our ViewModel is below
 
type MainViewModel() =  
    let mutable prods = Seq.toList ctx.Products 
    let a =  new PropertyChangedEventArgs("ProductData")
    let propChangedEvent = new DelegateEvent<PropertyChangedEventHandler>()
    interface INotifyPropertyChanged with 
        [<CLIEvent>]
        member x.PropertyChanged = propChangedEvent.Publish
    member x.ProductData with get() = prods
    member x.FilterCommand = 
        new FuncCommand(
            (fun d -> not(Seq.isEmpty ctx.Products)),
            (fun e -> prods <- Seq.toList ctx.Products 
                    |> List.filter (
                        fun p -> p.ProductName.Contains(e.ToString()));
                        propChangedEvent.Trigger([| box x; box a|])))

 

and below is the simple View (XAML)

 

<Window x:Class="FsMVVM.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 />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Margin="5">
            <TextBox Name="searchText" MinWidth="100" />
            <Button Content="Search" Command="{Binding FilterCommand}" 
                    CommandParameter="{Binding ElementName=searchText, Path=Text}" />
        </StackPanel>
        <ListView ItemsSource="{Binding ProductData}" Grid.Row="1">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Name" DisplayMemberBinding="{Binding ProductName}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

 

and finally, its wired up in the code-behind

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

and that's all it takes

Now, I know that there isnt a great deal of functional programming in this example, but now I have the building blocks for a more complex ViewModel, that will no doubt include a great deal more FP before it is finished

 

Dean

Tags: ,

DataBinding | F# | MVVM

Using CLR 4 Dynamics To Mock Bindable Objects in XAML

by Dean 3. February 2010 07:45

When I’m building prototypes in WPF or working on a GUI spike in an agile development team I often find it really unproductive to continuously switch between working in XAML (with my designer hat on), and working on the plumbing code (with my C# hat on).

Wouldn't it be nice to be able to model my data in XAML, and seamlessly use it with XAML Binding expressions that’ll be valid once the ‘real’ data gets plumbed in.

Well, thanks to the dynamic features in CLR v4 this is now a trivial task.

Firstly lets look at the XAML – you’ll quickly see the flexibility in using this approach -

<Window x:Class="DynamicWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <collections:Hashtable x:Key="data">
            <sys:Double x:Key="Prop1">50</sys:Double>
            <sys:String x:Key="Prop2">Hello</sys:String>
        </collections:Hashtable>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <TextBox Text="{Binding Path=Prop2, Mode=TwoWay}" />
        <TextBlock Text="{Binding Path=Prop2}" />
        <Button Content="{Binding Path=Prop1}" Height="{Binding Path=Prop1}" />
    </StackPanel>
</Window>

The critical thing to look at is the resources section (this could be in a separate resource dictionary if you wanted to keep things neat). Basically, I'm creating a hashtable that is going to model the data I anticipate will be available when I hook it all up after the design phase. Essentially, the hashtable keys are the property names of my object, and the hashtable values are the initial values of the properties.

In the example above, I’m expecting my data object to have 2 properties – one called “Prop1”which is a double, and has an initial value of 50, the other is called “Prop2” which is a string, and has an initial value of “Hello”.

Using the above approach, I could model (or mock) just about any simple object I can imagine.

Now we need to turn this hashtable into a real object, and this is where the new DynamicObject class in CLR 4 comes in.

public class DynamicDataObject : DynamicObject, INotifyPropertyChanged
{
    private Hashtable data;
    public event PropertyChangedEventHandler PropertyChanged;
 
    public DynamicDataObject(Hashtable data)
    {
        this.data = data;
    }
 
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = data.ContainsKey(binder.Name) ? data[binder.Name] : null;
        return true;
    }
 
    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        data[binder.Name] = value;
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(binder.Name));
        return true;
    }
}

When you inherit from DynamicObject, your object is essentially ‘dynamic’ which means that it’s members are discovered at runtime. In the class above, I'm using the hashtable passed in as the backing data to enable the correct member resolution at runtime. Effectively, this DynamicDataObject class will correctly mock a compiled CLR object with the same member signature.

Now, all we have to do is hook it up with a single line of code in our code-behind file:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new DynamicDataObject((Hashtable)this.Resources["data"]);
    }
}

And there you have it. The binding expression syntax in your XAML will work seamlessly with this solution. You can specify any type of binding, including the use of valueconverters etc.

Of course, any behaviour you wanted in your data objects cannot be implemented this way, but you shouldn’t mix data and logic anyway :)

 

Dean

Tags: , ,

DataBinding | XAML

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

Silverlight DataGrid – A Simple Pager Control

by Dean 11. February 2009 10:39

I love the fact that in Silverlight you can get all of your data onto the datagrid at the same time, rather than having to used a paged control in ASP.NET. However I’ve found that some users really want the data paging to remain, which means that I’ve got to roll may own DataGrid paging control.

I know there are a few example out there, but I wanted to create a control that was a simple as possible but covered all of the major bases – so as little ‘code-behind’ as possible, and all the styling done in Blend.

So this is what I came up with:

Screenshot

 

pagergrid

 

Here’s the XAML

 

<Grid x:Name="LayoutRoot" Background="White" Width="Auto" 
      Height="Auto" HorizontalAlignment="Left" VerticalAlignment="Top" 
      Margin="10,10,10,10">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" 
                    VerticalAlignment="Top">
        <TextBlock Text="Page 1 of 10 (Total 100 items)" x:Name="Total" 
                    HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <TextBlock Text="Page Size" Margin="10,0,0,0" 
                    HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <ComboBox Name="PageSize" SelectionChanged="PageSizeChanged" 
                    Margin="3,3,3,3" HorizontalAlignment="Center" 
                    VerticalAlignment="Center" />
    </StackPanel>
    <data:DataGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                    DataContext="{Binding Mode=OneWay, 
                        Source={StaticResource PersonCollectionDS}}" 
                    ItemsSource="{Binding}" x:Name="MainGrid" 
                    CanUserSortColumns="False" Grid.Row="1" />
    <Border Margin="0,3,0,3" Padding="5,5,5,5" BorderBrush="Gray" 
                    BorderThickness="1,1,1,1" Grid.Row="2">
        <StackPanel Height="Auto" HorizontalAlignment="Center" 
                    VerticalAlignment="Top" Width="Auto" x:Name="Pager" 
                    Orientation="Horizontal">
            <Button Content="&lt;&lt;" Margin="0,0,3,0" x:Name="FirstButton" 
                    Click="GoFirst"/>
            <Button Content="&lt;" Margin="0,0,3,0" x:Name="BackButton" 
                    Click="GoBack"/>
            <TextBlock Text="Page" Margin="3,0,0,0" HorizontalAlignment="Center" 
                    VerticalAlignment="Center"/>
            <ComboBox Name="PageSelector" Margin="3,0,0,0" 
                      SelectionChanged="PageChanged"/>
            <Button Content="&gt;" Margin="3,0,0,0" 
                    x:Name="ForwardButton" Click="GoForward"/>
            <Button Content="&gt;&gt;" Margin="3,0,0,0"
                    x:Name="LastButton" Click="GoLast"/>
        </StackPanel>
    </Border>
</Grid>

 

And Here’s the code-behind

 

public partial class Page : UserControl
{
    private readonly PersonCollection masterCollection = 
        new PersonCollection();
    private int pageSize = 10;
    private int currentPage;
    private int maxPage;
 
    public Page()
    {
        InitializeComponent();
        Loaded += PageLoaded;
    }
 
    private void PageLoaded(object sender, RoutedEventArgs e)
    {
        // this line just creates test data - ignore;
        masterCollection.BuildTestData(1000,1000);
 
        masterCollection.CollectionChanged += (s, a) => SetPageData();
        SetPageSize();
        PageSize.ItemsSource = new List<int>(new[] {10, 25, 50});
        PageSize.SelectedIndex = 0;
        SetPageData();
    }
 
    private void SetPageSize()
    {
        maxPage = (int)Math.Ceiling((double)masterCollection.Count 
            / (double)pageSize);
        for (var i = 1; i <= maxPage; i++)
            PageSelector.Items.Add(i);
    }
 
    private void SetPageData()
    {
        MainGrid.DataContext = masterCollection
            .OrderBy(p => p.Name)
            .Skip(pageSize * currentPage)
            .Take(pageSize);
        BackButton.IsEnabled = FirstButton.IsEnabled = currentPage > 0;
        ForwardButton.IsEnabled = LastButton.IsEnabled = 
            currentPage+1 < maxPage;
        Total.Text = string.Format("Page {0} of {1} (Total : {2})", 
            currentPage+1, maxPage, masterCollection.Count);
        PageSelector.SelectedIndex = currentPage;
    }
 
    private void GoBack(object sender, RoutedEventArgs e)
    {
        currentPage--;
        SetPageData();
    }
 
    private void GoForward(object sender, RoutedEventArgs e)
    {
        currentPage++;
        SetPageData();
    }
 
    private void GoLast(object sender, RoutedEventArgs e)
    {
        currentPage = maxPage-1;
        SetPageData();
    }
 
    private void GoFirst(object sender, RoutedEventArgs e)
    {
        currentPage = 0;
        SetPageData();
    }
 
    private void PageChanged(object sender, 
        SelectionChangedEventArgs e)
    {
        currentPage = (int)PageSelector.SelectedItem-1;
        SetPageData();
    }
 
    private void PageSizeChanged(object sender, 
        SelectionChangedEventArgs e)
    {
        pageSize = (int) PageSize.SelectedItem;
        currentPage = 0;
        SetPageSize();
        SetPageData();
    }
}

 

As you can see, it’s pretty simple stuff.

Because the Silverlight DataGrid doesn't expose the built-in column sorting events, implementing sorting on the columns is a little more tricky – you’d have to re-template the headers and include a button who’s click event can invoke a custom sorting algorithm, which would be easy enough to implement should you want.

Any comments ? let me know.

(Source Files Below)

DemoPager.zip

Dean

Tags: ,

DataBinding | Silverlight

Generic Filter Control for Silverlight DataGrid

by Dean 6. February 2009 14:13

During the creation of a recent demo Silverlight project, I tasked myself with creating a generic (re-useable) filter control that I could use to filter rows in a Silverlight DataGrid.

The control had to be lightweight, and work automatically with any object collection that the DataGrid was bound to – thus making it plug-and-play for any future uses.

I’ve created a great starting point with this, by designing a set of inter-operating classes that have the following key features.

  1. The Filter control of writtem completely in Xaml, using Xaml binding syntax (good designer supprt)
  2. By virtue of point 1, it is easily modified in Blend
  3. The filter control works by creating Lambda/Linq Expression trees to perform filtering.
  4. The filter control can ustilise the DescriptionAttribute for plug-and-play.

Here are a couple of screenshots:

1) No filtering

filter1

2) Adding a couple of filters, by clicking on the ‘Add New Filter’ button, setting the filter and clicking on ‘Apply All Filters’

filter2

 

As you can see, the compiled Expression tree effectively filtered the data in the main grid.

The advantages of this filter control are

  1. You could package it up, and drop it into any scenario – the only thing the filter control needs to know is the Type details of the objects in the data collection.
  2. You could play around with the visuals, and create a ‘FilteringDataGrid’ as a single control
  3. It could be a great starting point for implementing a Microsoft Excel – like ‘Auto Filter’ grid.

The ideas are limitless, and the link below will allow you to download the project and try it out for yourself.

SimpleFilterControl.zip

 

Here are some Caveats

  1. This is not production quality code. A lot of production-necessary engineering is missing for the sake of illustrating the approach(es) taken in as simple a way as possible.
  2. This code can be freely used, but comes with no guarantees whatsoever.
  3. I'm happy to hear about suggested improvements or alternative ideas, but if you have any criticisms about the quality of the code, please refer to point 1 above.

I hope those that take a look find it interesting, and I look forward to your comments.

 

Dean

Tags: ,

Silverlight | DataBinding

Silverlight DataGrid – Prototyping Tip For Column Headers

by Dean 5. February 2009 14:58

Working in investment banking, I often get asked to create semi-functional prototypes of user interfaces, where the development methodology is all about speed and not about code-quality.

Often these projects will be centred around the Silverlight DataGrid, and I want to get it up and running fast with whatever data object that needs to be used (the DataGrid will need to be bound to a collection of such objects).

By default, a DataGrid for Silverlight will automatically generate columns, which is a great little feature in these scenarios, which is a great time saver – especially when the underlying object that your rows are binding to is in a state of flux.

However, when automatically generating columns for your data, the column header is just set to the name of the property – which isn't going to impress the audience when you show off your prototype.

A great solution I put together, is to use the ‘DescriptionAttribute’ on your data object’s properties, and have the DataGrid automatically pick that up.

To Illustrate this, please find below the ‘before’ and ‘after’ code and screenshots.

 

1) Before

 

    public class Person
    {
        public string Name { get; set; }
 
        public DateTime Dob { get; set; }
 
        public int EmployeeId { get; set; }
 
        public double Salary { get; set; }
 
    }

 

<UserControl.Resources>
    <AttributesConverter:PersonCollection x:Key="PersonCollectionDS" 
        d:IsDataSource="True"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
    <data:DataGrid Name="PersonGrid" HorizontalAlignment="Left" 
        ItemsSource="{Binding Mode=OneWay, Source={StaticResource PersonCollectionDS}}" />
</Grid>

proto_before

 

2) After

 

    public class Person
    {
        [Description("Employee Name")]
        public string Name { get; set; }
 
        [Description("Date Of Birth")]
        public DateTime Dob { get; set; }
 
        [Description("Employee Id")]
        public int EmployeeId { get; set; }
 
        [Description("Salary Amount")]
        public double Salary { get; set; }
 
    }

<UserControl.Resources>
    <AttributesConverter:PersonCollection x:Key="PersonCollectionDS" 
        d:IsDataSource="True"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
    <data:DataGrid Name="PersonGrid" HorizontalAlignment="Left" 
        ItemsSource="{Binding Mode=OneWay, Source={StaticResource PersonCollectionDS}}" 
        AutoGeneratingColumn="ColumnGenerate"   />
</Grid>

private void ColumnGenerate(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    var attrib = gridDataType.GetProperty(e.PropertyName).GetCustomAttributes(
        typeof (DescriptionAttribute), true);
    e.Column.Header = attrib == null ? 
        e.PropertyName : 
        ((DescriptionAttribute)attrib[0]).Description;
}

proto_after

 

As you can see, the second screenshot has user-friendly headings.

The code differences between before and after are summarised below:

  1. In the ‘After’ code, our ‘Person’ data object has its properties decorated with the DescriptionAttribute attribute – this is where we set our user-friendly descriptions.
  2. In the ‘After’ code, our DataGrid sets a handler for the ‘AutoGeneratingColumn’ event.
  3. In the ‘After’ code, our code-behind handles the above event, and sets the column headers to match the property description custom attribute.

Pretty simple really, and a great productivity boon during prototype development. Simply setup your grid, and as your domain object changes so does your grid – automatically.

 

I hope this little tips helps anyone else out in this scenario.

 

If anyone has any further comments, please let me know.

 

Dean

Tags: ,

Silverlight | DataBinding

Enumeration Binding in Silverlight

by Dean 31. January 2009 10:57

A contractor colleague of mine (Phil Steel) had an interesting Silverlight problem yesterday. He wanted to populate a Silverlight ComboBox with an enumeration, and implement 2-way binding to a property on his Data class (i.e. binding his property to the ‘SelectedItem’ on the ComboBox.

His requirements were as follows:

  1. The solution must have full design-time support in Expression Blend
  2. The code must give the developer an opportunity to create metadata for the enumeration that can be used for ‘user friendly’ visual values in the ComboBox
  3. There must be full 2-way binding, so no need to tap into the ‘SelectionChanged’ event to update the data object.
  4. Minimal C# code, with as much as possible being re-useable ‘as is’ for any enumeration.

Ok, so I googled around and only found fragments of a possible solution, so I decided to engineer one myself.

The details are below

Task 1 : Create reusable code that converts a .NET enumeration into a collection

 

There are 2 classes we need as part of this task, and the code is below

public sealed class EnumContainer
{
    public int EnumValue { get; set; }
    public string EnumDescription { get; set; }
    public object EnumOriginalValue { get; set; }
    public override string ToString()
    {
        return EnumDescription;
    }
 
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        return EnumValue.Equals((int)obj);
    }
 
    public override int GetHashCode()
    {
        return EnumValue.GetHashCode();
    }
 
}
 
public class EnumCollection<T> : List<EnumContainer> where T : struct 
{
    public EnumCollection()
    {
        var type = typeof (T);
        if (!type.IsEnum)
            throw new ArgumentException("This class only supports Enum types");
        var fields = typeof (T).GetFields(BindingFlags.Static | BindingFlags.Public);
        foreach(var field in fields)
        {
            var container = new EnumContainer();
            container.EnumOriginalValue = field.GetValue(null);
            container.EnumValue = (int) field.GetValue(null);
            container.EnumDescription = field.Name;
            var atts = field.GetCustomAttributes(false);
            foreach (var att in atts)
                if (att is DescriptionAttribute)
                {
                    container.EnumDescription = ((DescriptionAttribute) att).Description;
                    break;
                }
            Add(container);
        }
        
    }
}

 

These 2 classes represent a bindable version of our enumeration. One class is an object that represents the enumeration values (EnumContainer), and the other is the collection.

(The part of the code above describing ‘CustomAttributes’ relates to the task below.

 

Task 2 (optional) : Add ‘user friendly’ metadata to your enumerations

 

This is a well known solution using the ‘DescriptionAttribute’ class

 

    public enum CustomerStatus
    {
        [Description("Not Yet Approved")]
        UnApproved,
 
        [Description("Pending Approval")]
        PendingApproval,
 
        [Description("Fully Approved")]
        Approved
    }

These descriptions will appear in your combobox

 

Task 3 : Create an IValueConverter to support 2-way binding of the ComboBox to the data object.

 

public class EnumValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                CultureInfo culture)
    {
        return (int) value;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
CultureInfo culture)
    {
        if (value == null)
            return null;
        if (value.GetType() == targetType)
            return value;
        return ((EnumContainer) value).EnumOriginalValue;
    }
}

 

Task 4 : Implement a solution to suit your needs

 

Ok, now that we have all the base code added to our project, it’s time to implement a solution to our problem. The only c# class that needs to be created is a simple collection class that inherits from EnumCollection and allows full designer support in Expression Blend

So here (for example) are our data classes that we will use to demonstrate our solution

1) Our example ‘Customer’ class

public class Customer
{
    public string CustomerName { get; set; }
    public CustomerStatus Status { get; set; }
}

2) Our example Enumeration (notice the use of the DescriptionAttribute, which gives the ComboBox user friendly test)

 

    public enum CustomerStatus
    {
        [Description("Not Yet Approced")]
        UnApproved,
 
        [Description("Pending Approval")]
        PendingApproval,
 
        [Description("Fully Approved")]
        Approved
    }

 

3) Our collection to provide Blend support (as you can see, its just a simple inheritor as XAML doesnt easily support generics).

public class CustomerStatusEnumeration : EnumCollection<CustomerStatus>
{
 
}

 

4) Here's an example of wiring up the data object in the code

 

private void PageLoaded(object sender, RoutedEventArgs e)
{
    customer = new Customer() { CustomerName = "Customer1", 
        Status = CustomerStatus.PendingApproval };
    DataContext = customer;
}

 

Task 5 : Wire it all up in the Xaml

<UserControl x:Class="TestEnum.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300" xmlns:TestEnum="clr-namespace:TestEnum" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
    <UserControl.Resources>
        <TestEnum:CustomerStatusEnumeration 
            x:Key="CustomerStatusEnumerationDS" 
            d:IsDataSource="True"/>
        <TestEnum:EnumValueConverter  
            x:Key="EnumConverter" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <TextBox Text="{Binding Mode=TwoWay, Path=CustomerName}" 
                 Margin="5" HorizontalAlignment="Left" VerticalAlignment="Top" />
        <ComboBox Name="Combo" HorizontalAlignment="Left" 
                  VerticalAlignment="Top" 
                  ItemsSource="{Binding Mode=OneWay, 
                        Source={StaticResource CustomerStatusEnumerationDS}}" 
                  SelectedItem="{Binding Status, Mode=TwoWay, 
                        Converter={StaticResource EnumConverter}}" 
                  Margin="5" Grid.Column="1"/>
        <Button Content="Update" Click="UpdateCustomer" Margin="5" 
                HorizontalAlignment="Left" 
                VerticalAlignment="Top" Grid.Column="2" />
    </Grid>
</UserControl>

An here’s a screenshot of the above solution

enumexample

As you can see, it’s a very simple and effective solution. And should we require to wire up a second enum to our Silverlight app then the only class we’d need to create (assuming the enum already exists) is the empty collection class as described above.

 

I hope you like the solution, any comments about how to improve it then please let me know.

 

Dean

Tags: , ,

WCF | Silverlight | DataBinding

Fixed - Issues With WCF and Mixed VS/Blend Development

by Dean 25. January 2009 12:37

Some of you may have come across an issue when developing ‘fast and dirty’ demo apps in Silverlight that have a WCF backend service on the web application.

When developing throwaway demo apps for clients, you need to take all of the shortcuts you can get, so I always use the ‘Add service reference’ feature of Visual Studio to add a service reference within my Silverlight app to the host ASP.NET service (not advisable for production-quality apps though). This is a great feature because as you change the service interface you can keep in sync on your Silverlight app using a single service ‘Update’ button.

However, the big drawback of this approach is that the Visual Studio tool that creates the service reference hard-codes the service Uri into the generated classes.

If you were just using Visual Studio then this wouldn't be a problem, but unfortunately VS and Blend use different development servers that cannot co-exist on the same TCP port, so for one of the two development environments the Uri of the service is going to be wrong.

for example, you create the service reference in Visual Studio, which is configured to run the website on url ‘http://localhost:43667/DemoApp’ , then the hard-coded Uri for the service will be something like ‘http://localhost:43667/DemoApp/Service1.svc’.

However, Expression Blend will always run the solution on a different port, so it will run the app on a base url of something like ‘http://localhost:52234/DemoApp/’ which means that the Silverlight app wont access the service and your app will not be able to access the service when run from Blend.

Your gut reaction might be to try the following:

  1. Fix the port in Visual Studio to be the same as the one in Blend. – This wont work because Blend will detect VS is on it’s preferred port and will automatically switch to an alternative port.
  2. Use your local version of IIS to avoid the clash of the ports. – This will work but doesn’t really fit in with the idea of creating a portable throwaway demo app.
  3. Work in both VS and Blend, but only press F5 while in VS. This works but is a pain in the butt.
  4. Play with crossdomain policy files etc. – Not really a solution, especially as VS always tears-down the dev server when in DEBUG mode.

However, there is a solution for this scenario that only requires changing a single line of code to your Silverlight project.

Below is the some ‘Before’ sample code

private void PageLoaded(object sender, RoutedEventArgs e)
{
    var service = new Service1Client();
    service.DoWorkCompleted += (source, args) => MessageBox.Show("DONE");
    service.DoWorkAsync();
}


And now here is the same code with an added line that fixes this issue.

 

private void PageLoaded(object sender, RoutedEventArgs e)
{
    var service = new Service1Client(new BasicHttpBinding(BasicHttpSecurityMode.None), 
        new EndpointAddress(Application.Current.Host.Source.AbsoluteUri.Replace(
            Application.Current.Host.Source.AbsolutePath,"/Service1.svc")));
    service.DoWorkCompleted += (source, args) => MessageBox.Show("DONE");
    service.DoWorkAsync();
}

 

Basically, we’ve just changed the way we’ve constructed the service object, and passed in the necessary parameters to make the service reference relative rather than absolute. (You may need to replace the ‘Service1.svc’ parameter with one that is relevant to your project).

This is a neat little code snippet to keep on hand when developing these kinds of apps

I hope this helps you fix this annoying issue in your Silverlight development.

If you have any further suggestions then let me know.

 

Dean

Tags: , ,

Silverlight | DataBinding | WCF

Generic IValueConverter for Silverlight or WPF

by Dean 24. January 2009 10:37

When using the GridView in ASP.NET it is very handy to be able to include a ‘FormatString’ attribute to bound columns and the like – enabling you to display those dates, currency values, numbers etc in a more readable form.

I was surprised that Silverlight or WPF doesn't offer this out of the box, and I couldn't find any ‘obvious’ answers when I googled the subject,

Therefore, I created a simple IValueConverter to achieve the same result.

Here's the code for the converter:

namespace CBSSilverlight
{
    public class FormatStringValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, 
            CultureInfo culture)
        {
            if (value == null)
                return string.Empty;
            return !string.IsNullOrEmpty(parameter.ToString()) ? string.Format(culture, 
                parameter.ToString(), value) : value.ToString();
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, 
            CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

As you can see, it doesn't support 2 way conversions, so it’s only really appropriate for read-only grids.

So in order to use this IValueConverter, the first step is to add the class with the code above into your VS2008 project.

Next, we need to include it as a static resource in our UserControl by adding the following to our XAML (don't forget to reference your namespace in the UserControl declaration)

 
<UserControl.Resources>
    <CBSSilverlight:FormatStringValueConverter x:Key="FormatConverter" />
</UserControl.Resources>
 

Now we can use the IValueConverter in our column declarations:

 

<data:DataGrid HorizontalAlignment="Left" 
               Grid.Row="1" Margin="0,0,0,1" 
               d:LayoutOverrides="Height" 
               ItemsSource="{Binding}" 
               AutoGenerateColumns="False" 
               MaxHeight="370" 
               SelectionChanged="SelectionChanged" >
    <data:DataGrid.Columns>
        <data:DataGridTextColumn 
            Header="Name" 
            Binding="{Binding Path=Name}" />
        <data:DataGridTextColumn 
            Header="Order Date" 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
                ConverterParameter=\{0:dd-MMM-yy HH:mm\}, Path=OrderDate}"/>
        <data:DataGridTextColumn 
            Header="Total" 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
                ConverterParameter=\{0:£0.00\}, Path=ItemsTotal}" />
        <data:DataGridTextColumn 
            Header="Discount" 
            Binding="{Binding 
            Path=DiscountCode}"/>
        <data:DataGridTextColumn 
            Header="Potage" 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
            ConverterParameter=\{0:£0.00\}, Path=Postage}"/>
        <data:DataGridTextColumn 
            Header="Total" 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
                ConverterParameter=\{0:£0.00\}, Path=GrandTotal}"/>
        <data:DataGridTextColumn 
            Header="Post" 
            Binding="{Binding Path=PostageType}"/>
        <data:DataGridTextColumn 
            Header="Num." 
            Binding="{Binding Path=TotalOrders}"/>
        <data:DataGridTextColumn 
            Header="First" 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
            ConverterParameter=\{0:dd-MMM-yy\}, Path=FirstShippedOrder}" />
        <data:DataGridTextColumn 
            Header="Last" 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
            ConverterParameter=\{0:dd-MMM-yy\}, Path=LastShippedOrder}" />
        <data:DataGridTextColumn 
            Header="Min. Val." 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
            ConverterParameter=\{0:£0.00\}, Path=MinOrderValue}" />
        <data:DataGridTextColumn 
            Header="Max. Val." 
            Binding="{Binding Converter={StaticResource FormatConverter}, 
            ConverterParameter=\{0:£0.00\}, Path=MaxOrderValue}" />
    </data:DataGrid.Columns>
</data:DataGrid>

As you can see, the converter elegantly handles Dates, Money and anything else you can dream of, just like in the ASP.NET GridView.

And here’s a screen-shot below of the converter in action

formatgrid

As you can see – Dates, Date/Times and Money columns look great.

If anyone has any comments, or suggests any improvements – please feel free to add a comment below

Thanks

Dean Chalk

Tags: , ,

DataBinding | Silverlight

ObservableCollection Base Class With Expression Blend Designer Support – An Example Project

by Dean 19. January 2009 17:31

In my previous post showed how to create a lightweight collection class that can be used in expression blend enabling design-support for data bound control development.

In this post, I thought I’d expand on my previous writings and actually create a sample project from beginning to end – demonstrating how easy it is to use this solution.

Step 1 – Create Your Project (Programmers Job)

In VS2008, create a Silverlight application project – Im going to call mine EmployeeInfo for the purpose of this blog. I also need to create my data classes, So I’m going to create 3

  1. My generic base collection class that provides all of the test data (CollectionBase.cs)
  2. My Domain/Business object – in this example, it’s going to be a ‘person’ object that represents employee data (Person.cs)
  3. My person-specific collection class that provides a data-binding target for Expression Blend design (PersonCollection.cs)

EmployeeInfoSolution

The code for CollectionBase.cs and PersonCollection.cs is available in the previous post here

The code for the Person.cs code file is below

using System;
using System;
 
namespace EmployeeInfo
{
    public class Person
    {
        public string Name { get; set; }
        public DateTime Dob { get; set; }
        public int EmployeeId { get; set; }
        public double Salary { get; set; }
    }
}

As you can see, my example Person class is pretty simple, but you can make it as complicated as you like.

Now, I know in my project Im going to want to use a Silverlight DataGrid control, so I know that I need to add an additional project reference for this, and it’s easier to do this while in Visual Studio. Simple click on ‘Add Reference’ in the solution explorer and select the System.Windows.Controls and System.Windows.Controls.Data references to the solution (see below)

AddRef

Save all your files, lets move on to Blend.

Step 2 – Use Your Collection in Expression Blend

Open up the project in Blend and you’ll see the same code files in the blend project.

EmployeeInfoSolutionBlend

 

Now lets drop a DataGrid into our Page in order to demonstrate our example.

When ‘Design’ mode, click on the Asset Library button on the far left and select DataGrid from the available controls. Double click on the DataGrid button to add a DataGrid to your Page.

With the DataGrid selected in the Object and Timeline panel, go to the Properties tab on the right and set the Horizontal and Vertical Alignments to ‘Stretch (this will expand the DataGrid to fit it’s parent, giving us a surface for the DataGrid in the designer).

Next, lets start using our new Data classes.

On the project tab on the right hand side, click on the ‘+CLR Object’ button to popup the object data source creation panel

SelectCLR

Select ‘PersonCollection’ and hit enter, you will now see a new entry in the Data section of the right tab panel

DataSourceAdded

Now the final operation is to add this DataSource to our DataGrid, which simply requires us to drag and drop the new DataSource (as named ‘PersonCollection (Array)’ in above screenshot) onto the DataGrid. As soon as you do this you get a popup confirming where you want to attach the DataSource

AddToGrid

Select ‘DataGrid’ and the select ‘ItemsSource’ when it asks what you’d like to bind to in you DataGrid.

Instantly you should see a DataGrid full of test data

FinalDataGeid

As you can see – instant test data.

And now you can begin working on this grid in Blend – maybe hand-crank your column definitions, wire in your ValueConverters or re-template your headers etc.

(There's a neat trick with this solution, in that every time you re-build - Ctrl+Shift+B – you get new data, so you can keep rebuilding until you get the test data that suits you the most)

However, the test data is just random strings and numbers, which should be enough in most cases, but if not, then you can do the following

Step 3 (optional) – Refine Your Test Data

In the preceding code, the data was randomly generated, to get a more accurate version of the data available at design  time we can override the BuildTestData() method, and inject our own test objects into the collection.

Here's an example:

public override void BuildTestData()
{
    Add(new Person
            {
                Dob = new DateTime(1966, 12, 1),
                EmployeeId = 1445667,
                Name = "John Smith",
                Salary = 45025
            });
    Add(new Person
            {
                Dob = new DateTime(1981, 7, 31),
                EmployeeId = 3342556,
                Name = "Dave Hanley",
                Salary = 88343
            });
    Add(new Person
            {
                Dob = new DateTime(1977, 3, 5),
                EmployeeId = 5544667,
                Name = "Kunwar Singh",
                Salary = 66500
            });
    Add(new Person
            {
                Dob = new DateTime(1972, 3, 1),
                EmployeeId = 5688833,
                Name = "James Madden",
                Salary = 58332
            });
    Add(new Person
            {
                Dob = new DateTime(1980, 5, 9),
                EmployeeId = 988776,
                Name = "Harvey Sanders",
                Salary = 77221
            });
    Add(new Person
            {
                Dob = new DateTime(1969, 8, 12),
                EmployeeId = 665887,
                Name = "Joshua Jones",
                Salary = 34700
            });
}

And now in Blend when we re-build we get this:

DateGridData

So, there we have the test data in Blend

Step 4 – Add The Real Data Feed When Ready

This part is really simple, and is demonstrated in the code below

public partial class Page : UserControl
{
    public Page()
    {
    InitializeComponent();
    Loaded += ControlLoaded;
    }
 
    private void ControlLoaded(object sender, RoutedEventArgs e)
    {
        service.GetEmployeesCompleted += (source, args) => 
            {if (args.Error == null && !args.Cancelled) {
            PersonGrid.ItemsSource = args.Result; }};
        service.GetEmployeesAsync();
    }
}

And there you have it

Finally, another great tip, if you want to override the BuildTestData() method, and already have a service that can provide plenty of data – and that is create a quick and dirty console application that retrieves a suitable number of objects from the service, then use the XmlSerializer to serialize the collection to an xml string. This can then be added to your test collection class and de-serialized to re-create the same data without needing any backend services.

Dean

Tags: ,

Silverlight | DataBinding

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

© Copyright 2010 Dean Chalk's Blog