代码之家  ›  专栏  ›  技术社区  ›  Martin Plante

ScrollViewer中多个itemsControl的性能问题

  •  3
  • Martin Plante  · 技术社区  · 14 年前

    我需要一个包含两个绑定列表的可滚动表面。首先,我使用了一个包含两个列表框的ScrollViewer,每个列表框都禁用了滚动,所以我仍然可以选择项目。由于加载时间性能不佳,我将列表框改为itemscontrol,但性能仍然很差。我的两个清单总共只有110个项目。

    <ScrollViewer Grid.Row="1">
        <StackPanel>
            <Button Style="{StaticResource EmptyNonSelectButtonStyle}" BorderThickness="0" HorizontalContentAlignment="Left" Click="AnyCityButton_Click">
                <TextBlock Text="{Binding Resources.CurrentLocationItem, Source={StaticResource LocalizedResources}}" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeLarge}" />
            </Button>
            <TextBlock Text="{Binding Resources.TopTenCitiesHeader, Source={StaticResource LocalizedResources}}" Style="{StaticResource PhoneTextSubtleStyle}" Margin="12,12,12,8" />
            <ItemsControl ItemsSource="{Binding TopTenCities}" ItemTemplate="{StaticResource CityDataTemplate}" HorizontalContentAlignment="Stretch" />
            <TextBlock Text="{Binding Resources.TopHundredCitiesHeader, Source={StaticResource LocalizedResources}}" Style="{StaticResource PhoneTextSubtleStyle}" Margin="12,12,12,8" />
            <ItemsControl ItemsSource="{Binding TopHundredCities}" ItemTemplate="{StaticResource CityDataTemplate}" HorizontalContentAlignment="Stretch" />
        </StackPanel>
    </ScrollViewer>
    

    我能做些什么来提高性能?我已经尝试在页面加载后设置itemsource,但它仍然很难看(空列表几秒钟),没有什么意义。

    谢谢您。

    3 回复  |  直到 14 年前
        1
  •  3
  •   AnthonyWJones    14 年前

    这个答案已经变成了一个怪物,但我想你会找到答案的。

    我们需要以某种方式使用 VirtualizingStackPanel 作为 ListBox . 我们需要收集所有要显示的项目(按钮、两个文本块和两组城市数据)到某种类型的单个可枚举项中。真正的诀窍是确定用于呈现项目的三个模板之一。

    最重要的是我们需要创造一种新的 ItemsControl . 现在我们可以通过简单地接受我们想要创建一个非常具体的 项目控制 只支持此任务。首先,这里是一个“10开场白”(英国媒体参考)。

    创建特定项控件的一个非常愚蠢的示例:

    public class SwitchingItemsControl : ItemsControl
    {
        public DataTemplate AlternativeItemTemplate { get; set; }
    
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            ContentPresenter cp = (ContentPresenter)element;
            if (AlternativeItemTemplate != null && (((int)item) & 1) == 1)
                cp.ContentTemplate = AlternativeItemTemplate;
            else
                cp.ContentTemplate = ItemTemplate;
    
            cp.Content = item;
        }
    }
    

    此控件假定其项是一组整数。它有一个 AlternativeItemTemplate 如果提供,它将在奇数/偶数基础上切换(请注意,这是项的一个方面)。

    现在让我们用 虚拟化跟踪面板 -

    <UserControl x:Class="CustomVirtualizingPanelInSL.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:SilverlightApplication1"
        Width="400" Height="300">
        <Grid x:Name="LayoutRoot" Background="White">
            <local:SwitchingItemsControl  x:Name="itemsControl" >
                <local:SwitchingItemsControl.Template>
                    <ControlTemplate TargetType="local:SwitchingItemsControl">
                        <ScrollViewer VerticalScrollBarVisibility="Visible">
                            <ItemsPresenter />
                        </ScrollViewer>
                    </ControlTemplate>
                </local:SwitchingItemsControl.Template>
                <local:SwitchingItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border CornerRadius="2" BorderBrush="Blue" BorderThickness="1" Margin="2">
                            <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" />
                        </Border>
                    </DataTemplate>
                </local:SwitchingItemsControl.ItemTemplate>
                <local:SwitchingItemsControl.AlternativeItemTemplate>
                    <DataTemplate>
                        <Border CornerRadius="2" BorderBrush="Red" BorderThickness="1" Margin="2">
                            <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" />
                        </Border>
                    </DataTemplate>
                </local:SwitchingItemsControl.AlternativeItemTemplate>
                <local:SwitchingItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel />
                    </ItemsPanelTemplate>
                </local:SwitchingItemsControl.ItemsPanel>
            </local:SwitchingItemsControl>
        </Grid>
    </UserControl>
    

    注意 ItemsPanel 正在使用 虚拟化跟踪面板 在一个 ScrollViewer .

    现在我们可以给它很多内容:

    namespace SilverlightApplication1
    {
        public partial class MainPage : UserControl
        {
            public MainPage()
            {
                InitializeComponent();
                itemsControl.ItemsSource = Enumerable.Range(0, 10000);
            }
        }
    

    }

    如果切换到标准 StackPanel 这需要很长时间才能加载,而通过虚拟化,它看起来是即时的。

    使用此信息,您应该能够创建具有以下属性的特殊项控件:

    • 按钮模板(数据模板)
    • HeaderTemplate(数据模板)
    • ToptenHeaderText(字符串)
    • TopHundRedHeaderText(字符串)
    • TopTenSource IEnumerable<City> )
    • Tiphunderedsource公司( IEnumerable<城市> )

    现在,您可以使用一些LINQ扩展方法创建单个可枚举的:

    itemsControl.ItemsSource =  Enumerable.Repeat((object)null, 1)
       .Concat(Enumerable.Repeat((object)TopTenHeadeText))
       .Concat(TopTenSource.Cast<object>())
       .Concat(Enumerable.Repeat((object)TopHundredText))
       .Concat(TopHundredSource.Cast<object>())
    

    现在你只需要重写 PrepareContainerForItemOverride 在两者之间选择 ButtonTemplate (对于第一个空项) HeaderTemplate 对于字符串类型的项或 ItemTemplate 对于类型的项 City .

        2
  •  1
  •   Martin Plante    14 年前

    谢谢你@anthonywjones,你的回答(几乎)正是我想要的。我决定在这里提供我自己的答案,以便其他读者知道我是如何使他的答案适应我的需要的。

    首先,正如建议的那样,我从itemscontrol派生,并提供第二个“template”属性,称为 HeaderTemplate :

    #region HeaderTemplate PROPERTY
    
    public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
      "HeaderTemplate",
      typeof( DataTemplate ),
      typeof( ItemsControlWithHeaders ),
      new PropertyMetadata( null, new PropertyChangedCallback( OnHeaderTemplateChanged ) ) );
    
    public DataTemplate HeaderTemplate
    {
      get { return ( DataTemplate )this.GetValue( HeaderTemplateProperty ); }
      set { this.SetValue( HeaderTemplateProperty, value ); }
    }
    
    private static void OnHeaderTemplateChanged( DependencyObject obj, DependencyPropertyChangedEventArgs args )
    {
      ItemsControlWithHeaders control = obj as ItemsControlWithHeaders;
      control.InvalidateArrange();
    }
    
    #endregion
    

    第二,我要压倒一切 PrepareContainerForItemOverride 提供我自己的模板选择逻辑。我所做的只是将任何“字符串”项重定向到 页眉模板 以及其他日常用品 ItemTemplate :

    protected override void PrepareContainerForItemOverride( DependencyObject element, object item )
    {
      base.PrepareContainerForItemOverride( element, item );
    
      ContentPresenter presenter = element as ContentPresenter;
    
      if( presenter != null )
      {
        if( item is string )
        {
          presenter.ContentTemplate = this.HeaderTemplate;
        }
        else
        {
          presenter.ContentTemplate = this.ItemTemplate;
        }
      }
    }
    

    现在可以这样使用此控件:

        <local:ItemsControlWithHeaders Grid.Row="1" ItemsSource="{Binding GroupedCities}" ScrollViewer.VerticalScrollBarVisibility="Auto">
            <local:ItemsControlWithHeaders.Template>
                <ControlTemplate TargetType="local:ItemsControlWithHeaders">
                    <ScrollViewer>
                        <ItemsPresenter />
                    </ScrollViewer>
                </ControlTemplate>
            </local:ItemsControlWithHeaders.Template>
            <local:ItemsControlWithHeaders.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </local:ItemsControlWithHeaders.ItemsPanel>
            <local:ItemsControlWithHeaders.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}" Style="{StaticResource PhoneTextSubtleStyle}" Foreground="{StaticResource PhoneAccentBrush}" Margin="12,12,12,8" />
                </DataTemplate>
            </local:ItemsControlWithHeaders.HeaderTemplate>
            <local:ItemsControlWithHeaders.ItemTemplate>
                <DataTemplate>
                    <Button Style="{StaticResource EmptyNonSelectButtonStyle}" BorderThickness="0" HorizontalContentAlignment="Left" Click="AnyCityButton_Click">
                        <TextBlock Text="{Binding Name, Mode=OneWay}" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeLarge}" />
                    </Button>
                </DataTemplate>
            </local:ItemsControlWithHeaders.ItemTemplate>
        </local:ItemsControlWithHeaders>
    

    要构建数据源,您必须传递给这个特殊的混合控制,Linq很好,但我选择了一个更明确的解决方案,在我的视图模型中实现:

    public IEnumerable<object> GroupedCities
    {
      get
      {
        yield return new CurrentLocationCityViewModel();
        yield return Localized.TopTenCitiesHeader; // string resource
    
        foreach( CityViewModel city in this.TopTenCities )
        {
          yield return city;
        }
    
        yield return Localized.TopHundredCitiesHeader; // string resource
    
        foreach( CityViewModel city in this.TopHundredCities )
        {
          yield return city;
        }
      }
    }
    

    我现在有一个通用的 ItemsControlWithHeaders 我可以在不止这个场景中重用。性能很好。对于像我这样的纯粹主义者来说,唯一剩下的问题是,基itemsControl在调试时会抱怨,因为“object”类型没有“name”属性。它产生了一个 System.Windows.Data Error: BindingExpression path error: 'Name' property not found 调试输出中的消息,可以忽略。

        3
  •  0
  •   John Gardner    14 年前

    可以使用一个列表/项控件,但使用不同的数据模板来获得相同的效果吗?

    或者你可以用一个轴控件来代替,把前10个位置放在一个轴上,前100个放在另一个轴上。