ScrollViewer中有多个ItemsControl的性能问题

时间:2010-09-30 15:12:34

标签: silverlight windows-phone-7

我需要一个包含两个绑定列表的可滚动表面。起初,我使用了一个ScrollViewer,里面有两个ListBox,每个都禁用了滚动,所以我仍然可以选择项目。看到加载时间性能不佳,我将ListBoxes更改为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>

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

谢谢。

3 个答案:

答案 0 :(得分:3)

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

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

我们需要创建一种新的ItemsControl类型。现在我们可以通过简单地接受我们想要创建一个仅支持此任务的非常具体的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,如果提供的话,它会在奇数/偶数之间切换(注意这是项目的一个方面)。

现在让我们使用VirtualizingStackPanel: -

<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正在使用VirtualizingStackPanel,并且会在ScrollViewer中显示。

现在我们可以提供很多内容: -

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

}

如果您切换到标准StackPanel,这需要花费很长时间才能加载,而虚拟化则会立即显示。

有了这些信息,您应该能够创建一个特殊的ItemsControl,它具有以下属性: -

  • ButtonTemplate(DataTemplate)
  • HeaderTemplate(DataTemplate)
  • TopTenHeaderText(String)
  • TopHundredHeaderText(String)
  • TopTenSource(IEnumerable<City>
  • TipHunderedSource(IEnumerable<City>

现在,您可以使用一些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类型的项目。

答案 1 :(得分:1)

谢谢你@AnthonyWJones,你的回答(几乎)正是我所寻找的。我决定在这里提供自己的答案,以便其他读者知道我如何根据自己的需要调整答案。

首先,正如所建议的那样,我从ItemsControl派生,并提供第二个“模板”属性,称为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以提供我自己的模板选择逻辑。我正在做的只是将任何“字符串”项重定向到HeaderTemplate,将其他项重定向到通常的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在DEBUG中抱怨,因为“对象”类型没有“Name”属性。它在调试输出中生成System.Windows.Data Error: BindingExpression path error: 'Name' property not found消息,可以忽略它。

答案 2 :(得分:0)

您可以使用一个list / itemscontrol,但是使用不同的datatemplate来获得相同的效果吗?

或者您可以使用枢轴控制,将前10个坐姿放在一个枢轴中,前100个放在另一个枢轴中。