WPF MeasureOverride вызывается несколько раз после нескольких уведомлений PropertyChanged
В моем приложении у меня есть график (пользовательская панель Wpf) с элементами управления 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 ответ
Может быть, вы можете избежать ненужных недействительности. Смотрите эту ссылку:
Точного ответа нет, потому что ваша проблема должна быть прояснена лучше. В любом случае, событие 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-приветствий