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

by Dean 6. May 2010 08:08

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

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

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

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

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

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

Here is my altered ‘MainViewModel'.cs’ class

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

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

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

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

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

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

popup

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

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

Dean

Tags: ,

MVVM | C# | WPF | Unit Tests

WPF – DataContext Virtualization With Paged Services

by Dean 27. April 2010 21:51

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

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

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

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

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

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

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

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

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

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

and here is my service implementation

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

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

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

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

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

now, we need to wire it all up

First here’s my WPF code-behind:

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

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

And finally – here’s the XAML

<Window x:Class="WCFDataPaging.WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Count : " />
            <TextBlock Text="{Binding Path=Count}" />
        </StackPanel>
        <ListView ItemsSource="{Binding}" Grid.Row="1">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
                        <GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
                        <GridViewColumn Header="Start Date" DisplayMemberBinding="{Binding StartDate}" />
                        <GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" />
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

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

And here it is in action:

virtualdata

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

 

Dean

Tags: , ,

DataBinding | WCF | XAML | WPF

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

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

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