Список событий WPF Listbox + Expander

У меня есть Expander в ItemTemplate ListBox. Рендеринг прекрасен. Проблема, с которой я столкнулся, заключается в том, что я хотел бы, чтобы событие ListBox_SelectionChanged срабатывало при раскрытии и / или выборе расширителя. Событие MouseDown, похоже, не всплывает до ListBox.

Мне нужен SelectedIndex ListBox. Поскольку ListBox_SelectionChanged не запускается, индекс равен -1, и я не могу определить, какой элемент был выбран.

Событие ListBox_SelectionChanged запускается, если пользователь нажимает на Содержимое расширителя после того, как оно было развернуто. Если они щелкают только по расширителю, событие не запускается. Это сбивает пользователя с толку, потому что визуально он думает, что уже щелкнул этот элемент, когда на самом деле щелкает заголовок Expander. Мне нужно, чтобы элемент ListBox был выбран, когда пользователь разворачивает Expander, потому что с точки зрения пользователя элемент теперь выбран, хотя на самом деле это не так.

Любые предложения о том, как заставить это работать, или альтернативные способы определения SelectedIndex списка с расширителями в нем?

Упрощенный код для справки:

<Window x:Class="WpfApplication3.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
    Loaded="Window_Loaded">
    <Grid Name="Root">
        <ScrollViewer>
            <ListBox SelectionChanged="ListBox_SelectionChanged" ItemsSource="{Binding}">
                <ItemsControl.ItemTemplate >
                    <DataTemplate>
                        <Border>
                            <Expander>
                                <Expander.Header>
                                    <TextBlock Text="{Binding Path=Name}"/>
                                </Expander.Header>
                                <Expander.Content>
                                    <StackPanel>
                                        <TextBlock Text="{Binding Path=Age}"/>
                                        <TextBlock Text="Line 2"/>
                                        <TextBlock Text="Line 3"/>
                                    </StackPanel>
                                </Expander.Content>
                            </Expander>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ListBox>
        </ScrollViewer>
    </Grid>
</Window>

Простой класс для привязки:

public class Person
{
    public string Name {
        get;
        set;
    }

    public int Age {
        get;
        set;
    }
}

Создание и заполнение данных для привязки:

private void Window_Loaded(object sender, RoutedEventArgs e) {

    data = new ObservableCollection<Person>();

    data.Add(new Person {
        Name = "One",
        Age=10
    });

    data.Add(new Person {
        Name = "Two",
        Age = 20
    });

    data.Add(new Person {
        Name = "Three",
        Age = 30
    });

    Root.DataContext = data;
}

Это событие, которое мне нужно (на самом деле, мне нужен только SelectedIndex)

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
    ListBox box = (ListBox)sender;

    // This value is not set because events from Expander are not bubbled up to fire SelectionChanged Event
    int index = box.SelectedIndex;
}

person IUnknown    schedule 25.07.2009    source источник


Ответы (3)


Альтернативный способ, не зависящий от IsSelected. Вы можете подключить событие Expanded / Collapsed расширителя к исходному коду и использовать следующий код, чтобы узнать индекс ListBox, по которому вы щелкнули.

DependencyObject dep = (DependencyObject)e.OriginalSource;

while ((dep != null) && !(dep is ListViewItem))
{
   dep = VisualTreeHelper.GetParent(dep);
}

if (dep == null)
     return;

int index = yourListBox.ItemContainerGenerator.IndexFromContainer(dep);
person Jobi Joy    schedule 26.07.2009

Вы хотели, чтобы элемент управления Expander управлял выделением ListBox. Вы можете легко заархивировать это, установив двухстороннюю привязку в свойстве IsExpanded Expander для непосредственного элемента ListBoxItem, который вы щелкнули.

 <Expander IsExpanded="{Binding IsSelected,Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}">

ОБНОВЛЕНИЕ: если вам нужно избежать автоматического сворачивания при выборе другого элемента, сделайте режим выбора Listbox множественным.

<ListBox SelectionMode="Multiple"
person Jobi Joy    schedule 26.07.2009
comment
Сейчас это действительно работает, но с нежелательным побочным эффектом. Когда я раскрываю второй расширитель, первый автоматически сворачивается. Есть ли способ сделать это без автоматического сворачивания предыдущего развернутого элемента? - person IUnknown; 26.07.2009
comment
Просто сделайте SelectionMode = Multiple в ListBox - person Jobi Joy; 26.07.2009
comment
Ах, извините, я должен был упомянуть, что пробовал это, но это не дало мне того, что я искал. Использование SelectionMode = Multiple позволяет предыдущим элементам оставаться развернутыми, но также оставляет их выбранными. Я хотел бы иметь возможность расширить второй или третий Expander, но при этом выбран только последний выбранный ListBoxItem (множественный выбор не разрешен). RelativeBind, как указано выше, позволяет установить для ListBoxItem IsSelected значение false только тогда, когда Expander свернут. - person IUnknown; 26.07.2009
comment
@Jobi Joy, что, если я хочу, чтобы RadListBoxItem выбирался только тогда, когда Expander раскрыт, а не когда Expander свернут? - person Shankar Raju; 01.11.2012

Спасибо, Джоби. Это довольно умно. Кроличья нора WPF становится все глубже и глубже.

Вот что я сделал на основании вашего предложения:

private void Expander_Expanded(object sender, RoutedEventArgs e) {
    DependencyObject dep = (DependencyObject)sender;

    while ((dep != null) && !(dep is ListBoxItem)) {
        dep = VisualTreeHelper.GetParent(dep);
    }

    if (dep == null)
        return;

    int index = PersonList.ItemContainerGenerator.IndexFromContainer(dep);

    PersonList.SelectedIndex = index;
}

private void Expander_Collapsed(object sender, RoutedEventArgs e) {
    DependencyObject dep = (DependencyObject)sender;

    while ((dep != null) && !(dep is ListBoxItem)) {
        dep = VisualTreeHelper.GetParent(dep);
    }

    if (dep == null)
        return;

    int index = PersonList.ItemContainerGenerator.IndexFromContainer(dep);

    if (PersonList.SelectedIndex == index)
        PersonList.SelectedIndex = -1;
}

Мне пришлось изменить ListViewItem на ListBoxItem (я использовал ListBox).

Кроме того, я использовал индекс для выбора или отмены выбора ListBox.SelectedIndex. Это дало мне опыт, который я искал.

  1. Когда кто-то впервые расширяет Expander, он выбирает только что развернутый ListBoxItem.

  2. Если кто-то расширяет другой Expander, предыдущий ListBoxItem отменяется, но остается развернутым, выбирается только что развернутый ListBoxItem.

  3. Если кто-то сворачивает выбранный Expander, ListBoxItem отменяется.

  4. Если есть несколько раскрытых расширителей, кто-то сворачивает невыбранный расширитель ListBoxItem, ранее выбранный ListBoxItem остается выбранным.

Спасибо за помощь - я думаю, что это очень полезный небольшой фрагмент кода для всех, кто использует Expanders в ListBox.

person IUnknown    schedule 26.07.2009
comment
круто .. Да, не просто расширитель в DataTemplate Listbox .. Любой элемент управления, такой как Button / checkbox или что-то еще, что вам действительно нужно, такого рода хак. - person Jobi Joy; 29.07.2009
comment
Я думаю, что ваше слово «взломать» более уместно, чем мой фрагмент рабочего кода. Это решение, приведенное выше, не очень красивое - оно работает, но не очень. Должно быть более элегантное решение, чем прогулка по визуальному графику. - person IUnknown; 29.07.2009