Search code examples
c#wpflistviewlistviewitem

Binding a list of object to a WPF listviewitem


I've the following classes in a WPF project

public class part
{
    public string number { get; set; }
    public string name { get; set; }
    public List<department> departments { get; set; }
}
public class department
{
    public string name { get; set; }
    public double hours { get; set; }
}

Each part contains a list of hours for different departments. What i'm trying to achieve is to view this in a WPF listview. My problem is that i'm not finding a good example on how i could bind the a list of objects to a listviewitem. I had a similiar case in a windows forms app. There i iterated through the objects in the list and created subitems by code. While this would also possible here by creating gridviewcolumns in code i do believe that it should also be achievable via binding or am i mistaken?

Example:

public void Test()
    {
       List<part> list_parts = new List<part>();
       List<department> list_departments = new List<department>();

        department d = new department();
        d.name = "Sawing";
        d.hours = 0.3;
        list_departments.Add(d);

        d = new department();
        d.name = "Miling";
        d.hours = 12.3;
        list_departments.Add(d);

        part Test = new part();
        Test.name = "Block";
        Test.number = "123";
        Test.departments = list_departments;
        list_parts.Add(Test);

        d = new department();
        d.name = "Sawing";
        d.hours = 1.2;
        list_departments.Add(d);

        d = new department();
        d.name = "Turning";
        d.hours = 5.8;
        list_departments.Add(d);

        d = new department();
        d.name = "Finishing";
        d.hours = 5.6;
        list_departments.Add(d);

        d = new department();
        d.name = "QA";
        d.hours = 0.5;
        list_departments.Add(d);

        Test = new part();
        Test.name = "Cylinder";
        Test.number = "234";
        list_parts.Add(Test);

        lv_parts.ItemsSource = list_parts;
    }
}

My XAML of the listview without binding for the child list

   <ListView x:Name="lv_parts" ItemsSource="{Binding list_parts}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="50px"/>
                            <RowDefinition Height="50px"/>
                        </Grid.RowDefinitions>
                        <Label Grid.Row="0" Content="{Binding number}"/>
                        <Label Grid.Row="1 " Content="{Binding name}"/>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

The expected outcome would look something like this:

enter image description here


Solution

  • Without any visual styling like e.g. background and foreground colors, your ListView should look like shown below. It uses an ItemsControl with a horizontal StackPanel to show the Departments collection.

    <ListView ItemsSource="{Binding Parts}">
        <ListView.View>
            <GridView>
                <GridViewColumn>
                    <GridViewColumn.Header>
                        <TextBlock>
                            <Run Text="Part Number"/>
                            <LineBreak/>
                            <Run Text="Part Name"/>
                        </TextBlock>
                    </GridViewColumn.Header>
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock>
                                <Run Text="{Binding Number}"/>
                                <LineBreak/>
                                <Run Text="{Binding Name}"/>
                            </TextBlock>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn>
                    <GridViewColumn.Header>
                        <TextBlock>
                            <Run Text="Departement Name"/>
                            <LineBreak/>
                            <Run Text="Hours"/>
                        </TextBlock>
                    </GridViewColumn.Header>
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <ItemsControl ItemsSource="{Binding Departments}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <StackPanel Orientation="Horizontal"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <TextBlock>
                                            <Run Text="{Binding Name}"/>
                                            <LineBreak/>
                                            <Run Text="{Binding Hours}"/>
                                        </TextBlock>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
    

    Note that the XAML above use a view model like shown below, with proper casing of class and property names.

    public class Part
    {
        public string Number { get; set; }
        public string Name { get; set; }
        public List<Department> Departments { get; set; }
    }
    
    public class Department
    {
        public string Name { get; set; }
        public double Hours { get; set; }
    }
    
    public class ViewModel
    {
        public ObservableCollection<Part> Parts { get; }
            = new ObservableCollection<Part>();
    }
    

    An instance of the view model would be assigned to the DataContext of the view:

    public MainWindow()
    {
        InitializeComponent();
    
        var vm = new ViewModel();
        DataContext = vm;
    
        vm.Parts.Add(new Part
        {
            Name = "Block",
            Number = "123",
            Departments = new List<Department>
            {
                new Department { Name = "Sawing" , Hours = 0.3 },
                new Department { Name = "Milling" , Hours = 12.3 },
            }
        });
    
        vm.Parts.Add(new Part
        {
            Name = "Cylinder",
            Number = "456",
            Departments = new List<Department>
            {
                new Department { Name = "Sawing" , Hours = 1.2 },
                new Department { Name = "Turning" , Hours = 5.8 },
                new Department { Name = "Finishing" , Hours = 5.6 },
                new Department { Name = "QA" , Hours = 0.5 },
            }
        });
    }