2012-10-23 13 views
16

Ho un progetto basato su WPF e MVVM. Il mio progetto si basa su una procedura guidata contenente un controllo contenuto che mostra le mie viste (Controlli utente) Voglio eseguire un comando dopo che la vista è stata caricata completamente, vorrei che l'utente vedesse l'interfaccia utente della vista immediatamente dopo il comando eseguito.Esegui il comando dopo aver caricato la vista WPF MVVM

Ho provato ad utilizzare:

<i:Interaction.Triggers> 
    <i:EventTrigger EventName="Loaded"> 
     <i:InvokeCommandAction Command="{Binding StartProgressCommand}"/> 
    </i:EventTrigger> 
</i:Interaction.Triggers> 

Ma il comando viene eseguito prima di vedere l'interfaccia utente vista e non è quello che sto cercando.

Qualcuno ha un'idea di come devo implementarlo?

risposta

16

Questo perché anche se tecnicamente la vista è caricata (cioè: tutti i componenti sono pronti in memoria), l'app non è ancora inattiva, e quindi l'interfaccia utente non è ancora aggiornata.

L'impostazione di un comando utilizzando i trigger di interazione sull'evento Loaded è già buona, poiché non vi è alcun evento migliore a cui collegarsi.
Ora per aspettare davvero fino a quando viene visualizzata l'interfaccia utente, fare questo nella vostra StartProgress() (sto supponendo qui che questo è il nome del metodo che StartProgressCommand punto a):

public void StartProgress() 
{ 
    new DispatcherTimer(//It will not wait after the application is idle. 
         TimeSpan.Zero, 
         //It will wait until the application is idle 
         DispatcherPriority.ApplicationIdle, 
         //It will call this when the app is idle 
         dispatcherTimer_Tick, 
         //On the UI thread 
         Application.Current.Dispatcher); 
} 

private static void dispatcherTimer_Tick(object sender, EventArgs e) 
{ 
    //Now the UI is really shown, do your computations 
} 
+1

Come fermare il thread? La sua chiamata all'infinito. – digz6666

+3

Vecchio lo so, ma per chi ha fretta ecco come terminare la discussione: Nel gestore eventi var timer = mittente come DispatcherTimer; timer.Stop(); – Shawn

+0

Non so davvero, se questo è un approccio molto buono, ma funziona! – peter70

0

È possibile controllare la proprietà IsLoaded per la vista, quando la vista è in forma caricata restituisce false, quando la vista è completamente caricata, questa proprietà diventa true.

Grazie, Rajnikant

+1

Non funziona. L'isLoaded ottiene il valore vero prima che la vista sia resa – Ofir

0

Hai provato il legame con l'evento ContentRendered? Si verificherà dopo l'evento caricato, ma non sono sicuro che sia o meno una garanzia che il thread dell'interfaccia utente abbia finito di dipingere la finestra.

+1

Io uso User Control e non una Window quindi l'evento ContentRendered non esiste. – Ofir

+0

Il controllo è stato aggiunto dinamicamente alla finestra? In caso contrario, leghiamo all'evento della finestra genitore – Dominik

0

È possibile scrivere un "Thread.Sleep (10000)" nella prima riga del metodo "CommandExecute". Usa lo stesso trigger caricato.

se non si desidera utilizzare Thread.Sleep, quindi è possibile utilizzare "DispatcherTimer". Avvia un timer nel tuo metodo di esecuzione del comando e sposta tutto il tuo codice in evento tick tick. imposta l'intervallo del timer su 2 secondi, in modo che l'utente immetta l'interfaccia utente.

+0

Ci ho pensato ma se possibile preferisco evitare di eseguire comandi base su ipotesi temporali – Ofir

+0

Anche se non mi piace usare il timer nella mia applicazione, ma non credo che avere eventi in WPF che verranno attivati ​​dopo pochi secondi dal caricamento. se vuoi mostrare l'interfaccia per un po 'prima di eseguire il comando, allora sei costretto a usare il timer. – Bathineni

+0

Accetto con Offir. Come regola generale, non fare mai supposizioni di tempo per risolvere le condizioni di gara. Giocare su Application.Idle come ha dimostrato Louis Kauffman è il migliore. – blearyeye

1

Usiamo una soluzione per il timer - anch'io ero molto dubbioso su questo, ma sembra funzionare bene.

public static class DispatcherExtensions 
{ 
    private static Dictionary<string, DispatcherTimer> timers = 
     new Dictionary<string, DispatcherTimer>(); 
    private static readonly object syncRoot = new object(); 

    public static void DelayInvoke(this Dispatcher dispatcher, string namedInvocation, 
     Action action, TimeSpan delay, 
     DispatcherPriority priority = DispatcherPriority.Normal) 
    { 
     lock (syncRoot) 
     { 
      RemoveTimer(namedInvocation); 
      var timer = new DispatcherTimer(delay, priority, (s, e) => 
       { 
        RemoveTimer(namedInvocation); 
        action(); 
       }, dispatcher); 
      timer.Start(); 
      timers.Add(namedInvocation, timer); 
     } 
    } 


    public static void CancelNamedInvocation(this Dispatcher dispatcher, string namedInvocation) 
    { 
     lock (syncRoot) 
     { 
      RemoveTimer(namedInvocation); 
     } 
    } 

    private static void RemoveTimer(string namedInvocation) 
    { 
     if (!timers.ContainsKey(namedInvocation)) return; 
     timers[namedInvocation].Stop(); 
     timers.Remove(namedInvocation); 
    } 


} 

Poi invochiamo con

Dispatcher.CurrentDispatcher.DelayInvoke("InitSomething",()=> { 
    DoSomething(); 
},TimeSpan.FromSeconds(1)); 
12

È possibile utilizzare il Dispatcher per questo e impostare la priorità per ApplicationIdle in modo che il eseguire quando tutto è finito

  Application.Current.Dispatcher.Invoke(
      DispatcherPriority.ApplicationIdle, 
      new Action(() => 
      { 
       StartProgressCommand.Invoke(args); 

      })); 

ulteriori informazioni sul dispatcher http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx

evviva. ste.

+0

Questo funziona per me :) – digz6666

+0

Questa sembra una soluzione molto più pulita rispetto alla risposta accettata. Perché avviare un timer quando si esegue solo un ciclo? –

+0

'Invoke' non sembra fare il trucco per me, quindi ho usato' BeginInvoke' invece di 'Invoke'. quello ha funzionato perfettamente. –

0

Sono venuto con questa soluzione per questo. Volevo utilizzare una proprietà booleana impostata come true all'inizio del lavoro e falsa alla fine per consentire di notificare all'utente il lavoro in background.

In sostanza, si usa

  • un DispatcherTimer per lanciare un metodo dopo UI di rendering in base alle this
  • Un metodo asincrono wich eseguirà il Action passato come parametro

chiamata:

this.LaunchThisWhenUiLoaded(() => { /*Stuff to do after Ui loaded here*/ }); 

Metodo:

private DispatcherTimer dispatchTimer; 
private Action ActionToExecuteWhenUiLoaded; 

/// <summary> 
/// Handy method to launch an Action after full UI rendering 
/// </summary> 
/// <param name="toExec"></param> 
protected void LaunchThisWhenUiLoaded(Action toExec) 
{    
    ActionToExecuteWhenUiLoaded = toExec; 
    // Call UiLoaded method when UI is loaded and rendered 
    dispatchTimer = new DispatcherTimer(TimeSpan.Zero, DispatcherPriority.ContextIdle, UiLoaded, Application.Current.Dispatcher); 
} 

/// <summary> 
/// Method called after UI rendered 
/// </summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
protected async void UiLoaded(object sender, EventArgs e) 
{ 
    this.IsBusy = true;  

    if (ActionToExecuteWhenUiLoaded != null) 
     await Task.Run(ActionToExecuteWhenUiLoaded); 
    dispatchTimer.Stop(); 

    this.IsBusy = false; 
} 

Forse non è il pulito, ma funziona come previsto.

1

un altro modo per farlo:

definire questa xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" e xmlns:mi="http://schemas.microsoft.com/expression/2010/interactions" sul tuo XAML UserControl e aggiungere Microsoft.Expression.Interactions assemblea sul progetto. utilizzare CallMethodAction sul grilletto, proprio come muggito:

<i:Interaction.Triggers> 
    <i:EventTrigger EventName="Loaded"> 
     <mi:CallMethodAction TargetObject="{Binding}" MethodName="StartProgressCommand"/> 
    </i:EventTrigger> 
</i:Interaction.Triggers> 

Mettere il triger all'interno dell'elemento radice del vostro UserControl, per esempio: griglia. E cambiare il tuo StartProgressCommand, nella classe ViewModel, dal comando per pianura vecchio metodo regolare, per esempio:

public void StartProgressCommand() 
{ 
    /* put your program logic here*/ 
} 

sarà eseguito il metodo esattamente una volta ogni volta che il controllo utente reso.

+0

Facile e preciso ... appena modificato con 'xmlns: i' che mancava – andrepaulo

+0

questo non funziona per me mi sta dicendo che il metodo o l'operazione non è implementata –

Problemi correlati