WPF MeasureOverride вызывается несколько раз после нескольких уведомлений PropertyChanged - PullRequest
1 голос
/ 05 августа 2011

В моем приложении у меня есть график (Custom Wpf Panel) с элементами управления Wpf в качестве элементов. Элементы - это пользовательские элементы управления, полученные из Wpf Control. Шаблоны данных связывают элементы управления с их моделями представления. Коллекция моделей представления "GraphElements" привязана к itemsControl, как показано ниже.

<ItemsControl x:Name="PART_GraphItemsControl" Grid.Column="1"
              VerticalAlignment="Stretch" 
              HorizontalAlignment="Left"
              ItemsSource="{Binding Path=GraphElements}"
              VirtualizingStackPanel.IsVirtualizing="True"
              VirtualizingStackPanel.VirtualizationMode="Recycling">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <local:GraphDesignerPanel HorizontalAlignment="Stretch"
                                      VerticalAlignment="Top" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

Элементы на графике могут варьироваться от 2 до 500. При определенном режиме приложения элементы отображают свое значение. Чтобы отобразить значение, ViewModel для элемента запускает INotifyPropertyChanged.PropertyChanged("VariableValue")

Проблема: Когда у меня есть 100+ элементов на графике, каждая модель представления элементов запускает INotifyPropertyChanged.PropertyChanged, чтобы отобразить значение элементов. Это вызывает MeasureOverride более 100 раз, что приводит к проблемам с памятью и производительностью.

Как можно уменьшить количество вызовов MeasureOverride?

XAML для отображения значения для элемента графика:

 <TextBlock  
    Text="{Binding Path=VariableValue, StringFormat={}({0})}" Width="60"
    FontSize="11" Name="txtBlk">
</TextBlock>

TextBlock выше свернут, если VariableValue равно нулю

<DataTrigger Binding="{Binding Path=VariableValue}" Value="{x:Null}">
    <Setter TargetName="txtBlk"  Property="Visibility" Value="Collapsed"/>
</DataTrigger>

ОБНОВЛЕНИЕ: Проблема воспроизводится в образце по ссылке, приведенной ниже. Скачать, собрать, отладить. После того, как приложение открыто, установите точку останова в Window.xaml.cs MeasureOverride. Вернитесь в приложение и нажмите кнопку «Нажмите меня». Точка останова достигнута 11 раз!.

http://sivainfotech.co.uk/measureoverrideissue.zip

Любые идеи с благодарностью.

1 Ответ

2 голосов
/ 05 августа 2011

Точного ответа нет, потому что ваша проблема должна быть прояснена лучше. В любом случае, событие PropertyChanged возникло просто для того, чтобы инициировать любое изменение подписчиков.

Вы не указываете, что это за элементы, как они размещены на панели и какой тип триггера они делают для запуска такого множества MeasureOverride. Вы можете выбрать другой подход: отсоединить все элементы, а затем связать их снова. Другое решение состоит в том, чтобы избежать любой привязки, которая может привести ко многим триггерам, и обновлять пользовательский интерфейс с помощью DispatcherTimer, вызывая InmvalidateVisual каждый тик.

Надеюсь, это поможет.

РЕДАКТИРОВАТЬ: я сделал трюк на ваших источниках. Я должен признать, что это то, что я никогда не использовал. Тем не менее, не стесняйтесь решать, использовать ли его.

1) поместите индикатор в MeasureOverride (избегайте точек останова):

    protected override Size MeasureOverride(Size availableSize)
    {
        System.Console.WriteLine("measure-override");
        return base.MeasureOverride(availableSize);
    }

2) в классе MyViewModel добавьте это:

    private bool _keepSync = true;
    public bool KeepSync
    {
        get { return this._keepSync; }
        set
        {
            if (this._keepSync != value)
            {
                this._keepSync = value;
                this.Sync();
            }
        }
    }

3) изменить набор MyProp (аналогично ширине):

        set
        {
            if (value != prop)
            {
                this.prop = value;
                if (this.KeepSync)
                    this.NotifyPropertyChanged("MyProp");
            }
        }

4) в классе Window1 добавьте следующее свойство DependencyProperty (полезно для демонстрации):

    public static readonly DependencyProperty KeepSyncProperty = DependencyProperty.Register(
        "KeepSync",
        typeof(bool),
        typeof(Window1),
        new PropertyMetadata(
            true,
            (obj, args) =>
            {
                var ctl = (Window1)obj;
                ctl.KeepSyncChanged(args);
            }));


    public bool KeepSync
    {
        get { return (bool)GetValue(KeepSyncProperty); }
        set { SetValue(KeepSyncProperty, value); }
    }


    private void KeepSyncChanged(DependencyPropertyChangedEventArgs args)
    {
        foreach (var vm in this.ViewModelCollection)
            vm.KeepSync = (bool)args.NewValue;
    }

5) измените обработчик нажатия кнопки следующим образом:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        for (var i = 0; i < ViewModelCollection.Count; i++)
        {
            ViewModelCollection[i].Width += 10;
            ViewModelCollection[i].MyProp = string.Format("Item #{0}: {1}", i, ViewModelCollection[i].Width);
        }
    }

Что ж, если вы попытаетесь запустить приложение, при нажатии кнопки вы увидите несколько «переопределений мер», отображаемых в окне вывода Visual Studio. Это оригинальное поведение. (Обратите внимание также на флажок ckeckbox, помеченный как true, но НЕ снимайте его!)

Теперь закройте приложение и перезапустите. Теперь снимите флажок, затем нажмите кнопку. Вы заметите гораздо меньше вызовов «измерения с переопределением».

Надеюсь, это поможет.

Приветствия

РЕДАКТИРОВАТЬ 2: Черт! ... Я забыл кусок! ... Извините!

private void Sync()
{
  if (this.KeepSync == false)
    return;

  this.NotifyPropertyChanged("MyProp");
  this.NotifyPropertyChanged("Width");
}

Re-ура

...