Проблема производительности с несколькими ItemsControl внутри ScrollViewer - PullRequest
3 голосов
/ 30 сентября 2010

Мне нужна одна прокручиваемая поверхность, которая содержит два связанных списка.Сначала я использовал 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 ]

3 голосов
/ 30 сентября 2010

Этот ответ превратился в монстра, но я прошёл сквозь него, и я думаю, что вы найдете ответ.

Нам нужно каким-то образом использовать 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 (для первого нулевого элемента), the HeaderTemplate для элемента типа string или ItemTemplate для элемента типа City.

1 голос
/ 04 октября 2010

Спасибо @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, чтобы предоставить собственную логику выбора шаблона. Я просто перенаправляю любой «строковый» элемент на 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, поскольку тип «объект» не имеет свойства «Имя». Он генерирует сообщение System.Windows.Data Error: BindingExpression path error: 'Name' property not found в выходных данных отладки, которое можно игнорировать.

0 голосов
/ 30 сентября 2010

Не могли бы вы использовать один элемент управления list / items, но с разными шаблонами данных, чтобы получить тот же эффект?

Или вместо этого вы можете использовать элемент управления pivot, поместив верхние 10 объектов в один, а топ 100 в другой..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...