I am trying to Group my collection which is based on my following model:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Role PersonRole { get; set; }
}
public class Role
{
public int Id { get; set; }
public string RoleName { get; set; }
}
My PersonCollection,
public ObservableCollection<Person> PersonList;
The collection is populated with three Person object, two with identical roles.
I want to group all my persons as per their RoleName. I have the following XAML:
<Grid.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding PersonList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="PersonRole.RoleName"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Grid.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource cvs}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonRole.RoleName}" FontWeight="Bold" Background="ForestGreen" Margin="0,5,0,0"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
However the rendered UI is not displaying the grouped RoleName in the list box. Also I would like to show the grouping horizontally where the groups appear horizontally while the grouped items appear vertically. Appreciate any help in advance.
Here's the output I am seeing:
Since you have already provided GroupDescriptons at your CollectionViewSource, groupStyle will pick the property name from there only.
All you need is to bind with Name property of CollectionViewGroup class to access the value of the property on which grouping is applied. In group style, binding is against CollectionViewGroup class and not against your Model class.
So, this is how you will do it:
<TextBlock Text="{Binding Name}" FontWeight="Bold"
Background="ForestGreen" Margin="0,5,0,0"/>
And regarding to align groups horizontally, you can set ItemsPanelTemplate of Group to be VirtualizingStackPanel with Orientation set to Horizontal which makes your groups to be aligned horizontally.
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontWeight="Bold"
Background="ForestGreen" Margin="0,5,0,0"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
Related
I have a viewmodel called Calendar.
in Calendar there is a list of CalendarDaySquare objects. They are displayed in a ItemsControl that is a uniformgrid (from the ItemsPanelTemplate).
On each of these calendarDaySquares I want to populate it with the Events Collection (of CalEvent objects)
public Class Calendar
{
// this is just a list that inherits from List<T>
public CalendarSquareList<CalendarDaySquare> Squares { get; private set; }
}
public class CalendarDaySquare
{
public List<CalEvent> Events {get; private set;}
public ObservableCollection<string> ObsColEvents{get; private set;}
}
Are the same thing, I just tried using the ObservableCollection b/c I've been stumped on this.
Here is my xaml :
<ItemsControl ItemsSource="{Binding Squares}"
Margin="0,0,0,263">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="7" Rows="5"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Margin" Value="1"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate xmlns:local="clr-namespace:BudgetCalendarWPF.ViewModel;assembly=BudgetCalendarWPF" DataType="CalendarDaySquare">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--</Button>
</ControlTemplate>
</Button.Template>
</Button>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I hope i've pasted the latest (i've been working on this all day so this is just what i've got in VS currently .. sad face )
Welp, I think i've got this kinda figured out but I'd love any suggestions.
I was trying to hard. I didn't realize it would automatically pick up the item as the type it was. so this worked :
<ListBox ItemsSource="{Binding Events}">
<ListBox.ItemTemplate>
<DataTemplate >
<Button HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Name}"/>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
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.
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>
I have a class which has two level of collections. The software is about salaries payment. The idea is a payment action consists of multiple paid employees. And each employees could have multiple salaries cuts. The Object would look like:
Payment -->Payment Object
Date
ID
Employees: -->ObservableCollection of EmpPayment Objects
Emp A -->And EmpPayment Object
Name
TotalCut --> An Integer Readonly Property that sums the Cuts.
Cuts --> Collection of Cut object
Cut1 --> Cut object 1
Cut2 --> Cut object 2
Emp B
Name
TotalCuts
Cuts
Cut1
Cut2
I am using a exceed DataGrid. So far so good except I want to display two values in one column using CellContentTemplate, that is the Cuts and the TotalCut. So the datagrid will look like:
Employee | Cuts
Emp A | [The Total Cut]
Cut 1
Cut 2
Emp B | [The Total Cut]
Cut 1
Cut 2
At the cuts column, i want to use a Xceed DropDownButton to show the total, and user can edit the cuts by editting the dropdowncontent. So far the XAML i made for the Employees ObservableCollection:
<xcdg:DataGridControl x:Name="GridEmployees" ItemsSource="{Binding Employees}" AutoCreateColumns="False">
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="Name" Title="Employee"/>
<xcdg:Column FieldName="Cuts" Title="Cuts">
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<xctk:DropDownButton Content="{Binding Path=TOTALCUT}">
<xctk:DropDownButton.DropDownContent>
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding CutDescription}"/>
<TextBox Text="{Binding Amount}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</xctk:DropDownButton.DropDownContent>
</xctk:DropDownButton>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
Binding to the Cuts ObservableCollection works well, but not the TotalCut. How do I bind the TotalCut [Intentionally written in all caps above] to that same column?
Using two columns is possible, but will not be pretty.
Forget the XCeed DataGridControl and welcome to standard DataGrid. Just use the DataGridTemplateColumn there.
The other alternative is just to use two columns in XCeed DataGridControl which is not preety.
All you have to do - provide your own cell editor and CellTemplate, which will bind to corresponding properties. Here's my working solution:
<xcdg:DataGridControl AutoCreateColumns="False" ItemsSource="{Binding Items}">
<xcdg:DataGridControl.View>
<xcdg:TableflowView ScrollingAnimationDuration="0" ContainerHeight="44"/>
</xcdg:DataGridControl.View>
<xcdg:DataGridControl.Resources>
<DataTemplate x:Key="EditDataTemplate" DataType="wpfApplication1:State">
<StackPanel>
<TextBox Text="{Binding Path=Name}"/>
<TextBox Text="{Binding Path=Post}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ReadDataTemplate" DataType="wpfApplication1:State">
<StackPanel>
<TextBlock HorizontalAlignment="Right" Text="{Binding Path=Name}"/>
<TextBlock HorizontalAlignment="Right" Text="{Binding Path=Post}"/>
</StackPanel>
</DataTemplate>
</xcdg:DataGridControl.Resources>
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="Zip" Title="Zip"/>
<xcdg:Column FieldName="Stat" Title="Zip2"
CellContentTemplate="{StaticResource ReadDataTemplate}">
<xcdg:Column.CellEditor>
<xcdg:CellEditor EditTemplate="{StaticResource EditDataTemplate}"/>
</xcdg:Column.CellEditor>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
and codebehind of "data object":
public class User
{
private string _zip;
public string Zip
{
get { return _zip; }
set
{
_zip = value;
Stat = new State();
Stat.Name = "stat: " + value;
}
}
public State Stat { get; set; }
}
public class State
{
public string Name { get; set; }
public string Post { get; set; }
}
Entire solution code
Does anybody know how to binding two ObservableCollections in one ListBox?
these two ObservableCollections both have a Property string "name" to display in the ListBox,
int the ListBox top area, will display the ObservableCollection1 items and in the ListBox bottom area I want display the ObservableCollection2 items, how to do that?
<ListBox x:Name="m_CtrlMediaList" Grid.Column="2" AllowDrop="True" SelectionMode="Extended">
<ListBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding directorys}"/>
<CollectionContainer Collection="{Binding files}"/>
</CompositeCollection>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding name, Mode=OneWay}" FontWeight="Bold" FontSize="14"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
the data is like:
class ElementFile
{
...
string name (get;set;}
...
}
class ElementDirectory
{
...
string name (get;set;}
...
public ObservableCollection<ElementDirectory> directorys { get; set; }
public ObservableCollection<ElementFile> files { get; set; }
...
}
Why can not display the "name"?
The composite collection is what you are looking for. Example.