Click here to Skip to main content
15,868,141 members
Articles / Desktop Programming / WPF

MVVM PRISM: Modal Windows by using Interaction Request with Generics

Rate me:
Please Sign up or sign in to vote.
4.89/5 (18 votes)
19 Sep 2012CPOL3 min read 133.8K   4.1K   49   50
How generics can help us in achieving modal windows in WPF PRISM
Animal's Modal View Animal's Modal View

Introduction

This article pretends to be the continuation of my other article MVVM PRISM: ProgressBar Interaction Request with Asynchronous Command. Here I will try to explain another interesting way to use modal windows in WPF by using PRISM and its way to do interactions. I am not going to enter to explain in detail every functionality of each component because so it is done in the related article above.

I assume you are familiar with WPF, PRISM and MVVM pattern.

How Does PRISM Try to Get Interactions?

If you have read PRISM’s official developer’s guide, you have probably seen the part of the document where it briefly explains the way to achieve modal interaction and you probably have had trouble to understand or get a custom way to extend IInteractionRequest in order to get more than a MessageBox.

Well, let’s focus on the subject. Basically, the main idea is to trigger a TriggerAction<T> on the UI’s thread from the view model by using property binding. Once the trigger has been launched, it will create the UserControl (the modal view) and it will be inserted to the element’s control collection which is holding the TriggerAction<T>.

For that view which the UI’s thread has just created, it will set a callback to a button in order to quit from the modal view and get back to the main view with any result.

Following the Line

The finality of my project is to find a way to show modal views presenting encapsulated data in a class by using the MVVM pattern, and of course trying to do it in the simplest way I can. Then, let’s try generics.

So following the specs, I’m raising the trigger by passing as argument a generic class (GenericInteractionRequestEventArgs<T>) containing a callback and an object with all data to be presented in the modal view. The callback accepts that generic type as parameter.

C#
public class GenericInteractionRequestEventArgs<T> : EventArgs
{
    public GenericInteractionRequestEventArgs
    (T _entity, Action<T> _callback, Action _cancelCallback) 
    {
        this.CancelCallback = _cancelCallback;
        this.Callback = _callback;
        this.Entity = _entity;
    }

    public Action CancelCallback { get; private set; }
    public Action<T> Callback { get; private set; }
    public T Entity { get; private set; }
}

public class GenericInteractionAction<T> : TriggerAction<Grid>
{
    private Dictionary<UIElement, bool> currentState = new Dictionary<UIElement, bool>();

    public static readonly DependencyProperty DialogProperty =
		DependencyProperty.Register("Dialog", typeof(GenericInteractionDialogBase<T>), typeof(GenericInteractionAction<T>), new PropertyMetadata(null));

    public GenericInteractionDialogBase<T> Dialog
    {
	get { return (GenericInteractionDialogBase<T>)GetValue(DialogProperty); }
	set { SetValue(DialogProperty, value); }
    }


    protected override void Invoke(object parameter)
    {
	var args = parameter as GenericInteractionRequestEventArgs<T>;
	this.SetDialog(args.Entity, args.Callback, args.CancelCallback, null);
    }

    private void SetDialog(T entity, Action<T> callback, Action cancelCallback, UIElement element)
    {            
	if (this.Dialog is Views.IGenericInteractionView<T>)
	{
		Views.IGenericInteractionView<T> view = this.Dialog as Views.IGenericInteractionView<T>;
		view.SetEntity(entity);

		EventHandler handler = null;
		handler = (s, e) =>
		{
			this.Dialog.ConfirmEventHandler -= handler;
			this.Dialog.CancelEventHandler -= handler;
			this.AssociatedObject.Children.Remove(this.Dialog);

			if (e.ToString() == InteractionType.OK.ToString())
				callback(view.GetEntity());
			else
				cancelCallback();

			this.RestorePreviousState();
		};
			
		this.Dialog.ConfirmEventHandler += handler;
		this.Dialog.CancelEventHandler += 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);
		this.SaveCurrentState();
	}
}

    private void SaveCurrentState()
    {
	IEnumerator en = this.AssociatedObject.Children.GetEnumerator();
	while (en.MoveNext())
	{
		if (en.Current != this.Dialog)
		{
			UIElement element = (UIElement)en.Current;
			this.currentState.Add(element, element.IsEnabled);
			element.IsEnabled = false;
		}
	}
    }

    private void RestorePreviousState()
    {
	foreach (UIElement element in this.currentState.Keys)            
		element.IsEnabled = this.currentState[element];
		
	this.currentState.Clear();
    }
}

Once the trigger has been invoked, it creates the modal view and by using its adapter, it sets the generic property of the view model in order to achieve property binding. Then if we modify some data and press on the modal view’s button, it gets back to the main view model by calling the callback and retrieving the object that has been modified. All of this is done thanks to the adapter, which is responsible to interact between the UI’s thread and the view models. In order to achieve all of this, we need to use a generic TriggerAction. That TriggerAction will contain a generic DependencyProperty that it will be the modal view that we want to show. This modal view must be typed like it is defined in the generic TriggerAction.

C#
public interface IGenericAdapter<T>
{
    IGenericViewModel<T> ViewModel { get; }
}

public interface IGenericInteractionView<T>
{
    void SetEntity(T entity);
    T GetEntity();
}

public interface IGenericViewModel<T> : IGenericInteractionView<T>, INotifyPropertyChanged
{
    T Entity { get; set; }
}

On the other hand, we got GenericInteractionDialogBase<T> that extends UserControl, therefore, it is the code behind each modal view. It will be the handler for routing the callbacks to the view model.

C#
public enum InteractionType
{
    OK,
    Cancel
}

public class GenericInteractionDialogBase<T> : UserControl
{        
    class InteractionEventArgs : EventArgs
    {
        internal InteractionType Type { get; private set; }

        internal InteractionEventArgs(InteractionType _type)
        {
            this.Type = _type;
        }

        public override string ToString()
        {
            return this.Type.ToString();
        }
    }

    public GenericInteractionDialogBase() { }

    public event EventHandler ConfirmEventHandler;
    public event EventHandler CancelEventHandler;

    public void Ok()
    {
        this.OnClose(new InteractionEventArgs(InteractionType.OK));
    }

    public void Cancel()
    {
        this.OnClose(new InteractionEventArgs(InteractionType.Cancel));
    }
        
    private void OnClose(InteractionEventArgs e)
    {
        var handler = (e.Type == InteractionType.OK) ? 
        this.ConfirmEventHandler : this.CancelEventHandler;
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

Using the Code

From now on, if we want to get a custom modal view, we just need to create three classes.
Taking for example the modal view which presents the Animal class, those classes are...

First of all, we need to create a TriggerAction for Animal implement the generic class. This is necessary in order to be used for raising the trigger from the main view.

C#
public class AnimalInteractionAction : GenericInteractionAction<Animal>
{

}

Secondly, we have to create a class for Animal extending GenericInteractionDialogBase. This will be the modal view which once the TriggerAction has been risen, it uses this UserControl as DependencyProperty in the TriggerAction and thanks to that, we are able to retrieve the modal view and set its properties.

C#
public class AnimalInteractionDialog : GenericInteractionDialogBase<animal>
{

}

Thirdly, we will create the modal view setting all XAML and implementing methods and properties in its code behind.

C#
public partial class AnimalInteractionDialogView :

AnimalInteractionDialog, IGenericInteractionView<Animal>, IGenericAdapter<Animal>
{
    private readonly IGenericAdapter<Animal> adapter;

    public AnimalInteractionDialogView()
    {
        this.adapter = new GenericAdapter<Animal>();
        this.DataContext = this.ViewModel;
        InitializeComponent();
    }

    public void SetEntity(Animal entity)
    {
        this.ViewModel.SetEntity(entity);
    }

    public Animal GetEntity()
    {
        return this.ViewModel.GetEntity();
    }

    public IGenericViewModel<Animal> ViewModel
    {
        get { return this.adapter.ViewModel; }
    }

}

Lastly, we just need to set the trigger binding in the main view's XAML. It looks like this:

XML
    <Window x:Class="GenericInteractionRequest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity.
    InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
        xmlns:interactionRequest="clr-namespace:GenericInteractionRequest.Views"
        xmlns:interactions="clr-namespace:GenericInteractionRequest.Interactions"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=
                    System.Windows.Interactivity"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
    
        <i:Interaction.Triggers>
        
            <prism:InteractionRequestTrigger 
        SourceObject="{Binding NotificationToPerson}">
                    <interactions:PersonInteractionAction>
                        <interactions:PersonInteractionAction.Dialog>
                            <interactionRequest:PersonInteractionDialogView />
                        </interactions:PersonInteractionAction.Dialog>
                    </interactions:PersonInteractionAction>
            </prism:InteractionRequestTrigger>

            <prism:InteractionRequestTrigger 
        SourceObject="{Binding NotificationToAnimal}">
                <interactions:AnimalInteractionAction>
                    <interactions:AnimalInteractionAction.Dialog>
                        <interactionRequest:AnimalInteractionDialogView />
                    </interactions:AnimalInteractionAction.Dialog>
                </interactions:AnimalInteractionAction>
            </prism:InteractionRequestTrigger>

        </i:Interaction.Triggers>

        <Button Content="Get People" Height="35" HorizontalAlignment="Left" 
        Margin="12,12,0,0" Name="button1" VerticalAlignment="Top" Width="102" 
        Command="{Binding Path=SetPeopleCommand}"/>
        <Button Content="Get Animal" Height="35" HorizontalAlignment="Left" 
        Margin="120,12,0,0" Name="button2" VerticalAlignment="Top" Width="107" 
        Command="{Binding Path=SetAnimalCommand}"/>
        <TextBlock Height="229" HorizontalAlignment="Left" Margin="12,70,0,0" 
        Name="textBlock1" Text="{Binding Path=Output}" 
        VerticalAlignment="Top" Width="479" />

    </Grid>

</Window>

History

  • 10/16/2011 - 1st revision 
  • 02/03/2012 - 2st revision (includes cancel callback support)
  • 09/19/2012 - 3rd revision (enable and disable controls behind the modal view)  

License

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


Written By
Software Developer (Senior)
Spain Spain
http://www.linkedin.com/in/gerard-castello-viader
https://github.com/gcastellov

Comments and Discussions

 
PraiseVery nice explanation Pin
Member 129594117-Apr-19 20:09
Member 129594117-Apr-19 20:09 
GeneralMy vote of 5 Pin
Member 84790325-Jul-18 0:08
Member 84790325-Jul-18 0:08 
GeneralMy vote of 2 Pin
tbayart28-Jul-14 22:34
professionaltbayart28-Jul-14 22:34 
QuestionIs it possible to move (drag&drop) the dialog? Pin
Member 106200097-Jul-14 2:54
Member 106200097-Jul-14 2:54 
GeneralMy Vote of 5 Pin
Dhadhan6-Apr-14 23:59
Dhadhan6-Apr-14 23:59 
QuestionDoes this work with Silverlight? I believe I only saw WPF mentioned. Pin
Yelnoc48-Oct-13 9:25
Yelnoc48-Oct-13 9:25 
Questiongran articulo Pin
francesc bernades17-Jan-13 7:07
francesc bernades17-Jan-13 7:07 
QuestionModal views without callbacks Pin
DaniloCodelines6-Nov-12 0:51
DaniloCodelines6-Nov-12 0:51 
AnswerRe: Modal views without callbacks Pin
Gerard Castelló Viader6-Nov-12 4:01
Gerard Castelló Viader6-Nov-12 4:01 
GeneralRe: Modal views without callbacks Pin
DaniloCodelines6-Nov-12 5:10
DaniloCodelines6-Nov-12 5:10 
Questionthis.Raised NULL Pin
jharri2629-Oct-12 9:21
jharri2629-Oct-12 9:21 
AnswerRe: this.Raised NULL Pin
Gerard Castelló Viader29-Oct-12 23:34
Gerard Castelló Viader29-Oct-12 23:34 
GeneralRe: this.Raised NULL Pin
jharri2630-Oct-12 3:09
jharri2630-Oct-12 3:09 
GeneralRe: this.Raised NULL Pin
Gerard Castelló Viader30-Oct-12 7:08
Gerard Castelló Viader30-Oct-12 7:08 
GeneralMy vote of 5 Pin
DaniloCodelines19-Sep-12 5:14
DaniloCodelines19-Sep-12 5:14 
GeneralRe: My vote of 5 Pin
Gerard Castelló Viader20-Sep-12 21:52
Gerard Castelló Viader20-Sep-12 21:52 
GeneralMy vote of 5 Pin
Kanasz Robert19-Sep-12 4:30
professionalKanasz Robert19-Sep-12 4:30 
GeneralRe: My vote of 5 Pin
Gerard Castelló Viader19-Sep-12 4:30
Gerard Castelló Viader19-Sep-12 4:30 
SuggestionMemory leaks Pin
DaniloCodelines17-Sep-12 15:59
DaniloCodelines17-Sep-12 15:59 
GeneralRe: Memory leaks Pin
Gerard Castelló Viader19-Sep-12 3:52
Gerard Castelló Viader19-Sep-12 3:52 
GeneralRe: Memory leaks Pin
DaniloCodelines19-Sep-12 4:31
DaniloCodelines19-Sep-12 4:31 
GeneralRe: Memory leaks Pin
DaniloCodelines19-Sep-12 4:51
DaniloCodelines19-Sep-12 4:51 
GeneralRe: Memory leaks Pin
Gerard Castelló Viader20-Sep-12 23:22
Gerard Castelló Viader20-Sep-12 23:22 
SuggestionRe: Memory leaks Pin
Dhadhan8-Apr-14 1:27
Dhadhan8-Apr-14 1:27 
QuestionHow about tab Pin
Ole Teilmann Mortensen8-Feb-12 2:33
Ole Teilmann Mortensen8-Feb-12 2:33 

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

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