手动控制窗口或画布渲染?

时间:2012-01-04 16:33:36

标签: .net wpf performance rendering

场合

我有一个应用程序可以计算多个元素的常量变化位置(通常在10到20之间同时)。计算在专用线程上完成,然后触发事件以指示计算完成。这个专用线程以每秒100帧的速度运行。

可视化

使用WPF完成元素的可视化。我实现了一个画布并编写了自定义代码,以便将每个元素的可视化表示添加到画布中。使用 Canvas.SetLeft Canvas.SetTop 执行定位。此代码在专用线程上的事件处理程序内运行,因此不会影响性能。

问题

由于元素不断移动,移动似乎是口吃。我现在唯一可以做出的假设就是WPF自己动手并试图达到每秒最多60帧。如果您对此可能发生的原因有其他意见,请赐教。

问题

当专用线程调用完成的计算时,如何告诉WPF渲染?如果可能的话,我想阻止渲染窗口/画布,直到完成计算,此时帧应该前进并呈现新信息。

注释

许多人不喜欢超过默认60帧/秒的尝试,我也不喜欢。但是,在渲染发生时,能够影响是绝对必要的。降低到每秒60帧。这是为了确保每秒60帧不会影响口吃问题。

渲染(代码)

根据Andrew Burnett-Thompson博士的要求,这是实际布局更新所涉及的代码。 MainViewModel包含一个带有ActorViewModel实例的ObservableCollection,由专用工作线程创建。然后将它们转换为ActorView实例以可视化计算的数据。在添加和删除实例时,我故意使用 Invoke ,而不是 BeginInvoke 来确保这些例程是性能问题的原因。

完成专用工作计算后,将在MainViewModel中调用UpdateLayout,此时将调用 UpdateSynchronizationCollectionLayout 来更新可视化。此时,不使用缩放和不透明度,并且在省略实例Viewbox时观察到相同的断续行为。除非我遗漏了某些内容,否则问题应该与我无法检查或控制渲染时间或速度这一事实有关。

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

1 个答案:

答案 0 :(得分:1)

默认情况下,您的应用程序代码在UI线程上运行,换句话说,如果您编写一个循环,通过其画布坐标更新所有对象的位置,则在循环退出之前,UI不会重新渲染。您需要使计算'原子'向UI线程发送更新,然后一次更新所有对象。

你提到:

  

使用Canvas.SetLeft和Canvas.SetTop执行定位。这个   代码在专用线程上的事件处理程序中运行,并且   因此不影响表现。

我认为它包装在Dispatcher.BeginInvoke中以将其编组到UI线程上?