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

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

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();
    private delegate bool BoolMethodInvoker(T item);
 
    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)
            collection.Clear();
        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 BoolMethodInvoker(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)
            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)
            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

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

New Contract – New Opportunities

by Dean 29. January 2010 10:59

Today I was happy to sign myself up for a 6 month contract to deliver high performance WPF and Winforms GUI projects for a AAA investment bank’s ‘front-office’ trading team.

The project will be very demanding and test my strong knowledge of multi-threaded WPF to the max :)

This blog will therefore become more focussed on WPF, especially any interesting posts I can write on high-performance and multi-threading issues.

 

Happy days !!

Dean

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Im Back !!

by Dean 21. January 2010 16:25

After a desperately busy year, where many things fell to the wayside (including this blog), I have returned to the world of Silverlight and WPF blogging.

I hope over the coming weeks to make regular contributions to this blog

 

Dean

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

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

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

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

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

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

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

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

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

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

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Silverlight | DataBinding | WCF

Silverlight and Related Blogs

by Dean 24. January 2009 14:25

I've spent some time tracking down Silverlight oriented (or related technology) blogs so I can keep on top of the community using FeedReader on my daily train journey into London.

If anyone would like to add these blog references into their own RSS reader, you can download the OPML file below

 

Silverlight and Related Feeds (OPML File)

 

Please let me know if I've missed anyone, and I’ll keep this file up-to-date

Dean

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Silverlight

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

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

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

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Silverlight | DataBinding

ObservableCollection Base Class With Expression Blend Designer Support

by Dean 18. January 2009 23:54

One of the big issues for me with using Expression Blend for design in conjunction with VS2008 for the hard coding, is that you need to create at least test data loading stubs in the middle tier, delivered over WCF/WS in order visualize databound controls in the Silverlight app.

This means that you often need to have everything ‘working’ on the data delivery backend before you can effectively design the visual elements, otherwise you’ll be trying to work with empty ListBoxes and DataGrids etc.

In order to solve this problem, I have designed a base collection class that automatically generates test data in debug builds, but loses all of that redundant functionality for efficiency and security in release builds.

Without further ado – here is the code for the base class I am using for my data collections
(explanations follow)

 

    public class CollectionBase<T> : ObservableCollection<T>
    {
        private static readonly Random rand = new Random(DateTime.Now.Second);
        public CollectionBase() : base()
        {
            BuildTestData();
        }
 
        [Conditional("DEBUG")]
        public virtual void BuildTestData()
        {
            var props = typeof (T).GetProperties();
            for (var i=0; i < rand.Next(5,30); i++)
            {
                var item = (T)Activator.CreateInstance(typeof(T));
                foreach (var prop in props)
                {
                    switch (prop.PropertyType.Name)
                    {
                        case "String":
                            prop.SetValue(item,GetTestString(),null);
                            break;
                        case "DateTime":
                            prop.SetValue(item, GetTestDateTime(),null);
                            break;
                        case "Double":
                            prop.SetValue(item, (double)rand.Next(10000), null);
                            break;
                        case "Int32":
                            prop.SetValue(item, rand.Next(10000), null);
                            break;
                        case "Decimal":
                            prop.SetValue(item, (decimal)rand.Next(10000), null);
                            break;
                        case "Boolean":
                            prop.SetValue(item, rand.Next(0,1) == 0, null);
                            break;
                    }
                }
                Add(item);
            }
        }
 
        private static DateTime GetTestDateTime()
        {
            return DateTime.Now.AddSeconds(rand.Next(-2500000, 0));
        }
 
        private static string GetTestString()
        {
            var count = rand.Next(5,10);
            var chars = new char[count];
            for (var i = 0; i < count ; i++ )
                chars[i] = (char)rand.Next('A', 'Z');
            return new string(chars);
        }
    }


And the code for an example collection class that inherits from this base class is below

    public class PersonCollection : CollectionBase<Person>
    {
 
    }

As you can see, once you have the base class you just create an inheritor with the appropriate data object type in the class declaration.

Once armed with collection classes for all of your required types, you can easily use drag and drop in Blend to create data sources form the collection classes.

Once these data sources are then dragged onto a data-bound control in Blend, you will automatically see data appear in the control – thus enabling you to effectively apply your design skills to the control.

Now let me explain certain aspects of the code above

  1. the use of the [Conditional(“DEBUG”)] attribute -
    This is required in order for the code not to appear in the compiled MSIL in release (production) builds. Having the test data code in release builds may represent a security risk and/or an impediment to performance.
  2. Making the BuildTestData() method ‘virtual’ will allow the implementer to create their own test data building logic. I know your not supposed to call virtual members in a class constructor, but I think the pro’s outweigh the cons.
  3. There are some private static methods for creating test strings and dates etc. These are static on order to increase efficiency in the CLR.

I hope you find the above class useful. Im using it in current projects, and its working well. Once I have my services up and running, I can simply change the bindings in order to get the real data flowing:

PersonGrid.ItemsSource = MainService.GetEmployees();

If anyone has any improvements that can be made to this idea, please let me know.

Dean Chalk

Addendum – For a good example of how to use this technique – go to my next blog post here

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Silverlight

Silverlight ‘Loading’ Spin Icon in XAML

by Dean 18. January 2009 15:36

When loading large object collections in Silverlight, there is enough of a time delay so that I need some kind of animated icon that indicates a ‘loading’ state. There were many such icons used when loading data via AJAX, which are basically animated Gif’s. As Gif’s aren't supported in Silverlight, I needed to create one. I decided therefore to create a design in Expression Design, and then animate it in Blend, with a little tidying up in VS2008.

Im not sure how well it’ll perform with large number of instances in a single control, but it does the trick for my needs.

The code for my animated spinning logo is below.

(Use the scaletransform in the grid to change the size)

<Grid x:Name="LayoutRoot" Background="White"> 
    <Grid.RenderTransform> 
        <ScaleTransform x:Name="SpinnerScale" ScaleX="0.5" ScaleY="0.5" /> 
    </Grid.RenderTransform> 
    <Canvas RenderTransformOrigin="0.5,0.5" Width="120" Height="120"> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="20.1696" Canvas.Top="9.76358" 
            Stretch="Fill" Fill="#E6000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="2.86816" Canvas.Top="29.9581" 
            Stretch="Fill" Fill="#CD000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="5.03758e-006" Canvas.Top="57.9341" 
            Stretch="Fill" Fill="#B3000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="12.1203" Canvas.Top="83.3163" 
            Stretch="Fill" Fill="#9A000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="36.5459" Canvas.Top="98.138" 
            Stretch="Fill" Fill="#80000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="64.6723" Canvas.Top="96.8411" 
            Stretch="Fill" Fill="#67000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="87.6176" Canvas.Top="81.2783" 
            Stretch="Fill" Fill="#4D000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="98.165" Canvas.Top="54.414" 
            Stretch="Fill" Fill="#34000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="92.9838" Canvas.Top="26.9938" 
            Stretch="Fill" Fill="#1A000000"/> 
        <Ellipse Width="21.835" Height="21.862" Canvas.Left="47.2783" Canvas.Top="0.5" 
            Stretch="Fill" Fill="#FF000000"/> 
        <Canvas.RenderTransform> 
            <RotateTransform x:Name="SpinnerRotate" Angle="0" /> 
        </Canvas.RenderTransform> 
        <Canvas.Triggers> 
            <EventTrigger RoutedEvent="ContentControl.Loaded"> 
                <BeginStoryboard> 
                    <Storyboard> 
                        <DoubleAnimation Storyboard.TargetName="SpinnerRotate" 
                                 Storyboard.TargetProperty="(RotateTransform.Angle)" 
                                 From="0" To="360" Duration="0:0:01" 
                                 RepeatBehavior="Forever" /> 
                    </Storyboard> 
                </BeginStoryboard> 
            </EventTrigger> 
        </Canvas.Triggers> 
    </Canvas> 
</Grid> 

 

Note: If you want to turn this animated spinner on and off (say, when accessing a WCF service), you could do so with something like the code below:

First, in your XAML make the spinner invisible by default

<Grid x:Name="Spinner" Background="White" Visibility="Collapsed">

Then in your code do something like below:

 
var client = new DataServiceClient();
client.DoWorkCompleted += (s, e) => Spinner.Visibility = Visibility.Collapsed;
Spinner.Visibility = Visibility.Visible;
client.DoWorkAsync();

 

hope some of you find this usefull

Dean

Currently rated 4.7 by 3 people

  • Currently 4.666667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Silverlight

Coming Soon

by Dean 17. January 2009 16:19
This blog is under construction and will be coming soon

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

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

© Copyright 2010 ButtonChrome.com