I am trying to implement a list inside the list, something like this (one level of nesting is enough for me):

enter image description here

It should be noted that the list itself is filled by pressing a button. With the help of ListBox, it did not work out, through bind (I do it programmatically through ItemSource) only one root level is possible. On XAML it looks like this:

<ListBox x:Name="listObjects" Margin="10,10,779,133" SelectionMode="Multiple"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Margin" Value="5"/> <Setter Property="Padding" Value="5"/> <Setter Property="Background" Value="LightSteelBlue"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border x:Name="brd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <CheckBox Focusable="False" Margin="{TemplateBinding Padding}" Foreground="{TemplateBinding Foreground}" IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"> <ContentPresenter/> </CheckBox> </Border> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="LimeGreen"/> <Setter Property="Foreground" Value="White"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderBrush" Value="Black"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle> </ListBox> 

Accordingly, one level is added conditionally as follows (I will not go into the details of completing the list):

 private void Load_Click(object sender, RoutedEventArgs e) { var list = new List<string>(); ls.Add("qwerty"); ls.Add("asdfgh"); listObjects.ItemsSource = list; } 

The question, in fact, is how to implement a multi-level list with reference to collections. Ready to understand and rewrite if you help with your code, etc.

  • one
    Just redefine ItemTemplate and in DataTemplate use one more ItemsControl - Andrey NOP
  • @AndreyNOP can still somehow in more detail, if simple, then with code inserts (or maybe links to examples, etc.). it is simple for someone, for someone there is not yet)) in more detail I mean not about templates, but how to set such a template. - PavelNewSky
  • one
    in the screenshot you have a TreeView, how to make it here: ru.stackoverflow.com/questions/584996/… - Gardes

2 answers 2

And so, for the implementation of this task, we should first acquire a certain class, which we indicate to our application as a certain data source for binding ( DataContext ), let it be an empty class with the name MyDataClass at the moment:

 public class MyDataClass { } 

Good. Next, we need to create the simplest class for one item in our list. Let it contain some string variable named Name and a certain List<string> collection named Films . The class itself is called Category :

 public class Category { public string Name { get; set; } public List<string> Films { get; set; } } 

Good! Now you can create an empty (currently) ObservableCollection<> in the previously created MyDataClass class. Let's call it MyCategory and let it be initially initialized. Our class will be like this:

 public class MyDataClass { public ObservableCollection<Category> MyCategory { get; set; } = new ObservableCollection<Category>(); } 

Ok, half of the way is covered, the design of the elements is left and everything is connected. Let's get in touch first! In the MainWindow at the very beginning, create our MyDataClass :

 public partial class MainWindow : Window { private MyDataClass MyData; } 

Then after InitializeComponent(); we need to initialize this class and set it as a DataContext . Also, let's immediately and fill all this with some data for the test:

 public MainWindow() { InitializeComponent(); MyData = new MyDataClass(); DataContext = MyData; MyData.MyCategory.Add(new Category { Name = "Боевики", Films = new List<string> { "Фильм 1", "Фильм 2", "Фильм 3" } }); MyData.MyCategory.Add(new Category { Name = "Комедии", Films = new List<string> { "Фильм 1", "Фильм 2", "Фильм 3" } }); } 

Well, almost finish. It remains to create a display element. In WPF counting each element can contain a bunch of others (for example, we can shove a picture, text, or something else into the button). In our case, we can override the element template at the ListBox to the one that would contain a text field and another ListBox :

 <ListBox ItemsSource="{Binding MyCategory}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Name}"/> <ListBox ItemsSource="{Binding Films}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> 

Notice, I have already specified the data for binding above, namely:

  • The main ListBox has ItemsSource="{Binding MyCategory}" , which links it to the data in our MyCategory collection
  • TextBlock - he indicated the place from where he should take the text Text="{Binding Name}" . That is, the bound collection MyCategory contains elements of type Category (our created collection for one element), that’s where we specified Name and we finally attached to it.
  • Internal ListBox - well, here it is already clear, we gave him the data to our Films list.

Well, let's run. The result should be as follows:

result


If you switch to Binding , then in this case, if you change a collection of any elements, you most likely will not update the data visually. This frightens many (personally, I have many friends who are repelled by this fact from Binding 'a.) By this I’ll write how to fight! Everything is simple, you need to implement INotifyPropertyChanged where something may change. In our case, this is the Category class. Let's add a couple of lines to it:

 public class Category: INotifyPropertyChanged { public string Name { get; set; } public List<string> Films { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 

Now we have to rewrite what can be updated, for example, the name:

 private string name; public string Name { get => name; set { name = value; OnPropertyChanged("Name"); } } 

That's all, now our interface will update the "Name" field (Name) in accordance with the changes in the MyCategory collection. For lists, it is better to use ObservableCollection as it can notify about data changes.


What you need is just an example of how to place a ListBox inside another ListBox , but I thought it would be useful for you to learn how to tie up all the data and how to use it, because these are the basics and the most juice of WPF . I tried to paint everything as detailed as possible for clarity. I hope helped. Good luck!

  • one
    "... many friends this fact repels from Binding'a ... I will write right away how to fight ...". You don’t have to fight with a building, you need to work with a building, to be able to work. If you write OnPropertyChanged([CallerMemberName] string propertyName ... , then OnPropertyChanged("Name"); do not write. This attribute [CallerMemberName] forces the compiler to determine the name of the property itself and substitute it into the method call, therefore it is enough to write so OnPropertyChanged(); ;. - Bulson
  • @EvgeniyZ Thank you for the detailed explanations, I figured this out. The question is how to make the connection between the external listbox and the internal one? well, for example, when selecting "action movies", so that all films from this section are selected. after winforms on wpf you have to rebuild thinking, of course ... - PavelNewSky
  • @PavelNewSky better use the standard TreeView and not reinvent the wheel without need. - user227049
  • @FoggyFinder I almost worked everything out for my own purposes, it only remains to understand how, during the selection, as in the example, "action films", everything stood out inside. and in TreeView you also need to figure out how to insert checkboxes there, how to make multiple selections, etc. - PavelNewSky
  • @PavelNewSky depends on how you organized your data types - user227049

If a multi-level list with one nesting level is presented during the thinking of the interface, then it is likely that it will stop here and think about the fact that maybe you just need to group the data by a certain key.

For grouping it is convenient to use standard tools ( CollectionViewSource ).

For example, you want to group data by genre property ( Genre ):

 <CollectionViewSource x:Key="cvs" Source="{Binding Data}"> <CollectionViewSource.GroupDescriptions> <PropertyGroupDescription PropertyName="Genre" /> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> 

where Data is a list of your objects.

In ListView the header template is set in GroupStyle.HeaderTemplate

 <ListView.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> 

A simple example of implementing this approach is by reference.