add a check with MessageBox when DataGrid is changed

前端 未结 3 1745
隐瞒了意图╮
隐瞒了意图╮ 2020-12-12 02:58

I have a desktop app already released (so I\'d appreciate an answer that keeps the changes and regression tests at a minimum) and I need to add a consistency check Can

相关标签:
3条回答
  • 2020-12-12 03:01

    What I've done till now is

    against MVVM, because I've put the alert in the Model

    The solution from @Tesseract, subclassing ObservableCollection and subscribing RemoveItem is already MVVM oriented.

    What is still missing is a correct, modern way to send the alert from the ViewModel. This is where Mahapps approach would be helpful.

    • Use an attached property in your Window to register your view model with the dialog sub-system.

    In your XAML

    Dialog:DialogParticipation.Register="{Binding}"
    

    where the attached property DialogPartecipation will keep track of the views through a dictionary

    public static class DialogParticipation
    {
        private static readonly IDictionary<object, DependencyObject> ContextRegistrationIndex = new Dictionary<object, DependencyObject
    
    • You can instantiate DialogCoordinator directly or inject the interface IDialogCoordinator into their view model

    The DialogCoordinator will match the ViewModel to the View

    public class DialogCoordinator : IDialogCoordinator
    {
        public static readonly IDialogCoordinator Instance = new DialogCoordinator();
    

    through the above said attached property (the context being the view model)

    var association = DialogParticipation.GetAssociation(context);
    

    and display the dialog, invoking the appropriate method on the retrieved view: this is how, if you have multiple windows open, the dialog will display on the correct window.

    0 讨论(0)
  • 2020-12-12 03:07

    If only the ObservableCollection implemented a "previewCollectionChanged", things would be so much easier.

    For your needs I would recommend creating a subclass of ObservableCollection and overloading the protected method RemoveItem.
    Depending on what you are doing with your application, you might want to override other methods than just RemoveItem (like ClearItems).

    When subclassing ObservableCollection, there are 5 protected methods you can override : ClearItems, RemoveItem, InsertItem, SetItem and MoveItem.
    These methods are, in the end, called by all the public methods, so overriding them gives you full control.

    Here's a small app you can run that demonstrates this :

    ObservableCollection SubClass

    public class ObservableCollectionWithDeletionControl<T> : ObservableCollection<T>
    {
        public delegate void DeletionDeniedEventHandler(object sender, int indexOfDeniedDeletion);
        public event DeletionDeniedEventHandler DeletionDenied;
    
        public bool CanDelete { get; set; }
    
        protected virtual void OnDeletionDenied(int indexOfDeniedDeletion)
        {
            if (DeletionDenied != null) { DeletionDenied(this, indexOfDeniedDeletion); }
        }
    
        protected override void RemoveItem(int index)
        {
            if (CanDelete)
            {
                base.RemoveItem(index);
            }
            else
            {
                OnDeletionDenied(index);
            }
        }
    }
    

    I use the DeletionDenied event so that this class is not responsible for showing the error window, and it makes it more reusable.

    ViewModel

    public class MainWindowViewModel
    {
        public MainWindow window { get; set; }
    
        public ObservableCollectionWithDeletionControl<Person> People { get; set; } = new ObservableCollectionWithDeletionControl<Person>();
    
        public MainWindowViewModel()
        {
            People.DeletionDenied += People_DeletionDenied;
        }
    
        private void People_DeletionDenied(object sender, int indexOfDeniedDeletion)
        {
            Person personSavedFromDeletion = People[indexOfDeniedDeletion];
            window.displayDeniedDeletion(personSavedFromDeletion.Name);
        }
    }
    

    The ViewModel for the MainWindow.
    It knows it's window for the sole purpose of displaying the error message.
    (I'm sure there's a better solution than this out there but I have yet to find a good and short way of displaying popup windows in mvvm.)
    When the DeletionDenied event triggers, the error window is called.

    Model

    public class Person : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public string Name
        {
            get { return _name; }
            set
            {
                if(_name == value) { return; }
                _name = value;
                if( PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs("Name")); }
            }
        }
    
        private string _name = "";
    }
    


    XAML

    <Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
        <DockPanel>
            <CheckBox DockPanel.Dock="Top" Content="Can delete" IsChecked="{Binding People.CanDelete}" Margin="5" HorizontalAlignment="Left"/>
            <DataGrid ItemsSource="{Binding People}" Margin="5,0"/>
        </DockPanel>
    </Window>
    


    XAML.CS

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        public void displayDeniedDeletion(string name)
        {
            TextBox errorMessage = new TextBox();
            errorMessage.Text = string.Format("Cannot delete {0} : access denied !", name);
    
            Window popupErrorMessage = new Window();
            popupErrorMessage.Content = errorMessage;
    
            popupErrorMessage.ShowDialog();
        }
    }
    

    APP MAIN

    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            MainWindow window = new MainWindow();
            MainWindowViewModel viewModel = new MainWindowViewModel();
    
            viewModel.window = window;
            window.DataContext = viewModel;
            window.Show();
    
            App.Current.MainWindow = window;
        }
    }
    

    I've set the ViewModel's window in the startup, but you should probably do it wherever you create your ViewModel


    0 讨论(0)
  • 2020-12-12 03:07

    Maybe it's ugly (really, it's not so ugly: imho it's a nice MVVM approach, also applicable to modern mahapps.metro Dialogs), but now I'm setting

    if (!CanBeDeleted(out validate_msg)) {
        PurchaseItem.MessageBoxText = validate_msg;
    

    an invisible TextBox

        <TextBox Visibility="Hidden" Name="tb_message"
            Text="{Binding MessageBoxText}"
            TextChanged="TextBox_TextChanged"
    

    where I'm dispatching the alert

        void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            string alert = tb_message.Text;
            if (alert != null && tb_message.Text.Length>0) {
                Dispatcher.BeginInvoke(
                    (Action)(() => { 
                                MessageBox.Show(alert, "Alert", MessageBoxButton.OK);
                                tb_message.Text = "";
                             }));
            }
        }
    

    Rolling back added/deleted items

    I see a connection with this other question Prevent adding the new Item on ObservableCollection.CollectionChanged event, in my case I'd say that prevent deleting is even more important. I don't know if there are more updated answers than this one (Can I rollback collection changes on collection changed event? which appears wrong) about this topic.

    While the PropertyChanged can be easily raised to rollback an item update, for the collection changes I've been forced to pass and reference the view dispatcher inside the CollectionChanged event

    PurchaseItem.dispatcher.BeginInvoke((Action)(() => RollBack(args)));
    

    to rollback the added/deleted items

        bool rollingBack = false;
        private void RollBack(NotifyCollectionChangedEventArgs args) {
                rollingBack = true;
                if (args.Action == NotifyCollectionChangedAction.Remove)
                {
                    foreach (var element in args.OldItems) {
                        PosInLocationsList.Add((PosInLocation)element);
                    }
                }
                if (args.Action == NotifyCollectionChangedAction.Add)
                {
                    foreach (var element in args.NewItems) {
                        PosInLocationsList.Remove((PosInLocation)element);
                    }
                }
                rollingBack = false;
        }
    
    0 讨论(0)
提交回复
热议问题