2
votes

A ListView displays a collection of the following class:

public class Employee
{
    private string _department;
    private string _manager;
    private string _name;
    private string _address;

    public string Department
    {
        get { return _department; }
    }
    public string Manager
    {
        get { return _manager; }
    }
    public string Name
    {
        get { return _name; }
    }
    public string Address
    {
        get { return _address; }
    }
}

There is a 1-to-1 relation between Department and Manager, so any 2 rows with the same department will also have the same manager.

I want to group by Department/Manager, with the group header showing "Department (Manager)".

My CollectionViewSource looks like

    <CollectionViewSource x:Key="cvsEmployees" Source="{Binding Employees}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="Department" />
            <PropertyGroupDescription PropertyName="Manager" />
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>

The plan is to not display the first level header (Department) and to somehow bind to both the Department (1st level) and the Manager (2nd level) from the 2nd level header.

3 questions:

  1. To avoid displaying the 1st level header, I have an empty data template in the groupstyle:

    <GroupStyle>
        <GroupStyle.HeaderTemplate>
           <DataTemplate>
           </DataTemplate>
        </GroupStyle.HeaderTemplate>
    </GroupStyle>
    

    This seems very clunky. Is there a more elegant way to skip a group header?

  2. How do I bind to the 1st grouping level property (Department) from the 2nd level header (Manager) to achieve the required "Department (Manager)" ?

  3. Is there a better way to do this than creating 2 grouping level?

Thanks

2
could you show your ItemsSource for the ListView , the actual .cs class which composes your ItemsSource. - eran otzap
class added in original question - Bashar C

2 Answers

3
votes

Solved the main stumbling block, question 2 above: how to bind from the group header to a property that is not the grouping property.

The solution is to change the data context to:{Binding Items}. The ItemSource properties are then available

<GroupStyle>
    <GroupStyle.HeaderTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal" Margin="0,10,0,3" DataContext="{Binding Items}" >
                <TextBlock Text="{Binding Path=Department}" FontWeight="Bold" Margin="3"/>
                <TextBlock Text="{Binding Path=Manager, StringFormat='({0})'}" Margin="3"/>
            </StackPanel>
        </DataTemplate>
    </GroupStyle.HeaderTemplate>
</GroupStyle>

2
votes

I would create another model part, which represents the dual grouping that you need to have happen:

Model Classes:

public class EmployeeModel {

        private readonly Employee _Employee;

        public DepartmentManager ManagementInfo { get; private set; }

        public string Name {
            get { return _Employee.Name; }
        }
        public string Address {
            get { return _Employee.Address; }
        }

        public EmployeeModel(Employee employee) {
            this._Employee = employee;
            this.ManagementInfo = new DepartmentManager(employee.Department, employee.Manager);
        }
    }

    public class DepartmentManager {

        public string Department { get; private set; }
        public string Manager { get; private set; }

        public DepartmentManager(string dept, string manager) {
            this.Department = dept;
            this.Manager= manager;
        }

        public override bool Equals(object obj) {
            var model = obj as DepartmentManager;
            if(null == model)
                return false;

            return Department.Equals(model.Department, StringComparison.InvariantCultureIgnoreCase) && 
                Manager.Equals(model.Manager, StringComparison.InvariantCultureIgnoreCase);
        }
    }

XAML:

    <CollectionViewSource x:Key="cvsEmpsModel" Source="{Binding EmployeesModel}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="ManagementInfo" />
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>

    <DataTemplate DataType="{x:Type models:EmployeeModel}">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Name}" />
            <TextBlock Text="{Binding Address}" />
        </StackPanel>
    </DataTemplate>

...
    <ListView ItemsSource="{Binding Source={StaticResource cvsEmpsModel}}">
            <ListView.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal" Margin="0,10,0,3" DataContext="{Binding Items}">
                                <TextBlock Text="{Binding Path=ManagementInfo.Manager}" FontWeight="Bold" Margin="3" />
                                <TextBlock Text="{Binding Path=ManagementInfo.Department, StringFormat='({0})'}" Margin="3" />
                            </StackPanel>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ListView.GroupStyle>
        </ListView>

Then in your Window/ViewModel:

this.EmployeesModel = new ObservableCollection<EmployeeModel>(MyListOfEmployersFromDB.Select(e => new EmployeeModel(e)));

Note, I've overriden Equals in the DepartmentManager class, but not GetHashCode, ideally you should do a custom implementation of that. I had to override equals so the grouping view source would correctly group the same entries. You could get rid of this need, buy constructing the DepartmentManager for the same Employees outside of the collection, and pass them into the EmployeeModel ctr.