WPF MVVM Modal Overlay Dialog only over a View (not Window)

前端 未结 3 1600
旧巷少年郎
旧巷少年郎 2020-12-07 12:07

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

3条回答
  •  借酒劲吻你
    2020-12-07 12:31

    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:

    MVVM dialog modal only inside the containing view

    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:

    
    
    
    
    
        
            
                
                    
                
            
            
                
                    
                
            
        
    
    

提交回复
热议问题