简体   繁体   中英

how can i display hierarchical data in a list box using data templates in wpf

I have a hierarchical collection of Groups and SubGroups like:

  • Group 1
    • SubGroup1
    • SubGroup2
  • Group 2
    • SubGroup1

What i want to do is to display this hierarchical data in a list box. i tried to create different data templates and i am able to show the SubGroups but am not able to display the Group Names.

The class that i use for this purpose is something like this:

public class Groups
{
public string Group {get; set;}
public List<string> SubGroup {get; set;}
}

I bind a List to ItemsSource of the listbox. What i need is a data template that i can use to display this data hierarchically.

i cannot use another control for this, i have to use a listbox.

Edit: By looking at the suggestion provided by Desty, i am able to display the values using this Data Template

<DataTemplate x:Key="SubGroups">
  <StackPanel>
    <Label Content="{Binding Path=Group}" />
    <ItemsControl Margin="30,0,0,0" ItemsSource="{Binding SubGroup}" />
  </StackPanel>
</DataTemplate>

The problem now is that i am unable to select individual sub groups from the list box. It selects the complete ListBoxItem and i am not able select individual sub groups.

What you need is a second class for SubGroups, so you can define one DataTemplate for Groups and one for SubGroups. Your model classes could be something like

public class Group {
  public string Name { get; set; }
  public List<SubGroup> SubGroups { get; set; }
  public Group(string name) { this.Name = name; }
}

public class SubGroup {
  public string Name { get; set; }
  public SubGroup(string name) { this.Name = name; }
}

In WPF you can define a DataTemplate for each type:

<Window.Resources>
  <DataTemplate DataType="{x:Type local:SubGroup}">
    <StackPanel Orientation="Horizontal">
      <TextBlock Text=" +-- "/><TextBlock Text="{Binding Name}"/>
    </StackPanel>
  </DataTemplate>
  <DataTemplate DataType="{x:Type local:Group}">
    <StackPanel>
      <TextBlock Text="{Binding Name}"/>
      <ItemsControl ItemsSource="{Binding SubGroups}"/>
     </StackPanel>
  </DataTemplate>
</Window.Resources>
<Grid>
  <ListBox ItemsSource="{Binding}" />
</Grid>

Edit (after reviewing Bhattis edit):

If sub group items shall also be clickable, then (IMHO) there cannot be a difference between a group and a subgroup from the ListBox's point of view. So my workaround guess is:

public class Groups {
  public string Group { get; set; }
} 

In the WPF window

<ListBox ItemsSource="{Binding}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel>
        <Label Content="{Binding Path=Group}" />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

And in the WPF code behind file:

public MainWindow() {
  InitializeComponent();
  Groups g1 = new Groups() { Group = "Parent 1" };
  Groups s1 = new Groups() { Group = "   Sub 1" };
  Groups s2 = new Groups() { Group = "   Sub 2" };
  Groups g2 = new Groups() { Group = "Parent 2" };
  Groups s3 = new Groups() { Group = "   Sub 1" };
  Groups s4 = new Groups() { Group = "   Sub 2" };
  this.DataContext = new List<Groups>() { g1, s1, s2, g2, s3, s4 };
}

Here every subgroup is nothing else than a group, so every "line" is clickable. Important is that the sequence of the list matters a lot, because the structure is no longer in the model.

If you must react differently depending on wether the user clicked a group or a subgroup, then you can build a inheritance hierarchy in your model and put the corresponding objects in the DataContext. To bring you in the right direction, I mean something like

public abstract class AbstractGroup {
    public string Name { get; set; }
    public AbstractGroup(string name) { this.Name = name; }
}
public class Group : AbstractGroup {
    public Group(string name) : base(name) {}
}
public class SubGroup : AbstractGroup {
    public SubGroup(string name) : base(name) {}
} 

Code-behind:

    InitializeComponent();
    AbstractGroup g1 = new Group("Parent 1");
    AbstractGroup s1 = new SubGroup("  Sub 1");
    AbstractGroup s2 = new SubGroup("   Sub 2");
    AbstractGroup g2 = new Group("Parent 2");
    AbstractGroup s3 = new SubGroup("   Sub 1");
    AbstractGroup s4 = new SubGroup("   Sub 2");
    this.DataContext = new List<AbstractGroup>() { g1, s1, s2, g2, s3, s4 };

XAML:

<ListBox ItemsSource="{Binding}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel>
        <Label Content="{Binding Path=Name}" />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Since you can get the selected object in the DataContext, typeof() helps you finding out which concrete type the selected item is.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM