wpf如何在虚拟化scrollviewer的stackpanel中调用所有可见容器上的measure

时间:2013-10-04 02:37:32

标签: c# wpf layout scrollviewer virtualizingstackpanel

目标:重新测量然后仅重绘滚动查看器的虚拟化堆栈面板中视图中的那些信号图。

当前版本:

目前我有一个依赖属性(UnitsOfTimePerAxisDivision),它会在滚动查看器(信号图形)中的容器更新时影响度量。

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

然而,这会更新每个信号图,包括那些不可见的信号图。运行测量然后重新绘制每个单独的信号太费时间了。

  1. 如何只更新可见的?
  2. 我想过做这样的测试,

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

    通过基本设置propertychangedcallback来测试每个信号是否可见。我最终还是会对所有信号进行测试,但至少我不会全部绘制它们。

    我的第二个问题是,是否有某种方法可以依靠在measure / arrange中调用的函数来实现它。

    我对虚拟化堆栈面板的理解如下: 在顶层,我们有一个包含滚动查看器的树视图。 scrollviewer的controltemplate包含一个网格,其中scrollcontentpresenter将项目放置在一个设置大小的列和行中。

            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>
    

    所以让我们假设我告诉scrollviewer重新测量并且滚动查看器在屏幕上占用100x100。这导致scrollviewer通过调用grids.measure(可用大小)来测量网格,其可用大小为100x100。

    网格然后扣除滚动条的大小(我们只猜十个),并告诉scrollviewerpresenter用90x90测量自己。演示者使用虚拟化堆栈面板,然后给孩子们无限大小来测量自己(我认为是纵向和横向)。但是,虚拟化堆栈面板仅测量子(信号图),直到它填充其大小为90x90,然后排列阶段从滚动查看器向下进入虚拟化堆栈面板。

    如果是这种情况,我可以在信号图的排列方法结束时拨打电话,只应安排和重新绘制屏幕上的信号图。

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

    但是,我发现当我在scrollviewer和signalgraph的measure覆盖中设置断点时,signalgraph中的那个没有被击中。

    我无法理解为什么在重新测量滚动查看器时不会重新测量信号图。

    1. 是否测量函数存储由其父项传递给它的最后一个大小约束(availableSize),然后除非约束发生变化,否则不会执行新测量?我认为可能导致无法测量的另一种可能性是滚动查看器是否独立于其子项的大小计算其大小,然后仅在大小是新的时才测量它们。

      但问题可能是这样的。如果有人有反射器并且可以获得实际的scrollviewer measureoverride代码,那将是惊人的。如果没有,我可能会尝试明天获得反射器,并弄清楚如何使用它。

      覆盖Measureoverride(size availableSize) {   大小newSize = availableSize。   if(availableSize!= this.DesiredSize)   {     测量孩子();   } }

    2. 如果不是这样,那么是什么导致无法测量信号图?

    3. 编辑:

      是的,我有一些虚拟化的东西。我并不是完全疯了。只是大多疯了。似乎应用这种风格打破了虚拟化。删除它可以使一切正常。

      使用内置虚拟化比尝试在每个存在的信号图上实际测试可见性要好得多。我仍然不知道上面#2的答案,但如果我可以使这个虚拟化工作,我将不需要它。但是,我想保持现有的风格,并弄清楚为什么风格会打破虚拟化。

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

      编辑2:

      在此页面上找到了解决方案: 我在这个页面上找到了解决方案:

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

      当覆盖默认的scrollviewer样式时,我没有将附加属性绑定到scrollviewer contentpresenter。唉,令人惊讶的是,找到一条线路有多难。我疯了,以为我不知道虚拟化实际应该做什么。

      现在唯一令人烦恼的事情是我仍然不完全了解布局过程的工作原理。如果我有一个具有metadaoption affectmeasure的依赖属性,然后依赖属性更改,该项将重新测量。如果元素的大小发生变化,父母会重新测量吗?

      这将是我的猜测,但它仍然无法解释为什么我在滚动查看器的测量周期中没有重新测量的scrollviewer子项。

2 个答案:

答案 0 :(得分:1)

使用Reflector没有意义。 VirtualizingStackPanel维护可见容器的列表。当您开始向上或向下滚动时,VirtualizingStackPanel将删除不再需要的容器,但会创建新的容器。最后,最后删除了5个旧项目,但添加了5个新项目,因此可见容器的数量始终保持不变,因此只需更改属性UnitsOfTimePerAxisDivision,不要担心哪些容器可见。

如果ScrollViewer将90x90传递给VirtualizingStackPanel,并且VirtualizingStackPanel将infinity传递给容器,则只有在处于无效测量状态时才会重新测量容器,否则它们将返回“旧”大小,即当前大小。

让WPF完成工作,不要担心性能问题。 VirtualizingStackPanel包含可见容器,因此不会对所有可能的容器进行任何重新测量或性能损失。即使您有1000件物品,也只会使用20件。

答案 1 :(得分:0)

所以问题是我没有在我的scrollviewer样式中将cancontentscroll属性附加到我的scrollcontent演示者。

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