2009-07-08 11 views
38

Ho un'applicazione WPF che richiama nuovamente MessageBox.Show() nel ViewModel (per verificare se l'utente desidera davvero eliminare). Funziona in realtà, ma va contro la grana di MVVM poiché ViewModel non dovrebbe determinare in modo esplicito cosa succede sulla Vista.Come hai implementato con successo la funzionalità MessageBox.Show() in MVVM?

Così ora sto pensando come posso meglio implementare la funzionalità MessageBox.Show() nella mia applicazione MVVM, opzioni:

  1. potevo avere un messaggio con il testo "Sei sicuro. ..?" insieme a due pulsanti Sì e No tutto in un bordo nel mio XAML e creare un trigger sul modello in modo che sia collassato/visibile in base a un ViewModelProperty denominato AreYourSureDialogueBoxIsVisible, e quindi quando ho bisogno di questa finestra di dialogo, assegnare AreYourSureDialogueBoxIsVisible a "true", e anche gestire i due pulsanti tramite DelegateCommand indietro nel mio ViewModel.

  2. In qualche modo potrei anche provare a gestirlo con i trigger in XAML in modo che il pulsante Elimina faccia effettivamente apparire un elemento Border con il messaggio e i pulsanti al suo interno, e il pulsante Sì ha effettivamente eliminato.

Entrambe le soluzioni sembrano essere troppo complesso per quello che era un paio di righe di codice con MessageBox.Show().

In che modo hai implementato con successo le finestre di dialogo nelle tue applicazioni MVVM?

+0

domanda simile: http://stackoverflow.com/questions/315180/model-view-presenter-and-modal-dialog-boxes-how-to –

risposta

5

Dei due che hai citato, preferisco l'opzione n. 2. Il pulsante Elimina sulla pagina fa solo apparire la finestra di dialogo "Conferma eliminazione". La finestra di dialogo "Conferma eliminazione" avvia effettivamente Elimina.

Avete controllato il numero WPF Line Of Business Slides and Demos di Karl Shifflett? So che fa qualcosa del genere. Proverò a ricordare dove.

MODIFICA: consultare Demo # 11 "Convalida dei dati in MVVM" (EditContactItemsControlSelectionViewModel.DeleteCommand). Karl chiama un popup dal ViewModal (What !? :-). Mi piace davvero la tua idea. Sembra più facile al Test delle unità.

+0

ho finalmente risolto questo con la soluzione # 1. Non si apre una finestra, ma "inserisce" un elemento Border nella parte superiore dello schermo attivandolo da Visibility = Collassato a Visibility = Visible. Ho creato i DelegateCommands: TurnOnDialogueBoxDelete, DeleteItem e CancelDialogueBoxDelete. Ho creato ViewModelProperties: DialogueBoxDeleteStatus, ItemIdMarkedForDeletion e DialogueBoxDeleteText. Quindi non è facile come un semplice MessageBox.Show() ma funziona bene. Sono sicuro che alla fine alcuni di questi possono essere estratti per quando sono necessarie altre finestre di dialogo. –

+0

E può essere testato sull'unità. :-) –

11

Servizi per il salvataggio. Utilizzando Onyx (dichiarazione di non responsabilità, io sono l'autore) questo è facile come:

public void Foo() 
{ 
    IDisplayMessage dm = this.View.GetService<IDisplayMessage>(); 
    dm.Show("Hello, world!"); 
} 

In un'applicazione in esecuzione, questo sarà indirettamente chiamare MessageBox.Show ("Ciao, mondo!"). Durante il test, il servizio IDisplayMessage può essere deriso e fornito al ViewModel per fare ciò che si vuole ottenere durante il test.

+0

Mi chiedo se l'accesso indiretto a Vista sia ancora considerato seguendo la pratica MVVM. – VCD

+0

Non è mai stato dalla maggior parte, ma semplicemente non sono d'accordo. Non c'è un accoppiamento stretto qui, quindi sfiderei qualcuno a mostrare * perché * c'è un problema di design qui. Quello che è un anti-pattern nell'esempio precedente è l'uso del localizzatore di servizi invece dell'iniezione di dipendenza. Indipendentemente da ciò, il concetto IDisplayMessage, la vera risposta qui, non dipende né dal localizzatore di servizi né dall'accesso indiretto di una vista. – wekempf

0

Vorrei semplicemente lanciarlo dalla VM. Non voglio dover usare il servizio di qualcun altro o scrivere il mio solo per gettare una messagebox.

+6

Sì, ma se stai testando il tuo modello di vista in un test di unità, che cosa accadrà? Il tuo test bloccherà in attesa che l'utente faccia clic su "sì" o "no"? – esylvestre

1

Ho appena creare un'interfaccia (IMessageDisplay o simili) che viene iniettato nella macchina virtuale, e ha metodi come un MessageBox (ShowMessage() ecc). È possibile implementarlo utilizzando una messagebox standard o qualcosa di più specifico per WPF (io uso this one on CodePlex un tizio chiamato Prajeesh).

In questo modo tutto è separato e verificabile.

1

Ho implementato un comportamento che ascolta un messaggio dal ViewModel. È basato sulla soluzione di Laurent Bugnion, ma dal momento che non utilizza il codice dietro ed è più riutilizzabile, penso che sia più elegante.

Check it out here

3

Che dire di sollevare un evento come "MessageBoxRequested" manipolato al codebehind della vista (in ogni caso si tratta di vista unico codice così ho non vedo alcun problema con questo codice sul codebehind).

+0

Vero, MVVM sta collegando il modello e la vista. Ma la visione dovrebbe comportarsi anche su se stesso. – jeuxjeux20

1

Solo nel caso qualcuno altro sta ancora leggendo e insoddisfatta:

Volevo solo per gestire 'notifica' di tipo MessageBox (vale a dire non mi interessa circa il DialogResult), ma il problema che ho con la maggior parte delle soluzioni di cui ho letto è che sembrano forzare indirettamente a scegliere l'implementazione View (ovvero, attualmente ho un MessageBox.Show, ma se in seguito deciderò semplicemente di interferire con la visibilità di un pannello nascosto direttamente nella mia vista, che non si collegherà molto bene con un'interfaccia INotification passata a ViewModel).

Così sono andato per una rapida e sporca:

Il ViewModel ha una proprietà string NotificationMessage, con i cambiamenti notificati ai PropertyChanged.

La vista si iscrive a PropertyChanged e, se vede passare la proprietà NotificationMessage, fa ciò che vuole.

OK, questo significa che la vista ha code-behind e il nome di PropertyChanged è hardcoded, ma sarebbe comunque codificato nell'XAML. Significa che evito tutte le cose come i convertitori per Visibilità e le proprietà per dire se la notifica è ancora visibile o meno.

(Certo questo è solo per un caso d'uso limitata (il fuoco e dimenticare), non ho pensato molto a come potrei voler estenderlo.)

0

Recentemente ho sono imbattuto in questo problema in cui mi ha dovuto sostituire MessageBox.Show nel ViewModels con alcuni meccanismi MVVM di messaggio di reclamo pienamente.

Per raggiungere questo obiettivo ho utilizzato InteractionRequest<Notification> e InteractionRequest<Confirmation> insieme a trigger di interazione e ho scritto le mie viste per la finestra di messaggio.

Quello che ho implementato è pubblicato here

3

Ad ampliare la risposta di Dean Chalk ora che il suo collegamento è kaput:

Negli App.xaml.cs file che abbiamo collegare il dialogo di conferma al ViewModel .

protected override void OnStartup(StartupEventArgs e) 
{ 
    base.OnStartup(e); 
    var confirm = (Func<string, string, bool>)((msg, capt) => MessageBox.Show(msg, capt, MessageBoxButton.YesNo) == MessageBoxResult.Yes); 
    var window = new MainWindowView(); 
    var viewModel = new MainWindowViewModel(confirm); 
    window.DataContext = viewModel; 
    ... 
} 

Nella vista (MainWindowView.xaml) abbiamo un pulsante che richiama un comando nel ViewModel

<Button Command="{Binding Path=DeleteCommand}" /> 

viewmodel (MainWindowViewModel.cs) utilizza un comando delegato visualizza " Sei sicuro?" dialogo ed esegui l'azione. In questo esempio è un SimpleCommand simile a this, ma qualsiasi implementazione di ICommand dovrebbe essere eseguita.

private readonly Func<string, string, bool> _confirm; 

//constructor 
public MainWindowViewModel(Func<string, string, bool> confirm) 
{ 
    _confirm = confirm; 
    ... 
} 

#region Delete Command 
private SimpleCommand _deleteCommand; 
public ICommand DeleteCommand 
{ 
    get { return _deleteCommand ?? (_deleteCommand = new SimpleCommand(ExecuteDeleteCommand, CanExecuteDeleteCommand)); } 
} 

public bool CanExecuteDeleteCommand() 
{ 
    //put your logic here whether to allow deletes 
    return true; 
} 

public void ExecuteDeleteCommand() 
{ 
    bool doDelete =_confirm("Are you sure?", "Confirm Delete"); 
    if (doDelete) 
    { 
     //delete from database 
     ... 
    } 
} 
#endregion