场合
我有一个应用程序可以计算多个元素的常量变化位置(通常在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 );
}
答案 0 :(得分:1)
默认情况下,您的应用程序代码在UI线程上运行,换句话说,如果您编写一个循环,通过其画布坐标更新所有对象的位置,则在循环退出之前,UI不会重新渲染。您需要使计算'原子'向UI线程发送更新,然后一次更新所有对象。
你提到:
使用Canvas.SetLeft和Canvas.SetTop执行定位。这个 代码在专用线程上的事件处理程序中运行,并且 因此不影响表现。
我认为它包装在Dispatcher.BeginInvoke中以将其编组到UI线程上?