Click here to Skip to main content
15,867,834 members
Articles / MVVM

Open Window, Dialog or Message Box from a ViewModel – Part 1

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
25 Mar 2011CPOL3 min read 11.4K   2  
Open Window, Dialog or Message Box from a ViewModel

Saying that a view-model belongs to the Application layer, and the Application layer doesn't reference upper layers, and must not create or use visual objects, how can I open a Window, Dialog or any kind of Message Box on-the-fly, based on some logic triggered by the view or view-model?

Well, there are several options doing that, one is using kind of service which encapsulates that, providing an interface, so the view-model doesn't really work directly with the upper layer or WPF.

This solution is somehow problematic since the service should be implemented in the Presentation layer and somehow should be exposed to the view-model.

Having DI container is possible but still a bit tricky.

What other options have we got?

Say that you have a view-model that contains a list of email messages and the view renders it as an ItemsControl. Now you want displaying the email message details in a separate window triggered by:

  1. The View - Double clicking or selecting an email and then press Enter same way as Outlook does.
  2. The View-model - Property of a view-model changes, for example: ShowMessageDetails.

Before discussing my solution, I would like to say something about these two options:

  • Opening a window triggered by the view when Routed event is raised or Routed command is executed is quite simple, in that case, you should have a OpenWindowAction, triggered by event or command.
  • Opening a window triggered by the view or view-model based on property change may be trickier, since property is a state, and it may by synchronized with the window state: Opened or Closed. In that case, changing the property should open or close the window, also closing the window should update the property.

Considering the fact that the window may be opened by the view or the view-model, based on event, command or property change, I propose two options: Custom Action and Behavior.

In this post, I'll cover the custom Action solution, and in the next post, I'll cover the Behavior solution which is a bit trickier.

Say that you've got: MessageListViewModel, MessageListView for the email messages view and MessageDetailsViewModel, MessageDetailsView for the email details view should be presented inside a window, let's say MessageDetailsDialog.

Having a SelectedMessage property in MessageListViewModel, let's look at the MessageListView:

XML
<UserControl x:Class="WPFOutlook.PresentationLayer.Views.MessageListView" 
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             xmlns:viewmodels="http://schemas.sela.co.il/advancedwpf" 
             xmlns:views="clr-namespace:WPFOutlook.PresentationLayer.Views" 
             xmlns:behaviors="clr-namespace:WPFOutlook.PresentationLayer.Behaviors" 
             xmlns:i="clr-namespace:System.Windows.Interactivity;
             assembly=System.Windows.Interactivity" 
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"> 
  
    <UserControl.DataContext> 
        <viewmodels:MessageListViewModel /> 
    </UserControl.DataContext> 
  
    <Grid> 
        <DataGrid ItemsSource="{Binding Messages}" 
                  SelectedItem="{Binding SelectedMessage}" 
                  AutoGenerateColumns="False" 
                  CanUserAddRows="False" 
                  CanUserDeleteRows="False"> 
             
            <DataGrid.Columns> 
                <DataGridTextColumn Header="From" 
                Binding="{Binding From}" IsReadOnly="True" /> 
                <DataGridTextColumn Header="Subject" 
                Binding="{Binding Subject}" IsReadOnly="True" /> 
                <DataGridTextColumn Header="Received" 
                Binding="{Binding Received}" IsReadOnly="True" /> 
                <DataGridTextColumn Header="Size" 
                Binding="{Binding Size}" IsReadOnly="True" /> 
            </DataGrid.Columns> 
             
            <i:Interaction.Triggers> 
                <i:EventTrigger EventName="MouseDoubleClick"> 
                    <behaviors:OpenWindowAction WindowUri="/Dialogs/MessageDetailsDialog.xaml" 
                                                IsModal="True" 
                    Owner="{Binding RelativeSource={RelativeSource Mode=FindAncestor, 
                    AncestorType={x:Type Window}}}" 
                    DataContext="{Binding SelectedMessage}" 
                    CloseCommand="{Binding CloseMessageDetailsCommand}" /> 
                </i:EventTrigger> 
            </i:Interaction.Triggers> 
  
        </DataGrid> 
    </Grid> 
</UserControl>

As you can see in line 30, I've attached the DataGrid with a Blend trigger, listening on MouseDoubleClick routed event. Whenever this event is raised, the custom OpenWindowAction is invoked which currently displays our window.

The OpenWindowAction action has the following properties:

  1. WindowUri – A simple Uri points to the XAML file defines our window
  2. IsModaltrue to open the window as modal, false as modeless
  3. Owner – The owner of our window in case we want opening it relative to the owner for example
  4. DataContext – view-model to set in our new window data context, in our case the message details view-model retrieved by the MessageListViewModel.SelectedMessage
  5. CloseCommand – A command notifying that the window is about to be closed, and also if it is possible

Of course, you can add additional properties such as: The type of the window instead or in addition to the WindowUri, a property saying that the view-model should be displayed in a popup instead of a window, etc.

Running this sample and double clicking on the single email, you'll notice the following:

imageimage

Now let's look at the OpenWindowAction:

C#
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Windows; 
using System.Windows.Interactivity; 
using System.ComponentModel; 
using System.Windows.Input; 
using WPFOutlook.ApplicationLayer.Common; 
using System.Windows.Threading; 
using System.Diagnostics; 
  
namespace WPFOutlook.PresentationLayer.Behaviors 
{ 
    public class OpenWindowAction : TriggerAction<DependencyObject> 
    {         
        protected override void Invoke(object parameter) 
        { 
            Assert(CloseCommandProperty.Name, CloseCommand, null); 
            Assert(WindowUriProperty.Name, WindowUri, null); 
  
            if (DataContext == null) 
            { 
                return; 
            } 
  
            if (_isOpen) 
            { 
                return; 
            } 
  
            var window = (Window)Application.LoadComponent(WindowUri); 
            window.Owner = Owner; 
            window.DataContext = DataContext; 
            window.Closing += window_Closing; 
  
            if (IsModal) 
            { 
                window.Show();                 
            } 
            else 
            { 
                window.ShowDialog(); 
            } 
  
            _isOpen = true; 
        } 
  
        private void window_Closing(object sender, CancelEventArgs e) 
        { 
            var window = sender as Window; 
            bool canClose = CloseCommand.CanExecute(window.DialogResult); 
            if (canClose) 
            { 
                CloseCommand.Execute(window.DialogResult); 
                _isOpen = false; 
            } 
  
            e.Cancel = !canClose; 
        } 
    } 
}

As you can see, the Invoke override checks the properties, creates a window from the given window URI, then displays the window.

Whenever the window is trying to close, the CloseCommand is queried by calling the CanExecute method. If everything is fine, the window closes.

You can download the code from here.

Next time, I'll show how to implement the open window Behavior.

License

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


Written By
Architect CodeValue
Israel Israel
Tomer Shamam is a Software Architect and a UI Expert at CodeValue, the home of software experts, based in Israel (http://codevalue.net). Tomer is a speaker in Microsoft conferences and user groups, and in-house courses. Tomer has years of experience in software development, he holds a B.A degree in computer science, and his writings appear regularly in the Israeli MSDN Pulse, and other popular developer web sites such as CodePlex and The Code Project. About his thoughts and ideas you can read in his blog (http://blogs.microsoft.co.il/blogs/tomershamam).

Comments and Discussions

 
-- There are no messages in this forum --