2012-01-04 44 views
1

現狀手動控制窗口或畫布渲染?

我有(同時通常10和20之間)計算數量的元件的常數改變位置的應用程序。計算是在一個專用線程上完成的,然後該線程觸發一個事件來指示計算已完成。這個專用線程以每秒穩定100幀的速度運行。

可視化

的元件的可視化使用WPF完成。我實現了一個畫布,並編寫了自定義代碼,將每個元素的可視化表示添加到畫布。定位使用Canvas.SetLeftCanvas.SetTop。此代碼在專用線程的事件處理程序內運行,因此不會影響性能。

問題

由於這樣的事實,所述元件被不斷地移動,運動似乎是口吃。我現在唯一能夠做出的假設是,WPF根據自己的信念提出並試圖達到每秒最多60幀。如果您對爲什麼會發生這種情況有額外的評論,請啓發我。

問題

我如何才能知道當專用線程已經調用完成計算WPF來呈現?如果可能的話,我想阻止渲染窗口/畫布直到計算完成,此時幀應該前進並呈現新的信息。

評論

很多人不喜歡嘗試超越默認的每秒60幀,而我也不會。然而,這是絕對必須要能夠影響渲染髮生在每秒60幀之前。這是爲了確保每秒60幀不影響口吃問題。

渲染(碼)

這是實際的佈局更新所涉及的代碼,如每安德魯伯內特-湯普森博士的請求。 MainViewModel包含一個由專用工作線程創建的具有ActorViewModel實例的ObservableCollection。然後將它們轉換成ActorView實例以可視化計算的數據。在添加和刪除實例時,我故意使用調用,而不是BeginInvoke確保這些例程是而不是性能問題的原因。

完成專用工作計算後,將在MainViewModel中調用UpdateLayout,此時會調用UpdateSynchronizationCollectionLayout以更新可視化。此時,縮放和不透明度未被利用,並且當實例視框被省略時觀察到相同的口吃行爲。除非我錯過了一些東西,否則這個問題應該與我無法檢查或控制渲染時間或速度的事實有關。

/// <summary> 
    /// Contains the added ActorViewModel instances and the created ActorView wrapped in a ViewBox. 
    /// </summary> 
    private Dictionary<ActorViewModel, KeyValuePair<Viewbox, ActorView>> _hSynchronizationCollection = new Dictionary<ActorViewModel, KeyValuePair<Viewbox, ActorView>>(); 

    /// <summary> 
    /// Update the synchronization collection with the modified data. 
    /// </summary> 
    /// <param name="sender">Contains the sender.</param> 
    /// <param name="e"></param> 
    private void _UpdateSynchronizationCollection(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     // Check if the action that caused the event is an Add event. 
     if (e.Action == NotifyCollectionChangedAction.Add) 
     { 
      // Invoke the following code on the UI thread. 
      Dispatcher.Invoke(new Action(delegate() 
      { 
       // Iterate through the ActorViewModel instances that have been added to the collection. 
       foreach(ActorViewModel hActorViewModel in e.NewItems) 
       { 
        // Initialize a new _hInstance of the ActorView class. 
        ActorView hActorView = new ActorView(hActorViewModel); 

        // Initialize a new _hInstance of the Viewbox class. 
        Viewbox hViewBox = new Viewbox { StretchDirection = StretchDirection.Both, Stretch = Stretch.Uniform }; 

        // Add the _hInstance of the ActorView to the synchronized collection. 
        _hSynchronizationCollection.Add(hActorViewModel, new KeyValuePair<Viewbox, ActorView>(hViewBox, hActorView)); 

        // Set the child of the Viewbox to the ActorView. 
        hViewBox.Child = hActorView; 

        // Add the _hInstance of the ActorView to the canvas. 
        CanvasDisplay.Children.Add(hViewBox); 
       } 
      })); 
     } 
     // Check if the action that caused the event is a Remove event. 
     else if (e.Action == NotifyCollectionChangedAction.Remove) 
     { 
      // Invoke the following code on the UI thread. 
      Dispatcher.Invoke(new Action(delegate() 
      { 
       // Iterate through the ActorViewModel instances that have been removed to the collection. 
       foreach(ActorViewModel hActorViewModel in e.OldItems) 
       { 
        // Check if the ActorViewModel _hInstance is contained in the synchronization collection. 
        if (_hSynchronizationCollection.ContainsKey(hActorViewModel)) 
        { 
         // Remove the ActorView from the canvas. 
         CanvasDisplay.Children.Remove(_hSynchronizationCollection[hActorViewModel].Key); 

         // Remove the ActorViewModel from the collection. 
         _hSynchronizationCollection.Remove(hActorViewModel); 
        } 
       } 
      })); 
     } 
    } 

    /// <summary> 
    /// Update the synchronization collection layout with the modified data. 
    /// </summary> 
    private void _UpdateSynchronizationCollectionLayout() 
    { 
     // Invoke the following code on the UI thread. 
     Dispatcher.Invoke(new Action(delegate() 
     { 
      // Iterate through each ActorViewModel in the synchronization collection. 
      foreach(KeyValuePair<ActorViewModel, KeyValuePair<Viewbox, ActorView>> hDictionaryKeyValuePair in _hSynchronizationCollection) 
      { 
       // Retrieve the ActorViewModel. 
       ActorViewModel hActorViewModel = hDictionaryKeyValuePair.Key; 

       // Retrieve the KeyValuePair for this ActorViewModel. 
       KeyValuePair<Viewbox, ActorView> hKeyValuePair = hDictionaryKeyValuePair.Value; 

       // Sets the height of the ViewBox in which the ActorView is displayed. 
       hKeyValuePair.Key.Height = hKeyValuePair.Value.ActualHeight * hActorViewModel.LayoutScale; 

       // Sets the width of the ViewBox in which the ActorView is displayed. 
       hKeyValuePair.Key.Width = hKeyValuePair.Value.ActualWidth * hActorViewModel.LayoutScale; 

       // Set the opacity factor of the ActorView. 
       hKeyValuePair.Value.Opacity = hActorViewModel.LayoutOpacity; 

       // Sets the hValue of the Left attached property for the given dependency object. 
       Canvas.SetLeft(hKeyValuePair.Key, hActorViewModel.LayoutLeft - (hActorViewModel.LayoutAlignment == MainAlignment.Center ? hKeyValuePair.Key.ActualWidth/2 : (hActorViewModel.LayoutAlignment == MainAlignment.Right ? hKeyValuePair.Key.ActualWidth : 0))); 

       // Sets the hValue of the Top attached property for the given dependency object. 
       Canvas.SetTop(hKeyValuePair.Key, hActorViewModel.LayoutTop); 

       // Sets the hValue of the ZIndex attached property for the given dependency object. 
       Canvas.SetZIndex(hKeyValuePair.Key, hActorViewModel.LayoutLayerIndex); 
      } 
     })); 
    } 

    /// <summary> 
    /// Initialize a new _hInstance of the MainWindow class. 
    /// </summary> 
    /// <param name="hMainViewModel">Contains the object that is used as data context in MainView.</param> 
    internal MainView(MainViewModel hMainViewModel) 
    { 
     // Add a subscriber that occurs when an item is added, removed, changed, moved, or the entire list is refreshed. 
     hMainViewModel.ActorViewModel.CollectionChanged += new NotifyCollectionChangedEventHandler(_UpdateSynchronizationCollection); 

     // Initialize the component. 
     InitializeComponent(); 

     // Set the subscriber that occurs when the layout changes. 
     hMainViewModel.LayoutChanged += new Action(_UpdateSynchronizationCollectionLayout); 
    } 
+0

+1,因爲出色的措辭Q :) 你不應該在60FPS口吃。人眼只能檢測到25FPS(或更少)。你可以發佈一些代碼片段,因爲可能有更多的東西在這裏看到(沒有雙關語意圖; P) – 2012-01-04 16:35:50

+0

@ Dr.AndrewBurnett-Thompson謝謝你的友好評論:)更新與'渲染'部分的問題,根據請求。我希望這會增加目前的狀況和問題。 – 2012-01-04 17:20:54

+0

沒問題Roel,這就是知識共享。在這裏有一些不太友善的字符;-) – 2012-01-04 17:22:00

回答

1

默認情況下,應用程序代碼,如果你寫的是通過他們的畫布座標更新你所有的物體的位置的循環在UI線程上運行,換句話說,用戶界面將不會重新渲染,直到你的循環退出。您需要使計算「原子」向UI線程發送更新,然後一次更新所有對象。

你提到:

定位是利用Canvas.SetLeft和Canvas.SetTop執行。此 代碼正在專用線程上的事件處理程序內運行,因此 不會影響性能。

我認爲這是包裹在Dispatcher.BeginInvoke將其編組到UI線程?

+0

根據另一個請求更新與「渲染」部分的問題。正如你所看到的,我正在使用Invoke將更新編組到正確的線程,但是,似乎這些更改不會直接生效,而是WPF保持其自己的渲染速度和時間。 – 2012-01-04 17:22:20

+0

這是正確的,它們不會立即生效,渲染是由框架在未來的某個時間點執行的。當通過CompositionTarget.Rendering事件(http://msdn.microsoft.com/en-us/library/system.windows.media.compositiontarget.rendering.aspx)進行渲染時,您可以收到通知,並且您可以更改你的UI在這個事件處理程序中。 – ColinE 2012-01-04 17:24:47

+0

我寫了一些代碼來跟蹤從專用線程調度到實際渲染(使用此事件)的時間(以蜱),並得出平均延遲爲4〜5毫秒的結論。這是性能下降的50%,所以渲染將發生在約50-55幀每秒。這解釋了口吃。我有沒有辦法影響這個? – 2012-01-04 17:37:47