Положение
У меня есть приложение, которое вычисляет постоянное изменение положения ряда элементов (обычно от 10 до 20 одновременно). Расчеты выполняются в выделенном потоке, который затем запускает событие, чтобы указать, что вычисления завершены. Этот выделенный поток работает со стабильной скоростью 100 кадров в секунду.
Визуализация
Визуализация элементов выполняется с использованием WPF. Я реализовал один холст и написал собственный код для добавления визуального представления каждого элемента на холст. Позиционирование выполняется с использованием Canvas.SetLeft и Canvas.SetTop . Этот код выполняется внутри обработчика событий в выделенном потоке и, следовательно, не влияет на производительность.
Задача
Из-за того, что элементы постоянно движутся, движение кажется заиканием . Единственное допущение, которое я могу сделать на данный момент, заключается в том, что WPF отрисовывает по своему усмотрению и пытается достичь максимальных 60 кадров в секунду. Если у вас есть дополнительные комментарии о том, почему это может произойти, пожалуйста, просветите меня.
Вопрос
Как я могу сказать WPF визуализировать, когда выделенный поток вызвал завершенные вычисления? Если это вообще возможно, я бы хотел предотвратить рендеринг окна / холста до тех пор, пока вычисления не будут завершены, после чего фрейм должен переместиться и отобразить новую информацию.
Комментарий
Многим людям не нравятся попытки выйти за пределы стандартных 60 кадров в секунду, и я тоже. Но абсолютно необходимо иметь возможность влиять на , когда рендеринг происходит до снижается до 60 кадров в секунду. Это должно гарантировать, что 60 кадров в секунду не влияют на проблему заикания.
Визуализация (код)
Это задействованный код для фактического обновления макета, согласно запросу доктора Эндрю Бернетта-Томпсона. MainViewModel содержит ObservableCollection с экземплярами ActorViewModel, созданными выделенным рабочим потоком. Затем они переводятся в экземпляры 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 );
}