【问题标题】:WPF Listbox + Expander eventsWPF 列表框 + 扩展器事件
【发布时间】:2009-07-25 23:18:57
【问题描述】:

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

我需要的是 ListBox 的 SelectedIndex。因为 ListBox_SelectionChanged 没有被触发,所以索引为 -1,我无法确定选择了哪个项目。

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

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

【问题讨论】:

    标签: wpf events listbox expander


    【解决方案1】:

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

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

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

    <ListBox SelectionMode="Multiple"
    

    【讨论】:

    • 它现在可以工作了——但是有一个不受欢迎的副作用。当我展开第二个扩展器时,第一个会自动折叠。有没有办法在不自动折叠之前展开的项目的情况下做到这一点?
    • 只需在 ListBox 上设置 SelectionMode="Multiple"
    • 啊 - 对不起,我应该提到我尝试过,但它没有给我我想要的东西。使用 SelectionMode="Multiple" 确实允许以前的项目保持展开状态,但它也会使其处于选中状态。我希望能够扩展第二个或第三个扩展器,但只选择最近选择的 ListBoxItem(不允许多选)。如上所述,RelativeBind 仅允许在展开器折叠时将 ListBoxItem IsSelected 设置为 false。
    • @Jobi Joy,如果我希望仅在展开器展开时而不是在展开器折叠时选择 RadListBoxItem 怎么办?
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      谢谢乔比。这很聪明。 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. 如果有人折叠选定的扩展器,则取消选择 ListBoxItem。

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

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

      【讨论】:

      • cool.. 是的,不仅仅是 Listbox 的 DataTemplate 中的扩展器。任何控件,如 Button/checkbox 或任何你真正需要这种 hack 的东西。
      • 我认为你的“hack”这个词比我的作品“code sn-p”更合适。上面的这个解决方案不是很漂亮——它有效——但不是很漂亮。应该有一个比走可视化图更优雅的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-14
      • 2015-08-23
      • 1970-01-01
      相关资源
      最近更新 更多