2017-02-20 5 views
1

У меня есть List из Lists и отобразить его с вложенными ListBoxes:Получить все выбранные элементы на нескольких вложенных друг в друга ListBoxes с SelectionMode Multiple/Расширенная

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?

Я нашел fewsolutions, получив один выбранный элемент, но не смог понять, как это сделать в моем сценарии.
У меня есть идея, как это сделать, обернув массивы string, но я бы предпочел не делать этого.

+0

«Обертывание строковых массивов» означает запись режимов просмотра?Вы должны написать viewmodels. –

+0

@EdPlunkett Даже если бы я использовал MVVM для этого приложения (чего я не по разным причинам), было бы досадно «разбить» список списков. Я бы хотел, чтобы моя модель просмотра содержала список списков строк и список выбранных строк. Однако это кажется немного сложным. –

ответ

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. Надеюсь, что помогает.

+0

Это очень простое решение и отлично работает для простого примера, который я вам предоставил. К сожалению, в моем реальном приложении мой код находится в другом классе. Поэтому, когда я решил использовать это решение, мне пришлось бы найти способ сделать FlatStringList доступным для другого класса. Это может быть самый простой подход. –

1

Самый простой способ будет перебирать пункты в ListBox эс:

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

Обратите внимание, что ListBoxItem может виртуализировать, если у вас есть много внутренней IEnumerable<string>. Затем вы должны заставить поколение контейнеров или отключить виртуализацию интерфейса:

WPF ListView virtualization. How to disable ListView virtualization?

Это может повлиять на производительность отрицательно, так что если это вопрос, вы, вероятно, следует рассмотреть привязку к IEnumerable<YourType> и связать SelectedItems недвижимость от внутреннего ListBox до свойства YourType по поведению.

С SelectedItems объект ListBox доступен только для чтения, вы не можете напрямую связываться с ним: https://blog.magnusmontin.net/2014/01/30/wpf-using-behaviours-to-bind-to-readonly-properties-in-mvvm/.

+0

Хорошее решение. Не здорово, что нам приходится возиться с визуальным деревом, но нормально. Не проблема с 'SelectedItems', что это не' DependencyProperty'? Это просто чтение только не должно мешать мне использовать одностороннюю привязку, так? –

+1

Справа. Основная проблема заключается в том, что это не свойство зависимостей, поэтому вы не можете привязываться к нему. – mm8

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

Bind свойство IsSelected из ListBoxItems:

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

Это проще, чем я имел в виду. Тем не менее, я все еще не большой поклонник того, чтобы обернуть каждую строку. Было бы неплохо иметь список списков строк и список выбранных строк. Но, похоже, это не так просто, как я надеялся. –

Смежные вопросы