2012-10-23 32 views
16

我有一個基於WPF和MVVM的項目。 我的項目是基於一個嚮導,其中包含一個顯示我的視圖的內容控件(用戶控件) 我想在視圖完全加載後執行一個命令,我希望用戶在命令完成後立即看到視圖UI執行。加載視圖後執行命令WPF MVVM

我試着使用:

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

但在此之前我看到的視圖界面中執行命令,它是不是我要找的。

有沒有人有一個想法,我應該如何實施它?

回答

16

這是因爲即使在技術上加載了視圖(即:所有組件都已準備好在內存中),您的應用程序還沒有閒置,因此UI尚未刷新。

使用Loaded事件中的交互觸發器設置命令已經很好,因爲沒有更好的事件要附加到。
我們真的等到顯示的UI,在做到這一點你StartProgress()(我假設在這裏,這是該方法的名稱StartProgressCommand點):

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

如何停止線程?它的呼喚不休。 – digz6666

+3

老我知道,但對於那些急於這裏是如何結束線程: 在您的事件處理程序 var timer = sender as DispatcherTimer; timer.Stop(); – Shawn

+0

我真的不知道,如果這是一個非常好的方法,但它的工作原理! – peter70

0

您可以檢查IsLoaded屬性的視圖,當視圖處於加載形式時它返回false,當視圖完全加載時,該屬性將變爲true。

感謝, Rajnikant

+1

它不工作。在呈現視圖之前,isLoaded會獲得真實值 – Ofir

0

您是否嘗試過綁定到ContentRendered事件?它會在加載事件後發生,但我不確定這是否是UI線程已完成繪製窗口的保證。

+1

我使用用戶控件而不是窗口,因此事件ContentRendered不存在。 – Ofir

+0

您的控件是否動態添加到窗口中?如果沒有,綁定到父窗口的事件 – Dominik

0

您可以在「CommandExecute」方法的第一行中寫入「Thread.Sleep(10000)」。使用相同的加載觸發器。

如果你不想使用Thread.Sleep,那麼你可以去「DispatcherTimer」。在您的命令執行方法中啓動一個計時器,並將您的所有代碼移至計時器滴答事件。 將您的計時器間隔設置爲2秒,以便用戶可以查看UI。

+0

我想過它,但如果可能的話,我寧願避免執行基於時間假設的命令 – Ofir

+0

即使我不喜歡在我的應用程序中使用計時器,但我不認爲我們在WPF中有任何事件會在加載幾秒鐘後被觸發。如果你想在執行命令之前顯示一段UI,那麼你不得不使用定時器。 – Bathineni

+0

同意Offir。作爲一般規則,永遠不要爲了解決競爭條件做出時間假設。在Louis Kauffman展示的Application.Idle中進行觸發是最好的。 – blearyeye

1

我們使用一個計時器解決方案 - 我也對此非常懷疑,但它似乎工作正常。

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); 
    } 


} 

然後我們調用使用

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

您可以使用調度,這和優先級設置爲ApplicationIdle,使其在執行時,一切都已經結束

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

      })); 

更多信息在調度員http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority.aspx

歡呼聲。 ste。

+0

這適用於我:) – digz6666

+0

這似乎比接受的答案更清潔的解決方案。當你只運行一個循環時,爲什麼要啓動一個計時器? –

+0

'Invoke'似乎並沒有爲我所用,所以我使用'BeginInvoke'而不是'Invoke'。那完美的工作。 –

0

我來到這個解決方案這個。 我想在開始工作時將布爾屬性設置爲true,並在結尾處將false設置爲允許通知用戶後臺工作。

基本上,它使用

  • 一個DispatcherTimer後UI根據this
  • 異步方法至極將執行作爲參數

呼叫傳遞的Action渲染啓動一個方法:

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

方法:

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; 
} 

也許不乾淨,但它按預期工作。

1

另一種方式來做到這一點:

定義你的用戶控件XAML這個xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"xmlns:mi="http://schemas.microsoft.com/expression/2010/interactions"並在您的項目中添加Microsoft.Expression.Interactions組裝。使用CallMethodAction你的扳機,就像波紋管:

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

把TRIGER你的用戶控件的根元素,e.g內:網格。改變你的StartProgressCommand,在你的ViewModel類,從命令老式常規方法,e.g:

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

它可以準確地運行的方法每個用戶控件呈現時間一次。

+0

簡單而準確......剛剛使用缺少的'xmlns:i'編輯 – andrepaulo

+0

這對我不起作用它告訴我該方法或操作未實現 –