Il presente articolo è stato tradotto automaticamente.

Modelli

WPF applicazioni con il modello struttura ViewModel di visualizzazione del modello

Josh Smith

Nell'articolo viene descritto:

  • Modelli e WPF
  • Modello MVP
  • Perché è migliore di WPF MVVM
  • Creazione di un'applicazione con MVVM
In questo articolo vengono utilizzate le seguenti tecnologie:
WPF, associazione dati

Download codice disponibile dalla Raccolta di codice di MSDN
Selezionare il codice in linea

Contenuto

Ordine di confronto tra e. CHAOS
L'evoluzione del modello visualizzazione ViewModel
Perché gli sviluppatori WPF piace MVVM
L'applicazione di esempio
L'inoltro di logica di comando
Gerarchia di classi ViewModel
Classe ViewModelBase
Classe CommandViewModel
Classe MainWindowViewModel
Applicare una visualizzazione a un ViewModel
I dati modello e repository (in inglese)
Nuovo modulo di immissione dei dati clienti
Visualizza tutti i clienti
Disposizione dei

non è facile lo sviluppo dell'interfaccia utente di un'applicazione software professionisti. Può essere una sfumatura murky di dati, struttura di interazione, progettazione visiva, la connettività, il multithreading, protezione, internazionalizzazione, convalida, unit test e un tocco di voodoo. Considerare che un'interfaccia utente espone il sistema sottostante e deve soddisfare i requisiti stilistiche imprevisti degli utenti, può essere area di molte applicazioni più volatile.

Vi sono modelli di progettazione comuni che possono aiutare a tame questo beast difficoltoso, ma correttamente la separazione e la risoluzione meticolosamente di problemi di può essere difficile. Il sono i motivi più complessi, più probabile che i collegamenti utilizzati in un secondo momento cui undermine tutti i precedenti sforzi per eseguire operazioni quella a destra.

Non è sempre i modelli di progettazione in errore. In alcuni casi si utilizzano modelli di progettazione complessa, che richiedono la scrittura di una grande quantità di codice perché la piattaforma di interfaccia utente in uso non prestito stesso anche con un modello più semplice. Cosa è necessario è una piattaforma che semplifica la creazione di interfacce utente utilizzando modelli di progettazione semplice time-tested, approvato dallo sviluppatore. Fortunatamente, Windows Presentation Foundation (WPF) fornisce esattamente che.

Mentre il mondo del software continua ad adottare WPF a un tasso crescente, la comunità di WPF sviluppo proprio ecosistema di modelli e procedure. In questo articolo, verrà esaminare alcune delle procedure ottimali per la progettazione e implementare applicazioni client con WPF. Sfruttando alcune funzionalità di base di WPF in combinazione con il modello di progettazione modelli Visualizza ViewModel MVVM verrà scorrere un programma di esempio viene illustrato semplicemente come semplice può essere per creare un'applicazione WPF "funzionalità destra".

Al termine di questo articolo, sarà deselezionare come si adattino i modelli di dati, i comandi associazione dati, il sistema di risorse e il motivo MVVM tutti i al insieme per creare una struttura semplice verificabile, affidabile in cui qualsiasi WPF possibile thrive dell'applicazione. Il programma di esempio che accompagna questo articolo può fungere da un modello per un'applicazione reale WPF che utilizza MVVM come relativa architettura di base. Gli unit test nella soluzione demo visualizzare quanto sia semplice per verificare il funzionamento dell'interfaccia utente di un'applicazione quando questa funzionalità è presente in un insieme di classi ViewModel. Prima di entrare nei dettagli, esaminare perché occorre utilizzare un modello come MVVM in primo luogo.

Ordine e chaos

È non necessari e counterproductive utilizzare modelli di progettazione in un semplice programma di "Hello, World!". Qualsiasi sviluppatore competente può comprendere poche righe di codice un'idea. Tuttavia, come il numero di funzionalità di un programma aumenta, il numero di righe di codice e lo spostamento parti aumentare conseguenza. Infine, la complessità di un sistema e i problemi ricorrenti contiene, incoraggia agli sviluppatori di organizzare il codice in modo che sia più semplice per comprendere, discutere, estendere e risoluzione dei problemi. È significativamente il chaos conoscitivo di un sistema complesso applicando nomi noti a determinati entità di codice sorgente. È determinare il nome per applicare a una porzione di codice da prendere in considerazione il ruolo funzionale nel sistema.

Gli sviluppatori struttura spesso intenzionalmente il codice secondo un modello di progettazione, a differenza di consentire i modelli emersi organically. Niente errato con entrambi approccio, ma in questo articolo, è possibile esaminare i vantaggi dell'utilizzo in modo esplicito MVVM come l'architettura di un'applicazione WPF. I nomi di determinate classi includono noti termini dal criterio MVVM, ad esempio terminano con "ViewModel" se la classe è un'astrazione di una visualizzazione. Questo approccio consente di evitare il chaos conoscitivo indicato in precedenza. Al contrario, Fortunatamente è possibile esiste in uno stato di chaos controllata, che è stato naturale della politica nei progetti di sviluppo software più professionale.

L'evoluzione del modello visualizzazione ViewModel

Ever dall'utenti creare interfacce utente software, sono stati modelli di progettazione comuni a rendere più semplici. Ad esempio, il modello di modello visualizzazione relatore +++ (MVP) ha apprezzato popolarità su varie piattaforme di programmazione dell'interfaccia utente. MVP è una variazione del motivo di controller di visualizzazione del modello, è stato intorno a decenni. Nel caso in cui è mai stato utilizzato il modello MVP prima, di seguito è riportato una spiegazione semplificata. Visualizzati sullo schermo è la visualizzazione, i dati che viene visualizzato sono il modello e il relatore esegue due insieme. La visualizzazione si basa su un relatore compilarlo con i dati del modello, rispondere all'input dell'utente, fornire convalida dell'input (ad esempio delegando al modello) e altre attività di questo tipo. Se si desidera approfondire il relatore di visualizzazione modello, è possibile suggerire che leggere Paul Jean BoodhooColonna Design Patterns agosto 2006.

Indietro nel 2004 Martin Fowler pubblicato un articolo relativo a un modello denominatoModello di presentazionePM. Il modello di PM è simile a MVP che separa una visualizzazione dal relativo comportamento e stato. La parte interessante del motivo PM è che viene creata un'astrazione di una visualizzazione, denominato modello di presentazione. Una visualizzazione, diventa quindi, semplicemente un rendering di un modello di presentazione. Nella spiegazione del Fowler, Mostra che il modello di presentazione spesso Aggiorna la visualizzazione, in modo che rimangano sincronizzati tra loro i due. Tale logica di sincronizzazione non esiste come codice nelle classi di modelli di presentazione.

In 2005 unveiled Gossman di John attualmente una delle WPF e Silverlight Architects in Microsoft, ilModello modello visualizzazione ViewModel MVVMsul suo blog. MVVM è identico a modello del Fowler di presentazione, in quanto entrambi i modelli funzionalità un'astrazione di una visualizzazione che contiene lo stato e il comportamento di una visualizzazione. Modello di presentazione Fowler ha introdotto come strumento di creazione di un'astrazione di indipendenti dalla piattaforma dell'interfaccia utente di una visualizzazione mentre Gossman introdotta MVVM come un modo standardizzato per sfruttare la funzionalità di base di WPF per semplificare la creazione di interfacce utente. In questo senso, È necessario considerare MVVM da una specializzazione del più generale PM motivo, tailor-made per le piattaforme WPF e Silverlight.

Nell'articolo eccellente Glenn blocco"Prism: Patterns for Building Applications composito con WPF"nel numero di settembre 2008, ha spiega Guida sui applicazione composito Microsoft per WPF. Il termine che ViewModel non è mai utilizzato. Al contrario, il termine modello di presentazione viene utilizzato per descrivere l'astrazione di una visualizzazione. In questo articolo, tuttavia, farà riferimento al modello come MVVM e l'astrazione di una visualizzazione come un ViewModel. È possibile trovare che questo terminologia è molto più prevelant nella comunità WPF e Silverlight.

A differenza di relatore in MVP, un ViewModel non è necessario un riferimento a una visualizzazione. La visualizzazione associa a proprietà di un ViewModel che, a sua volta, espone i dati contenuti in oggetti del modello e altri lo stato specifico nella visualizzazione. Le associazioni tra la visualizzazione e ViewModel sono semplici costruire perché un oggetto ViewModel è impostato come DataContext di una visualizzazione. Valori della proprietà nel cambia ViewModel, i nuovi valori automaticamente propagano la visualizzazione tramite associazione dati. Quando l'utente fa clic su un pulsante nella visualizzazione, un comando sul ViewModel esegue per eseguire l'azione richiesta. Il ViewModel, non la visualizzazione, esegue tutte le modifiche apportate ai dati del modello.

Le classi di visualizzazione non dispongono di alcuna idea che le classi di modello esistono, mentre il ViewModel e modello sono della visualizzazione. Infatti, il modello è completamente oblivious al fatto che ViewModel e visualizzazione esistano. Si tratta di una struttura molto con accoppiamento ridotto, che paga dividendi in molti modi, come si vedrà breve.

Perché gli sviluppatori WPF piace MVVM

Quando uno sviluppatore diventa familiarità con WPF e MVVM, potrebbe essere difficile distinguere i due. MVVM è franca la lingua di sviluppatori WPF perché è anche adatto alla piattaforma di WPF e WPF è stato progettato per consentono di creare applicazioni utilizzando il modello MVVM (tra gli altri). Infatti, Microsoft è stato Utilizzo MVVM internamente per lo sviluppo applicazioni WPF, ad esempio Microsoft Expression Blend, durante la piattaforma WPF di base in fase di creazione. Molti aspetti di WPF, ad esempio i controllo inferiore aspetto modello e i dati modelli, utilizzano la separazione sicuro di visualizzazione di stato e comportamento promosso da MVVM.

L'aspetto più importante singolo di WPF che rende MVVM un motivo di grande da utilizzare è l'infrastruttura di associazione dei dati. Dalle proprietà di associazione di una visualizzazione per un ViewModel, è possibile ottenere debole associazione tra i due e rimuovere completamente la necessità di scrivere codice in un ViewModel una visualizzazione aggiornata direttamente. Il sistema di associazione dei dati supporta anche la convalida dell'input, che fornisce un modo standardizzato di trasmissione gli errori di convalida a una visualizzazione.

Due altre funzionalità di WPF che rendono pertanto utilizzabile questo modello sono i modelli di dati e il sistema delle risorse. I modelli di dati applicano visualizzazioni gli oggetti ViewModel visualizzati nell'interfaccia utente. È possibile dichiarare modelli in XAML e lasciare che il sistema di risorse individuare e applicare tali modelli è in fase di esecuzione automaticamente. È possibile ottenere più informazioni sulla associazione e modelli di dati nel mio articolo 2008 luglio"I dati e WPF: personalizzare la visualizzazione dei dati con associazione dati e WPF."

Se non fosse per il supporto per i comandi in WPF, il motivo MVVM è molto meno potente. In questo articolo mostra come un ViewModel può esporre i comandi per una visualizzazione, consentendo la visualizzazione da utilizzare la funzionalità. Se non si ha familiarità con il controllo, È consigliabile leggere l'articolo di Brian noyes completo "WPF avanzate: informazioni su routing eventi e i comandi di WPF" dal numero di settembre 2008.

Oltre alle funzionalità WPF e Silverlight 2 che costituiscono un modo naturale per la struttura di un'applicazione MVVM, il motivo è inoltre comune perché le classi ViewModel facili da unit test. Quando si logica di interazione di un'applicazione vive in un insieme di classi ViewModel, è possibile scrivere facilmente codice che si verifica. In un certo senso, visualizzazioni e gli unit test sono solo due tipi diversi di ViewModel consumer. Con una suite di test per ViewModels un'applicazione fornisce gratuita e veloce regressione test, che consente di ridurre il costo di gestione di un'applicazione nel tempo.

Oltre a promuovere la creazione di test di regressione automatico, testabilità delle classi ViewModel può essere utile nella progettazione di interfacce utente facili da interfaccia correttamente. Durante la progettazione di un'applicazione, è spesso possibile decidere se qualcosa deve essere nella visualizzazione o la ViewModel da imagining che si desidera scrivere uno unit test per utilizzare il ViewModel. Se si è scrive gli unit test per il ViewModel senza creare tutti gli oggetti dell'interfaccia utente, è possibile inoltre completamente interfaccia il ViewModel perché non ha dipendenze su specifici elementi di visual.

Infine, per gli sviluppatori che lavorano con finestre di progettazione visiva, utilizzo MVVM semplifica notevolmente l'creare un flusso di lavoro uniformi finestra di progettazione e per gli sviluppatori. Poiché una visualizzazione è semplicemente un consumer arbitrario un ViewModel, è facile solo visualizzazione di una copia da CD uscita e trascinamento in una nuova visualizzazione per il rendering di un ViewModel. Questo passaggio semplice consente la rapida creazione di prototipi e valutazione delle interfacce utente effettuate da finestre di progettazione.

Il team di sviluppo possibile concentrarsi sulla creazione di classi ViewModel affidabile e il team di progettazione può focalizzarsi su effettua le visualizzazioni di semplice. L'output di entrambi i team di connessione può comportare poco più di garantisce l'le associazioni corrette esistano nel file XAML una visualizzazione.

L'applicazione di esempio

A questo punto, sono stati revisionati cronologia e teoria dell'operazione del MVVM. Inoltre esaminato perché è così popolare tra gli sviluppatori WPF. Ora è full-immersion e visualizzare il modello di azione. L'applicazione di dimostrazione che accompagna questo articolo utilizza MVVM in diversi modi. Esso fornisce un'origine fertile degli esempi per inserire i concetti in un contesto significativo. È stata creata l'applicazione di esempio in Visual Studio 2008 SP1 con Microsoft .NET Framework 3.5 SP1. Gli unit test eseguire nel sistema test di unità di Visual Studio.

L'applicazione può contenere qualsiasi numero di aree di "lavoro" ciascuno dei quali l'utente possibile aprire facendo clic su un collegamento di comando nell'area di spostamento a sinistra. Tutte le aree di lavoro risiedono in un controllo TabControl nell'area di contenuto principale. L'utente può chiudere un'area di lavoro facendo clic sul pulsante Chiudi sulla elemento scheda dell'area di lavoro. L'applicazione dispone di due aree di lavoro disponibili: Tutti I Clienti e "nuovo cliente". Dopo aver esecuzione dell'applicazione e aprire alcune aree di lavoro, l'interfaccia utente simile figura 1 .

fig01.gif

Nella figura 1 aree di lavoro

Solo un'istanza di area di lavoro di tutti I clienti può essere aperta in un momento ma un numero qualsiasi di aree di lavoro New Customer può essere aperto contemporaneamente. Quando l'utente decide di creare un nuovo cliente, utente necessario compilare il modulo di immissione dati nella Figura 2 .

fig02.gif

Nella figura 2 nuovo modulo di immissione dei dati clienti

Una volta compilato il modulo di immissione dati con valori validi e facendo clic sul pulsante Salva, verrà visualizzato il nuovo nome del cliente nella linguetta della articolo e cliente viene aggiunta all'elenco di tutti i clienti. L'applicazione non dispone di supporto per l'eliminazione o modifica di un cliente esistente, ma tale funzionalità e molte altre funzionalità simile a, sono facile da implementare creando in primo piano dell'architettura dell'applicazione esistente. Ora che si dispone di una conoscenza generale di cosa l'applicazione di dimostrazione, si verificare come è stato progettato e implementato.

L'inoltro di logica di comando

Ogni visualizzazione nell'applicazione è un file codebehind vuoto, tranne per il codice boilerplate standard che chiama InitializeComponent nel costruttore della classe. In effetti, è possibile rimuovere file codebehind le visualizzazioni dal progetto e l'applicazione sarebbe comunque compilare ed eseguire correttamente. Nonostante la mancanza di metodi di gestione degli eventi nelle visualizzazioni, quando l'utente fa clic sui pulsanti, l'applicazione deve reagire e soddisfa le richieste dell'utente. Questo funziona a causa delle associazioni che sono state stabilite per la proprietà Command di controlli collegamento ipertestuale, Button e MenuItem visualizzato nell'interfaccia utente. Tali associazioni assicurarsi che quando l'utente fa clic sui controlli, oggetti ICommand esposti dal ViewModel esecuzione. L'oggetto comando può essere considerato come un adattatore che consente di utilizzare funzionalità di un ViewModel da una visualizzazione dichiarata nel codice XAML.

Quando un ViewModel espone una proprietà di istanza di tipo I­Command, tale oggetto ViewModel l'oggetto comando in genere utilizzato per ottenere il processo eseguito. Una possibile implementazione motivo consiste nel creare una classe privata nidificata all'interno della classe ViewModel, in modo che il comando dispone di accesso a privati i membri del relativo ViewModel che lo contiene e non contamina lo spazio dei nomi. Tale classe nidificata implementa l'interfaccia di ICommand e un riferimento all'oggetto ViewModel che lo contiene viene inserito nel relativo costruttore. Tuttavia, creando una classe nidificata che implementa ICommand per ogni comando esposte da un ViewModel possibile bloat dimensioni della classe ViewModel. Altro codice indica un potenziale maggiore per i bug.

Nell'applicazione di dimostrazione, la classe RelayCommand risolve questo problema. RelayCommand consente di inserire logica il comando tramite delegati passati al relativo costruttore. Questo approccio consente per l'implementazione comando brevi, concisa nelle classi ViewModel. RelayCommand è una variante semplificata di DelegateCommand trovato nelLibreria dell'applicazione composita Microsoft. La classe Relay­Command è illustrata nella Figura 3 .

Figura 3 la classe RelayCommand

public class RelayCommand : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;        

    #endregion // Fields

    #region Constructors

    public RelayCommand(Action<object> execute)
    : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;           
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members
}

L'evento CanExecuteChanged, che fa parte dell'implementazione di interfaccia di ICommand ha alcune funzionalità interessanti. Delega la sottoscrizione di eventi all'evento CommandManager.RequerySuggested. Ciò garantisce che WPF controllo infrastruttura richiede tutti che RelayCommand oggetti se sono possibile eseguire ogni volta che richiede i comandi incorporati. Il seguente codice dalla classe t CustomerViewModel, esaminerà approfondita in seguito, illustrato come configurare un RelayCommand con le espressioni lambda:

RelayCommand _saveCommand;
public ICommand SaveCommand
{
    get
    {
        if (_saveCommand == null)
        {
            _saveCommand = new RelayCommand(param => this.Save(),
                param => this.CanSave );
        }
        return _saveCommand;
    }
}

Gerarchia di classi ViewModel

La maggior parte delle classi ViewModel necessario le stesse caratteristiche. Sono spesso necessario implementare l'interfaccia INotifyPropertyChanged, in genere necessario disporre di un nome descrittivo visualizzato e, nel caso di aree di lavoro, devono poter chiudere (cioè da rimuovere dall'interfaccia utente). Questo problema naturalmente presta a creazioni di una classe di base ViewModel o due, in modo che le nuove classi ViewModel possono ereditino tutte le funzionalità comuni da una classe base. Le classi ViewModel costituiscono la gerarchia di ereditarietà riportata nella Figura 4 .

fig04.gif

Nella figura 4 gerarchia di ereditarietà

Con una classe base per tutte le ViewModels è assolutamente un requisito. Se si preferisce ottenere funzionalità di classi per la composizione di molte classi di dimensioni inferiori, invece di utilizzare l'ereditarietà, non è un problema. Proprio come qualsiasi altro modello di progettazione MVVM è un insieme di linee guida, non regole.

Classe ViewModelBase

ViewModelBase è la classe principale nella gerarchia, motivo, implementa l'interfaccia INotifyPropertyChanged comunemente utilizzato e si dispone di una proprietà DisplayName. L'interfaccia INotifyPropertyChanged contiene un evento denominato PropertyChanged. Ogni volta che una proprietà su un oggetto ViewModel ha un nuovo valore, è possibile generare l'evento PropertyChanged per notificare il sistema di associazione di WPF del nuovo valore. Alla ricezione di tale notifica, il sistema di associazione query la proprietà e la proprietà associata di un elemento dell'interfaccia utente riceverà il nuovo valore.

Affinché in WPF sapere quali proprietà dell'oggetto ViewModel è stata modificata, la classe PropertyChangedEventArgs espone una proprietà PropertyName di tipo String. Deve essere attenzione a passare il nome corretto della proprietà in questo argomento dell'evento; in caso contrario, WPF corrisponderà alla query la proprietà non valido per un nuovo valore.

Un aspetto interessante di ViewModelBase è che esso consente di verificare che una proprietà con un determinato nome esista effettivamente nell'oggetto ViewModel. Ciò risulta molto utile refactoring, perché la modifica nome di una proprietà tramite la funzionalità refactoring di Visual Studio 2008 stringhe in codice sorgente che si verificano per contenere il nome di tale proprietà non verrà aggiornato (né necessario). Generare l'evento PropertyChanged con un nome di proprietà non corretto nell'evento argomento può causare sottili bug che sono difficili da individuare, pertanto questa funzionalità poco può essere un enorme timesaver. Il codice da ViewModelBase che aggiunge il supporto utile è illustrato nella Figura 5 .

Nella figura 5 verifica di una proprietà

// In ViewModelBase.cs
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
    this.VerifyPropertyName(propertyName);

    PropertyChangedEventHandler handler = this.PropertyChanged;
    if (handler != null)
    {
        var e = new PropertyChangedEventArgs(propertyName);
        handler(this, e);
    }
}

[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
    // Verify that the property name matches a real,  
    // public, instance property on this object.
    if (TypeDescriptor.GetProperties(this)[propertyName] == null)
    {
        string msg = "Invalid property name: " + propertyName;

        if (this.ThrowOnInvalidPropertyName)
            throw new Exception(msg);
        else
            Debug.Fail(msg);
    }
}

Classe CommandViewModel

La sottoclasse ViewModelBase concreta più semplice è CommandViewModel. Espone una proprietà denominata comando di tipo I­Command. MainWindowViewModel espone un insieme di questi oggetti tramite la proprietà Commands. L'area di spostamento sul lato sinistro della finestra principale visualizza un collegamento per ogni CommandViewModel esposti da MainWindowView­Model, ad esempio "Visualizza tutti i clienti "e " Crea nuovo cliente". Quando l'utente fa clic su un collegamento, pertanto l'esecuzione uno dei tali comandi un'area di lavoro aperto nel controllo TabControl nella finestra principale. La definizione della classe Command­ViewModel è illustrata di seguito:

public class CommandViewModel : ViewModelBase
{
    public CommandViewModel(string displayName, ICommand command)
    {
        if (command == null)
            throw new ArgumentNullException("command");

        base.DisplayName = displayName;
        this.Command = command;
    }

    public ICommand Command { get; private set; }
}

Nel file MainWindowResources.xaml esiste un Data­Template con la chiave è "CommandsTemplate". MainWindow utilizza tale modello per eseguire il rendering Dell'CommandViewModels indicato in precedenza insieme. Il modello semplicemente esegue il rendering di ogni oggetto CommandViewModel come un collegamento in un ItemsControl. Proprietà Command ogni collegamento ipertestuale viene associato alla proprietà comando di un Command­ViewModel. Tale codice XAML è illustrato nella Figura 6 .

Nella figura 6 rendering all'elenco dei comandi

<!-- In MainWindowResources.xaml -->
<!--
This template explains how to render the list of commands on 
the left side in the main window (the 'Control Panel' area).
-->
<DataTemplate x:Key="CommandsTemplate">
  <ItemsControl ItemsSource="{Binding Path=Commands}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <TextBlock Margin="2,6">
          <Hyperlink Command="{Binding Path=Command}">
            <TextBlock Text="{Binding Path=DisplayName}" />
          </Hyperlink>
        </TextBlock>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</DataTemplate>

Classe MainWindowViewModel

Come visto in precedenza nel diagramma delle classi, la classe di WorkspaceViewModel deriva da ViewModelBase e aggiunge la possibilità di chiudere. Per chiudere, intendo che qualcosa rimuove l'area di lavoro dall'interfaccia utente in fase di esecuzione. Tre classi derivano da WorkspaceViewModel: MainWindowViewModel, AllCustomersViewModel e CustomerViewModel. Richiesta della MainWindowViewModel di chiusura è gestita dalla classe applicazione, che crea il MainWindow e relativo ViewModel come illustrato nella Figura 7 .

Nella figura 7 crea il ViewModel

// In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    MainWindow window = new MainWindow();

    // Create the ViewModel to which 
    // the main window binds.
    string path = "Data/customers.xml";
    var viewModel = new MainWindowViewModel(path);

    // When the ViewModel asks to be closed, 
    // close the window.
    viewModel.RequestClose += delegate 
    { 
        window.Close(); 
    };

    // Allow all controls in the window to 
    // bind to the ViewModel by setting the 
    // DataContext, which propagates down 
    // the element tree.
    window.DataContext = viewModel;

    window.Show();
}

MainWindow contiene una voce di menu cui proprietà Command è associato CloseCommand proprietà il MainWindowViewModel. Quando l'utente fa clic su tale voce di menu, i risponde classe applicazione chiamando metodo Chiudi della finestra, come segue:

<!-- In MainWindow.xaml -->
<Menu>
  <MenuItem Header="_File">
    <MenuItem Header="_Exit" Command="{Binding Path=CloseCommand}" />
  </MenuItem>
  <MenuItem Header="_Edit" />
  <MenuItem Header="_Options" />
  <MenuItem Header="_Help" />
</Menu>

MainWindowViewModel contiene un insieme osservabile di oggetti WorkspaceViewModel, denominato aree di lavoro. La finestra principale contiene un TabControl cui proprietà ItemsSource è associata a tale insieme. Ogni elemento scheda è associato un pulsante Chiudi con proprietà di comando è associata a CloseCommand della relativa istanza WorkspaceViewModel corrispondente. Una versione abridged del modello che configura ogni elemento scheda è illustrata nel codice che segue. Il codice è disponibile in MainWindowResources.xaml e il modello viene illustrato come eseguire il rendering un elemento scheda con un pulsante di chiusura:

<DataTemplate x:Key="ClosableTabItemTemplate">
  <DockPanel Width="120">
    <Button
      Command="{Binding Path=CloseCommand}"
      Content="X"
      DockPanel.Dock="Right"
      Width="16" Height="16" 
      />
    <ContentPresenter Content="{Binding Path=DisplayName}" />
  </DockPanel>
</DataTemplate>

Quando l'utente sceglie il pulsante Chiudi in un elemento scheda, che viene eseguito del Workspace­ViewModel CloseCommand causano l'evento Request­Close generare. MainWindowViewModel esegue il monitoraggio evento RequestClose del relativo aree di lavoro e rimuove l'area di lavoro dall'insieme di aree di lavoro su richiesta. Poiché il Main­Window TabControl ha la proprietà ItemsSource associata a insieme di WorkspaceViewModels osservabile, la rimozione di un elemento dall'insieme determina la generazione area di lavoro corrispondente da rimuovere dal controllo TabControl. Tale logica da Main­WindowViewModel è illustrato nella Figura 8 .

Nella figura 8 rimozione dall'interfaccia utente di area di lavoro

// In MainWindowViewModel.cs

ObservableCollection<WorkspaceViewModel> _workspaces;

public ObservableCollection<WorkspaceViewModel> Workspaces
{
    get
    {
        if (_workspaces == null)
        {
            _workspaces = new ObservableCollection<WorkspaceViewModel>();
            _workspaces.CollectionChanged += this.OnWorkspacesChanged;
        }
        return _workspaces;
    }
}

void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null && e.NewItems.Count != 0)
        foreach (WorkspaceViewModel workspace in e.NewItems)
            workspace.RequestClose += this.OnWorkspaceRequestClose;

    if (e.OldItems != null && e.OldItems.Count != 0)
        foreach (WorkspaceViewModel workspace in e.OldItems)
            workspace.RequestClose -= this.OnWorkspaceRequestClose;
}

void OnWorkspaceRequestClose(object sender, EventArgs e)
{
    this.Workspaces.Remove(sender as WorkspaceViewModel);
}

Nel progetto UnitTests il file MainWindowViewModelTests.cs contiene un metodo di test verifica che questa funzionalità funzioni correttamente. La facilità con cui è possibile creare unit test per le classi ViewModel è un grande punto di vendita del motivo MVVM perché consente di testing semplice di funzionalità dell'applicazione senza scrivere codice tocca l'interfaccia utente. Tale metodo di test è illustrato nella Figura 9 .

Figura 9 il metodo di test

// In MainWindowViewModelTests.cs
[TestMethod]
public void TestCloseAllCustomersWorkspace()
{
    // Create the MainWindowViewModel, but not the MainWindow.
    MainWindowViewModel target = 
        new MainWindowViewModel(Constants.CUSTOMER_DATA_FILE);

    Assert.AreEqual(0, target.Workspaces.Count, "Workspaces isn't empty.");

    // Find the command that opens the "All Customers" workspace.
    CommandViewModel commandVM = 
        target.Commands.First(cvm => cvm.DisplayName == "View all customers");

    // Open the "All Customers" workspace.
    commandVM.Command.Execute(null);
    Assert.AreEqual(1, target.Workspaces.Count, "Did not create viewmodel.");

    // Ensure the correct type of workspace was created.
    var allCustomersVM = target.Workspaces[0] as AllCustomersViewModel;
    Assert.IsNotNull(allCustomersVM, "Wrong viewmodel type created.");

    // Tell the "All Customers" workspace to close.
    allCustomersVM.CloseCommand.Execute(null);
    Assert.AreEqual(0, target.Workspaces.Count, "Did not close viewmodel.");
}

Applicare una visualizzazione a un ViewModel

MainWindowViewModel indirettamente aggiunge e rimuove gli oggetti Workspace­ViewModel da e verso Tab­Control della finestra principale. Basandosi sull'associazione dati, la proprietà contenuta di un TabItem riceve un oggetto derivato ViewModelBase da visualizzare. ViewModelBase non è un elemento dell'interfaccia utente, pertanto non è alcun supporto inerente per il rendering di se stesso. Per impostazione predefinita, in WPF un non visivo oggetto sottoposto a è rendering visualizzando i risultati di una chiamata al metodo ToString in un TextBlock. Che chiaramente è non ciò che occorre, a meno che non gli utenti sia un desiderio di masterizzazione per visualizzare il nome tipo di nostro classi ViewModel!

È possibile stabilire facilmente la che WPF DataTemplates tipizzata come eseguire il rendering un oggetto ViewModel utilizzando. Un DataTemplate digitato non include un valore di x: Key ad esso assegnato ma è stata l'impostata a un'istanza di classe Type la proprietà p tipo di dati. Se WPF tenta di eseguire il rendering uno degli oggetti del ViewModel, verrà controllare vedere se il sistema di risorsa è un DataTemplate digitato nell'ambito cui tipo di dati è lo stesso (o una classe di base) il tipo di oggetto il ViewModel. Se ne trova uno, viene utilizzato tale modello per eseguire il rendering oggetto ViewModel a cui fa riferimento la scheda proprietà dell'elemento contenuto.

Il file MainWindowResources.xaml ha un Resource­Dictionary. Tale dizionario viene aggiunto alla gerarchia di risorse della finestra principale, che significa che le risorse che contiene siano nell'ambito di risorsa della finestra. Quando contenuto una voce di scheda è impostata su un oggetto ViewModel, un DataTemplate tipizzato da questo dizionario fornisce una visualizzazione (ovvero un controllo utente per il rendering, come illustrato nella Figura 10 .

Nella figura 10 fornisce una visualizzazione

<!-- 
This resource dictionary is used by the MainWindow. 
-->
<ResourceDictionary
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:vm="clr-namespace:DemoApp.ViewModel"
  xmlns:vw="clr-namespace:DemoApp.View"
  >

  <!-- 
  This template applies an AllCustomersView to an instance 
  of the AllCustomersViewModel class shown in the main window.
  -->
  <DataTemplate DataType="{x:Type vm:AllCustomersViewModel}">
    <vw:AllCustomersView />
  </DataTemplate>

  <!-- 
  This template applies a CustomerView to an instance  
  of the CustomerViewModel class shown in the main window.
  -->
  <DataTemplate DataType="{x:Type vm:CustomerViewModel}">
    <vw:CustomerView />
  </DataTemplate>

 <!-- Other resources omitted for clarity... -->

</ResourceDictionary>

Non è necessario scrivere alcun codice che determina quale visualizzare da visualizzare per un oggetto ViewModel. Il sistema di risorse WPF non tutto il lavoro per consentire è liberare l'attenzione operazioni più importanti. In scenari più complessi, è possibile selezionare a livello di codice la visualizzazione, ma nella maggior parte delle situazioni in cui non è necessaria.

I dati modello e repository (in inglese)

Si è visto come oggetti ViewModel sono caricati, visualizzati e abbinati la shell di applicazione. Ora che plumbing generale sia in posizione, è possibile rivedere dettagli di implementazione più specifici al dominio dell'applicazione. Prima di ottenere profondità in aree di due lavoro l'applicazione, tutti I clienti e "New Customer", seguito esaminare innanzitutto il modello di dati e le classi di accesso ai dati. La struttura di tali classi ha quasi nulla da eseguire con il motivo MVVM poiché è possibile creare una classe ViewModel per adattare praticamente qualsiasi oggetto dati in specificando un nome significativo per WPF.

La classe modello unico nel programma demo è cliente. Tale classe dispone di poche proprietà che rappresentano informazioni su un cliente di una società, quali il nome, cognome e indirizzo di posta elettronica. Fornisce i messaggi di convalida implementando l'interfaccia IDataErrorInfo standard, che esisteva per anni prima WPF colpire la strada. La classe Customer contiene nulla che suggerisce che viene utilizzato in un'architettura MVVM o anche in un'applicazione WPF. La classe può facilmente derivino da una raccolta di business legacy.

Dati devono provenire da e si trovano in un punto qualsiasi. In questa applicazione, un'istanza della classe CustomerRepository carica e consente di memorizzare tutti gli oggetti di cliente. Accade caricare i dati del cliente da un file XML, ma il tipo di origine dati esterna è irrilevante. I dati potrebbero provenire da un database, un servizio Web, una named pipe, un file nel disco o anche pigeons portante: semplicemente non è rilevante. Purché si dispone di un oggetto .NET con alcuni dati, indipendentemente da dove proviene da, il motivo MVVM possibile ottenere i dati sullo schermo.

La classe CustomerRepository espone alcuni metodi che consentono di ottenere tutti i clienti oggetti disponibili, aggiungere nuovi un cliente nell'archivio e verificare se un cliente è già nel repository. Poiché l'applicazione non consente all'utente di eliminare un cliente, l'archivio non sarà consentito rimuovere un cliente. L'evento CustomerAdded genera quando un nuovo cliente immesso CustomerRepository, tramite il metodo AddCustomer.

Chiaramente, modello di dati dell'applicazione sono molto piccole rispetto a ciò che richiedono applicazioni aziendali reali, ma che non è importante. Che cos'è importante conoscere è come le classi ViewModel utilizzare del cliente e CustomerRepository. Si noti che Customer­ViewModel è un wrapper per un oggetto Customer. Espone lo stato di un cliente e altre stato utilizzato dal controllo Customer­View, tramite un insieme di proprietà. Lo stato di un cliente non duplicato di CustomerViewModel; semplicemente espone tramite delega, simile al seguente:

public string FirstName
{
    get { return _customer.FirstName; }
    set
    {
        if (value == _customer.FirstName)
            return;
        _customer.FirstName = value;
        base.OnPropertyChanged("FirstName");
    }
}

Quando l'utente crea un nuovo cliente e fa clic sul pulsante Salva il controllo CustomerView, il Customer­ViewModel associato che visualizzazione verrà aggiungere il nuovo oggetto di clienti la Customer­Repository. Che genera CustomerAdded evento il repository generato, che consente di AllCustomers­ViewModel sapere che deve aggiungere un nuovo Customer­ViewModel relativo insieme AllCustomers. In un certo senso, Customer­Repository agisce come un meccanismo di sincronizzazione tra vari ViewModels che gestiscono oggetti Customer. Ad esempio una potrebbe considerare questo come utilizza il modello di progettazione mediatore. Sarà esaminare più il funzionamento nelle sezioni future, ma per ora fa riferimento al diagramma illustrato nella Figura 11 per una conoscenza generale di come tutte le parti adatta insieme.

fig11.gif

Nella figura 11 relazioni con clienti

Nuovo modulo di immissione dei dati clienti

Quando l'utente fa clic il collegamento "Crea nuovo cliente", MainWindowViewModel viene aggiunta una nuova CustomerViewModel all'elenco delle aree di lavoro e un controllo CustomerView visualizzata. Dopo che l'utente digita i valori validi in campi di input, il pulsante Salva passa allo stato attivato in modo che l'utente può mantenere le nuove informazioni sul cliente. È nulla compreso lo straordinario, solo un modulo di immissione standard dati con convalida input e un pulsante Salva.

La classe cliente ha incorporato convalida supporta, disponibile tramite l'implementazione di interfaccia IDataErrorInfo. Che convalida garantisce il cliente disponga di un nome, un indirizzo di posta elettronica corretto, e, se il cliente è una persona e un cognome. Se IsCompany proprietà il cliente restituisce true, non è la proprietà Cognome (LastName) avere un valore (l'idea da che una società non dispone di un cognome. Questa logica di convalida potrebbe senso dalla prospettiva dell'oggetto Customer, ma non soddisfa le esigenze dell'interfaccia utente. L'interfaccia utente richiede un utente specificare se un nuovo cliente è una persona o una società. Il selettore di tipo cliente inizialmente presenta il valore "(non specificato)". Come possibile l'interfaccia utente di stabilire l'utente che il tipo di cliente è specificato se la proprietà IsCompany di un cliente consente solo di un valore VERO o FALSO?

Supponendo che si disponga di controllo completo sul sistema di software intera, è possibile modificare la proprietà IsCompany di tipo nullable <bool>, consentirebbe per il valore ". Tuttavia, del mondo reale non è sempre così semplice. Si supponga che non è possibile modificare la classe Customer perché si tratta di una raccolta legacy di proprietà di un team diverso nell'azienda. Cosa succede se è non semplice modo mantenere che " valore a causa dello schema di database esistente? Cosa succede se altre applicazioni già utilizzare la classe Customer e basano sulla proprietà da un valore Boolean normale? Ancora una volta, con un ViewModel tratta il rescue.

Il metodo di test nella Figura 12 mostra il funzionamento di questa funzionalità in CustomerViewModel. CustomerViewModel espone una proprietà CustomerTypeOptions in modo che il selettore di tipo cliente disponga di tre stringhe da visualizzare. Inoltre, espone una proprietà CustomerType che memorizza la stringa selezionata nel selettore di. Quando CustomerType è impostata, un valore Boolean per proprietà di IsCompany dell'oggetto Customer sottostante è mappato il valore di stringa. Nella figura 13 sono riportate le due proprietà.

Figura 12 il metodo di test

// In CustomerViewModelTests.cs
[TestMethod]
public void TestCustomerType()
{
    Customer cust = Customer.CreateNewCustomer();
    CustomerRepository repos = new CustomerRepository(
        Constants.CUSTOMER_DATA_FILE);
    CustomerViewModel target = new CustomerViewModel(cust, repos);

    target.CustomerType = "Company"
    Assert.IsTrue(cust.IsCompany, "Should be a company");

    target.CustomerType = "Person";
    Assert.IsFalse(cust.IsCompany, "Should be a person");

    target.CustomerType = "(Not Specified)";
    string error = (target as IDataErrorInfo)["CustomerType"];
    Assert.IsFalse(String.IsNullOrEmpty(error), "Error message should 
        be returned");
}

Nella Figura 13 CustomerType proprietà

// In CustomerViewModel.cs

public string[] CustomerTypeOptions
{
    get
    {
        if (_customerTypeOptions == null)
        {
            _customerTypeOptions = new string[]
            {
                "(Not Specified)",
                "Person",
                "Company"
            };
        }
        return _customerTypeOptions;
    }
}
public string CustomerType
{
    get { return _customerType; }
    set
    {
        if (value == _customerType || 
            String.IsNullOrEmpty(value))
            return;

        _customerType = value;

        if (_customerType == "Company")
        {
            _customer.IsCompany = true;
        }
        else if (_customerType == "Person")
        {
            _customer.IsCompany = false;
        }

        base.OnPropertyChanged("CustomerType");
        base.OnPropertyChanged("LastName");
    }
}

Il controllo CustomerView contiene un controllo ComboBox associato a tali proprietà, come illustrato di seguito:

<ComboBox 
  ItemsSource="{Binding CustomerTypeOptions}"
  SelectedItem="{Binding CustomerType, ValidatesOnDataErrors=True}"
  />

Quando l'elemento selezionato in tale controllo ComboBox viene modificato, IDataErrorInfo interfaccia l'origine dati viene richiesto di verificare se il nuovo valore è valido. Che si verifica perché l'associazione di proprietà SelectedItem è impostata ValidatesOnDataErrors su true. Poiché l'origine dati è un oggetto Customer­ViewModel, il sistema di associazione richiede che Customer­ViewModel per un errore di convalida nella proprietà CustomerType. Nella maggior parte dei casi, CustomerViewModel delega tutte le richieste per l'oggetto cliente che contiene errori di convalida. Tuttavia, poiché cliente non dispone di alcun concetto di disporre di uno stato non selezionato per la proprietà IsCompany, deve gestire la classe CustomerViewModel convalida il nuovo elemento selezionato nel controllo ComboBox. Tale codice viene visualizzato nella Figura 14 .

Nella figura 14 convalida di un oggetto CustomerViewModel

// In CustomerViewModel.cs
string IDataErrorInfo.this[string propertyName]
{
    get
    {
        string error = null;

        if (propertyName == "CustomerType")
        {
            // The IsCompany property of the Customer class 
            // is Boolean, so it has no concept of being in
            // an "unselected" state. The CustomerViewModel
            // class handles this mapping and validation.
            error = this.ValidateCustomerType();
        }
        else
        {
            error = (_customer as IDataErrorInfo)[propertyName];
        }

        // Dirty the commands registered with CommandManager,
        // such as our Save command, so that they are queried
        // to see if they can execute now.
        CommandManager.InvalidateRequerySuggested();

        return error;
    }
}

string ValidateCustomerType()
{
    if (this.CustomerType == "Company" ||
       this.CustomerType == "Person")
        return null;

    return "Customer type must be selected";
}

L'aspetto fondamentale del codice è che implementazione Dell'CustomerViewModel di IDataErrorInfo possibile gestire le richieste per la convalida proprietà specifici ViewModel e delegare altre richieste all'oggetto clienti. Consente di utilizzare logica di convalida in classi di modelli e dispone di convalida aggiuntivi per le proprietà senso solo classi ViewModel.

La possibilità di salvare un CustomerViewModel è disponibile per una visualizzazione tramite la proprietà SaveCommand. Questo comando utilizza la classe RelayCommand esaminata precedenti per consentire CustomerViewModel decidere se è possibile salvare stesso e operazioni da eseguire quando detto per salvare relativo stato. In questa applicazione, salvare un nuovo cliente semplicemente significa aggiungerlo a un CustomerRepository. Decidere se il nuovo cliente è pronto per essere salvato richiede il consenso dell'utente da due parti. L'oggetto di clienti deve essere richiesto se è valida o meno e la Customer­ViewModel deve decidere se è valido. Questa decisione di due parti è necessaria a causa di proprietà specifico ViewModel e convalida esaminato in precedenza. Il salvataggio logica per Customer­ViewModel è illustrato nella Figura 15 .

Figura 15 Salva la logica per CustomerViewModel

// In CustomerViewModel.cs
public ICommand SaveCommand
{
    get
    {
        if (_saveCommand == null)
        {
            _saveCommand = new RelayCommand(
                param => this.Save(),
                param => this.CanSave
                );
        }
        return _saveCommand;
    }
}

public void Save()
{
    if (!_customer.IsValid)
        throw new InvalidOperationException("...");

    if (this.IsNewCustomer)
        _customerRepository.AddCustomer(_customer);

    base.OnPropertyChanged("DisplayName");
}

bool IsNewCustomer
{
    get 
    { 
        return !_customerRepository.ContainsCustomer(_customer); 
    }
}

bool CanSave
{
    get 
    { 
        return 
            String.IsNullOrEmpty(this.ValidateCustomerType()) && 
            _customer.IsValid; 
    }
}

L'utilizzo di un ViewModel semplifica creare una vista che può visualizzare un oggetto Customer e consentire per elementi quali un stato " di una proprietà booleana. Nonché la possibilità di comunicare facilmente il cliente per salvare relativo stato. Se la visualizzazione sono stata associata direttamente a un oggetto Customer, la visualizzazione richiederebbe molto codice per utilizzare correttamente questo. In un'architettura MVVM ben strutturata, codebehind per la maggior parte delle visualizzazioni deve essere vuoto o, al massimo solo contenere codice che modifica i controlli e risorse contenute in tale visualizzazione. Talvolta è inoltre necessario scrivere codice in codebehind una visualizzazione che interagisce con un oggetto ViewModel, ad esempio l'associazione di un evento o chiama un metodo che altrimenti sarebbe molto difficile da richiamare dal ViewModel stesso.

Visualizza tutti i clienti

L'applicazione di dimostrazione contiene inoltre un'area di lavoro vengono visualizzati tutti i clienti in un controllo ListView. I clienti nell'elenco sono raggruppati in base ai se sono una società o di una persona. L'utente può selezionare una o più clienti contemporaneamente e visualizzare la somma delle loro vendite totale nell'angolo inferiore destro.

L'interfaccia utente è il controllo AllCustomersView, che esegue il rendering di un oggetto AllCustomersViewModel. Ogni ListView­Item rappresenta un oggetto CustomerViewModel nell'insieme AllCustomers esposta dall'oggetto AllCustomerViewModel. Nella sezione precedente si è visto, eseguire il come un CustomerViewModel possibile rendering come un modulo di immissione dati e ora lo stesso oggetto CustomerViewModel esatto rendering dell'viene come un elemento in un controllo ListView. La classe CustomerViewModel non dispone di alcuna idea elementi visivi, quali visualizzarla, che è il motivo per cui è possibile riutilizzare questo.

AllCustomersView crea i gruppi visualizzati nel controllo ListView. Questo esegue associando il controllo ListView ItemsSource un Collection­ViewSource configurato come Figura 16 .

Figura 16 CollectionViewSource

<!-- In AllCustomersView.xaml -->
<CollectionViewSource
  x:Key="CustomerGroups" 
  Source="{Binding Path=AllCustomers}"
  >
  <CollectionViewSource.GroupDescriptions>
    <PropertyGroupDescription PropertyName="IsCompany" />
  </CollectionViewSource.GroupDescriptions>
  <CollectionViewSource.SortDescriptions>
    <!-- 
    Sort descending by IsCompany so that the ' True' values appear first,
    which means that companies will always be listed before people.
    -->
    <scm:SortDescription PropertyName="IsCompany" Direction="Descending" />
    <scm:SortDescription PropertyName="DisplayName" Direction="Ascending" />
  </CollectionViewSource.SortDescriptions>
</CollectionViewSource>

L'associazione tra un ListViewItem e un oggetto CustomerViewModel viene stabilita dalla proprietà ItemContainerStyle il controllo ListView. Lo stile assegnato che proprietà viene applicata a ogni ListViewItem, che consente di proprietà di un ListViewItem da associare alle proprietà la CustomerViewModel. Un'associazione importante in tale stile crea un collegamento tra la proprietà IsSelected di un ListViewItem e la proprietà IsSelected di Customer­ViewModel, come illustrato di seguito:

<Style x:Key="CustomerItemStyle" TargetType="{x:Type ListViewItem}">
  <!--   Stretch the content of each cell so that we can 
  right-align text in the Total Sales column.  -->
  <Setter Property="HorizontalContentAlignment" Value="Stretch" />
  <!-- 
  Bind the IsSelected property of a ListViewItem to the 
  IsSelected property of a CustomerViewModel object.
  -->
  <Setter Property="IsSelected" Value="{Binding Path=IsSelected, 
    Mode=TwoWay}" />
</Style>

Quando un CustomerViewModel sia selezionata o deselezionata, che genera la somma dei vendite totali ai clienti selezionati tutte le per modificare. La classe AllCustomersViewModel è responsabile della manutenzione tale valore in modo che ContentPresenter sotto il controllo ListView possa visualizzare il numero corretto. Nella figura 17 viene illustrato come AllCustomersViewModel consente di controlla ogni cliente per selezionata o deselezionata e di notifica di visualizzazione che è necessario aggiornare il valore di visualizzazione.

Figura 17 monitoraggio selezionati o deselezionati

// In AllCustomersViewModel.cs
public double TotalSelectedSales
{
    get
    {
        return this.AllCustomers.Sum(
            custVM => custVM.IsSelected ? custVM.TotalSales : 0.0);
    }
}

void OnCustomerViewModelPropertyChanged(object sender, 
    PropertyChangedEventArgs e)
{
    string IsSelected = "IsSelected";

    // Make sure that the property name we're 
    // referencing is valid.  This is a debugging 
    // technique, and does not execute in a Release build.
    (sender as CustomerViewModel).VerifyPropertyName(IsSelected);

    // When a customer is selected or unselected, we must let the
    // world know that the TotalSelectedSales property has changed,
    // so that it will be queried again for a new value.
    if (e.PropertyName == IsSelected)
        this.OnPropertyChanged("TotalSelectedSales");
}

L'interfaccia utente associa alla proprietà TotalSelectedSales e applica valuta (monetario) formattazione il valore. L'oggetto ViewModel possibile applicare la formattazione, invece della visualizzazione, restituendo un valore di tipo String anziché un valore double dalla proprietà TotalSelectedSales di valuta. In .NET Framework 3.5 SP1 è stato aggiunto alla proprietà ContentStringFormat di ContentPresenter se è necessario assegnare una versione precedente di WPF, sarà necessario applicare la formattazione nel codice di valuta:

<!-- In AllCustomersView.xaml -->
<StackPanel Orientation="Horizontal">
  <TextBlock Text="Total selected sales: " />
  <ContentPresenter
    Content="{Binding Path=TotalSelectedSales}"
    ContentStringFormat="c"
  />
</StackPanel>

Disposizione dei

WPF è molto per offrire agli sviluppatori di applicazioni e formazione per sfruttare la potenza che richiede un turno di approccio mentale. Il modello ViewModel di visualizzazione del modello è un insieme semplice ed efficace di linee guida per la progettazione e implementazione di un'applicazione WPF. Consentono di creare una separazione sicuro tra dati, comportamento e presentazione, semplificando l'controllo chaos che è lo sviluppo di software.

Vorrei ringraziare Gossman John per il supporto in questo articolo.

Davide Ruspini è passionale sull'utilizzo di WPF per creare esperienze utente grande. È stato conferito il titolo di Microsoft MVP per il suo lavoro della comunità di WPF. Davide funziona per Infragistics nel gruppo di progettazione di software. Se non si è a un computer, egli apprezzino il piano di esecuzione, lettura relative cronologia ed esplorazione città di New York con sua girlfriend. È possibile visitare il blog di Josh all' joshsmithonwpf.wordpress.com.