WPF 列表框 + 扩展事件

发布于 2024-07-29 06:44:25 字数 2944 浏览 8 评论 0原文

我在列表框的 ItemTemplate 中有一个 Expander。 渲染得很好。 我遇到的问题是,我希望在展开和/或选择扩展器时触发 ListBox_SelectionChanged 事件。 MouseDown 事件似乎没有冒泡到 ListBox。

我需要的是列表框的SelectedIndex。 因为 ListBox_SelectionChanged 不会被触发,所以索引为 -1,我无法确定已选择哪个项目。

如果用户在扩展器展开后单击其内容,则会触发 ListBox_SelectionChanged 事件。 如果他们仅单击扩展器,则不会触发该事件。 这会让用户感到困惑,因为从视觉上看,他们认为在实际单击扩展器标题时已经单击了该项目。 我需要在用户展开扩展器时选择列表框项目,因为就用户而言,现在该项目实际上未被选择。

关于如何让它工作或确定带有扩展器的列表框的 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;
}

I have an Expander in the ItemTemplate of a ListBox. Renders fine. The issue I have run into is that I would like the ListBox_SelectionChanged event to fire when the expander is expanded and/or selected. The MouseDown event does not seem to bubble up to the ListBox.

What I need is the SelectedIndex of the ListBox. Because the ListBox_SelectionChanged does not get fired, the index is -1 and I cannot determine which item has been selected.

The ListBox_SelectionChanged Event is fired if a user clicks on the Content of the Expander after it has been expanded. If they only click on the expander, the event is not fired. This is confusing to the user because visually, they think they have already clicked on that item when actually clicking on the Expander Header. I need the ListBox Item selected when the user Expands the Expander because as far as the user is concerned, the item is now selected when it really isn't.

Any suggests on how to get this to work or alternate ways of determining the SelectedIndex of the list box with expanders in it?

Simplified code for reference:

<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>

Simple class for Binding:

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

    public int Age {
        get;
        set;
    }
}

Creating and populating the data for binding:

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;
}

This is the event I need (really just the SelectedIndex I need)

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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

怪我入戏太深 2024-08-05 06:44:25

您想要的是让 Expander 控件控制 ListBox Selection。 您可以通过将 Expander 的 IsExpanded 属性上的 TwoWay Binding 设置为您单击的直接 ListBoxItem 来轻松存档此内容。

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

更新:如果您需要避免选择另一个项目时自动折叠,请将列表框选择模式设置为多个。

<ListBox SelectionMode="Multiple"

What you wanted is to get the Expander control controls the ListBox Selection. You can easily archive this by setting a TwoWay Binding on the IsExpanded property of the Expander to the immediate ListBoxItem which you clicked.

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

UPDATE : If you need to avoid the automatic collapse when you select another item, make Listbox selection mode to multiple.

<ListBox SelectionMode="Multiple"
拔了角的鹿 2024-08-05 06:44:25

另一种不依赖于 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);

An alternate way which doesnt depends on the IsSelected, You can hook an Expanded/Collapsed event of expander to the code behind and use the following code to find out the ListBox index on which you clicked.

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);
墨离汐 2024-08-05 06:44:25

谢谢乔比。 这非常聪明。 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 将被选中。

    如果有人展开另一个 Expander,则先前

  3. 如果有人折叠选定的 Expander,则取消选择 ListBoxItem。

  4. 如果展开了多个 Expanders,有人折叠了未选中的 ListBoxItem 展开器,则之前选中的 ListBoxItem 保持选中状态。

    如果展开了多个 Expanders,有人折叠了

感谢您的帮助 - 我认为对于在列表框中使用扩展器的任何人来说,这是一个非常有用的小代码片段。

Thanks Jobi. That's pretty clever. The rabbit hole of WPF keeps getting deeper and deeper.

Here is what I did based on your suggestion:

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;
}

I had to change the ListViewItem to ListBoxItem (I was using a ListBox).

Also, I used the index to select or de-select the ListBox.SelectedIndex. This give me the experience I was looking for.

  1. The first time someone expands an Expander, it selects the newly expanded ListBoxItem.

  2. If someone expands another Expander, the previous ListBoxItem is deselected, but remains expanded, the newly expanded ListBoxItem is selected.

  3. If someone collapses a selected Expander, the ListBoxItem is deselected.

  4. If there are several Expanders expanded, someone collapses a non-selected ListBoxItem expander, the previously selected ListBoxItem remains selected.

Thanks for the help - I think this is a very useful little code snippet for anyone who uses Expanders in a ListBox.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文