Ручное управление рендерингом окон или холстов? - PullRequest
1 голос
/ 04 января 2012

Положение

У меня есть приложение, которое вычисляет постоянное изменение положения ряда элементов (обычно от 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 );
    }

1 Ответ

1 голос
/ 04 января 2012

По умолчанию код вашего приложения выполняется в потоке пользовательского интерфейса, другими словами, если вы пишете цикл, который обновлял местоположение всех ваших объектов через их координаты холста, пользовательский интерфейс не будет повторно отображаться, пока ваш цикл не завершится.Вы должны сделать свои вычисления «атомарными», посылая обновления в поток пользовательского интерфейса, а затем обновлять все объекты за один раз.

Вы упоминаете, что:

Позиционирование выполняется с использованием Canvas.SetLeft и Canvas.SetTop.Этот код выполняется внутри обработчика событий в выделенном потоке и, таким образом, не влияет на производительность.

Я предполагаю, что это обернуто в Dispatcher.BeginInvoke для перенаправления его в поток пользовательского интерфейса?

...