I have an ObservableCollection of class
Public class Object
{
public string Name;
public Employee Employee;
}
public class Employee
{
// few properties
}
Here is my XMAL code for CollectionViewSource:
<CollectionViewSource x:Key="cvsTasks"
Source="{Binding Reels}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Name" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
Here is my DataGrid code:
<DataGrid ItemsSource="{Binding Source={StaticResource cvsTasks}}"/>
Now CollectionViewSource having PropertyGroupDescription on Name and I want to bind my DataGrid with Employee property of CollectionViewSource .
I dont know, if i understand your question correctly.
View:
<Window.Resources>
<CollectionViewSource x:Key="cvsTasks" Source="{Binding List}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Name" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid >
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Source={StaticResource cvsTasks}}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="EmpID" Binding="{Binding Employee.ID}"/>
<DataGridTextColumn Header="EmpDeportment" Binding="{Binding Employee.Department}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
ViewModel:
public class ViewModel {
public System.Collections.ObjectModel.ObservableCollection<Model.Root> List { get; set; }
public ViewModel() {
List = new System.Collections.ObjectModel.ObservableCollection<Model.Root> {
new Model.Root {
Name = "Peter",
Employee = new Model.Employee {
ID = 1,
Department = "IT"
}
},
new Model.Root {
Name = "Hans",
Employee = new Model.Employee {
ID = 2,
Department = "accounting"
}
},
new Model.Root {
Name = "Bilbo",
Employee = new Model.Employee {
ID = 3,
Department = "ceo"
}
}
};
}
}
Model:
public class Model {
public class Employee {
public int ID { get; set; }
public string Department { get; set; }
}
public class Root {
public string Name { get; set; }
public Employee Employee { get; set; }
}
}
And the result:
Related
Goal
I am aiming to achieve the following:
Load Person List to DataGrid ✔️
Load Positions List to DataGrid column into a ComboBox ✔️
Set the Person's Position value to Position's ComboBox ❌
Visual Output
Code...
Models
I have 2 models
public class Person
{
public string Name { get; set; }
public Position Position { get; set; }
}
public class Position
{
public int PositionId { get; set; }
public string PositionTitle { get; set; }
}
View Model
public class ViewModel : BaseViewModel
{
public ViewModel()
{
People = new ObservableCollection<Person>();
People.Add(new Person { Name = "Name 1", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
People.Add(new Person { Name = "Name 2", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
People.Add(new Person { Name = "Name 3", Position = new Position { PositionId = 2, PositionTitle = "Position Title 2" } });
Positions = new ObservableCollection<Position>();
Positions.Add(new Position { PositionId = 1, PositionTitle = "Position Title 1" });
Positions.Add(new Position { PositionId = 2, PositionTitle = "Position Title 2" });
}
private ObservableCollection<Person> people;
public ObservableCollection<Person> People
{
get { return people; }
set
{
people = value;
OnPropertyChanged();
}
}
private ObservableCollection<Position> _positions;
public ObservableCollection<Position> Positions
{
get { return _positions; }
set
{
_positions = value;
OnPropertyChanged();
}
}
}
public class Person
{
public string Name { get; set; }
public Position Position { get; set; }
}
public class Position
{
public int PositionId { get; set; }
public string PositionTitle { get; set; }
}
View
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Position Title">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Positions, RelativeSource={RelativeSource AncestorType=DataGrid}}"
DisplayMemberPath="PositionTitle"
SelectedValue="{Binding Path=Position}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Question
How do I set the SelectedItem/Index of Position to what the Person's Position is set to?
You could override the Equals method of your Position class to define that two objects with the same id should be considered equal:
public class Position
{
public int PositionId { get; set; }
public string PositionTitle { get; set; }
public override bool Equals(object obj) =>
obj is Position p && PositionId == p.PositionId;
public override int GetHashCode() => PositionId.GetHashCode();
}
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Positions,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
DisplayMemberPath="PositionTitle"
SelectedItem="{Binding Path=Position,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}" />
</DataTemplate>
Nope.. doesn't work
Full answer, for all inconsistencies in the code:
If the type is used for binding, then for correct operation it must either be immutable or implement the INotifyPropertyChanged interface:
namespace PeoplePosition
{
public class Position
{
public int PositionId { get; }
public string PositionTitle { get; }
public Position(int positionId, string positionTitle)
{
PositionId = positionId;
PositionTitle = positionTitle;
}
}
}
using Simplified;
namespace PeoplePosition
{
public class Person : BaseInpc // Implementation of the "INPC" interface
{
private string _name;
private Position _position;
public string Name { get => _name; set => Set(ref _name, value); }
public Position Position { get => _position; set => Set(ref _position, value); }
}
}
If you need to ensure equality of instances by value, then you need to override the Equals and GetHashCode methods (as #mm8 already wrote).
But for mutable types, this is a bad decision, which in some cases can lead to bugs.
If you need to set the values of a reference type corresponding to some collection, then you do not need to re-create instances of this type, but assign one of the elements of the collection that already contains all the valid values.
using System.Collections.ObjectModel;
namespace PeoplePosition
{
public class ViewModel
{
public ViewModel()
{
Positions.Add(new Position(1, "Position Title 1"));
Positions.Add(new Position(2, "Position Title 2"));
People.Add(new Person { Name = "Name 1", Position = Positions[0] });
People.Add(new Person { Name = "Name 2", Position = Positions[0] });
People.Add(new Person { Name = "Name 3", Position = Positions[1] });
}
public ObservableCollection<Person> People { get; }
= new ObservableCollection<Person>();
public ObservableCollection<Position> Positions { get; }
= new ObservableCollection<Position>();
}
}
Complete XAML code example demonstrating correct operation.
<Window x:Class="PeoplePosition.PeoplePositionsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PeoplePosition"
mc:Ignorable="d"
Title="PeoplePositionsWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<UniformGrid Columns="1">
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" IsReadOnly="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Position Title">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Positions,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
DisplayMemberPath="PositionTitle"
SelectedItem="{Binding Path=Position,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Binding="{Binding Position.PositionId, Mode=OneWay}"
Header="PositionId"/>
<DataGridTextColumn Binding="{Binding Position.PositionTitle, Mode=OneWay}"
Header="Position Title"/>
</DataGrid.Columns>
</DataGrid>
</UniformGrid>
</Window>
I wanted to use "Xceed DataGrid for WPF" community edition to show items in WPF application that are already coming correct for a Propertygrid.
The textbox (first name) is working fine, but combobox is not working. Problem is combobox is not populating anything and it is not setting the gender correctly. My simple code is given below.
XAML:
<Window.Resources>
<xcdg:DataGridCollectionViewSource x:Key="mySource" Source="{Binding Path=SelectedEntity}" />
</Window.Resources>
<Grid>
<xcdg:DataGridControl ItemsSource="{Binding Source={StaticResource mySource}}" AutoCreateColumns="True">
<xcdg:DataGridControl.Columns>
<xcdg:Column FieldName="FirstName" Title="First Name" />
<xcdg:Column FieldName="Gender" Title="Gender" >
<xcdg:Column.CellContentTemplate>
<DataTemplate x:Name="clmGenderTmp">
<ComboBox SelectedValuePath="GenderID"
DisplayMemberPath="Name"
ItemsSource="{Binding Path=SelectedEntity.Gender, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectedValue="{xcdg:CellEditorBinding}"/>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</Grid>
Xaml.cs is:
InitializeComponent();
this.DataContext = new MainWindowViewModel();
Data class is:
using System.ComponentModel;
using Xceed.Wpf.DataGrid;
public enum Genders { Male, Female }
public class Person
{
public Person(string firstName, Genders gender)
{
FirstName = firstName;
Gender = gender;
}
[DisplayName("Given name")]
public string FirstName { get; set; }
[Browsable(true)]
public Genders Gender { get; set; }
}
View Model is:
public class MainWindowViewModel
{
public List<object> SelectedEntity { get; set; }
public MainWindowViewModel()
{
SelectedEntity = new List<object>();
this.SelectedEntity.Add(new Person("Mathew", Genders.Male));
this.SelectedEntity.Add(new Person("Mark", Genders.Female));
}
}
It seems that Window does not have a property SelectedEntity. ;-) What you wanted to bind to is DataContext.SelectedEntity.Gender. Anyway, this will not work at all, because you are trying to bind a single value (Gender) as a ItemsSource.
You need to provide a list of (enum) values to the combobox.
How to bind an enum to a combobox control in WPF?
EDIT
As my answer was obviously not concrete enough, I provide a full example:
XAML:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Firstname}"
Margin="10" />
<ComboBox ItemsSource="{Binding Path=AvailableGenders}"
SelectedValue="{Binding Path=Gender}"
Grid.Column="1" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
PersonViewModel:
public class PersonViewModel : ViewModelBase
{
private Genders _gender;
private string _firstname;
public string Firstname
{
get
{
return _firstname;
}
set
{
_firstname = value;
OnPropertyChanged();
}
}
public Genders Gender
{
get
{
return _gender;
}
set
{
_gender = value;
OnPropertyChanged();
}
}
public List<Genders> AvailableGenders
{
get
{
return Enum.GetValues(typeof(Genders)).Cast<Genders>().ToList();
}
}
}
MainWindow Ctor:
public MainWindow()
{
InitializeComponent();
List<PersonViewModel> persons = new List<PersonViewModel>();
persons.Add(new PersonViewModel() { Firstname = "John", Gender = Genders.female });
persons.Add(new PersonViewModel() { Firstname = "Partyboy", Gender = Genders.male });
persons.Add(new PersonViewModel() { Firstname = "r2d2", Gender = Genders.robot });
persons.Add(new PersonViewModel() { Firstname = "KlausMaria", Gender = Genders.shemale });
DataContext = persons;
}
I got a similar example from How to add ComboBox column in XCeed DataGridControl (WPF)
Just publishing my code, if anyone faces same issue.
ViewModel:
public class ViewModel
{
public DataTable Persons { get; set; }
public DataTable Gender { get; set; }
public ViewModel()
{
Persons = new DataTable();
Persons.Columns.Add("FirstName", typeof(string));
Persons.Columns.Add("GenderID", typeof(int));
Persons.Rows.Add("Mathew",1);
Persons.Rows.Add("Gil", 2);
Gender = new DataTable();
Gender.Columns.Add("Name", typeof(string));
Gender.Columns.Add("GenderID", typeof(int));
Gender.Rows.Add("Male", 1);
Gender.Rows.Add("Female", 2);
}
}
Code Behind:
InitializeComponent();
viewModel = new ViewModel();
this.DataContext = viewModel;
XMAL:
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type xcdg:GroupByControl}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</Grid.Resources>
<xcdg:DataGridControl ItemsSource="{Binding Persons}" >
<xcdg:DataGridControl.Columns>
<xcdg:Column x:Name="clmFN" FieldName="FirstName"/>
<xcdg:Column x:Name="clmGender" FieldName="GenderID">
<xcdg:Column.CellContentTemplate>
<DataTemplate x:Name="clmGenderTmp">
<ComboBox SelectedValuePath="GenderID"
DisplayMemberPath="Name"
ItemsSource="{Binding Path=DataContext.Gender, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectedValue="{xcdg:CellEditorBinding}"/>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</Grid>
I have a data grid.
the item source MySource is an observableCollection<myClass>.
The class myClass has a property BackgroundOfRow - its type is Brush.
I want to bind the RowBackground attribute to this property in the xaml.
how can I do it?
my xaml now is:
<DataGrid AutoGenerateColumns="False"
ItemSource="{Binding Source={StaticResource myViewModel}, Path=MySource}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name"
Binding="{Binding Path=FirstName}"
FontFamily="Arial"
FontStyle="Italic" />
<DataGridTextColumn Header="Last Name"
Binding="{Binding Path=LastName}"
FontFamily="Arial"
FontWeight="Bold" />
</DataGrid.Columns>
</DataGrid>
You can bind the Background property in the RowStyle of DataGrid:
View:
<DataGrid ItemsSource="{Binding EmployeeColl}>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{Binding BackgroundOfRow}"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
Model:
public class Employee
{
public int ID { get; set; }
public int Name { get; set; }
public int Surname { get; set; }
public Brush BackgroundOfRow { get; set; }
}
ViewModel:
private ObservableCollection<Employee> employeeColl;
public ObservableCollection<Employee> EmployeeColl
{
get { return employeeColl; }
set
{
employeeColl = value;
OnPropertyChanged("EmployeeColl");
}
}
private void PopulateDataGrid()
{
employeeColl = new ObservableCollection<Employee>();
for (int i = 0; i < 100; i++)
{
if(i%2==0)
employeeColl.Add(new Employee() { ID = i, BackgroundOfRow = Brushes.CadetBlue});
else
employeeColl.Add(new Employee() { ID = i, BackgroundOfRow = Brushes.Green });
}
}
First datagrid is binded to ObservableCollection from my MainViewModel class. Second datagrid which i want to filter is binded like this:
ICollectionView icv;
icv = CollectionViewSource.GetDefaultView(list1);
icv.Filter = FilterTable;
dataGrid1.ItemsSource = icv;
list1 is same ObservableCollection from first datagrid. With this code my both datagrids were filtered. Is there any way to filter only second datagrid, but not first?
You can use two different view and get this filter done. refer below code.
<StackPanel>
<DataGrid ItemsSource="{Binding PersonsViews}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Age" Binding="{Binding Age}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid ItemsSource="{Binding PersonsFileterViews}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Age" Binding="{Binding Age}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
class MainViewModel
{
private ObservableCollection<Person> perList = new ObservableCollection<Person>();
public ObservableCollection<Person> PersonList
{
get { return perList; }
set { perList = value; }
}
public ICollectionView PersonsViews { get; set; }
public ICollectionView PersonsFileterViews { get; set; }
public MainViewModel()
{
perList.Add(new Person() { Age = 1, Name = "Test1"});
perList.Add(new Person() { Age = 2, Name = "Test2" });
perList.Add(new Person() { Age = 3, Name = "Test3" });
perList.Add(new Person() { Age = 4, Name = "Test4" });
PersonsViews = new CollectionViewSource { Source = PersonList }.View;
PersonsFileterViews = new CollectionViewSource { Source = PersonList }.View;
PersonsFileterViews.Filter = new Predicate<object>(x => ((Person)x).Age > 2);
}
}
public class Person
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
My little project looks now quite different. Now I have ObservableCollection PackagesList with data which I want to bind with whole DataGrid (via Binding Path) and ObservableCollection DestinationItememsSource where I store cases for DataGridComboboxColumn (as ItemsSourceBinding). SelectedItem property in DatagridComboboxColumn is one value from PackageInfo (and it is one of DestinationNames cases ofc). Binding on DatagridTextBoxColumns is ok.
XAML:
<Window x:Class="test01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="400" Loaded="Window_Loaded">
<Grid>
<Border x:Name="mainBorder" Margin="20">
<DataGrid x:Name="mainDataGrid" x:Uid="mainDataGrid" AutoGenerateColumns="False"
AlternationCount="2" SelectionMode="Single" HorizontalAlignment="Stretch">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=id}"
Header="ID" Width="Auto" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Path=name}"
Header="Name" Width="Auto" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Path=quantity}"
Header="Quantity" Width="Auto" IsReadOnly="True"/>
<DataGridTextColumn Binding="{Binding Path=price}"
Header="Price" Width="Auto" IsReadOnly="True"/>
<DataGridComboBoxColumn ItemsSource="{Binding DestinationItemsSource}"
SelectedItemBinding="{Binding Path=destination, UpdateSourceTrigger=PropertyChanged}"
Header="Destination" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
</Border>
</Grid>
</Window>
C#:
public class PackageInfo
{
public int id { get; set; }
public string name { get; set; }
public int quantity { get; set; }
public double price { get; set; }
public string destination { get; set; }
}
public class DestinationNames : INotifyPropertyChanged
{
public string name { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public DestinationNames(string value)
{
this.name = value;
}
public string DestinationName
{
get { return name; }
set
{
name = value;
OnPropertyChanged("DestinationName");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public partial class MainWindow : Window
{
public ObservableCollection<DestinationNames> DestinationItememsSource { get; set; }
public ObservableCollection<PackageInfo> PackagesList { get; set; }
public MainWindow()
{
InitializeComponent();
}
public void Window_Loaded(object sender, RoutedEventArgs e)
{
LoadPackages();
}
public void LoadPackages()
{
DestinationItememsSource = new ObservableCollection<DestinationNames>();
DestinationItememsSource.Add(new DestinationNames("London"));
DestinationItememsSource.Add(new DestinationNames("Plymouth"));
DestinationItememsSource.Add(new DestinationNames("Birmingham"));
DestinationItememsSource.Add(new DestinationNames("Cambridge"));
PackagesList = new ObservableCollection<PackageInfo>();
PackagesList.Add(new PackageInfo { id = 1, name = "Potato", quantity = 3, price = 2.2, destination = "London" });
PackagesList.Add(new PackageInfo { id = 2, name = "Tomato", quantity = 5, price = 3.8, destination = "Plymouth" });
PackagesList.Add(new PackageInfo { id = 3, name = "Carrot", quantity = 1, price = 5.1, destination = "London" });
PackagesList.Add(new PackageInfo { id = 4, name = "Pea", quantity = 6, price = 1.8, destination = "Plymouth" });
PackagesList.Add(new PackageInfo { id = 5, name = "Bean", quantity = 2, price = 1.5, destination = "Birmingham" });
mainDataGrid.ItemsSource = PackagesList;
}
}
How to bind this DatagridComboboxColumn properly? Should I use INotifyCollectionChanged ?? What if I want to all data in datagrid will be automatically synced with ObservableCollection ?? Please help with some example.
Have PackageInfo implement INotifyPropertyChanged.
An ObservableCollection automatically implements INotifyCollectionChanged.
You will need to add List or ObservableCollection Destinations as a property of PackageInfo
NO class DestinationNames
Just a class DestinationName
Your binding to DestinationItememsSource does not work, because it isnt part of the PackageInfo object. You also cant bind to the Windows DataContext via FindAncestor or ElementName-binding because DataGrid-Columns wont be added to the visual tree.
One solution I can think of is using the CollectionViewSource:
<Window.Resources>
<CollectionViewSource Source="{Binding RelativeSource={RelativeSource Self}, Path=DestinationItememsSource}" x:Key="destinations">
<!--here you can also add Group and SortDescriptions-->
</CollectionViewSource>
</Window.Resources>
<DataGridComboBoxColumn ItemsSource="{Binding Source={StaticResource ResourceKey=destinations}}".../>
I cant test it atm, because I'm still downloading VS 2012.^^
Anotherone would be to simply add a List of Destinations to your PackageInfo object (but this would cause a lot of redundancies).