Prism pop-up new window in WPF

15,848

Solution 1

Do you use Prism 7?
if Yes, then stop reading now and go to this Prism 7 answer below
if No, then continue reading


Update
What lead me to put another answer was the inability to apply the accepted answer on my project which using the Prism 6,
but after putting the original answer (see it below) and discussing it in comments, I discovered that the core problem was: The Prism 6 changed the namespaces of some classes, all the classes which used in the accepted answer is still exists in Prism 6, but in another dlls and namespaces
So if you are using Prism 6, you can apply the accepted answer with those modifications

first replace those namesapces

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pi="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pit="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"

with the following namespaces

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://prismlibrary.com/"

second update the XAML as the following

<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
    <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
            <prism:PopupWindowAction>
                <prism:PopupWindowAction.WindowContent>
                    <views:CustomPopupView />
                </prism:PopupWindowAction.WindowContent>
            </prism:PopupWindowAction>
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>
</Button>

NOTE 1
Make sure that the view you are using (in the example above <views:CustomPopupWindow>) is NOT a window, or you will receive an exception.

NOTE 2
These modifications are required ONLY in case you are using Prism 6. because (As I said in the Original Answer below) the dlls which used by the accepted answer is deprecated in Prism 6.

NOTE 3
Make sure you are referencing the Prism.Wpf dll, which could be downloaded from Nuget.


Original Answer
The accepted answer is directed to the Prism 5, it uses this library which is deprecated in the Prism 6.

Actually the article which you reference in your question was very helpful (at least for me), and it does not crash.

I will try to summary that article.

ViewModel

public class ViewModel : BindableBase
{
    public ViewModel()
    {
        _showWindowCommand = new DelegateCommand(ShowWindow);
        _interactionRequest = new InteractionRequest<Confirmation>();
    }

    private readonly DelegateCommand _showWindowCommand;
    private InteractionRequest<Confirmation> _interactionRequest;

    public ICommand ShowWindowCommand
    {
        get { return _showWindowCommand; }
    }

    public IInteractionRequest InteractionRequest
    {
        get { return _interactionRequest; }
    }

    private void ShowWindow()
    {
        _interactionRequest.Raise(
            new Confirmation(),
            OnWindowClosed);
    }

    private void OnWindowClosed(Confirmation confirmation)
    {
        if (confirmation.Confirmed)
        {
            //perform the confirmed action...
        }
        else
        {

        }
    }
}

XAML

<Button Command="{Binding ShowWindowCommand}" Content="Show Window" >
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Raised" SourceObject="{Binding InteractionRequest}">
            <i:EventTrigger.Actions>
                <local:ShowWindowAction></local:ShowWindowAction>
            </i:EventTrigger.Actions>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

and you will need to use those namespaces

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:The namespace which contains the ShowWindowAction">

ActionTrigger

using System;
using Prism.Interactivity.InteractionRequest;
using System.Windows.Interactivity;
using System.Windows;

public class ShowWindowAction : TriggerAction<FrameworkElement>
{
    protected override void Invoke(object parameter)
    {
        InteractionRequestedEventArgs args = parameter as InteractionRequestedEventArgs;
        if (args != null)
        {
            Confirmation confirmation = args.Context as Confirmation;
            if (confirmation != null)
            {
                // Replace ParametersWindow with your own window.
                ParametersWindow window = new ParametersWindow();
                EventHandler closeHandler = null;
                closeHandler = (sender, e) =>
                {
                    window.Closed -= closeHandler;
                    args.Callback();
                };
                window.Closed += closeHandler;
                window.Show();
            }
        }
    }
}

Explanation

  1. You need Prism.Core and Prism.Wpf dlls (at least) to make this code work.
  2. ShowWindow method, will trigger the Invoke method of the ShowWindowAction, which will really show the window.
  3. you can handle the closing of the window in the OnWindowClosed, which we passed it as a callback to the ShowWindowAction class, and we called it from there when the the window really closed.

Solution 2

Luckily, Prism 5.0 (and I assume 6.0 too, haven't worked with it yet), has a class called InteractionRequest<T> which you can use from code to raise interaction requests.

An interaction request is basically a window, that asks the user for a certain action and calls a callback (if necessary or desired) with the users decisions/actions.

public class ShellViewModel : BindableBase
{
    private readonly IRegionManager regionManager;

    public ShellViewModel(IRegionManager regionManager)
    {
        if (regionManager == null)
            throw new ArgumentNullException("regionManager");

        this.regionManager = regionManager;
        this.OptionSettingConfirmationRequest = new InteractionRequest<IConfirmation>();

        openConnectionOptionsCommand = new DelegateCommand(RaiseConnectionOptionsRequest);
    }

    public InteractionRequest<IConfirmation> OptionSettingConfirmationRequest { get; private set; }

    private readonly ICommand openConnectionOptionsCommand;
    public ICommand OpenConnectionOptionsCommand { get { return openConnectionOptionsCommand; } }

    private void RaiseConnectionOptionsRequest()
    {
        this.OptionSettingConfirmationRequest.Raise(new Confirmation { Title = "Options not saved. Do you wish to save?" }, OnConnectionOptionsResponse);
    }

    protected virtual void OnConnectionOptionsResponse(IConfirmation context)
    {
        if(context.Confirmed)
        {
            // save it
        }

        // otherwise do nothing
    }
}

In XAML you would do something like

<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
    <i:Interaction.Triggers>
        <pit:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
            <pie:LazyPopupWindowAction RegionName="ConnectionSettings" 
                                NavigationUri="ConnectionSettingsView" IsModal="True" />
        </pit:InteractionRequestTrigger>
    </i:Interaction.Triggers>
</Button>

I used my own implemetation of PopupWindowAction (see github project page for it's implementation) called LazyPopupWindowAction, which will instantiate the embedded ConnectionSettingsView View on click. If you don't care that your view is instantiated only once, feel free to use PopupWindowAction, then it will be instantiated at the same time as the View containing the action.

It's basically copy & paste with cutting some useless lines from one of my projects. I used IConfirmation and INotification interfaces instead of the concrete implementations.

XAML with PopupWindowAction

<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
    <i:Interaction.Triggers>
        <pit:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
            <pi:PopupWindowAction>
                <pi:PopupWindowAction.WindowContent>
                    <views:CustomPopupView />
                </pi:PopupWindowAction.WindowContent>
            </pi:PopupWindowAction>
        </pit:InteractionRequestTrigger>
    </i:Interaction.Triggers>
</Button>

Namespace declarations

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pi="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pit="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pie="clr-namespace:MyProject.UI.Prism.Interactivity;assembly=MyProject.UI"

Update: Since people keep asking about the LazyPopupWindowAction, I've put the source in a GitHub Gist. Basically it's based on the PopupWindowAction from Prims 5 (and for Prism, haven't test it with Prism 6 yet, probably won't work w/o adjustments) and does the exact same thing, but also adds Region and Navigation support with the opened window, something that I needed in my application.

One thing I disliked about the default implementation was, that the view and it's viewmodel will be instantiated at the same time the Shell gets instantiated and the ViewModel remains in it's state, when you close it (it was actually just hidden).

Solution 3

This answer is for Prism 7 only,
if you use a previous version of Prism (6 and below)
then this answer is NOT for you

Prism 7 changed the way of opening new windows drastically.
Here is the offical documentation if you want to read it.

Here is also a Youtube video explaining this idea by the creator of the Prism library.


Prism 7 introduced DialogService, a completely new way to open new window.

  1. Create new UserControl.xaml which will represent the content of the window.
    the simplest, empty content could be
<UserControl x:Class="YourUserControlName"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
    </Grid>
</UserControl>
  1. Create the corresponding view-odel for this view.
    This view-model MUST implement IDialogAware interface.
    Here an example
public class BaseDialogViewModel : IDialogAware
{
    public string Title { get; }

    public event Action<IDialogResult> RequestClose;

    public virtual void RaiseRequestClose(IDialogResult dialogResult)
    {
        RequestClose?.Invoke(dialogResult);
    }

    public virtual bool CanCloseDialog()
    {
        return true;
    }

    public virtual void OnDialogClosed()
    {

    }

    public virtual void OnDialogOpened(IDialogParameters parameters)
    {

    }
}
  1. You have to register the Dialog like this
public void RegisterTypes(IContainerRegistry containerRegistry)
{
    // replace 'YourUserControlName' with the class of the view which you created in setp 1
    containerRegistry.RegisterDialog<YourUserControlName>();
}
  1. The last step is to show the dialog whenever you want
    Usually, you want to show the dialog when the user clicks on the button or does an action,
    so the following code usually will be executed when some command executed,
    but again it is up to you.
_dialogService.ShowDialog(nameof(YourUserControlName), new DialogParameters(), action);
  • the _dialogService is of type IDialogService and is injected in the view-model, where you will use it, example
public MainViewModel(IDialogService dialogService) 
{
   this._dialogService = dialogService;
}

Al the Previous steps are required to show the window.


There are some other optional steps If you want them (Not required)

  1. You can specify the properties of the Window by adding the following prism:Dialog.WindowStyle xaml element
<UserControl x:Class="YourUserControlName"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True">

    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
            <Setter Property="ResizeMode" Value="NoResize"/>
            <Setter Property="ShowInTaskbar" Value="False"/>
            <Setter Property="WindowState" Value="Maximized"/>
        </Style>
    </prism:Dialog.WindowStyle>

    <Grid>
    </Grid>
</UserControl>
  1. You can create an extension method for the functionality of showing the window
public static class DialogServiceExtensions
{
    public static void ShowWindowTest(this IDialogService dialogService, Action<IDialogResult> action)
    {
        dialogService.ShowDialog(nameof(WindowTestView), new DialogParameters(), action);
    }
}

Prism documentation recommends that but does NOT require it.


if you want a boilerplate setup for new Prism 7 WPF application with .NET Core 3.1, then you can check-out this Github repository
It contains the above-metioned setup and a lot of other useful features for starting a WPF Prism Application.

Disclaimer: I am the author of the repository

Share:
15,848
Neil
Author by

Neil

Updated on June 09, 2022

Comments

  • Neil
    Neil almost 2 years

    How can I open/close a new window in WPF without violating rules of the MVVM pattern?
    I just want to mimic the login module of ms office outlook.

    I've already read this article, but there are an error in passing a parameter confirmation

    I'm currently using prism 5.0.

  • Neil
    Neil over 8 years
    thank you for giving me some idea..regarding this...THANKS YOU VERY MUCH
  • Hakan Fıstık
    Hakan Fıstık about 7 years
    According to this link this library has been Deprecated, do you any other solutions for Prism 6, Thanks.
  • Hakan Fıstık
    Hakan Fıstık about 7 years
    Another note : how do you set Label="Options" of the Button class, there is no property like this for the Button. Do you mean Content="Options" instead ?
  • Tseng
    Tseng about 7 years
    What's the difference? The PopupWindowAction is still there in Prism 6 (github.com/PrismLibrary/Prism/blob/master/Source/Wpf/Prism.‌​Wpf/…) and both IConfirmation and Confirmation classes are still present in both frameworks. Except that Prism5 was (at that point) still maintained by MS and Prims6 is no in community hands, nothing on the usage of the classes changed. There is no need for own TriggerAction except when you need something that PopupWindowAction doesn't offer (i.e. lazy loading, that I required fresh view and viewmodel on each call)
  • Tseng
    Tseng about 7 years
    @HakamFostok: And yes, it was meant to be Content='"Options", most of the code I copied over from a project I was working at that time and retrofitted it. I was using RibbonControlsLibrary and it's RibbonSplitButton which uses Label rather than Content for the description
  • Hakan Fıstık
    Hakan Fıstık about 7 years
    @Tseng yes you are right, I think the differences is the namespaces, the classes is still exists, but the namespaces has changed. Also I just provided another way by using custom ActionTrigger maybe this could be helpful for others, also I am using an i:EventTrigger instead of InteractionRequestTrigger, but yes in general our answers is very near to each other.
  • Shimmy Weitzhandler
    Shimmy Weitzhandler over 6 years
    What's the ShellViewModel in your code? Does it refer to MainWindowViewModel? And where should the InteractionRequest be located, is it in MainWindow.xaml?
  • Shimmy Weitzhandler
    Shimmy Weitzhandler over 6 years
    How do I cause the interaction from a different place outside the ShellViewModel?
  • Hakan Fıstık
    Hakan Fıstık over 6 years
    @Shimmy you put the comment in the wrong place, I do not have ShellViewModel :)
  • Shimmy Weitzhandler
    Shimmy Weitzhandler over 6 years
    OK Sorry, I'm deleting my previous comment. I have a different question tho, I have a function Task<Credentials> GetCredentials which I'm calling the interaction request from it. My problem is that we get the interaction result in a callback. How can I return the result from the callback to the upper function?
  • Lewis Cianci
    Lewis Cianci over 5 years
    This answer is a little bit confusing. It says not to use a Window otherwise it will crash, but doesn't specify what should be used instead? Edit: This is further confused by having the word "Window" in "CustomPopupWindow"
  • Hakan Fıstık
    Hakan Fıstık over 5 years
    @LewisCianci you have to use 'UserControl' instead of the window, I will update my answer. Thank you