1

In my application, I have a graph (Custom Wpf Panel) with Wpf controls as elements. The elements are custom controls derived from Wpf Control. DataTemplates associate the controls to their view models. The view models collection, "GraphElements" is binded to the itemsControl as shown below.

<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>

The elements in the graph can vary from 2 to 500. Under certain mode of the application, the elements display their value. To display the value, the ViewModel for the element fires the INotifyPropertyChanged.PropertyChanged("VariableValue")

Issue: When i have 100+ elements in the graph, each elements view model fires INotifyPropertyChanged.PropertyChanged to have the elements value displayed. This calls MeasureOverride 100+ times leading to memory and peformance issues.

How can I reduce the number of MeasureOverride calls?

XAML for Value display for the graph element:

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

The TextBlock above is collapsed if VariableValue is null

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

UPDATE: The issue is reproducible in the sample at the link given below. Download, build, debug. Once App is opened, set a breakpoint in Window.xaml.cs MeasureOverride. Come back to the App and hit the "Click Me" button. The breakpoint is hit 11 times !.

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

Any ideas greatly appreciated.

Adi Lester
  • 24,731
  • 12
  • 95
  • 110
  • Maybe you can avoid unnecessary invalidations. See this link: [Build Your Tree Top-Down](http://msdn.microsoft.com/en-us/library/bb613542.aspx) – LPL Aug 05 '11 at 16:07

1 Answers1

3

There's no a precise answer, because your problem should be clarified better. Anyway, the PropertyChanged event has born just to trigger any change to the subscribers.

You don't specify what the elements are, how are hosted in the panel, and what kind of trigger they do to fire so many MeasureOverride. You may choose a different approach: unbind all the elements, then bind them again. Another solution is to avoid any binding that could lead to many triggers, and update the UI via a DispatcherTimer calling an InmvalidateVisual every tick.

Hope it helps.

EDIT: I made a trick on your sources. I must admit that is something I never used. However, feel free to decide whether to use it.

1) place an indicator within the MeasureOverride (avoid the breakpoints):

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

2) within the MyViewModel class add this:

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

3) modify the MyProp-set (same as for Width):

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

4) within the Window1 class add the following DependencyProperty (useful for the demo):

    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) modify the button click handler as follows:

    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);
        }
    }

Well, if you try to run the application, when you click the button, you'll notice several "measure-override" displayed in the output window of Visual Studio. That's the original behavior. (Note also the ckeckbox marked as true, but DO NOT unmark it!)

Now, shut down the app and restart. Now, UNMARK the checkbox, then click on the button. You'll notice much less "measure-override" calls.

Hope this helps.

Cheers

EDIT2: DAMN!...I forget a piece!...Sorry!

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

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

Re-cheers

Mario Vernari
  • 6,649
  • 1
  • 32
  • 44
  • Hi Mario, Thanks for your suggestions. I have specified a link to a sample App in which the issue is reproducible. Please help if time permits. – Chougule Vinay Aug 09 '11 at 20:21
  • Thanks Mario, It is not working for me. Uncheck the checkbox; KeepSync is set to false in all view models. Click the button, 0 measureoverride calls, UI not updated. – Chougule Vinay Aug 10 '11 at 09:36
  • That's right! The aim is just to avoid the update as long the viewmodels have been updated. Once the process has been completed, you switch the KeepSync as true, and the UI will be updated all at once. I don't see any other way to do it (with WPF). – Mario Vernari Aug 10 '11 at 10:34
  • The number of measure-override calls is the same for me. 7 before and 7 after. I wonder how you got fewer calls. May be all items were not visible. – Chougule Vinay Aug 10 '11 at 13:01
  • I haven't the code here, but when I tried there was only one M/O after marking the checkbox. Even pressing ten times the button. At this point I am not sure about what are you looking for. Once you subscribe for a notification (INotifyPropertyChanged), any change will be notified to the UI. The only way to stop this behavior is to temporarily detach the binding. – Mario Vernari Aug 10 '11 at 13:14
  • Marked as Answer: It PARTLY solved the issue. Avoided firing property changed event under certain conditions and then finally reloaded the itemsControl. – Chougule Vinay Aug 15 '11 at 10:55