Skip to content

A New IDialogService for WPF #1666

Closed
Closed
@brianlagunas

Description

Currently, the only way to show any type of dialog with Prism is by using he PopupWindowAction in combination with System.Windows.Interactivity. To be honest, I really dislike this approach. It's over complex, highly verbose, difficult to implement, and is very limited. The limitations are covered pretty well in Issue #864

Instead, I would like to create a new IDialogService API that will replace the PopupWindowAction altogether. This service will allow developers to show any dialog they want either modal, or non-modal, and have complete control over their dialog logic.

The current implementation looks like this:

    public interface IDialogService
    {
        void Show(string name, IDialogParameters parameters, Action<IDialogResult> callback);
        void ShowDialog(string name, IDialogParameters parameters, Action<IDialogResult> callback);
    }

The idea here is that Prism will no longer provide any built-in dialogs like Notification or Confirmation. Mainly because the Prism implementations are UGLY and will never match the styling of your beautiful WPF application. So, it's important that you are able to register your own dialogs.

Create Your Dialog View

Your dialog view is a simple UserControl that can be designed anyway you please. The only requirement it has a ViewModel that implements IDialogAware set as it's DataContext. Preferrably, it will utilize the ViewModelLocator

<UserControl x:Class="HelloWorld.Dialogs.NotificationDialog"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True"
             Width="300" Height="150">
    <Grid x:Name="LayoutRoot" Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TextBlock Text="{Binding Message}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="0" TextWrapping="Wrap" />
        <Button Command="{Binding CloseDialogCommand}" Content="OK" Width="75" Height="25" HorizontalAlignment="Right" Margin="0,10,0,0" Grid.Row="1" IsDefault="True" />
    </Grid>
</UserControl>

Create Your Dialog ViewModel

Next you need a ViewModel that implements IDialogAware which is defined as follows

    public interface IDialogAware
    {
        bool CanCloseDialog();
        string IconSource { get; set; }
        void OnDialogClosed();
        void OnDialogOpened(IDialogParameters parameters);
        string Title { get; set; }
        event Action<IDialogResult> RequestClose;
    }

There is currently a DialogViewModelBase class that implements this for you to make it easier to create your dialog VMs.

    public class DialogViewModelBase : BindableBase, IDialogAware
    {
        private string _iconSource;
        public string IconSource
        {
            get { return _iconSource; }
            set { SetProperty(ref _iconSource, value); }
        }

        private string _title;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        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)
        {
            
        }
    }

In your VM, you place any new properties and methods that you need in order for your dialog to function. The important thing is when you want to close the dialog, just call RaiseCloseDialog and provide your DialogResult.

    public class NotificationDialogViewModel : DialogViewModelBase
    {
        private string _message;
        public string Message
        {
            get { return _message; }
            set { SetProperty(ref _message, value); }
        }

        public DelegateCommand CloseDialogCommand { get; set; }

        public NotificationDialogViewModel()
        {
            Title = "Notification";
            CloseDialogCommand = new DelegateCommand(CloseDialog);
        }

        private void CloseDialog()
        {
            RaiseRequestClose(new DialogResult(true));
        }

        public override void OnDialogOpened(IDialogParameters parameters)
        {
            Message = parameters.GetValue<string>("message");
        }
    }

Register the Dialog

To register a dialog, you must have a View (UserControl) and a corresponding ViewModel (which must implement IDialogAware). In the RegisterTypes method, simply register your dialog like you would any other service by using the IContainterRegistery.RegisterDialog method.

 protected override void RegisterTypes(IContainerRegistry containerRegistry)
 {
     containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
 }

Using the Dialog Service

To use the dialog service you simply ask for the service in your VM ctor.

public MainWindowViewModel(IDialogService dialogService)
{
    _dialogService = dialogService;
}

Then call either Show or ShowDialog providing the name of the dialog, any parameters your dialogs requires, and then handle the result via a call back

private void ShowDialog()
{
    var message = "This is a message that should be shown in the dialog.";
    //using the dialog service as-is
    _dialogService.ShowDialog("NotificationDialog", new DialogParameters($"message={message}"), r =>
    {
        if (!r.Result.HasValue)
            Title = "Result is null";
        else if (r.Result == true)
            Title = "Result is True";
        else if (r.Result == false)
            Title = "Result is False";
        else
            Title = "What the hell did you do?";
    });
}

Simplify your Application Dialog APIs

The intent of the dialog API is not to try and guess exactly what type of parameters your need for all of your dialogs, but rather to just create and show the dialogs. To simplify common dialogs in your application the guidance will be to create an extension methods to simplify your applications dialogs.

For example:

public static class DialogServiceEstensions
{
    public static void ShowNotification(this IDialogService dialogService, string message, Action<IDialogResult> callBack)
    {
        dialogService.ShowDialog("NotificationDialog", new DialogParameters($"message={message}"), callBack);
    }
}

Then to call your Notifications use the new and improved API that you created specifically for your app.

    _dialogService.ShowNotification(message, r =>
    {
        if (!r.Result.HasValue)
            Title = "Result is null";
        else if (r.Result == true)
            Title = "Result is True";
        else if (r.Result == false)
            Title = "Result is False";
        else
            Title = "What the hell did you do?";
    });

I Need your Help

You can check out the current dialog code here: https://github.com/PrismLibrary/Prism/tree/Interactivity-Improvements/Source/Wpf/Prism.Wpf/Services/Dialogs

You can also play with the sample here: https://github.com/PrismLibrary/Prism/tree/Interactivity-Improvements/Sandbox/Wpf/HelloWorld

Play with it, tell me what you think. Experiment, and try to find what does or doesn't work for you.

A couple of things that I know I still need to figure out

  • How to control the host dialog Window properties
  • How to control the parent Window of the dialog (maybe it's always the active window)
  • How to create an extension point to provide custom Windows to act as the dialog host (think Infragistics, DevExpress, Telerik, etc)
  • What exactly should I return in the DialogResult? Right now it's just a bool? Result and IDialogParameters Parameters
  • probably some other stuff I am not thinking about.

To clarify, this is to replace the PopupWindowAction. I want to remove that mess completely from Prism

What does everyone think?

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions