Gestione delle finestre di dialogo in WPF con MVVM


235

Nel modello MVVM per WPF, la gestione delle finestre di dialogo è una delle operazioni più complesse. Poiché il modello della vista non è a conoscenza della vista, la comunicazione nella finestra di dialogo può essere interessante. Posso esporre un fatto ICommandche quando la vista lo invoca, può apparire una finestra di dialogo.

Qualcuno conosce un buon modo per gestire i risultati dalle finestre di dialogo? Sto parlando di finestre di dialogo come MessageBox.

Uno dei modi in cui lo abbiamo fatto è stato un evento sul modello di visualizzazione a cui la vista si sarebbe abbonata quando fosse richiesta una finestra di dialogo.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Questo va bene, ma significa che la vista richiede un codice che è qualcosa a cui vorrei stare lontano.


Perché non associarsi a un oggetto helper nella vista?
Paul Williams,

1
Non sono sicuro cosa intendi.
Ray Booysen,

1
Se capisco la domanda, non vuoi che la VM si apra finestre di dialogo e non vuoi code-behind nella vista. Inoltre sembra che tu preferisca i comandi agli eventi. Sono d'accordo con tutti questi, quindi uso una classe di supporto nella vista che espone un comando per gestire la finestra di dialogo. Ho risposto a questa domanda su un altro thread qui: stackoverflow.com/a/23303267/420400 . Tuttavia, l'ultima frase fa sembrare che non si desideri alcun codice, ovunque nella vista. Comprendo questa preoccupazione, ma il codice in questione è solo una condizione e non è probabile che cambi.
Paul Williams,

4
Questo modello di vista dovrebbe sempre essere responsabile della logica alla base della creazione della finestra di dialogo, questa è l'intera ragione della sua esistenza in primo luogo. Detto questo, non (e non dovrebbe) fare il sollevamento pesante della creazione della vista stessa. Ho scritto un articolo su questo argomento su codeproject.com/Articles/820324/… in cui mostro che l'intero ciclo di vita delle finestre di dialogo può essere gestito tramite la normale associazione di dati WPF e senza interrompere il modello MVVM.
Mark Feldman,

Risposte:


131

Suggerisco di rinunciare ai dialoghi modali degli anni '90 e invece di implementare un controllo come overlay (tela + posizionamento assoluto) con visibilità legata a un ritorno booleano nella VM. Più vicino a un controllo di tipo ajax.

Questo è molto utile:

<BooleanToVisibilityConverter x:Key="booltoVis" />

come in:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Ecco come ne ho implementato uno come controllo utente. Facendo clic sulla "x" si chiude il controllo in una riga di codice nel codice del controllo utente dietro. (Dato che ho il mio Views in un .exe e ViewModels in una dll, non mi sento male per il codice che manipola l'interfaccia utente.)

Finestra di dialogo Wpf


20
Sì, mi piace anche questa idea, ma vorrei vedere alcuni esempi di questo controllo in termini di come mostrarlo, e recuperare i risultati del dialogo da esso ecc. Soprattutto nello scenario MVVM in Silverlight.
Roboblob,

16
Come si impedisce all'utente di interagire con i controlli al di sotto di questa finestra di dialogo?
Andrew Garrison,

17
Il problema con questo approccio è che non è possibile aprire una seconda finestra di dialogo modale dalla prima, almeno non senza alcune pesanti modifiche al sistema di sovrapposizione ...
Thomas Levesque,

6
Un altro problema con questo approccio è che la "finestra di dialogo" non può essere spostata. Nelle nostre applicazioni, dobbiamo avere una finestra di dialogo mobile in modo che l'utente possa vedere cosa c'è dietro.
JAB

13
Questo approccio mi sembra terribile. Cosa mi sto perdendo? In che modo è meglio di una vera finestra di dialogo?
Jonathan Wood,

51

Dovresti usare un mediatore per questo. Il mediatore è un modello di progettazione comune noto anche come Messenger in alcune delle sue implementazioni. È un paradigma di tipo Register / Notify e consente a ViewModel e Views di comunicare attraverso un meccanismo di messaggistica a basso accoppiamento.

Dovresti dare un'occhiata al gruppo di discepoli di WPF di google e cercare solo il mediatore. Sarai molto contento delle risposte ...

Puoi comunque iniziare con questo:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

Godere !

Modifica: puoi vedere la risposta a questo problema con MVVM Light Toolkit qui:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338


2
Marlon Grech ha appena pubblicato una nuovissima implementazione del mediatore: marlongrech.wordpress.com/2009/04/16/…
Roubachof

21
Solo un'osservazione: il modello Mediatore non è stato introdotto dai Discepoli WPF, è un modello GoF classico ... ( dofactory.com/Patterns/PatternMediator.aspx ). Bella risposta altrimenti;)
Thomas Levesque,

10
Per favore, Dio, non usare un mediatore o un messaggero maledetto. Questo tipo di codice con dozzine di messaggi che volano in giro diventa molto difficile da eseguire il debug a meno che tu non riesca in qualche modo a ricordare tutti i molti punti dell'intera base di codice che si iscrivono e gestiscono ogni evento. Diventa un incubo per i nuovi sviluppatori. In effetti, considero l'intera libreria MvvMLight un enorme anti-pattern per il suo uso pervasivo e non necessario di messaggi asincroni. La soluzione è semplice: chiama un servizio di dialogo separato (cioè IDialogService) del tuo progetto. L'interfaccia ha metodi ed eventi per i callback.
Chris Bordeman,

34

Una buona finestra di dialogo MVVM dovrebbe:

  1. Essere dichiarato con solo XAML.
  2. Ottieni tutto il suo comportamento dal databinding.

Sfortunatamente, WPF non fornisce queste funzionalità. La visualizzazione di una finestra di dialogo richiede una chiamata code-behind a ShowDialog(). La classe Window, che supporta le finestre di dialogo, non può essere dichiarata in XAML, quindi non può essere facilmente analizzata nel database DataContext.

Per risolvere questo problema, ho scritto un controllo stub XAML che si trova nell'albero logico e inoltra la banca dati a Windowe gestisce la visualizzazione e l'occultamento della finestra di dialogo. Puoi trovarlo qui: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

È davvero semplice da usare e non richiede strane modifiche al ViewModel e non richiede eventi o messaggi. La chiamata di base è simile alla seguente:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

Probabilmente vuoi aggiungere uno stile che imposta Showing. Lo spiego nel mio articolo. Spero che questo ti aiuta.


2
Questo è un approccio davvero interessante al problema di mostrare finestre di dialogo in MVVM.
Dthrasher,

2
"Showing a dialog requires a code-behind"mmm puoi chiamarlo in ViewModel
Brock Hensley il

Aggiungerei il punto 3: sei libero di legarti ad altri oggetti all'interno della vista. Lasciare vuoto il codice della finestra di dialogo implica che non vi è alcun codice C # da nessuna parte nella vista e che il databinding non implica l'associazione alla VM.
Paul Williams,

25

Uso questo approccio per dialogare con MVVM.

Tutto quello che devo fare ora è chiamare quanto segue dal mio modello di visualizzazione.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

da quale libreria proviene uiDialogService?
aggietech,

1
nessuna biblioteca. è solo una piccola interfaccia e implementazione: stackoverflow.com/questions/3801681/… . per essere onesti ha qualche sovraccarico in più per le mie esigenze :) (altezza, larghezza,
impostazioni della

16

La mia soluzione attuale risolve la maggior parte dei problemi che hai citato ma è completamente astratta dalle cose specifiche della piattaforma e può essere riutilizzata. Inoltre, non ho utilizzato alcun binding code-behind con DelegateCommands che implementano ICommand. La finestra di dialogo è fondamentalmente una vista, un controllo separato che ha il proprio ViewModel e viene mostrato dal ViewModel della schermata principale ma attivato dall'interfaccia utente tramite l'associazione DelagateCommand.

Vedi la soluzione completa di Silverlight 4 qui Finestre di dialogo modali con MVVM e Silverlight 4


Proprio come l'approccio di @Elad Katz, la tua risposta è priva del contenuto collegato - per favore, migliora la tua risposta inserendola in quanto quella che è considerata una buona risposta qui su SO. Tuttavia, grazie per il tuo contributo! :)
Yoda,

6

Ho davvero lottato con questo concetto per un po 'durante l'apprendimento (ancora imparando) MVVM. Quello che ho deciso e quello che penso altri già deciso ma che non mi era chiaro è questo:

Il mio pensiero originale era che un ViewModel non dovrebbe essere autorizzato a chiamare direttamente una finestra di dialogo poiché non ha alcuna decisione di decidere come deve apparire una finestra di dialogo. Per questo motivo ho iniziato a pensare a come potevo passare i messaggi come avrei fatto con MVP (ad esempio View.ShowSaveFileDialog ()). Tuttavia, penso che questo sia l'approccio sbagliato.

È corretto per un ViewModel chiamare direttamente una finestra di dialogo. Tuttavia, quando stai testando un ViewModel, ciò significa che la finestra di dialogo apparirà durante il test o fallirà tutti insieme (mai realmente provato questo).

Quindi, ciò che deve accadere è che durante il test sia usare una versione "test" della finestra di dialogo. Ciò significa che per ogni finestra di dialogo che hai, devi creare un'interfaccia e deridere la risposta della finestra di dialogo o creare una simulazione di test che avrà un comportamento predefinito.

Dovresti già utilizzare una sorta di Service Locator o IoC che puoi configurare per fornirti la versione corretta a seconda del contesto.

Utilizzando questo approccio, ViewModel è ancora testabile e, a seconda di come prendi in giro le finestre di dialogo, puoi controllare il comportamento.

Spero che questo ti aiuti.


6

Ci sono due buoni modi per farlo, 1) un servizio di dialogo (facile, pulito) e 2) vista assistita. View assisted fornisce alcune funzioni pulite, ma di solito non ne vale la pena.

SERVIZIO DI DIALOGO

a) un'interfaccia del servizio di dialogo come tramite il costruttore o un contenitore di dipendenze:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) L'implementazione di IDialogService dovrebbe aprire una finestra (o immettere un controllo nella finestra attiva), creare una vista corrispondente al nome del tipo dlgVm specificato (utilizzare la registrazione o la convenzione del contenitore o un ContentPresenter con i DataTemplates associati al tipo). ShowDialogAsync dovrebbe creare un TaskCompletionSource e restituire la sua elica .Task. La stessa classe DialogViewModel necessita di un evento che è possibile richiamare nella classe derivata quando si desidera chiudere e guardare nella vista della finestra di dialogo per chiudere / nascondere effettivamente la finestra di dialogo e completare TaskCompletionSource.

b) Per usare, chiama semplicemente waitit this.DialogService.ShowDialog (myDlgVm) sulla tua istanza di una classe derivata da DialogViewModel. Dopo aver atteso i ritorni, guarda le proprietà che hai aggiunto nella finestra di dialogo VM per determinare cosa è successo; non hai nemmeno bisogno di una richiamata.

VISUALIZZAZIONE ASSISTITA

Questo ti consente di ascoltare un evento sul modello di visualizzazione. Tutto ciò potrebbe essere racchiuso in un comportamento misto per evitare il codice e l'utilizzo delle risorse se sei così incline (FMI, sottoclasse la classe "Comportamento" per vedere una sorta di proprietà collegata miscelabile sugli steroidi). Per ora, lo faremo manualmente su ogni vista:

a) Creare un OpenXXXXXDialogEvent con un payload personalizzato (una classe derivata da DialogViewModel).

b) Far iscrivere la vista all'evento nel suo evento OnDataContextChanged. Assicurati di nascondere e annullare l'iscrizione se il vecchio valore! = Null e nell'evento Unloaded di Window.

c) Quando l'evento si attiva, tieni la vista aperta, che potrebbe trovarsi in una risorsa sulla tua pagina, oppure potresti trovarla per convenzione altrove (come nell'approccio del servizio di dialogo).

Questo approccio è più flessibile, ma richiede più lavoro da utilizzare. Non lo uso molto. L'unico vantaggio è la possibilità di posizionare fisicamente la vista all'interno di una scheda, ad esempio. Ho usato un algoritmo per posizionarlo nei limiti del controllo utente corrente o, se non abbastanza grande, attraversare l'albero visivo fino a trovare un contenitore abbastanza grande.

Ciò consente alle finestre di dialogo di essere vicine al luogo in cui vengono effettivamente utilizzate, di oscurare solo la parte dell'app correlata all'attività corrente e di consentire all'utente di spostarsi all'interno dell'app senza dover spostare manualmente le finestre di dialogo, anche con più quasi- le finestre di dialogo modali si aprono su diverse schede o viste secondarie.


Un servizio di dialogo è molto più semplice, certamente, e quello che faccio di solito. Inoltre, consente di chiudere facilmente la finestra di dialogo della vista dal modello della vista padre, che è necessario quando il modello della vista padre si sta chiudendo o annullando.
Chris Bordeman,

4

Usa un comando freezable

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}

Questo codice richiede un po 'di lavoro, ma è di gran lunga la migliore idea, specialmente per le finestre di dialogo di sistema come file o finestre di dialogo della stampante. Le finestre di dialogo appartengono a Visualizza se succede. Per le finestre di dialogo dei file, il risultato (nome file selezionato) può essere passato al comando interno come parametro.
Anton Tykhyy,

3

Penso che la gestione di una finestra di dialogo dovrebbe essere responsabilità della vista e la vista deve avere un codice per supportarla.

Se si modifica ViewModel - Visualizza l'interazione per gestire le finestre di dialogo, ViewModel dipende da tale implementazione. Il modo più semplice per affrontare questo problema è rendere responsabile la visualizzazione dell'esecuzione dell'attività. Se ciò significa mostrare una finestra di dialogo, va bene, ma potrebbe anche essere un messaggio di stato nella barra di stato ecc.

Il mio punto è che il punto centrale del modello MVVM è quello di separare la logica aziendale dalla GUI, quindi non dovresti mescolare la logica della GUI (per visualizzare una finestra di dialogo) nel livello aziendale (ViewModel).


2
La VM non gestirà mai la finestra di dialogo, nel mio esempio avrebbe semplicemente un evento che richiederebbe che la finestra di dialogo si accendesse e restituisse informazioni in qualche forma di EventArgs. Se la vista è responsabile, come restituisce le informazioni alla VM?
Ray Booysen, il

Supponiamo che la VM debba eliminare qualcosa. La VM chiama un metodo su Visualizza Elimina che restituisce un valore booleano. La vista può quindi eliminare direttamente l'elemento e restituire true oppure mostrare una finestra di conferma e restituire true / false in base alla risposta degli utenti.
Cameron MacFarland,

La VM non è a conoscenza della finestra di dialogo ma ha solo chiesto alla vista di eliminare qualcosa, che la vista ha confermato o negato.
Cameron MacFarland,

Ho sempre pensato che il punto di MVVM fosse Model: business logic, ViewModel: GUI logic e View: no logic. Il che è in qualche modo contraddetto dal tuo ultimo paragrafo. Spiega per favore!
David Schmitt,

2
Innanzitutto, è necessario determinare se la richiesta di conferma pre-eliminazione è una logica aziendale o una logica di visualizzazione. Se si tratta di una logica aziendale, il metodo DeleteFile nel modello non deve farlo, ma restituisce piuttosto l'oggetto della domanda di conferma. Ciò includerà un riferimento al delegato che esegue la cancellazione effettiva. Se non è una logica aziendale, la VM deve creare una VM della domanda in DeleteFileCommand, con due membri ICommand. Uno per sì e uno per no. Probabilmente ci sono argomenti per entrambe le viste, e in RL la maggior parte dell'uso probabilmente incontrerà entrambe.
Guge,

3

Un'alternativa interessante è usare i controller che sono responsabili di mostrare le viste (finestre di dialogo).

Il modo in cui funziona è dimostrato dal WPF Application Framework (WAF) .


3

Perché non solo generare un evento nella macchina virtuale e iscriverti all'evento nella vista? Ciò manterrebbe la logica dell'applicazione e la vista separate e consentirebbe comunque di utilizzare una finestra figlio per le finestre di dialogo.


3

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

Come fare in modo che WPF si comporti come se MVVM fosse supportato immediatamente


1
Dovresti includere qui il codice completo in quanto è ciò che SO richiede per ottenere risposte valide. Tuttavia, l'approccio collegato è piuttosto accurato, quindi grazie per quello! :)
Yoda,

2
@yoda il codice completo è piuttosto lungo, ed è per questo che preferirei collegarmi ad esso. Ho modificato la mia risposta per riflettere le modifiche e indicare un collegamento non interrotto
Elad Katz,

Grazie per il miglioramento Tuttavia, è meglio fornire codice 3 scroll a pagina intera a lungo qui su SO piuttosto che un link che potrebbe essere offline un giorno. I buoni articoli per argomenti complessi sono sempre piuttosto lunghi - e non vedo alcun vantaggio nell'aprire una nuova scheda, passare ad essa e scorrere lì oltre lo scorrimento sulla stessa pagina / scheda in cui ero prima. ;)
Yoda,

@EladKatz Ho visto che hai condiviso parte della tua implementazione WPF nel link che hai fornito. Hai qualche soluzione per aprire una nuova finestra da ViewModel? Fondamentalmente ho due forme e ognuna ha un ViewModel. Un utente fa clic su un pulsante e si apre un altro modulo e viewmodel1 invia il suo oggetto a viewmodel2. Nel modulo 2 l'utente può modificare l'oggetto e quando chiudono la finestra, l'oggetto aggiornato verrà rispedito al primo ViewModel. Hai qualche soluzione per questo?
Ehsan,

2

Penso che la vista potrebbe avere un codice per gestire l'evento dal modello di vista.

A seconda dell'evento / dello scenario, potrebbe anche avere un trigger di evento che si iscrive per visualizzare gli eventi del modello e una o più azioni da richiamare in risposta.




1

Karl Shifflett ha creato un'applicazione di esempio per mostrare le finestre di dialogo utilizzando l'approccio di servizio e l'approccio Praction InteractionRequest.

Mi piace l'approccio di servizio - È meno flessibile, quindi gli utenti hanno meno probabilità di rompere qualcosa :) È anche coerente con la parte WinForms della mia applicazione (MessageBox.Show) Ma se prevedi di mostrare molte finestre di dialogo diverse, InteractionRequest è un modo migliore di andare.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/


1

So che è una vecchia domanda, ma quando ho fatto questa ricerca, ho trovato molte domande correlate, ma non ho trovato una risposta molto chiara. Quindi realizzo la mia implementazione di una finestra di dialogo / messagebox / popin e la condivido!
Penso che sia "a prova di MVVM" e provo a renderlo semplice e corretto, ma sono nuovo di WPF, quindi sentiti libero di commentare o anche di fare una richiesta pull.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Puoi usarlo in questo modo:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

O così se vuoi un popin più sofisticato:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

E sta mostrando cose come questa:

2


1

L'approccio standard

Dopo aver trascorso anni ad affrontare questo problema in WPF, ho finalmente capito il modo standard di implementare i dialoghi in WPF. Ecco i vantaggi di questo approccio:

  1. PULITO
  2. Non viola il modello di progettazione MVVM
  3. ViewModal non fa mai riferimento a nessuna delle librerie dell'interfaccia utente (WindowBase, PresentationFramework ecc.)
  4. Perfetto per i test automatizzati
  5. Le finestre di dialogo possono essere sostituite facilmente.

Quindi qual è la chiave. È DI + IoC .

Ecco come funziona. Sto usando MVVM Light, ma questo approccio può essere esteso anche ad altri framework:

  1. Aggiungi un progetto Applicazione WPF alla tua soluzione. Chiamalo app .
  2. Aggiungi una libreria di classi ViewModal. Chiamalo VM .
  3. L'app fa riferimento al progetto VM. Il progetto VM non sa nulla dell'app.
  4. Aggiungi riferimento NuGet a MVVM Light ad entrambi i progetti . In questi giorni sto usando MVVM Light Standard , ma stai bene anche con la versione completa di Framework.
  5. Aggiungi un'interfaccia IDialogService al progetto VM:

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. Esponi una proprietà statica pubblica di IDialogServicetipo nel tuo ViewModelLocator, ma lascia che la parte di registrazione venga eseguita dal livello Visualizza. Questa è la chiave .:

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Aggiungi un'implementazione di questa interfaccia nel progetto App.

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. Mentre alcune di queste funzioni sono generiche ( ShowMessage, AskBooleanQuestionecc.), Altre sono specifiche di questo progetto e usano Windows personalizzate . Puoi aggiungere più finestre personalizzate allo stesso modo. La chiave è mantenere elementi specifici dell'interfaccia utente nel livello Visualizza ed esporre i dati restituiti utilizzando i POCO nel livello VM .
  9. Esegui la registrazione IoC sulla tua interfaccia nel livello Visualizza usando questa classe. Puoi farlo nel costruttore della vista principale (dopo la InitializeComponent()chiamata):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. Ecco qua Ora hai accesso a tutte le funzionalità della finestra di dialogo sia a livello VM che a livello di visualizzazione. Il tuo livello VM può chiamare queste funzioni in questo modo:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. Così pulito, vedi. Il livello VM non sa nulla di come una domanda Sì / No verrà presentata all'utente dal livello UI e può comunque funzionare correttamente con il risultato restituito dalla finestra di dialogo.

Altri vantaggi gratuiti

  1. Per scrivere unit test, puoi fornire un'implementazione personalizzata IDialogServicenel tuo progetto Test e registrare quella classe in IoC nel costruttore della tua classe di test.
  2. Dovrai importare alcuni spazi dei nomi come Microsoft.Win32per accedere alle finestre di dialogo Apri e Salva. Li ho lasciati fuori perché esiste anche una versione WinForms di queste finestre di dialogo, inoltre qualcuno potrebbe voler creare la propria versione. Si noti inoltre che alcuni degli identificatori utilizzati in DialogPresentersono nomi delle mie finestre (ad es SettingsWindow.). Dovrai rimuoverli sia dall'interfaccia che dall'implementazione o fornire le tue finestre.
  3. Se la VM esegue il multi-threading, chiama MVVM Light DispatcherHelper.Initialize()all'inizio del ciclo di vita dell'applicazione.
  4. Ad eccezione di ciò DialogPresenterche viene iniettato nel livello Vista, è necessario registrare altri ViewModals ViewModelLocatore quindi una proprietà statica pubblica di quel tipo deve essere esposta per poter consumare il livello Vista. Qualcosa come questo:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. Per la maggior parte, i dialoghi non dovrebbero avere alcun codice per cose come l'associazione o l'impostazione di DataContext ecc. Non dovresti nemmeno passare le cose come parametri del costruttore. XAML può fare tutto per te, in questo modo:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. L'impostazione in DataContextquesto modo offre tutti i tipi di vantaggi in fase di progettazione come Intellisense e il completamento automatico.

Spero che aiuti tutti.


0

Stavo riflettendo su un problema simile quando mi chiedevo come dovesse apparire il modello di vista per un'attività o una finestra di dialogo .

La mia soluzione attuale si presenta così:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

Quando il modello di visualizzazione decide che è richiesto l'input dell'utente, visualizza un'istanza SelectionTaskModelcon le possibili scelte per l'utente. L'infrastruttura si occupa di visualizzare la vista corrispondente, che in tempo utile chiamerà la Choose()funzione con la scelta dell'utente.


0

Ho lottato con lo stesso problema. Ho escogitato un modo per comunicare tra View e ViewModel. Puoi iniziare a inviare un messaggio da ViewModel a View per dire che mostra una finestra di messaggio e riporterà con il risultato. Quindi ViewModel può rispondere al risultato restituito dalla vista.

Lo dimostro nel mio blog :



0

Siamo spiacenti, ma devo intervenire. Ho esaminato diverse soluzioni suggerite prima di trovare lo spazio dei nomi Prism.Wpf.Interactivity nel progetto Prism. È possibile utilizzare le richieste di interazione e l'azione della finestra popup per ruotare una finestra personalizzata o per esigenze più semplici sono presenti popup popup di notifica e conferma. Questi creano finestre vere e sono gestiti come tali. è possibile passare un oggetto di contesto con qualsiasi dipendenza necessaria nella finestra di dialogo. Usiamo questa soluzione nel mio lavoro da quando l'ho trovata. Abbiamo numerosi sviluppatori senior qui e nessuno ha trovato niente di meglio. La nostra soluzione precedente era il servizio di dialogo in un overlay e l'utilizzo di una classe presentatore per farlo accadere, ma dovevi avere fabbriche per tutti i modelli di visualizzazione dei dialoghi, ecc.

Questo non è banale ma non è nemmeno super complicato. Ed è integrato in Prism ed è quindi la migliore (o migliore) pratica IMHO.

I miei 2 centesimi!


-1

EDIT: sì, sono d'accordo che questo non è un approccio MVVM corretto e ora sto usando qualcosa di simile a quanto suggerito da blindmeis.

Uno dei modi in cui potresti farlo è

Nel tuo modello di visualizzazione principale (dove apri il modale):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

E nella finestra modale View / ViewModel:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

o simile a quanto pubblicato qui WPF MVVM: come chiudere una finestra


2
Non ero il downvote, ma sospetto che sia perché il modello di vista ha un riferimento diretto alla vista.
Brian Gideon,

@BrianGideon, grazie per il tuo commento. Sono d'accordo che questa non è una soluzione disaccoppiata. In effetti, non sto usando qualcosa di simile a Whar suggerito da blindmeis. Grazie ancora.
Simone,

È una cattiva forma arrivare alla vista quando è così facile non farlo.
Chris Bordeman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.