【问题标题】:Get all selected items across multiple nested ListBoxes with SelectionMode Multiple/Extended使用 SelectionMode Multiple/Extended 跨多个嵌套列表框获取所有选定项
【发布时间】:2017-02-20 15:27:22
【问题描述】:

我有一个ListLists 并用嵌套的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 数组来做到这一点,但我不想这样做。

【问题讨论】:

  • “包装字符串数组”是否意味着编写视图模型?您应该编写视图模型。
  • @EdPlunkett 即使我将 MVVM 用于此应用程序(出于各种原因我不这样做),“分解”列表列表也会很烦人。我希望我的视图模型保存字符串列表和选定字符串列表。不过,这似乎有点棘手。

标签: c# wpf xaml listbox nested


【解决方案1】:

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

<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。 希望对您有所帮助。

【讨论】:

  • 这是一个非常简单的解决方案,非常适合我提供的简单示例。不幸的是,在我的实际应用程序中,我的代码在不同的类中。因此,当我决定使用此解决方案时,我必须找到一种方法使 FlatStringList 可用于其他类。这可能仍然是最简单的方法。
【解决方案2】:

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

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

请注意,如果您有很多内部IEnumerable&lt;string&gt;ListBoxItem 可能会被虚拟化掉。然后,您将不得不强制生成容器或禁用 UI 虚拟化:

WPF ListView virtualization. How to disable ListView virtualization?

这可能会对性能产生负面影响,因此如果这是一个问题,您可能应该考虑绑定到IEnumerable&lt;YourType&gt;,并使用行为将内部ListBoxSelectedItems 属性绑定到YourType 的属性。

由于ListBoxSelectedItems 属性是只读的,因此您不能直接绑定到它:https://blog.magnusmontin.net/2014/01/30/wpf-using-behaviours-to-bind-to-readonly-properties-in-mvvm/

【讨论】:

  • 很好的解决方案。我们不得不摆弄视觉树并不是很好,但没关系。 SelectedItems 不是DependencyProperty 的问题不是吗?它只是只读不应该阻止我使用单向绑定,对吧?
  • 对。主要问题是它不是依赖属性,所以你不能绑定到它。
【解决方案3】:

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

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)
                    {
                        // ....
                    }
                }
        };
    }

【讨论】:

  • 这比我想象的要简单。但是,我仍然不是必须包装每个字符串的忠实粉丝。最好只有字符串列表和选定字符串列表。但这似乎并不像我希望的那么容易。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 1970-01-01
  • 1970-01-01
  • 2015-09-27
  • 1970-01-01
  • 1970-01-01
  • 2010-10-04
相关资源
最近更新 更多