使用SelectionMode Multiple / Extended在多个嵌套ListBox中获取所有选定项

时间:2017-02-20 15:27:22

标签: c# wpf xaml listbox nested

我有List Lists并使用嵌套ListBoxes显示它:

MainWindow.xaml.cs

using System.Collections.Generic;

namespace WPF_Sandbox
{
    public partial class MainWindow
    {

        public IEnumerable<IEnumerable<string>> ListOfStringLists { get; set; } = new[] { new[] { "a", "b" }, new[] { "c", "d" } };

        public MainWindow()
        {
            InitializeComponent();

            DoSomethingButton.Click += (sender, e) =>
            {
                // do something with all selected items
            };
        }

    }
}

MainWindow.xaml

<Window x:Class="WPF_Sandbox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        x:Name="ThisControl">
    <StackPanel>
        <ListBox ItemsSource="{Binding ListOfStringLists, ElementName=ThisControl}">
            <ListBox.ItemTemplate>
                <ItemContainerTemplate>
                    <ListBox ItemsSource="{Binding}" SelectionMode="Multiple">
                        <ListBox.ItemTemplate>
                            <ItemContainerTemplate>
                                <TextBlock Text="{Binding}" />
                            </ItemContainerTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </ItemContainerTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Name="DoSomethingButton" Content="DoSomething" />
    </StackPanel>
</Window>

如何在所有ListBoxes中获取所有选定的项目?

我发现few solutions获得了一个选定的项目,但无法弄清楚如何在我的方案中应用这些项目。
我知道如何通过包装string数组来实现这一点,但我不想这样做。

3 个答案:

答案 0 :(得分:1)

最简单的方法是遍历ListBox es中的项目:

private void DoSomethingButton_Click(object sender, RoutedEventArgs e)
{
    List<string> selectedStrings = new List<string>();
    foreach (IEnumerable<string> array in outerListBox.Items.OfType<IEnumerable<string>>())
    {
        ListBoxItem lbi = outerListBox.ItemContainerGenerator.ContainerFromItem(array) as ListBoxItem;
        if (lbi != null)
        {
            ListBox innerListBox = GetChildOfType<ListBox>(lbi);
            if (innerListBox != null)
            {
                foreach (string selectedString in innerListBox.SelectedItems.OfType<string>())
                    selectedStrings.Add(selectedString);
            }
        }
    }
}

private static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
    if (depObj == null)
        return null;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
    {
        var child = VisualTreeHelper.GetChild(depObj, i);
        var result = (child as T) ?? GetChildOfType<T>(child);
        if (result != null)
            return result;
    }
    return null;
}

请注意,如果你有很多内部ListBoxItemIEnumerable<string>可能会被虚拟化。然后,您必须强制生成容器或禁用UI虚拟化:

https://blog.magnusmontin.net/2014/01/30/wpf-using-behaviours-to-bind-to-readonly-properties-in-mvvm/

这可能会对性能产生负面影响,因此如果这是一个问题,您应该考虑绑定到IEnumerable<YourType>并将内部SelectedItems的{​​{1}}属性绑定到ListBox的属性{1}}使用行为。

由于YourType的{​​{1}}属性是只读的,因此您无法直接绑定到IO.foreach

答案 1 :(得分:1)

我只是向内部ListBox添加一个事件处理程序,如果不是以MVVM的方式做事:

<ListBox ItemsSource="{Binding}" SelectionMode="Multiple" SelectionChanged="ListBox_SelectionChanged">

然后在你的代码背后实现ListBox_SelectionChanged,如此:

public List<string> FlatStringList = new List<string>();
private void ListBox_SelectionChanged(object sender,System.Windows.Controls.SelectionChangedEventArgs e)
{
    FlatStringList.AddRange(e.AddedItems.Cast<string>());
    foreach(string s in e.RemovedItems)
    {
        FlatStringList.Remove(s);
    }            
}

这假设您不介意将所选字符串存储在平面列表中。然后,您可以实施DoSomething按钮点击事件处理程序,以便对FlatStringList执行某些操作。 希望有所帮助。

答案 2 :(得分:1)

为什么不创建一个包装器(如你所说):

public class MyString : INotifyPropertyChanged
{
    public MyString(string value) { Value = value; }

    string _value;
    public string Value { get { return _value; } set { _value = value; RaisePropertyChanged("Value"); } }

    bool _isSelected;
    public bool IsSelected { get { return _isSelected; } set { _isSelected = value; RaisePropertyChanged("IsSelected"); } }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    }
} 

绑定ListBoxItems的IsSelected属性:

<StackPanel>
    <ListBox ItemsSource="{Binding ListOfStringLists, ElementName=ThisControl}">
        <ListBox.ItemTemplate>
            <ItemContainerTemplate>
                <ListBox ItemsSource="{Binding}" SelectionMode="Multiple">
                    <ListBox.ItemTemplate>
                        <ItemContainerTemplate>
                            <TextBlock Text="{Binding Value}" />
                        </ItemContainerTemplate>
                    </ListBox.ItemTemplate>
                    <ListBox.ItemContainerStyle>
                        <Style TargetType="{x:Type ListBoxItem}">
                            <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
                        </Style>
                    </ListBox.ItemContainerStyle>
                </ListBox>
            </ItemContainerTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Name="DoSomethingButton" Content="DoSomething"  />
</StackPanel>

你已经完成了:

    public IEnumerable<IEnumerable<MyString>> ListOfStringLists { get; set; } = new[] { new[] { new MyString("a"), new MyString("b") { IsSelected = true } }, new[] { new MyString("c"), new MyString("d") } };

    public MainWindow()
    {
        this.InitializeComponent(); 

        DoSomethingButton.Click += (sender, e) =>
        {
            foreach (var i in ListOfStringLists)
                foreach (var j in i)
                {
                    if (j.IsSelected)
                    {
                        // ....
                    }
                }
        };
    }