Click here to Skip to main content
Click here to Skip to main content

Async MVVM Modern UI

, 16 Aug 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Using MVVM with Windows 8 Modern UI.

Modern UI Async Loading

Introduction

At this moment there is a lot of information and tutorials about using MVVM with Windows 8 Modern UI, and there is also information about using async. The goal of this article is to show a complete example with databinding, commands, and properties using async methodology and keeping the design mode with data templates.

Background

I recommend you know the basics of:

  • MVVM
  • async
  • XAML databinding
  • Lambda expressions

It is not strictly necessary but it will make easier to learn the union of both methodologies.

Model

The model is very simple, I usually prefer to add a Presenter for the View instead of adding a Converter, use the option you want. Note that in W8RP the Date Formatting has changed to GetDateTimeFormats.

public class FileModel
{
  public string Name { get; set; }
  public DateTime Date { get; set; }

  public string DatePresenter
  {
      get
      {
         if(this.Date!=null)
          return this.Date.GetDateTimeFormats()[0];
         return String.Empty;
      }
  }

  public override string ToString()
  {
      return Name;
  }
}

ViewModel

The viewmodel implements a customized version of INotifyPropertyChanged in order to be async and updates the UI when it is capable.

Implementing INotifyPropertyChanged

To update the UI when you are using async, you have to use the current window dispatcher. As you can see I create an async method that updates the UI called 'UIThreadAction', and several methods like 'PropertyChangedAsync' that calls 'PropertyChanged' inside the dispatcher action.

#region INotifyPropertyChanged with Dispatcher
public event PropertyChangedEventHandler PropertyChanged;
private CoreDispatcher _dispatcher = null;
private async Task UIThreadAction(Action act)
{
    await _dispatcher.RunAsync(CoreDispatcherPriority.Normal,()=> act.Invoke());
}
private async void PropertyChangedAsync(string property)
{
    if (PropertyChanged != null)
        await UIThreadAction(()=> PropertyChanged(this, new PropertyChangedEventArgs(property)));
}
#endregion

Properties and Fields

  • Booleans 'IsBusy' and 'IsIdle' to activate 'Read' and 'Cancel' buttons.
  • The collection 'Files'. Note: Being an ObservableCollection it is not necessary to call PropertyChangedAsync when you clear or add items.
  • The FileModel 'CurrentFile' to set the selected item from the Files collection. Note: An ObservableCollection does not have Current Item, but the Control has it, so using Two Way Binding gives you the current item.
  • The command 'GetFilesCommand' to read files.
  • The command 'CancelCommand' to stop reading files.

Note: As you can see I call 'PropertyChangedAsync' when I change properties, because the UI has to be updated when the dispatcher can.

/// <summary>
/// Set Buttons Enable/Disable while reading
/// </summary>
public bool IsBusy
{
    get { return !_isidle; }
}
private bool _isidle;
public bool IsIdle
{
    get { return _isidle; }
    set
    {
        _isidle = value;
        PropertyChangedAsync("IsIdle");
        PropertyChangedAsync("IsBusy");
    }
}

/// <summary>
/// Set Name of the current file
/// </summary>
private FileModel _currentfile;
public FileModel CurrentFile
{
    get { return _currentfile; }
    set
    {
        _currentfile = value;
        PropertyChangedAsync("CurrentFile");
    }
}

public ObservableCollection<FileModel> Files { get; set; }
public DelegateCommand<object> GetFilesCommand { get; set; }
public DelegateCommand<object> CancelCommand { get; set; }

Constructor

Here I initialize the Commands, the dispatcher, and the collection. Note: Never instance a collection again, just clear it.

The 'CancelCommand' is simple, just set _cancel to true. I have not to checked anything else because IsIdle is Binding so the Button is activated just when I tell it can. The 'GetFilesCommand' has implemented an action with the prefix async (I found this out by trial and error I did not find it in any article) and inside I call 'await GetFilesAsync' to get me the files as the app can.

public MainViewModel()
{
    _dispatcher = Window.Current.Dispatcher;

    Files = new ObservableCollection<FileModel>();
    IsIdle = true;

    CancelCommand = new DelegateCommand<object> ((ob) =>
        {
            _cancel = true;
        });


    GetFilesCommand = new DelegateCommand<object> (
        async (ob) =>
        {
            IsIdle = false;
            Files.Clear();

            await GetFilesAsync();
        });

    GetFilesCommand.Execute(null);
}

The heart

I split 'GetFilesAsync' to show that it is simply calling a method inside Task.Run. That method needs to have async as prefix to know that you want it in the 'background'. 'addfile' is the action that adds files while _cancel is not raised, as you see I do not need any special logic to know the value of cancel.

The most important thing is to have data on design time and in runtime, the last part calling Invoke from the dispatcher in runtime, and normally from design time.

  • In design mode I call it 10 times
  • In runtime I call it 10K times to view the behavior of loading items on the go
public Task GetFilesAsync()
{
    return Task.Run(() => GetFiles());
}

Action addfile = null;
public async void GetFiles()
{
    int i = 0;
    Random rnd = new Random(DateTime.Now.Millisecond);

    addfile = () =>
        {
            if (!_cancel)
            {
                Files.Add(new FileModel()
                {
                    Date = DateTime.Now.AddDays(rnd.Next(-10, 0)),
                    Name = String.Concat("prueba", i, ".xml")
                });
            }
            else
            {
                i = 10000;
                _cancel = false;
            }
        };


    while (++i < (Windows.ApplicationModel.DesignMode.DesignModeEnabled ? 10 : 10000))
    {
        if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
            addfile.Invoke();
        else
            await UIThreadAction(() => addfile.Invoke());
    }
    IsIdle = true;
}

View

Finally we arrive to the XAML, I have created a simple page with the command buttons, the GridView, and the TextBlock for the selected item. Take care of the following:

  • I create an Instance of the ViewModel in Page.DataContext
  • The First Button binds Command with 'GetFilesCommand' and bind IsEnabled with IsIdle
  • The Second Button binds Command with 'CancelCommand' and bind IsEnabled with IsBusy
  • The TextBlock bind Text to CurrentFile
  • The GridView binds ItemsSource with 'Files' and SelectedItem with CurrentFile TwoWay mode
<Page
    x:Class="AsyncMVVM.MainPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AsyncMVVM"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="using:AsyncMVVM.ViewModel"
    mc:Ignorable="d">
    
    <Page.DataContext>
        <vm:MainViewModel/>
    </Page.DataContext>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="7*"/>
            <RowDefinition Height="41*"/>
        </Grid.RowDefinitions>
        <Button Content="Read Files" HorizontalAlignment="Left" Height="56" 
           Margin="61,46,0,0" VerticalAlignment="Top" Width="118" 
           Command="{Binding GetFilesCommand}" IsEnabled="{Binding IsIdle}"/>
        <Button Content="Cancel" HorizontalAlignment="Left" Height="56" 
           Margin="184,46,0,0" VerticalAlignment="Top" Width="118" 
           Command="{Binding CancelCommand}" IsEnabled="{Binding IsBusy}"/>
        <TextBlock HorizontalAlignment="Left" Height="34" Margin="488,68,0,0" 
          TextWrapping="Wrap"  VerticalAlignment="Top" Width="403" Text="{Binding CurrentFile}"/>
        <GridView  HorizontalAlignment="Left" Height="580" Margin="61,14,0,0" 
                   VerticalAlignment="Top" Width="1275" Grid.Row="1" ItemsSource="{Binding Files}" 
                   SelectedItem="{Binding CurrentFile, Mode=TwoWay}">
            <GridView.ItemTemplate>
                <DataTemplate>
                <Border Background="LimeGreen" Width="140"  BorderBrush="Lime" BorderThickness="3" >
                    <Grid >
                    <TextBlock Text="{Binding Name}"></TextBlock>
                    <TextBlock Margin="0,50,0,0" Text="{Binding DatePresenter}"></TextBlock>
                    </Grid>
                </Border>
            </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </Grid>
</Page>

Using the code

To compile it succesfully you need the delegate command code:

public class DelegateCommand<T> : ICommand
{
    readonly Action<T> callback;

    public DelegateCommand(Action<T> callback)
    {
        this.callback = callback;
    }

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

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        if (callback != null) { callback((T)parameter); }
    }
}

Points of Interest

I encourage you to run the demo, because I think the best is to view it to know what is really happening.

History

v 1.0 Original version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Juan Pablo G.C.
Software Developer Expediteapps
Spain Spain
I'm Electronic Engineer, I did my end degree project at Astrophysical Institute and Tech Institute. I'm HP Procurve AIS and ASE ,Microsoft 3.5 MCTS
I live in Canary Islands ,developing customized solutions
 
I'm developing with WPF4, SL4 MVVM, MVC3 Razor and WP7 projects, more info at my websites. Improving with Android and IOS.
 
Web:
Expediteapps


Take a look to my blog Juan Pablo G.C.
Mareinsula

Comments and Discussions

 
QuestionCannot load the project PinmemberCoddioz18-Nov-12 23:21 
Please advise

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 16 Aug 2012
Article Copyright 2012 by Juan Pablo G.C.
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid