Grouping of items in ListView WPF - wpf

I have a ListView where I want to group the items based on a field of the item object. Below is the code I have:
<ListView ItemsSource="{x:Bind MyVM.CollectionOfClassA, Mode=OneWay}"
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontSize="15" FontWeight="Bold" Text="{Binding DateTimePropertyOfClassA}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
Is there something I am missing? I want to group the items based on the DateTime property of ClassA objects. Also, if there is no item for any particular day, I would still like to show that date with empty list under that group (for that day). How can I achieve it?
Edit: I am not able to use CollectionViewSource since my VM contains the collection of ClassA object (which is bound as item source to the listview) and I want to group the items based on one property of those ClassA objects. I am sure there is something I am missing out on. But I am not able to figure it out.

Here is the solution for anyone looking for it (thanks to the WPF masters out there https://social.msdn.microsoft.com/Forums/windowsapps/en-US/812ed260-e113-4a8b-9322-226ed56ac90c/grouping-of-items-in-listview-wpf?forum=wpdevelop&prof=required):
public class ClassA
{
public DateTime DateTimePropertyOfClassA { get; set; }
}
public class MyVM
{
public MyVM()
{
//return a grouped collection:
Grouped = from x in CollectionOfClassA group x by x.DateTimePropertyOfClassA into grp orderby grp.Key select grp;
}
public IList<ClassA> CollectionOfClassA { get; set; } = new List<ClassA>()
{
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-01-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA =DateTime.Parse("2016-06-01")}
};
public IEnumerable<object> Grouped { get; }
}
Xaml:
<Page.Resources>
<CollectionViewSource x:Name="cvs"
IsSourceGrouped="True"
Source="{x:Bind MyVM.Grouped, Mode=OneWay}"/>
</Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding Source={StaticResource cvs}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontSize="15" FontWeight="Bold" Text="{Binding Key}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</StackPanel>

I suggest creating a collection in the ViewModel IObservableCollection<ClassA> Properties (or whatever name), add it in the MainWindow class and then simply bind it in the ListView.
<ListView ItemsSource="{Binding Path=Properties}">

Related

How to bind object data from Combobox to ListBox

This is the custom object:
public class FileItem
{
public string Name { get; set; }
public string Path { get; set; }
}
Collection:
public ObservableCollection<FileItem> collection { get; set; }
Combobox:
<ComboBox
Name="cbCollection"
ItemsSource="{Binding interfaces}"/>
ListBox:
<ListBox
Name="lbCollection "
ItemsSource="{Binding collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So my Combobox is populated with my object collection and i want to see all its properties in my ListBox.
Currently:
I can see only the property Name.
i can see all the objects from my Combobox and not only the selected one.
If you want to see more than just the name property, you'll need to extend the data template to include the additional properties.
If you want to see the selected item's properties, then you should bind to the combo box's SelectedItem property. Actually I don't think you want a ListBox as there is only one selected item.
This should get you started:
<ContentControl Content="{Binding ElementName=cbCollection, Path=SelectedItem}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:FileItem}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>

How to get the first visible group key in the grouped listview

I have a ListView which is grouped by the datetime field of the items in the itemsource. I want to get the group key of the top item visible. Also, I would like to get the changed group key as soon as a new item is scrolled to the top of the listview. How can I achieve it?
Code:
public class ClassA
{
public DateTime DateTimePropertyOfClassA { get; set; }
}
public class MyVM
{
public MyVM()
{
//return a grouped collection:
Grouped = from x in CollectionOfClassA group x by x.DateTimePropertyOfClassA into grp orderby grp.Key select grp;
}
public IList<ClassA> CollectionOfClassA { get; set; } = new List<ClassA>()
{
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-01-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA =DateTime.Parse("2016-06-01")}
};
public IEnumerable<object> Grouped { get; }
}
Xaml:
<Page.Resources>
<CollectionViewSource x:Name="cvs"
IsSourceGrouped="True"
Source="{x:Bind MyVM.Grouped, Mode=OneWay}"/>
</Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding Source={StaticResource cvs}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontSize="15" FontWeight="Bold" Text="{Binding Key}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</StackPanel>

WPF ListView grouping by 2 columns but display only 1 group header

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:
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?
How do I bind to the 1st grouping level property (Department) from the 2nd level header (Manager) to achieve the required "Department (Manager)" ?
Is there a better way to do this than creating 2 grouping level?
Thanks
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>
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.

Rendering Collection of collections - ItemsControl

I have an object model like below:
public class ViewModel
{
public List<Group> Groups{ get; set; }
}
public class Group
{
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
}
public class Contact
{
public string Name { get; set; }
public bool IsOnline { get; set; }
}
and I'm binding the groups to an itemscontrol like this:
<ItemsControl ItemsSource="{Binding Path=Groups}"
ItemTemplate="{StaticResource GroupTemplate}" >
</ItemsControl>
and I have datatemplate for rendering them.
<DataTemplate x:Key="GroupTemplate" DataType="{x:Type Group}">
</DataTemplate>
<DataTemplate x:Key="ContactTemplate" DataType="{x:Type Contact}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanle>
</DataTemplate>
How can I get the contacts displayed inside the items control? The contacts is a collection inside each group and my viewmodel has a collection of groups. To complicate it a bit further, I have different datatemplates for different contacts, and I should be using a datatemplateselector for choosing the appropriate contact template. Also please note, I have nothing to display in the group template, and I only need to show Contacts.
Thanks,
-Mike
Use another ItemsControl in the first template:
<DataTemplate x:Key="GroupTemplate" DataType="{x:Type my:Group}">
<ItemsControl ItemsSource="{Binding Contacts}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type my:Contact}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
And with a template selector:
<DataTemplate x:Key="GroupTemplate" DataType="{x:Type my:Group}">
<ItemsControl ItemsSource="{Binding Contacts}"
ItemTemplateSelector="{StaticResource yourContactItemSelector}"/>
</DataTemplate>

Treeview binding problem

I have a window MainWindow.xaml and
private static Tutorial tutorial; there.
Also I have class Structure.cs where I describe child types
public class Tutorial
{
public string Name { get; set; }
public IList<Chapter> Chapters = new List<Chapter>();
}
public class Chapter
{
public string Name { get; set; }
public IList<Unit> Units = new List<Unit>();
}
public class Unit
{
public string Name { get; set; }
public IList<Frame> Frames = new List<Frame>();
...
}
I want to bind tutorial structure to treeview. How can I do this?
I tried this way.
<TreeView Grid.Row="2" x:Name="treeViewStruct" Margin="5,0,5,0" Background="LemonChiffon" BorderBrush="Bisque" BorderThickness="1" ScrollViewer.VerticalScrollBarVisibility="Auto" IsTextSearchEnabled="True" Cursor="Hand">
<TreeView.Resources>
<HierarchicalDataTemplate DataType = "{x:Type Structure:Chapter}"
ItemsSource = "{Binding Path=Units}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Structure:Unit}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
It doesn't work.
Please, help! I'm a newbie in WPF. I need dynamic tree
so that when I add a chapter or a unit in the object tutorial, tree is updated.
And for this way of binding please throw the idea how can I get a collection item, when I selected some tree node.
This may help :
<HierarchicalDateTemplate DataType = "{x:Type local:Tutorial}"
ItemsSource="{Binding Chapters}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<HierarchicalDateTemplate DataType = "{x:Type local:Chapter}"
ItemsSource="{Binding Units}"
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<DateTemplate DataType = "{x:Type local:Unit}"
<TextBlock Text="{Binding Name}"/>
</DateTemplate>

Resources