Mantieni MVVM mentre usi le risorse XAML

1

Contesto:

Sto creando un'applicazione WPF usando MVVM. Ho un Page che visualizza uno stato che informa su quale attività l'applicazione sta eseguendo su Sfondo.

Ho un contenitore e associo il suo Content a una proprietà su ViewModel .

Per un'illustrazione, dai un'occhiata al seguente codice:

<StackPanel x:Key="Status_Success" Orientation="Horizontal">
    <iconPacks:PackIconMaterial Kind="Check" />
    <TextBlock>Success!</TextBlock>
</StackPanel>

<StackPanel x:Key="Status_Error" Orientation="Horizontal">
    <iconPacks:PackIconMaterial Kind="Exclamation" />
    <TextBlock>Error!</TextBlock>
</StackPanel>

Se l'attività in background ha esito positivo, impostare la proprietà content su Status_Success StackPanel . Altrimenti, avrei impostato il contenuto su Status_Error .

Ecco il binding:

<Controls:TransitioningContentControl [...] Content="{Binding CurrentStatusElement}">

Problema:

Bene, in primo luogo ho creato tutto StackPanels come Resources nel mio Page . Ma come ho detto, sto utilizzando MVVM, quindi non ho accesso diretto alle risorse della pagina da ViewModel .

approcci:

Ecco alcuni possibili approcci (queste non sono le uniche possibilità, sto prendendo in considerazione):

1. Creare gli StackPanel sul ViewModel:

StackPanel _StatusSuccessElement = new StackPanel();

_StatusSuccessElement.Children.Add([...];

[...]

2. Crea un nuovo Resource Dictionary e importalo in ViewModel :

var resourceDictionary = new ResourceDictionary()
{
    Source = new Uri("SymbolTemplates.xaml", UriKind.Relative)
};

StackPanel _StatusSuccessElement = resourceDictionary["Status_Success"] as StackPanel;

3. Crea un elemento (Pagina / UserControl / qualunque) e crea una nuova istanza di esso su View Model

var _StatusSuccessElement = new StatusSuccessElement();

Domanda:

  1. Quali di questi approcci si adattano meglio a MVVM e perché?

  2. Se nessuno, qual è l'approccio migliore per prevenire le violazioni del pattern?

posta appa yip yip 20.01.2017 - 13:20
fonte

2 risposte

1

Non vuoi alcun elemento dell'interfaccia utente in ViewModel. ViewModel non dovrebbe sapere nulla su StackPanels o Risorse. L'intero punto di MVVM è separare i controlli dell'interfaccia utente dalla gestione dello stato sottostante.

Questo è il motivo per cui ti stai imbattendo nel problema.

Chiedi al ViewModel di esporre uno stato e la vista reagisce a quello stato.

In questo caso, vorrei creare un'enumerazione per il tuo stato:

public enum State
{
    Success,
    Error
}

Esporre lo stato corrente come proprietà di ViewModel. La vista può quindi reagire a quello stato e visualizzare l'icona e il testo che vuoi (o qualcos'altro interamente!). In questo caso, vorrei usare un DataTrigger. Puoi anche utilizzare un convertitore.

Esempio utilizzando un DataTrigger:

<StackPanel>
    <iconPacks:PackIconMaterial>
        <iconPacks:PackIconMaterial.Style>
            <Style TargetType="{x:Type iconPacks:PackIconMaterial}">
                <Setter Property="Kind" Value="Check" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=CurrentStatus}" Value="{x:Static local:State.Error}">
                        <Setter Property="Kind" Value="Exclamation" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </iconPacks:PackIconMaterial.Style>
    </iconPacks:PackIconMaterial>
    <TextBlock>
        <TextBlock.Style>
            <Style TargetType="TextBlock">
                <Setter Property="Text" Value="Success!" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=CurrentStatus}" Value="{x:Static local:State.Error}">
                        <Setter Property="Text" Value="Error!" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
    </TextBlock>
</StackPanel>
    
risposta data 20.01.2017 - 15:27
fonte
2

Utilizza DataTemplates impliciti .

Creare una classe ViewModel diversa per ogni stato che si desidera mappare e impostare il tipo corretto come CurrentStatusElement. Imposta le risorse della tua pagina come

<DataTemplate DataType={x:type viewModels:CheckVM}>
    <StackPanel Orientation="Horizontal">
        <iconPacks:PackIconMaterial Kind="Check" />
        <TextBlock>Success!</TextBlock>
    </StackPanel>
</Datetemplate

<DataTemplate DataType={x:type viewModels:ExclamationVM}>
    <StackPanel Orientation="Horizontal">
        <iconPacks:PackIconMaterial Kind="Exclamation" />
        <TextBlock>Error!</TextBlock>
    </StackPanel>
</Datetemplate

Ogni volta che CurrentStatusElement cambia il modello corretto verrà utilizzato in base al tipo di proprietà associata.

Questo è preferito alle altre soluzioni perché puoi testare la VM senza istanziare gli elementi dell'interfaccia utente, puoi avere viste diverse che condividono tutti lo stesso modello di vista e puoi cambiare le viste senza cambiare i test o i modelli view.

    
risposta data 20.01.2017 - 14:36
fonte

Leggi altre domande sui tag