Prism. Closing a dialog created with IDialogService

Viewed 5280

I am trying to use a new IDialogService which was discussed in github issue 1666. A New IDialogService for WPF. I like this new feature but I can't find a solution for one case of using IDialogService in compare with InteractionRequest.

There is a button, pressing on which non-modal dialog is opened. If user press the same button one more time, while dialog still open, dialog close. How this behavior should be implemented in a proper way?

MainWindowViewModel

public class MainWindowViewModel : BindableBase
{
    private readonly IDialogService _dialogService;
    public DelegateCommand CustomPopupCommand { get; }

    public MainWindowViewModel(IDialogService dialogService)
    {
        _dialogService = dialogService;
        CustomPopupCommand = new DelegateCommand(OpenClosePopup);
    }

    private void OpenClosePopup()
    {
        // It looks like some additional logic should be implemented here.
        // How to save previously opened IDialogAware instance and close it if needed?
        _dialogService.Show("CustomPopupView", new DialogParameters("Title=Good Title"), result => { });
    }
}

CustomPopupViewModel

public class CustomPopupViewModel : BindableBase, IDialogAware
{
    private string _title;
    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }
    public DelegateCommand<object> CloseCommand { get; }

    public CustomPopupViewModel()
    {
        CloseCommand = new DelegateCommand<object>(CloseDialog);
    }

    public event Action<IDialogResult> RequestClose;

    public void OnDialogOpened(IDialogParameters parameters)
    {
        Title = parameters.GetValue<string>(nameof(Title));
    }

    public void OnDialogClosed()
    {
    }

    public bool CanCloseDialog()
    {
        return true;
    }

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

    private void CloseDialog(object button)
    {
        RaiseRequestClose(
            new DialogResult(button is ButtonResult buttonResult ? buttonResult : ButtonResult.Cancel));
    }
}

I have no idea how can it be implemented in proper way because method IDialogService.Show() fully decoupled from knowing about ViewModel and View. Of course except the name of View.

2 Answers

You can always send an event through the event aggregator, probably you have to pass some id in the dialog parameters to close the right dialog if there's more than one open at a time.

But this feels really clunky, I'd prefer to get an IDisposable from Show/ShowDialog that closes the dialog on Dispose.

public CustomPopupViewModel(IEventAggregator eventAggregator)
{
    eventAggregator.GetEvent<CloseDialogEvent>().Subscribe( id => { if (id == _id) CloseMe(); } );
}

public void OnDialogOpened(IDialogParameters parameters)
{
    _id = parameters.GetValue<string>("id");
}

_dialogService.Show("CustomPopupView", new DialogParameters("id=12345"), result => { });

_eventAggregator.GetEvent<CloseDialogEvent>().Publish("12345");

I find it simplest to use Prism implementation of the subscriber pattern I use a class that will be used in the pattern and is communicated:

public class DialogStatus
{ 
    public bool DialogResult { get; set; }
}

In my sample, I show you how I do this using a Login Dialog in WPF using Prism 8.0.0.1909

in the App.cs

protected override void OnInitialized()
{
   var login = Container.Resolve<LoginDialog>();
   var result = login.ShowDialog();
   if (result.HasValue && result.Value)
   {
       base.OnInitialized();
   }
   else
   {
      Application.Current.Shutdown();
   }
}

in LoginDialog.cs in my Dialogs folder

public partial class LoginDialog : Window
{
    public LoginDialog(IEventAggregator eventAggregator)
    {
        InitializeComponent();
        eventAggregator.GetEvent<CloseDialogWindowEvent>().Subscribe(OnCloseWindow);
    }

    private void OnCloseWindow(DialogStatus obj)
    {
        base.DialogResult = obj.DialogResult;
    }
}

now anywhere in my code, in a ViewModel of view a custom control's view model, the only thing I need to do is pass the IEventAggregator in in the constructor and save it in a field.

private readonly IEventAggregator _eventAggregator;
public LoginControlViewModel(IAuthenticationService authenticationService
                            , IConnectFileImporterService connectFileImporterService
                            , IDialogService dialogService
                            , IEventAggregator eventAggregator)
{

   _eventAggregator= eventAggregator;
   // the other code
}

I can now close my dialog, and in this sample return true to falls to my OnInitalize in my App.cs from anywhere by calling

_eventAggregator.GetEvent<CloseDialogWindowEvent>().Publish(new CloseDialogWindowEvent() { DialogResult = true });

or

_eventAggregator.GetEvent<CloseDialogWindowEvent>().Publish(new CloseDialogWindowEvent() { DialogResult = false});
Related