Introduction
I’m pretty sure that if you have been working on a project by using PRISM and MVVM pattern, at some point you have got to deal with Interaction Requests either to present some data or to simulate an interaction like it was done in WinForms with the MessageBox
.
Interaction Request can be a bit confusing if you are facing it for the first time. Furthermore, there’s a limited amount of information about it, even not much for WPF desktop version. That is why I decided to post this article. I hope this is going to be useful at least to open the field a bit and show that Interaction Request can be a very powerful resource in your application.
This article pretends to show how you can use a custom Interaction Request Dialog that holds a progress bar which is bound to asynchronous command that does some stuff in the background. The dialog reports the progress and allows the user to cancel the BackgroundWorker
that is performing the job. Once the thread is done, the dialog automatically closes itself.
In this article, I assume that you are familiar with MVVM pattern and WPF PRISM.
Understanding the Parts
PRISM allows you to interact with the GUI from the View Model by using InteractionRequest<T>
. For this article, I have used InteractionRequest<Notification>
because I just want to show some data and cancel an operation. From our View Model, we can raise a Notification
to the GUI’s thread in order to be caught by a trigger. Once the trigger has been launched, it invokes a TriggerAction<T>
which is the handler to create and manipulate de dialog. In this case, it is a TriggerAction<Grid>
due to the trigger definition's hold by a Grid
.
The following code example shows how the MainWindow
’s XAML looks like:
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Content="Sync Command" Command="{Binding Path=CommonCommand}" Grid.Row="0" />
<Button Content="Async Command" Command="{Binding Path=AsyncCommand}" Grid.Row="1" />
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding NotificationToProgress}">
<interactions:InteractionProgressDialog>
<interactions:InteractionProgressDialog.Dialog>
<interactionRequest:ProgressbarInteractionDialog />
</interactions:InteractionProgressDialog.Dialog>
</interactions:InteractionProgressDialog>
</prism:InteractionRequestTrigger>
<prism:InteractionRequestTrigger SourceObject="{Binding NotificationToClose}">
<interactions:InteractionCloseDialog>
</interactions:InteractionCloseDialog>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
</Grid>
The interaction dialog has been designed by using MVVM pattern but adding a new feature in order to interact with its View Model from the View and achieving a data communication by using property binding. If you are familiar with MVVM, you have probably heard talk about adapters. An adapter is just another layer between the View
and ViewModel
with a clear interface to communicate from one place to another for achieving binding.
The following code example shows the Adapter and View Model’s interface.
public interface IProgressbarView
{
void SetProggessStep(int step);
void SetProgressMessage(string message);
void SetTitle(string title);
}
public interface IProgressbarViewModel : Views.IProgressbarView, INotifyPropertyChanged
{
int Step { get; set; }
string Message { get; }
string Title { get; }
}
public interface IProgressbarAdapter : Views.IProgressbarView
{
IProgressbarViewModel ViewModel { get; }
}
So what have we got…
First of all, we got a Command with a BackgroundWorker
that does all the dirty job. That Command implements INotifyPropertyChanged
and by using it, it will report the progress to the main View Model.
Once the main View Model has caught a progress notification from the AsynCmd
, it will raise a Notification
to the View
by using InteractionRequest
.
void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case IS_BUSY_PROPERTY:
AsynCmd cmd = this.AsyncCommand as AsynCmd;
if (!cmd.IsCancelPending)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs(IS_EXECUTING_PROPERTY));
if (cmd.IsBusy)
App.Current.Dispatcher.Invoke
(new InteractionDelegate(this.ShowInteraction));
else
App.Current.Dispatcher.Invoke
(new InteractionDelegate(this.CloseInteraction));
}
break;
case PROGRESS_PROPERTY:
App.Current.Dispatcher.Invoke(new InteractionDelegate(this.SetProgressStep));
break;
}
}
private void ShowInteraction()
{
(this.NotificationToProgress as ProgressRequest)
.ShowDialog(TITLE_DIALOG, INITIAL_MESSAGE_DIALOG, this.CancelProcess);
}
private void CloseInteraction()
{
(this.NotificationToClose as CloseRequest).Close();
}
private void SetProgressStep()
{
(this.NotificationToProgress as ProgressRequest)
.SetProgressStep(this.Progress,
string.Format(PROGRESS_MESSAGE_DIALOG, this.Progress));
}
private void CancelProcess(Notification notification)
{
(this.AsyncCommand as AsynCmd).CancelProcess();
}
Then the view’s trigger will invoke InteractionProgressDialog
which in the first call will creates the dialog’s view and will add the view as a control to the control collection of its container.
For the next notifications, InteractionProgressDialog
will set dialog’s View Model’s properties by using the adapter.
protected override void Invoke(object parameter)
{
var args = parameter as InteractionRequestedEventArgs;
if (args != null)
{
Notification notification = args.Context;
UIElement element = InteractionDialogBase.FindDialog(this.AssociatedObject);
this.SetDialog(notification, args.Callback, element);
}
}
private void SetDialog
(Notification notification, Action callback, UIElement element)
{
ProgressRequest.ProgressMessage msg =
notification.Content as ProgressRequest.ProgressMessage;
if (this.Dialog is IProgressbarView)
{
IProgressbarView view = (IProgressbarView)this.Dialog;
view.SetProggessStep(msg.Step);
view.SetProgressMessage(msg.Message);
if (msg.Title != null)
view.SetTitle(msg.Title);
}
if (element == null)
{
EventHandler handler = null;
handler = (s, e) =>
{
this.Dialog.Closed -= handler;
this.AssociatedObject.Children.Remove(this.Dialog);
callback();
};
if (msg.Initialize)
{
this.Dialog.Closed += handler;
this.Dialog.SetValue(Grid.RowSpanProperty,
this.AssociatedObject.RowDefinitions.Count == 0 ? 1 :
this.AssociatedObject.RowDefinitions.Count);
this.Dialog.SetValue(Grid.ColumnSpanProperty,
this.AssociatedObject.ColumnDefinitions.Count == 0 ? 1 :
this.AssociatedObject.ColumnDefinitions.Count);
this.AssociatedObject.Children.Add(this.Dialog);
}
}
}
Once the AsynCmd
has completed its work, the main View Model will report the view that everything is done by using NotificationToClose
. That notification will be caught by another view’s trigger that uses another handler (InteractionCloseDialog
) to close the dialog.
Some Thoughts
In this article, I talked about an interesting way to achieve modal views in MVVM, however I ‘m not saying that this is the proper way to do it. I just posted this article because I know that InteractionRequest
is a slightly confusing part of PRISM and because it did make me crazy for a while.
Another way to achieve the same result could be done by using a hidden dialog into the main View and focusing all binding to the main’s View Model like it is done in HTML with JavaScript and div
s.
I will be glad to get feedback about it, so feel free to write a comment.
History