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
andIDialogParameters 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?