As usual with these posts, although the specific code in the article relates to MVVM Cross, it should be applicable to any MVVM framework. Where it is specific, I’ll try to flag that up.
The Scenario
In the current game that I’m writing in MVVM Cross (yes, that’s right - a game), I have a situation where the user selects to create a “New Game” and the game world needs to be generated. As this can take some time, I want to display a progress bar. Roughly speaking, the process flow is as follows:
On the Menu Page, the User selects “New Game”. The Menu View Model calls a service that creates the game world. It then calls the Main Page View Model to display the main page.
The Problem
Basically, communicating between view models without tightly coupling them together.
That’s what messages are for isn’t it?
Yes.
Here’s my implementation.
First step is to add the progress bar to the screen that you want to update:
[sourcecode language=“xml”]
…
There are three properties bound here: None exist yet. Obviously, if you want the progress bar to always display, you might not need all three.
Let's create the properties in the VM:
``` csharp
private bool \_isLoading = false;
public bool IsLoading
{
get { return \_isLoading; }
set
{
\_isLoading = value;
RaisePropertyChanged(() => IsLoading);
}
}
private int \_loadingProgress = 0;
public int LoadingProgress
{
get { return \_loadingProgress; }
set
{
\_loadingProgress = value;
RaisePropertyChanged(() => LoadingProgress);
}
}
private int \_totalProgress = 0;
public int TotalProgress
{
get { return \_totalProgress; }
set
{
\_totalProgress = value;
RaisePropertyChanged(() => TotalProgress);
}
}
You will also need to declare the BooleanToVisiblityConverter:
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Now there’s something to bind to. Next, set-up the messenger.
Messenger
You need to register the messenger object. I have a base model that I do this in, but anywhere before you use it is fine:
Mvx.LazyConstructAndRegisterSingleton();
(Warning: MVVM Cross specific) A caveat here; if you use something like `RegisterType` to register the messenger object, that will also (appear to) work. However, it won’t actually work; it will create a fresh instance of the messenger object each time. If you decide to do it that way then you will need to inject the messenger object into the subscriber and publisher (it has to be the same instance).
Next, you need to create the message:
class UpdateProgressMessage : MvxMessage
{
public UpdateProgressMessage(object sender, int currentProgress, int totalProgress) : base(sender)
{
CurrentProgress = currentProgress;
TotalProgress = totalProgress;
}
public int CurrentProgress { get; set; }
public int TotalProgress { get; set; }
}
This isn’t much, but the important thing is the constructor. Okay, now let’s subscribe to that message at the start of the task:
\_subscriptionTag = Messenger.Subscribe(UpdateProgress);
(A note on `_subscriptionTag` later)
`UpdateProgress` is a method defined like this:
private void UpdateProgress(UpdateProgressMessage updateProgressMessage)
{
LoadingProgress = updateProgressMessage.CurrentProgress;
TotalProgress = updateProgressMessage.TotalProgress;
IsLoading = (updateProgressMessage.CurrentProgress > 0 &&
updateProgressMessage.TotalProgress != updateProgressMessage.CurrentProgress);
}
Notice that you accept the `UpdateProgressMessage`.
That note on `_subscriptionTag`
Firstly, _subscriptionTag is a class level variable, defined like this:
private MvxSubscriptionToken \_subscriptionTag;
Once the task is complete you can unsubscribe. For MVVM Cross specifically, the subscription uses weak references, meaning that as soon as `_subscriptionTag` goes out of scope, it will be garbage collected, and will effectively unsubscribe (which is why it needs to be class level in the first place). Having said that, personally, I prefer things to happen in code when I tell them to, and not at some undefined and unpredictable point in the future; so, to unsubscribe:
Messenger.Unsubscribe(subscriptionTag);
Publish
Finally, just wire in the publish code. I have a helper method that instantiates the services and calls the game setup code, but this should work anywhere in the application:
Messenger.Publish(new UpdateProgressMessage(this, count, total));
Because `IMvxMessenger` is registered as a singleton, once you resolve it, you should be able to publish this from anywhere.
A Word About Threading
Okay, so this will all work, but where the task runs on the UI thread, you probably won’t see it update. Consequently, I start a new task for my long running process. Remember that if you do this, then subscribing to messages on the main thread may not work.
Conclusion
Okay, so now I have a progress bar for the set-up of my game. Like I said at the start, I think this should work for any architecture that supports IoC and messaging, but MVVM Cross seems to handle this very neatly.