aggiungi un controllo con MessageBox quando viene modificato DataGrid

Ho già rilasciato un’app desktop (quindi gradirei una risposta che mantenga le modifiche e i test di regressione come minimo) e ho bisogno di aggiungere un controllo di coerenza CanBeDeleted quando la griglia viene modificata.

  

Sto usando UpdateEnabled per qualcosa di diverso (permessi di profilo) e non voglio rendere la lettura di DataGrid solo: preferirei (a meno che non sia troppo complesso ) vedere un avviso di blocco (un MessageBox ) che impedisce le modifiche.

Quello che ho fatto fino ad ora è

  1. contro MVVM, perché ho messo l’avviso nel modello (ma posso accettarlo, se rende le modifiche rapide e semplici)
  2. non funziona, perché la seconda parte (vedi sotto) delle mie modifiche produce un’eccezione di operazione non valida

ViewModel contiene il seguente elenco

  [Association(ThisKey="Partita", OtherKey="Partita", CanBeNull=true, IsBackReference=true)] public ObservableCollection posin_locations_list = new ObservableCollection(); public ObservableCollection PosInLocationsList { get { return posin_locations_list; } set { posin_locations_list = value; OnPropertyChanged( () => PosInLocationsList ); } } 

e sto aggiungendo il controllo di coerenza qui

  string _storage; [Column(Name = "storage"), PrimaryKey] public string Storage { get { return _storage; } set { if (this.loadedEF) { string validate_msg; if (!PurchasePosIn.CanBeDeleted(out validate_msg)) { // against MVVM MessageBox.Show(validate_msg, "Alert", MessageBoxButton.OK); OnPropertyChanged( () => Storage ); return; } Persistence.MyContext.deletePosInLocation(this); } _storage = value; OnPropertyChanged( () => Storage ); if (this.loadedEF) { Persistence.MyContext.insertPosInLocation(this); } } } 

e qui (seconda parte)

  internal void posin_locations_list_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs args) { string validate_msg; if (!CanBeDeleted(out validate_msg)) { // indirectly produces an invalid operation exception MessageBox.Show(validate_msg, "Alert", MessageBoxButton.OK); return; } if (args.OldItems != null) foreach(var oldItem in args.OldItems) { if ( ((PosInLocation)oldItem).Partita != null) Persistence.MyContext.deletePosInLocation((PosInLocation)oldItem); } if (args.NewItems != null) foreach(var newItem in args.NewItems) { PosInLocation newPosInLocation = (PosInLocation)newItem; if ( newPosInLocation.Partita == null) { newPosInLocation.Partita = this.Partita; newPosInLocation.PurchasePosIn = this; newPosInLocation.loadedEF = true; } } } 

Se solo ObservableCollection implementasse un “previewCollectionChanged”, le cose sarebbero molto più semplici.

Per le tue esigenze ti consiglio di creare una sottoclass di ObservableCollection e di sovraccaricare il metodo protetto RemoveItem.
A seconda di ciò che si sta facendo con la propria applicazione, si potrebbe voler sovrascrivere altri metodi oltre a RemoveItem (come ClearItems).

Quando sottoclassi ObservableCollection, ci sono 5 metodi protetti che puoi sovrascrivere: ClearItems, RemoveItem, InsertItem, SetItem e MoveItem.
Questi metodi, alla fine, vengono chiamati da tutti i metodi pubblici, quindi sovrascriverli ti dà il pieno controllo.

Ecco una piccola app che puoi eseguire dimostrando questo:

Sottoclass ObservableCollection

 public class ObservableCollectionWithDeletionControl : ObservableCollection { 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); } } } 

Uso l’evento DeletionDenied in modo che questa class non sia responsabile della visualizzazione della finestra di errore e la rende più riutilizzabile.

ViewModel

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

ViewModel per MainWindow.
Sa che è la finestra al solo scopo di visualizzare il messaggio di errore.
(Sono sicuro che ci sia una soluzione migliore di questa, ma devo ancora trovare un modo buono e breve per visualizzare windows popup in mvvm.)
Quando si triggers l’evento DeletionDenied, viene richiamata la finestra di errore.

Modello

 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

       

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 PRINCIPALE

 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; } } 

Ho impostato la finestra di ViewModel all’avvio, ma probabilmente dovresti farlo ovunque crei ViewModel

Forse è brutto (davvero, non è così brutto: imho è un bel approccio MVVM , applicabile anche ai moderni makpps.metro Dialogs ), ma ora sto impostando

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

un TextBox invisibile

   

dove sto spedendo l'avviso

  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 = ""; })); } } 

Rollback degli articoli aggiunti / eliminati

Vedo una connessione con quest'altra domanda Prevenire l'aggiunta del nuovo Item sull'evento ObservableCollection.CollectionChanged , nel mio caso direi che prevenire l'eliminazione è ancora più importante. Non so se ci sono risposte più aggiornate di questa ( posso ripristinare le modifiche alla raccolta sull'evento modificato della raccolta? Che sembra sbagliato ) su questo argomento.

Mentre il PropertyChanged può essere facilmente sollevato per il rollback di un aggiornamento di un elemento, per le modifiche alla collezione sono stato costretto a passare e fare riferimento al dispatcher della vista all'interno dell'evento CollectionChanged

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

per ripristinare gli elementi aggiunti / eliminati

  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; } 

Quello che ho fatto fino ad ora è

contro MVVM, perché ho messo l’avviso nel modello

La soluzione di @Tesseract, sottoclass di ObservableCollection e sottoscrizione di RemoveItem è già orientata MVVM.

Quello che manca ancora è un modo corretto e moderno per inviare l’avviso dal ViewModel. È qui che l’approccio di Mahapps sarebbe utile.

  • Utilizzare una proprietà associata nella finestra per registrare il modello di visualizzazione con il sottosistema della finestra di dialogo.

Nel tuo XAML

 Dialog:DialogParticipation.Register="{Binding}" 

dove la proprietà associata DialogPartecipation terrà traccia delle viste attraverso un dizionario

 public static class DialogParticipation { private static readonly IDictionary ContextRegistrationIndex = new Dictionary 
  • È ansible creare un'istanza di DialogCoordinator direttamente o iniettare l'interfaccia IDialogCoordinator nel proprio modello di visualizzazione

Il DialogCoordinator abbinerà ViewModel alla View

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

attraverso la suddetta proprietà allegata (il context è il modello di vista)

 var association = DialogParticipation.GetAssociation(context); 

e visualizza la finestra di dialogo, invocando il metodo appropriato nella vista recuperata: questo è il modo in cui, se hai più windows aperte, la finestra di dialogo verrà visualizzata nella finestra corretta.