2015-06-06 11 views
5

Dopo ore di ricerca, sono ancora senza risposta a questa domanda. Ho letto questo bel writing su MVVM asincrono e ho reso il mio viewmodel utilizzare il metodo factory.MVVM Come impostare datacontext quando viewmodel utilizza async

public class MainViewModel 
{ 
    // sic - public, contrary to the pattern in the article I cite 
    // so I can create it in the Xaml as below 
    public MainViewModel() 
    { 
    } 

    private async Task InitializeAsync() 
    { 
     await DoSomethingAsync(); 
    } 

    public static async Task<MainViewModel> CreateAsync() 
    { 
     var ret = new MainViewModel(); 
     await ret.InitializeAsync(); 
     return ret; 
    } 
} 

Questo è chiaro per me, ma non riesco a capire come fare istanza di MainViewModel e impostarlo su DataContext in MainPage. Non posso semplicemente scrivere

<Page.DataContext> 
    <viewModel:MainViewModel/> 
</Page.DataContext> 

perché dovrei usare MainViewModel.CreateAsync() - metodo. E non posso farlo su code-behind, che voglio anche fare, perché il code-behind -constructor è un metodo normale, non un metodo asincrono. Quindi quale è il modo corretto di continuare?

+0

WP8.1! = WPF. Più è il peccato. Non vediamo l'ora che arrivino i giorni in cui possiamo sincronizzarli. – Will

risposta

5

reso il mio ViewModel di utilizzare il metodo factory

Sono normalmente un fan di questo approccio - è il mio modo preferito per aggirare la limitazione "no costruttori asincrone". Tuttavia, non funziona bene nel pattern MVVM.

Questo perché le VM sono l'interfaccia utente, logicamente parlando. E quando un utente naviga verso uno schermo in un'app, l'app deve rispondere allo immediatamente (in modo sincrono). Non deve necessariamente visualizzare qualcosa di utile, ma è necessario visualizzare qualcosa. Per questo motivo, la costruzione VM deve essere sincrona.

Quindi, invece di provare a costruire in modo asincrono la tua VM, per prima cosa decidi come vuoi che l'interfaccia utente "di caricamento" o "incompleta" venga visualizzata. Il tuo costruttore di macchine virtuali (sincrone) dovrebbe inizializzarsi in quello stato, e può dare il via ad un lavoro asincrono che la aggiorna alla VM al suo completamento.

Questo non è troppo difficile da eseguire a mano, oppure è possibile utilizzare l'approccio NotifyTaskCompletion che ho descritto in un MSDN article on async MVVM data binding per guidare la transizione di stato utilizzando i binding di dati.

2

In primo luogo, si dovrebbe rendere il costruttore predefinito come privato per evitare un uso improprio della classe (l'articolo che si cita fa questo - il costruttore è private).

L'approccio che si sta utilizzando per impostare DataContext non è adatto per MVVM modello (il View non dovrebbe creare la sua ViewModel stesso). Dovresti creare il tuo View e il ViewModel nel livello di livello superiore e farlo legare a quel livello. Dice che se il Page è il vostro principale View si dovrebbe crearle in App.xaml.cs sovrascrivendo OnStartup, qualcosa di simile:

var page = new Page(); 
var dataService = new YourDataService(); // iff Create or the ctor require arguments 
var viewModel = await MainViewModel.CreateAsync(dataService); 
page.DataContext = viewModel; 
page.Show(); 
3

si deve inizializzare il ViewModel prima che la finestra è aperta. Vai al tuo file App.xaml e rimuovi la parte: StartupUri="MainWindow.xaml". Poi si va al App.xaml.cs e aggiunge questo:

protected async override void OnStartup(StartupEventArgs e) 
{ 
    base.OnStartup(e); 
    var mainWindow = new MainWindow { DataContext = await CreateAsync() }; 
    mainWindow.Show(); 
} 
+0

Grazie, sembra carino. Ma la mia app è per il wp 8.1, quindi non ha la proprietà StartupUri sulla mia app.xaml. Quale parte di App.xaml.cs dovrei modificare e in che modo? –

2

vorrei ri-fattore. Rendere la costruzione/istanza MainViewModel leggera. Quindi creare un metodo Load o Initialize sulla VM. Dal codice sottostante creare un'istanza, impostarlo su DataContext, quindi richiamare il metodo init e lasciarlo funzionare.

E.g.

/// <summary>Interaction logic for MainWindow.xaml</summary> 
public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     this.InitializeComponent(); 

     var dc = new MainViewModel(); 
     dc.Initialize("Hello", " ", "world"); 
     this.DataContext = dc; 
    } 
} 

public class MainViewModel 
{ 
    /// <summary>Simple constructor</summary> 
    public MainViewModel() { } 

    public void Initialize(params object[] arguments) 
    { 
     //use the task to properly start a new thread as per: 
     //http://stackoverflow.com/a/14904107/1144090 and 
     //https://msdn.microsoft.com/en-us/library/hh965065.aspx 
     //(what would happen if we simply invoke init async here?) 
     this.InitializeAsync(arguments) 
      .ContinueWith(result => 
      { 
       if (!result.IsFaulted) 
        return; 

       MessageBox.Show("Unexpected error: " + Environment.NewLine + result.Exception.ToString()); 
      }); 
    } 

    private async Task InitializeAsync(params object[] arguments) 
    { 
     await Task.Delay(2333); 

     MessageBox.Show(String.Concat(arguments)); 
    } 
} 

notare che questa è la soluzione rapida e-sporco, le altre due risposte (in coppia con un quadro di iniezione di dipendenza) vi darà una corretta struttura di alto livello per la vostra soluzione.

+0

Windows non dovrebbe inizializzare ViewModels. Si finisce con un codice molto fragile ed è difficile gestire in modo pulito ciò che dovrebbe accadere se l'intializzazione fallisce (ad esempio, mostrare una finestra di messaggio come questa non è realistico nel caso generale). Suggerisco caldamente di considerare prima gli approcci nelle altre risposte. –

+0

@RubenBartelink è davvero il contenitore IOC che dovrebbe gestire l'avvio dell'app. Sì, l'avvio di app sovrascritto come negli altri esempi è più "corretto". Posto più corretto per inserire init nel contenitore IOC. –

Problemi correlati