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.
public bool IsBusy
{
get { return !_isidle; }
}
private bool _isidle;
public bool IsIdle
{
get { return _isidle; }
set
{
_isidle = value;
PropertyChangedAsync("IsIdle");
PropertyChangedAsync("IsBusy");
}
}
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.