Question

Goal: To Remeasure and then redraw only those signalgraphs that are in view in the virtualizing stack panel of a scrollviewer.

Current build:

Currently I have a dependency property (UnitsOfTimePerAxisDivision) that affects measure whenever the container in the scrollviewer (signalgraph) is updated.

public static readonly DependencyProperty UnitsOfTimePerAxisDivisionProperty =
      DependencyProperty.Register("UnitsOfTimePerAxisDivision",
      typeof(int), typeof(SignalGraph),
      new FrameworkPropertyMetadata(1), FrameworkPropertyMetadataOptions.AffectsMeasure);

However, this updates every signalgraph, including those that are not visible. Running measure and then redraw on every single signal is way too time consuming.

  1. How can I update only the visible ones?

I thought about doing some sort of test like this,

In WPF, how can I determine whether a control is visible to the user?

by basically setting a propertychangedcallback in which I test whether each signal is visible. I will end up testing all the signals still, but at least I won't be drawing all of them.

My 2nd question is whether there is some way I can implement this by relying on functions called in measure/arrange.

My understanding of virtualizing stack panel is as follows: At the top level we have a treeview that contains a scrollviewer. the scrollviewer's controltemplate contains a grid in which the scrollcontentpresenter places the items within a column and row of set size.

        ControlTemplate TargetType="{x:Type ScrollViewer}">
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="*"/>
              <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
              <RowDefinition Height="*"/>
              <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <ScrollContentPresenter 
                Grid.Column="0"
                Grid.Row="0" />
            <ScrollBar 
                Margin="{Binding RelativeSource={RelativeSource AncestorType={x:Type DaedalusGraphViewer:GraphViewer}}, 
                        Path=GraphHeight, Converter={StaticResource ScrollBarTopMarginConverter}}"
                Grid.Row="0"
                Grid.Column="1"
                Width="18"
                Name="PART_VerticalScrollBar"
                Orientation="Vertical"
                Value="{TemplateBinding VerticalOffset}"
                Maximum="{TemplateBinding ScrollableHeight}"
                ViewportSize="{TemplateBinding ViewportHeight}"
                Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
            <ScrollBar 
                Height="18"
                Name="PART_HorizontalScrollBar"
                Orientation="Horizontal"
                Grid.Row="1"
                Grid.Column="0"
                Value="{TemplateBinding HorizontalOffset}"
                Maximum="{TemplateBinding ScrollableWidth}"
                ViewportSize="{TemplateBinding ViewportWidth}"
                Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
          </Grid>
        </ControlTemplate>

so let's assume for a second that I tell scrollviewer to remeasure and that the scrollviewer takes up 100x100 on the screen. This results in the scrollviewer measuring the grid by calling the grids.measure(available size) with the available size of 100x100.

The grid then deducts the size of the scrollbars (we will just guess ten), and tells scrollviewerpresenter to measure itself with 90x90. The presenter uses the virtualizing stackpanel, which then gives the children infinite size to measure themselves with (both vertically and horizontally I think). However, the virtualizing stack panel only measures children (signalgraphs) until it has filled its size of 90x90, and then the arrange phase proceeds from the scrollviewer down to the virtualizing stack panel.

If that's the case, I can place a call at the end of signalgraph's arrange method and only the onscreen signalgraphs should be arranged and redrawn.

  protected override Size ArrangeOverride(Size finalSize)
    {
      visuals.Clear();
      DrawSignal();
      return base.ArrangeOverride(finalSize);
    }

However, I find when I set a breakpoint in measure override for both the scrollviewer and the signalgraph, the one in the signalgraph is not hit.

I can't understand why signalgraphs are not remeasured if the scrollviewer is remeasured.

  1. Is it the case that the measure function stores the last size constraint (availableSize) passed to it by its parent and then doesn't perform a new measure unless the constraint changed? The other possibility I considered that might result in no measurement is if the scrollviewer calculates its size independently of the size of its children and then measures them only if the size is new.

    But maybe the issue is something like this. if someone has reflector and can get me the actual scrollviewer measureoverride code, that would be amazing. If not, I may try to get reflector tomorrow and figure out how to use it.

    override Measureoverride ( size availableSize) { Size newSize = availableSize. if (availableSize != this.DesiredSize) { Measure chilren(); } }

  2. if this is not the case, then what causes signalgraph not to be measured?

edit:

Yay, I got something to virtualize. i'm not totally crazy. Just mostly crazy. It appears that applying this style breaks virtualization. Removing it makes everything work.

Using the built - in virtualization is a lot better than trying to actually test visibility on each signalgraph that exists. I still don't know the answer to #2 above, but i won't need it if i can get this virtualization working. However, I would like to keep my current style and just figure out why the style is breaking virtualization.

<!--Style for scrollviewer for signals-->
  <Style x:Key="SignalScrollViewerStyle" TargetType="{x:Type Components:SignalScrollViewer}">
    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type ScrollViewer}">
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="*"/>
              <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
              <RowDefinition Height="*"/>
              <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <ScrollContentPresenter 
                Grid.Column="0"
                Grid.Row="0" />
            <ScrollBar 
                Margin="{Binding RelativeSource={RelativeSource AncestorType={x:Type DaedalusGraphViewer:GraphViewer}}, 
                        Path=GraphHeight, Converter={StaticResource ScrollBarTopMarginConverter}}"
                Grid.Row="0"
                Grid.Column="1"
                Width="18"
                Name="PART_VerticalScrollBar"
                Orientation="Vertical"
                Value="{TemplateBinding VerticalOffset}"
                Maximum="{TemplateBinding ScrollableHeight}"
                ViewportSize="{TemplateBinding ViewportHeight}"
                Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
            <ScrollBar 
                Height="18"
                Name="PART_HorizontalScrollBar"
                Orientation="Horizontal"
                Grid.Row="1"
                Grid.Column="0"
                Value="{TemplateBinding HorizontalOffset}"
                Maximum="{TemplateBinding ScrollableWidth}"
                ViewportSize="{TemplateBinding ViewportWidth}"
                Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

edit 2:

found the solution on this page: I found the solution on this page:

"http://social.msdn.microsoft.com/Forums/vstudio/en-US/deae32d8-d7e2-4737-8c05-bbd860289425/lost-virtualization-on-styled-scrollviewer?forum=wpf

when overriding the default scrollviewer style i didn't bind the attached property to the scrollviewer contentpresenter. uggh, amazing how hard that one line was to find. I was going crazy, thinking that I didn't know what virtualization was actually supposed to do.

The only bothersome thing now is that I still don't fully understand how the layout process works. If I have a dependency property with the metadaoption affectsmeasure, and then the dependency property changes, the item will remeasure. Will the parents then remeasure if the size of the element changes?

that would be my guess, but it still doesn't explain why I had scrollviewer children that were not remeasuring during the scrollviewer's measure cycle.

Était-ce utile?

La solution 2

So the issue was my failure to attach the cancontentscroll property to my scrollcontent presenter in my scrollviewer style.

 <ScrollContentPresenter 
                CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"

Autres conseils

There is no point in using Reflector. VirtualizingStackPanel maintains a list of visible containers. When you start scrolling up or down the VirtualizingStackPanel will remove containers which won't be needed anymore but creates new one. At the end up end up removing 5 old items but adding 5 new so the count of visible containers always stays the same therefore just change the property UnitsOfTimePerAxisDivision and dont worry about which containers are visible.

If ScrollViewer passes 90x90 to VirtualizingStackPanel and futhermore the VirtualizingStackPanel passes infinity to the containers, the containers will only get remeasured when in invalid measure state else they return back the "old" size which is their current size.

Just let the WPF do the job and don't worry about performance issues. The VirtualizingStackPanel holds visible containers so there wont be any remeasuring or performance hit on all possible containers. Only 20 will be used even though you have 1000 items.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top