2011-06-28 3 views
14

Возможно ли узнать индекс текущего элемента в ItemsControl?WPF ItemsControl текущий индекс ListItem в ItemsSource

EDIT Это работает!

<Window.Resources> 

    <x:Array Type="{x:Type sys:String}" x:Key="MyArray"> 
     <sys:String>One</sys:String> 
     <sys:String>Two</sys:String> 
     <sys:String>Three</sys:String> 
    </x:Array> 

</Window.Resources> 

<ItemsControl ItemsSource="{StaticResource MyArray}" AlternationCount="100"> 
    <ItemsControl.ItemTemplate> 
        <DataTemplate> 
            <StackPanel Margin="10"> 

       <!-- one --> 
               <TextBlock Text="{Binding Path=.,  
                    StringFormat={}Value is {0}}" /> 

       <!-- two --> 
                <TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex),  
                    RelativeSource={RelativeSource TemplatedParent},  
                    FallbackValue=FAIL,  
                    StringFormat={}Index is {0}}" /> 

       <!-- three --> 
                <TextBlock Text="{Binding Path=Items.Count,  
                    RelativeSource={RelativeSource FindAncestor,  
                        AncestorType={x:Type ItemsControl}},  
                    StringFormat={}Total is {0}}" /> 

            </StackPanel> 
        </DataTemplate> 
    </ItemsControl.ItemTemplate> 
</ItemsControl> 

Это выглядит следующим образом:

enter image description here

+0

Одним из способов было бы создать собственный IValueConverter и передать вещи свойство в качестве параметра, а затем получить индекс, глядя на коллекцию, хотя это не очень эффективно. – eulerfx

+0

Это тоже не является надежным. Что делать, если он фильтруется или сортируется с помощью CollectionViewSource? Умный, признаюсь, но есть лучшие способы. –

ответ

19

Я спросил то же самое время назад here

Существует не встроенный индекс собственности, но вы можете установить AlternationCount вашего ItemsControl на что-то выше, чем ваше количество элементов, и связываетесь с AlternationIndex

<TextBlock Text="{Binding 
    Path=(ItemsControl.AlternationIndex), 
    RelativeSource={RelativeSource Mode=TemplatedParent}, 
    FallbackValue=FAIL, 
    StringFormat={}Index is {0}}" /> 

Следует отметить, что это решение может не работать, если ваш ListBox использует виртуализацию как bradgonesurfing pointed out here.

+1

Cheers. Просто не забудьте установить свой AlternationCount выше ожидаемого количества элементов в ItemsControl! –

+0

@ Rachel: Вы знаете решение, если кому-то нужен индекс на основе индекса, вместо индекса на основе нуля? Я думаю, мы могли бы добавить конвертер, чтобы увеличить счет, но просто любопытно, если вы знаете о лучшем способе. – VoodooChild

+4

Я бы этого не сделал. Посмотрите мои результаты экспериментов на наличие ошибок. http://stackoverflow.com/questions/6511180/wpf-itemscontrol-the-current-listitem-index-in-the-itemssource/17962582#17962582 – bradgonesurfing

1

Да это! ItemsControl предоставляет имущество ItemContainerGenerator. У ItemContainerGenerator есть методы, такие как IndexFromContainer, которые могут быть использованы для поиска индекса данного элемента. Обратите внимание: если вы привязываете свой ItemsControl к коллекции объектов, для каждого из них автоматически создается контейнер. Контейнер для каждого связанного элемента можно найти с помощью метода ContainerFromItem.

+2

Можете ли вы получить доступ к тем, кто находится в XAML? –

+0

нет, вы не можете. если вам нужно сделать это без кода, почему бы не использовать шаблон MVVM? создайте список элементов, содержащих их индекс. – ColinE

+3

Потому что мой CollectionViewSource может изменить порядок или фильтровать этот список. –

6

Когда вы используете Alternation Count помните, что вы также можете привязать свойство AlternationCount к текущему счету Предметы коллекции, с которой вы связываетесь с AlternationCount является DependencyProperty.

AlternationCount="{Binding Path=OpeningTimes.Count,FallbackValue='100'}" 

Надеюсь, это поможет.

5

Это не совсем ответ, а предложение. Не используйте технику AlternationIndex, как было предложено. Кажется, что он работает сначала, но есть более серьезные побочные эффекты. Кажется, что вы не можете гарантировать, что AlternationIndex начинается с 0.

На первом рендеринга работает правильно

enter image description here

но изменение размера Сетку, а затем расширяющийся результаты в индексе не начиная с нуля больше. Вы можете увидеть эффект ниже изображения

enter image description here

Это был сгенерирован из следующего XAML. Есть некоторые пользовательские компоненты, но вы получите эту идею.

<DataGrid 
    VirtualizingPanel.VirtualizationMode="Recycling" 
    ItemsSource="{Binding MoineauPumpFlanks.Stator.Flank.Boundary, Mode=OneWay}" 
    AlternationCount="{Binding MoineauPumpFlanks.Stator.Flank.Boundary.Count, Mode=OneWay}" 
    AutoGenerateColumns="False" 
    HorizontalScrollBarVisibility="Hidden" 
    > 
    <DataGrid.Columns> 
     <DataGridTemplateColumn Header="Id"> 
      <DataGridTemplateColumn.CellTemplate> 
       <DataTemplate> 
        <TextBlock 
          Margin="0,0,5,0" 
          TextAlignment="Right" 
          Text="{Binding RelativeSource={ RelativeSource 
                  Mode=FindAncestor, 
                  AncestorType=DataGridRow}, 
              Path=AlternationIndex}"/> 
       </DataTemplate> 
      </DataGridTemplateColumn.CellTemplate> 
     </DataGridTemplateColumn> 
     <DataGridTemplateColumn > 
      <DataGridTemplateColumn.Header> 
       <StackPanel Orientation="Horizontal"> 
        <TextBlock Text="Point ["/> 
        <Controls:DisplayUnits DisplayUnitsAsAbbreviation="True" DisplayUnitsMode="Length"/> 
        <TextBlock Text="]"/> 
       </StackPanel> 
      </DataGridTemplateColumn.Header> 
      <DataGridTemplateColumn.CellTemplate> 
       <DataTemplate> 
        <Controls:LabelForPoint ShowUnits="False" Point="{Binding}" /> 
       </DataTemplate> 
      </DataGridTemplateColumn.CellTemplate> 
     </DataGridTemplateColumn> 
    </DataGrid.Columns> 
</DataGrid> 

Я ищу альтернативное решение :(

+4

Я думаю, что это делает виртуализация. Я бы не рекомендовал использовать «AlternationIndex» в тех случаях, когда у вас достаточно элементов для виртуализации. – Rachel

+0

Придется попробовать, будет ли виртуализация делать это. Тем не менее это неприятный крест, с которым я не хочу иметь ничего общего. В моем другом ответе подробно описано простое безопасное решение, которое у меня есть. – bradgonesurfing

+0

FWIW, кто-то придумал [этот другой подход] (https://stackoverflow.com/a/31436840/656243) в ответ на аналогичный вопрос. –

0

Более надежный способ заключается в использовании конвертер значение для создания новой коллекции с индексом. С парой помощников, это довольно безболезненно. Я используйте ReactiveUI'sIEnumerable<T>.CreateDerivedCollection() и класс помощника, который я написал для других целей под названием Indexed.

public struct Indexed<T> 
{ 
    public int Index { get; private set; } 
    public T Value { get; private set; } 
    public Indexed(int index, T value) : this() 
    { 
     Index = index; 
     Value = value; 
    } 

    public override string ToString() 
    { 
     return "(Indexed: " + Index + ", " + Value.ToString() + ")"; 
    } 
} 

public class Indexed 
{ 
    public static Indexed<T> Create<T>(int indexed, T value) 
    { 
     return new Indexed<T>(indexed, value); 
    } 
} 

и преобразователь

public class IndexedConverter : IValueConverter 
{ 
    public object Convert 
     (object value 
     , Type targetType 
     , object parameter 
     , CultureInfo culture 
     ) 
    { 
     IEnumerable t = value as IEnumerable; 
     if (t == null) 
     { 
      return null; 
     } 

     IEnumerable<object> e = t.Cast<object>(); 

     int i = 0; 
     return e.CreateDerivedCollection<object, Indexed<object>> 
      (o => Indexed.Create(i++, o)); 

    } 

    public object ConvertBack(object value, Type targetType, 
     object parameter, CultureInfo culture) 
    { 
     return null; 
    } 
} 

и в XAML я могу сделать

<DataGrid 
    VirtualizingPanel.VirtualizationMode="Recycling" 
    ItemsSource="{Binding 
     MoineauPumpFlanks.Stator.Flank.Boundary, 
     Mode=OneWay, 
     Converter={StaticResource indexedConverter}}" 
    AutoGenerateColumns="False" 
    HorizontalScrollBarVisibility="Hidden" 
    > 
    <DataGrid.Columns> 

     <DataGridTemplateColumn Header="Id"> 

      <DataGridTemplateColumn.CellTemplate> 
       <DataTemplate> 
        <!-- Get the index of Indexed<T> --> 
        <TextBlock 
          Margin="0,0,5,0" 
          TextAlignment="Right" 
          Text="{Binding Path=Index}"/> 
       </DataTemplate> 
      </DataGridTemplateColumn.CellTemplate> 
     </DataGridTemplateColumn> 

      <DataGridTemplateColumn Header="Point" > 
      <DataGridTemplateColumn.CellTemplate> 
       <DataTemplate> 
        <!-- Get the value of Indexed<T> --> 
        <TextBlock Content="{Binding Value}" /> 
       </DataTemplate> 
      </DataGridTemplateColumn.CellTemplate> 
     </DataGridTemplateColumn> 
    </DataGrid.Columns> 
</DataGrid> 
+0

Разве это не так плохо на стороне производительности? Я имею в виду, что наблюдаемые коллекции предоставляют точную информацию в своих событиях «CollectionChanged» о том, какие изменения были внесены в какие элементы, поэтому нет необходимости обновлять полный список, потому что обработчик точно знает, что изменилось. И здесь мы представляем конвертер, который при любом изменении списка предоставит полный список GUI, который связывается с свойством списка. Или есть какая-то дополнительная магия, о которой я сейчас не думаю? –

+0

Нет, он не генерирует полный новый список при каждом изменении. CreateDerivedCollection создает новую коллекцию readonly. Он только обновляет части последующей сборки, которые необходимо изменить, основываясь на изменениях в коллекции вверх по течению. – bradgonesurfing

+0

, но разве это не делает индексы недействительными, если сбор данных вверх по течению обменивается двумя экземплярами? – Firo

Смежные вопросы