I\'m pretty much new to the MVVM architecture design...
I was struggling lately to find a suitable control already written for such a purpose but had no luck, so I r
Well not exactly an answer to my question, but here is the result of doing this dialog, complete with code so you can use it if you wish - free as in free speech and beer:

XAML Usage in another view (here CustomerView):
Triggering from parent ViewModel (here CustomerViewModel):
public ModalDialogViewModel Dialog // dialog view binds to this
{
get
{
return _dialog;
}
set
{
_dialog = value;
base.OnPropertyChanged("Dialog");
}
}
public void AskSave()
{
Action OkCallback = () =>
{
if (Dialog != null) Dialog.Hide();
Save();
};
if (Email.Length < 10)
{
Dialog = new ModalDialogViewModel("This email seems a bit too short, are you sure you want to continue saving?",
ModalDialogViewModel.DialogButtons.Ok,
ModalDialogViewModel.CreateCommands(new Action[] { OkCallback }));
Dialog.Show();
return;
}
if (LastName.Length < 2)
{
Dialog = new ModalDialogViewModel("The Lastname seems short. Are you sure that you want to save this Customer?",
ModalDialogViewModel.CreateButtons(ModalDialogViewModel.DialogMode.TwoButton,
new string[] {"Of Course!", "NoWay!"},
OkCallback,
() => Dialog.Hide()));
Dialog.Show();
return;
}
Save(); // if we got here we can save directly
}
Here is the code:
ModalDialogView XAML:
ModalDialogView code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DemoApp.View
{
///
/// Interaction logic for ModalDialog.xaml
///
public partial class ModalDialog : UserControl
{
public ModalDialog()
{
InitializeComponent();
Visibility = Visibility.Hidden;
}
private bool _parentWasEnabled = true;
public bool IsShown
{
get { return (bool)GetValue(IsShownProperty); }
set { SetValue(IsShownProperty, value); }
}
// Using a DependencyProperty as the backing store for IsShown. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsShownProperty =
DependencyProperty.Register("IsShown", typeof(bool), typeof(ModalDialog), new UIPropertyMetadata(false, IsShownChangedCallback));
public static void IsShownChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == true)
{
ModalDialog dlg = (ModalDialog)d;
dlg.Show();
}
else
{
ModalDialog dlg = (ModalDialog)d;
dlg.Hide();
}
}
#region OverlayOn
public UIElement OverlayOn
{
get { return (UIElement)GetValue(OverlayOnProperty); }
set { SetValue(OverlayOnProperty, value); }
}
// Using a DependencyProperty as the backing store for Parent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OverlayOnProperty =
DependencyProperty.Register("OverlayOn", typeof(UIElement), typeof(ModalDialog), new UIPropertyMetadata(null));
#endregion
public void Show()
{
// Force recalculate binding since Show can be called before binding are calculated
BindingExpression expressionOverlayParent = this.GetBindingExpression(OverlayOnProperty);
if (expressionOverlayParent != null)
{
expressionOverlayParent.UpdateTarget();
}
if (OverlayOn == null)
{
throw new InvalidOperationException("Required properties are not bound to the model.");
}
Visibility = System.Windows.Visibility.Visible;
_parentWasEnabled = OverlayOn.IsEnabled;
OverlayOn.IsEnabled = false;
}
private void Hide()
{
Visibility = Visibility.Hidden;
OverlayOn.IsEnabled = _parentWasEnabled;
}
}
}
ModalDialogViewModel:
using System;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Windows;
using System.Linq;
namespace DemoApp.ViewModel
{
///
/// Represents an actionable item displayed by a View (DialogView).
///
public class ModalDialogViewModel : ViewModelBase
{
#region Nested types
///
/// Nested enum symbolizing the types of default buttons used in the dialog -> you can localize those with Localize(DialogMode, string[])
///
public enum DialogMode
{
///
/// Single button in the View (default: OK)
///
OneButton = 1,
///
/// Two buttons in the View (default: YesNo)
///
TwoButton,
///
/// Three buttons in the View (default: AbortRetryIgnore)
///
TreeButton,
///
/// Four buttons in the View (no default translations, use Translate)
///
FourButton,
///
/// Five buttons in the View (no default translations, use Translate)
///
FiveButton
}
///
/// Provides some default button combinations
///
public enum DialogButtons
{
///
/// As System.Window.Forms.MessageBoxButtons Enumeration Ok
///
Ok,
///
/// As System.Window.Forms.MessageBoxButtons Enumeration OkCancel
///
OkCancel,
///
/// As System.Window.Forms.MessageBoxButtons Enumeration YesNo
///
YesNo,
///
/// As System.Window.Forms.MessageBoxButtons Enumeration YesNoCancel
///
YesNoCancel,
///
/// As System.Window.Forms.MessageBoxButtons Enumeration AbortRetryIgnore
///
AbortRetryIgnore,
///
/// As System.Window.Forms.MessageBoxButtons Enumeration RetryCancel
///
RetryCancel
}
#endregion
#region Members
private static Dictionary _translations = null;
private bool _dialogShown;
private ReadOnlyCollection _commands;
private string _dialogMessage;
private string _dialogHeader;
#endregion
#region Class static methods and constructor
///
/// Creates a dictionary symbolizing buttons for given dialog mode and buttons names with actions to berform on each
///
/// Mode that tells how many buttons are in the dialog
/// Names of buttons in sequential order
/// Callbacks for given buttons
///
public static Dictionary CreateButtons(DialogMode mode, string[] names, params Action[] callbacks)
{
int modeNumButtons = (int)mode;
if (names.Length != modeNumButtons)
throw new ArgumentException("The selected mode needs a different number of button names", "names");
if (callbacks.Length != modeNumButtons)
throw new ArgumentException("The selected mode needs a different number of callbacks", "callbacks");
Dictionary buttons = new Dictionary();
for (int i = 0; i < names.Length; i++)
{
buttons.Add(names[i], callbacks[i]);
}
return buttons;
}
///
/// Static contructor for all DialogViewModels, runs once
///
static ModalDialogViewModel()
{
InitTranslations();
}
///
/// Fills the default translations for all modes that we support (use only from static constructor (not thread safe per se))
///
private static void InitTranslations()
{
_translations = new Dictionary();
foreach (DialogMode mode in Enum.GetValues(typeof(DialogMode)))
{
_translations.Add(mode, GetDefaultTranslations(mode));
}
}
///
/// Creates Commands for given enumeration of Actions
///
/// Actions to create commands from
/// Array of commands for given actions
public static ICommand[] CreateCommands(IEnumerable actions)
{
List commands = new List();
Action[] actionArray = actions.ToArray();
foreach (var action in actionArray)
{
//RelayExecuteWrapper rxw = new RelayExecuteWrapper(action);
Action act = action;
commands.Add(new RelayCommand(x => act()));
}
return commands.ToArray();
}
///
/// Creates string for some predefined buttons (English)
///
/// DialogButtons enumeration value
/// String array for desired buttons
public static string[] GetButtonDefaultStrings(DialogButtons buttons)
{
switch (buttons)
{
case DialogButtons.Ok:
return new string[] { "Ok" };
case DialogButtons.OkCancel:
return new string[] { "Ok", "Cancel" };
case DialogButtons.YesNo:
return new string[] { "Yes", "No" };
case DialogButtons.YesNoCancel:
return new string[] { "Yes", "No", "Cancel" };
case DialogButtons.RetryCancel:
return new string[] { "Retry", "Cancel" };
case DialogButtons.AbortRetryIgnore:
return new string[] { "Abort", "Retry", "Ignore" };
default:
throw new InvalidOperationException("There are no default string translations for this button configuration.");
}
}
private static string[] GetDefaultTranslations(DialogMode mode)
{
string[] translated = null;
switch (mode)
{
case DialogMode.OneButton:
translated = GetButtonDefaultStrings(DialogButtons.Ok);
break;
case DialogMode.TwoButton:
translated = GetButtonDefaultStrings(DialogButtons.YesNo);
break;
case DialogMode.TreeButton:
translated = GetButtonDefaultStrings(DialogButtons.YesNoCancel);
break;
default:
translated = null; // you should use Translate() for this combination (ie. there is no default for four or more buttons)
break;
}
return translated;
}
///
/// Translates all the Dialogs with specified mode
///
/// Dialog mode/type
/// Array of translations matching the buttons in the mode
public static void Translate(DialogMode mode, string[] translations)
{
lock (_translations)
{
if (translations.Length != (int)mode)
throw new ArgumentException("Wrong number of translations for selected mode");
if (_translations.ContainsKey(mode))
{
_translations.Remove(mode);
}
_translations.Add(mode, translations);
}
}
#endregion
#region Constructors and initialization
public ModalDialogViewModel(string message, DialogMode mode, params ICommand[] commands)
{
Init(message, Application.Current.MainWindow.GetType().Assembly.GetName().Name, _translations[mode], commands);
}
public ModalDialogViewModel(string message, DialogMode mode, params Action[] callbacks)
{
Init(message, Application.Current.MainWindow.GetType().Assembly.GetName().Name, _translations[mode], CreateCommands(callbacks));
}
public ModalDialogViewModel(string message, Dictionary buttons)
{
Init(message, Application.Current.MainWindow.GetType().Assembly.GetName().Name, buttons.Keys.ToArray(), CreateCommands(buttons.Values.ToArray()));
}
public ModalDialogViewModel(string message, string header, Dictionary buttons)
{
if (buttons == null)
throw new ArgumentNullException("buttons");
ICommand[] commands = CreateCommands(buttons.Values.ToArray());
Init(message, header, buttons.Keys.ToArray(), commands);
}
public ModalDialogViewModel(string message, DialogButtons buttons, params ICommand[] commands)
{
Init(message, Application.Current.MainWindow.GetType().Assembly.GetName().Name, ModalDialogViewModel.GetButtonDefaultStrings(buttons), commands);
}
public ModalDialogViewModel(string message, string header, DialogButtons buttons, params ICommand[] commands)
{
Init(message, header, ModalDialogViewModel.GetButtonDefaultStrings(buttons), commands);
}
public ModalDialogViewModel(string message, string header, string[] buttons, params ICommand[] commands)
{
Init(message, header, buttons, commands);
}
private void Init(string message, string header, string[] buttons, ICommand[] commands)
{
if (message == null)
throw new ArgumentNullException("message");
if (buttons.Length != commands.Length)
throw new ArgumentException("Same number of buttons and commands expected");
base.DisplayName = "ModalDialog";
this.DialogMessage = message;
this.DialogHeader = header;
List commandModels = new List();
// create commands viewmodel for buttons in the view
for (int i = 0; i < buttons.Length; i++)
{
commandModels.Add(new CommandViewModel(buttons[i], commands[i]));
}
this.Commands = new ReadOnlyCollection(commandModels);
}
#endregion
#region Properties
///
/// Checks if the dialog is visible, use Show() Hide() methods to set this
///
public bool DialogShown
{
get
{
return _dialogShown;
}
private set
{
_dialogShown = value;
base.OnPropertyChanged("DialogShown");
}
}
///
/// The message shown in the dialog
///
public string DialogMessage
{
get
{
return _dialogMessage;
}
private set
{
_dialogMessage = value;
base.OnPropertyChanged("DialogMessage");
}
}
///
/// The header (title) of the dialog
///
public string DialogHeader
{
get
{
return _dialogHeader;
}
private set
{
_dialogHeader = value;
base.OnPropertyChanged("DialogHeader");
}
}
///
/// Commands this dialog calls (the models that it binds to)
///
public ReadOnlyCollection Commands
{
get
{
return _commands;
}
private set
{
_commands = value;
base.OnPropertyChanged("Commands");
}
}
#endregion
#region Methods
public void Show()
{
this.DialogShown = true;
}
public void Hide()
{
this._dialogMessage = String.Empty;
this.DialogShown = false;
}
#endregion
}
}
ViewModelBase has :
public virtual string DisplayName { get; protected set; }
and implements INotifyPropertyChanged
Some resources to put in the resource dictionary: