1

在我的应用程序中,我有一个带有 Wpf 控件作为元素的图形(自定义 Wpf 面板)。这些元素是从 Wpf Control 派生的自定义控件。DataTemplate 将控件与它们的视图模型相关联。视图模型集合“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>

如果 VariableValue 为 null,则折叠上面的 TextBlock

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

更新:该问题可在下面给出的链接的示例中重现。下载、构建、调试。打开 App 后,在 Window.xaml.cs MeasureOverride 中设置断点。回到应用程序并点击“点击我”按钮。断点被击中 11 次!

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

任何想法都非常感谢。

4

1 回答 1

3

没有准确的答案,因为您的问题应该得到更好的澄清。无论如何,PropertyChanged 事件的诞生只是为了触发订阅者的任何更改。

您没有指定元素是什么,如何在面板中托管,以及它们执行什么样的触发器来触发如此多的 MeasureOverride。您可以选择不同的方法:取消绑定所有元素,然后再次绑定它们。另一种解决方案是避免任何可能导致许多触发器的绑定,并通过 DispatcherTimer 在每个滴答声中调用 InmvalidateVisual 来更新 UI。

希望能帮助到你。

编辑:我对你的消息来源做了一个把戏。我必须承认这是我从未使用过的东西。但是,请随意决定是否使用它。

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(与 Width 相同):

        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 的输出窗口中显示了几个“measure-override”。这就是原始行为。(还要注意标记为 true 的复选框,但不要取消标记!)

现在,关闭应用程序并重新启动。现在,取消标记复选框,然后单击按钮。您会注意到更少的“测量覆盖”调用。

希望这可以帮助。

干杯

EDIT2:该死!...我忘了一块!...对不起!

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

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

再欢呼

于 2011-08-05T16:09:03.380 回答