Qual è il modo migliore in MVVM per creare un menu che visualizza varie pagine?
-
06-07-2019 - |
Domanda
Voglio creare una semplice applicazione con il modello MVVM.
Questa applicazione avrà due parti principali:
- menu in alto
- contenuto in basso
La navigazione sarà semplice:
- ogni voce di menu (ad es. " Gestisci clienti " o " Visualizza report ") riempie l'area del contenuto con una nuova pagina che presenta alcune funzionalità particolari
Ho già fatto questo prima con il codice dietro dove il gestore di eventi code-behind per le voci di menu aveva tutte le pagine caricate e quella che doveva essere visualizzata veniva caricata come figlia di StackPanel. Questo, tuttavia, non funzionerà in MVVM poiché non si desidera riempire manualmente StackPanel ma visualizzare ad es. un " PageItem " oggetto con un DataTemplate, ecc.
Quindi quelli di voi che hanno realizzato una semplice applicazione di menu di scelta rapida come questa con MVVM, qual era la struttura di base dell'applicazione? Sto pensando in questo senso:
MainView.xaml:
<DockPanel LastChildFill="False">
<Menu
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
dove il menu è pieno di una raccolta di " PageItems " e DataTemplate visualizza il titolo di ciascun oggetto "PageItem" " come intestazione di ciascun MenuItem.
E ContentControl sarà riempito con una coppia View / ViewModel che ha funzionalità complete, ma non ne sono sicuro.
Soluzione
In primo luogo, penso che dovresti mantenere il gestore di eventi code-behind, non ha senso cambiare un semplice gestore di eventi a 2 righe in un mostro complesso guidato da comandi senza motivo pratico (e non dire testabilità, questo è il principale menu, verrà testato ogni volta che esegui l'app).
Ora, se vuoi seguire la rotta MVVM pura, tutto ciò che devi fare per fare in modo che il tuo menu attivi un comando, prima, in alcune sezioni delle risorse aggiungi questo stile:
<Style x:Key="MenuItemStyle" TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding DataContext.SwitchViewCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
<Setter Property="CommandParameter"
Value="{Binding}"/>
</Style>
Questo stile renderà la voce di menu un SwitchViewCommand sul modello di vista collegato con DataContext di MenuItem come parametro del comando.
La vista effettiva è la stessa del tuo codice con un riferimento aggiuntivo a quello stile di ItemContainerStyle (quindi si applica alla voce di menu e non al contenuto di DataTemplate):
<DockPanel LastChildFill="False">
<Menu DockPanel.Dock="Top"
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
Ora nel modello di visualizzazione che ti serve (ho usato le stringhe perché non ho il tuo codice PageItem):
private string _selectedViewItem;
public List<string> PageItemsMainMenu { get; set; }
public string SelectedPageItem
{
get { return _selectedViewItem; }
set { _selectedViewItem = value; OnNotifyPropertyChanged("SelectedPageItem"); }
}
public ICommand SwitchViewCommand { get; set; }
E usa qualunque classe di comando usi per fare in modo che il comando chiami questo codice:
private void DoSwitchViewCommand(object parameter)
{
SelectedPageItem = (string)parameter;
}
Ora, quando l'utente fa clic su una voce di menu, la voce di menu chiamerà SwitchViewCommand con la voce di pagina come parametro.
Il comando chiamerà DoSwitchViewCommand che imposterà la proprietà SelectedPageItem
La proprietà genererà NotifyPropertyChanged che renderà l'aggiornamento dell'interfaccia utente tramite l'associazione dei dati.
Oppure puoi scrivere un gestore di eventi a 2 righe, a tua scelta
Altri suggerimenti
Potrei immaginare un ObservableCollection nella VM, che contiene tutte le pagine per essere richiamabili dal menu. Quindi associare un ItemsControl e ContentControl ad esso per fare in modo che ContentControl mostri sempre CurrentItem da quell'elenco. Naturalmente, il menu si legherà solo ad alcune proprietà del titolo mentre ContentControl adotterà l'intero articolo e inserirà una vista appropriata in base al tipo.
Un'altra opzione è quella di utilizzare un ListBox invece di un menu, stile ListBox per assomigliare a un menu e quindi è possibile associare al valore selezionato, in questo modo:
<DockPanel LastChildFill="False">
<ListBox
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
IsSynchronizedWithCurrentItem="True"/>
<ContentControl
Content="{Binding PageItemsMainMenu/}"/>
</DockPanel>
Nota IsSynchronizedWithCurrentItem = " True " per impostare l'elemento selezionato e {Binding PageItemsMainMenu /} con la barra finale per usarlo.