I've got some component objects in a CollectionViewSource i need to sort, these objects all have a custom type. The grouping is done on the type and the components are sorted by their name. What I now need to do is sort the grouping on the component type But i need to sort these component types depending on an external source, So the objects looks a bit like this:
public class ComponentType
{
public Guid Identification
{
get;
}
}
public class Component
{
public string Name
{
get;
}
public ComponentType Type
{
get;
}
}
The collection view is created like so:
this.ComponentCollection = new CollectionViewSource();
this.ComponentCollection.Source = this.Components;
this.ComponentCollection.GroupDescriptions.Clear();
this.ComponentCollection.GroupDescriptions.Add(new PropertyGroupDescription("ComponentType"));
this.ComponentCollection.SortDescriptions.Clear();
this.ComponentCollection.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
this.ComponentCollection.Filter += this.FilterComponent;
this.ComponentCollection.View.Refresh();
RaisePropertyChanged(() => this.ComponentCollection);
I also have the following dictionary in the same calss i'm creating the CollectionViewSource which looks like this:
public Dictionary<Guid, int> ComponentTypePositions
Where the key is the identificaiton of the component type and int is the position of which type should come first.
It is not possible to put the position as a property in the ComponentType or Component class, it needs to be a separate list.
How do i sort the grouping according to the corresponding number in the ComponentTypePositions dictionary?
You need to create a new type based on Component class which includes your position numbers and use this for the elements of your source list. This can be done inline with an anonymous type:
ComponentCollection = new CollectionViewSource();
ComponentCollection.Source = (from c in Components
select new
{
Name = c.Name,
Type = c.Type,
Pos = ComponentTypePositions[c.Type.Identification]
}).ToList();
ComponentCollection.GroupDescriptions.Clear();
ComponentCollection.GroupDescriptions.Add(new PropertyGroupDescription("Type"));
ComponentCollection.SortDescriptions.Clear();
ComponentCollection.SortDescriptions.Add(new SortDescription("Pos", ListSortDirection.Ascending));
ComponentCollection.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
ComponentCollection.Filter += FilterComponent;
ComponentCollection.View.Refresh();
If your source list need to be editable, you can use your own custom list type for it:
public class ComponentListElement
{
private Component comp;
public ComponentListElement(Component comp, Dictionary<Guid, int> positionMap)
{
this.comp = comp;
this.Pos = positionMap[comp.Type.Identification];
}
public string Name { get { return comp.Name; } }
public ComponentType Type { get { return comp.Type; } }
public int Pos { get; private set; }
}
public class ComponentList : Collection<ComponentListElement>
{
private Dictionary<Guid, int> positionMap;
public ComponentList(Dictionary<Guid, int> positionMap)
{
this.positionMap = positionMap;
}
public void Add(Component item)
{
base.Add(new ComponentListElement(item, positionMap));
}
}
And use it like this:
ComponentList componentList = new ComponentList(ComponentTypePositions);
foreach (var item in Components)
{
componentList.Add(item);
}
ComponentCollection.Source = componentList;
I am trying to build a datagrid where columns are generated dynamically (this works fine) but I am unable to create bindings for the columns which update automatically (something like INotifyPropertyChanged).
Creating columns dynamically and want to use dictionary elements for binding which can be modified/added dynamically. No errors seen in debug output of visual studio.
I think I am really missing something minor here.
clicking button does not populate the second column
ViewModel:
class DataGridAttachedPropertyViewModel {
public ObservableCollection<DataGridColumn> ColumnCollection { get; set; }
public ObservableCollection<AttachedPropertyEmployee> SomEmployees { get; set; }
public ICommand myCommand { get; set; }
public DataGridAttachedPropertyViewModel() {
this.ColumnCollection = new ObservableCollection<DataGridColumn>();
DataGridTextColumn tc = new DataGridTextColumn();
tc.Header = "Sample Column";
// tc.Binding = new Binding("name");
Binding forCurrent = new Binding("SimpleDict[f]");
forCurrent.Mode = BindingMode.TwoWay;
tc.Binding = forCurrent;
DataGridTextColumn tt = new DataGridTextColumn();
tt.Header = "Column x";
// tc.Binding = new Binding("name");
Binding forTheCurrent = new Binding("SimpleDict[x]");
forTheCurrent.Mode = BindingMode.TwoWay;
tt.Binding = forTheCurrent;
myCommand = new DelegateCommand(ButtonBase_OnClick);
this.ColumnCollection.Add(tc);
this.SomEmployees = new ObservableCollection<AttachedPropertyEmployee>();
this.SomEmployees.Add(new AttachedPropertyEmployee("Rajat","Norwalk"));
this.SomEmployees.Add(new AttachedPropertyEmployee("Matthew", "Norwalk"));
}
public void ButtonBase_OnClick() {
foreach (var VARIABLE in SomEmployees) {
VARIABLE.SimpleDict["x"] = "x";
}
}
}
AttachedPropertyEmployee.cs
public class AttachedPropertyEmployee : INotifyPropertyChanged {
private Dictionary<string, string> dict;
public Dictionary<string, string> SimpleDict {
get { return this.dict; }
set
{
if (this.dict != value) {
this.dict = value;
this.NotifyPropertyChanged("SimpleDict");
}
}
}
public AttachedPropertyEmployee(string Name, string Address) {
this.SimpleDict = new Dictionary<string, string>();
SimpleDict["f"] ="b";
this.name = Name;
this.address = Address;
}
public string name;
public string address { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName) {
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
XAML:
<Window x:Class="LearnInteractivity.LearnDataGridAttachedProperty"
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:LearnInteractivity"
mc:Ignorable="d"
Title="LearnDataGridAttachedProperty" Height="300" Width="300">
<!--
Put a datargrid and an attached property and update columns dynamincally.
-->
<StackPanel>
<DataGrid
local:DataGridColumnsBehavior.BindableColumns="{Binding ColumnCollection}"
x:Name="dgg"
AutoGenerateColumns="False"
ItemsSource="{Binding SomEmployees}"></DataGrid>
<Button Content="Populate" Command="{Binding myCommand}"></Button>
</StackPanel>
I see two problems here.
The first is that Dictionary<TKey,TValue> doesn't implement INotifyCollectionChanged, so when you change values in it, no event is raised and the UI never knows about it. You could look for an ObservableDictionary<K,V> and use that (IIRC there are a few implementations around), or you can do it the quick and dirty way:
public void ButtonBase_OnClick() {
foreach (var VARIABLE in SomEmployees) {
VARIABLE.SimpleDict["x"] = "x";
VARIABLE.NotifyPropertyChanged("SimpleDict");
}
}
That will notify the grid that SimpleDict has changed.
The second problem is that in the DataGridAttachedPropertyViewModel constructor, you forgot to add tt to ColumnCollection.
this.ColumnCollection.Add(tc);
this.ColumnCollection.Add(tt);
More thoughts:
I would be more comfortable adding something like this to AttachedPropertyEmployee:
public void SetColumValue(string key, string value) {
SimpleDict[key] = value;
NotifyPropertyChanged("SimpleDict");
}
And use that in your loop instead:
public void ButtonBase_OnClick() {
foreach (var VARIABLE in SomEmployees) {
VARIABLE.SetColumnValue("x", "x");
}
}
Incidentally, I'd change SimpleDict to Dictionary<String, Object> so you can support more types than just string, and leave formatting to the UI. And I might consider exposing a ReadOnlyDictionary<K,V> in the SimpleDict property, with the writable dictionary a private field -- so callers would have no choice but to use SetColumnValue(k,v) to set the column values.
Hello I have a DataGrid and I have different reports that I want to show. I'm going to change the classes so they are shorter in here but Idea is the same.
Lets say that I Have an Interface called IReports
public interface IReports
{
}
and three classes called Students, Classes, Cars
public class Students:IReports
{
public string Name { get; set; }
}
public class Classes : IReports
{
public string ClassName { get; set; }
public string StudentName { get; set; }
}
public class Cars : IReports
{
public int Mileage { get; set; }
public string CarType { get; set; }
public string StudentName { get; set; }
}
The List
private List<IReports> _reportsTable;
public List<IReports> ReportsTable
{
get { return _reportsTable; }
set { SetProperty(ref (_reportsTable), value); }
}
the DataGrid
<DataGrid ItemsSource="{Binding ReportsList}"
Grid.Column="1"
Grid.Row="0"
AutoGenerateColumns="True"
Grid.RowSpan="6"/>
Okay, so what is important here is they all have different property names and some have more some have less. How can I bind the DataGrid to look at the different properties? This is MVVM if that makes any difference.
Update: What this will always only use one of the classes at a time.but when someone changes a combobox it will fire an event that will fill the IList<IReports>.
What this will always only use one of the classes at a time. but when someone changes a combobox it will fire an event that will fill the IList<IReports>.
The way I understand the above is that you never mix different elements inside the list (i.e. it contains only Classes, Students or Cars). All the other answers are assuming the list contains mixed content, but if that's the true, then DataGrid is simply not the right presenter for such content.
If the above assumption is correct, then the only problem is how to represent different lists with a single bindable property. As can be seen in Data Binding Overview, when dealing with collection, data binding does not really care if they are generic or not. The recognizable source types are the non generic IEnumerable, IList and IBindingList. However, the collection view implementation is using some rules to determine the element type of the collection, by seeking for generic type argument of implemented IEnumerable<T> interfaces by the actual data source class, by checking the first available item, or taking the information from ITypedList implementation etc. All the rules and their precedence can be seen in the Reference Source.
With all that in mind, one possible solution could be to change the ReportsTable property type to allow assigning List<Classes> or List<Students or List<Cars>. Any common class/interface will work (remember, data binding will check the actual type returned by GetType()) like object, IEnumerable, IList, IEnumerable<IReports> etc., so I'll choose the closest covariant type to List<IReports which is IReadOnlyList<IReports>:
private IReadOnlyList<IReports> _reportsTable;
public IReadOnlyList<IReports> ReportsTable
{
get { return _reportsTable; }
set { SetProperty(ref (_reportsTable), value); }
}
Now when you do this
viewModel.ReportsTable = new List<Students>
{
new Students { Name = "A" },
new Students { Name = "B" },
new Students { Name = "C" },
new Students { Name = "D" },
};
you get
while with this
viewModel.ReportsTable = new List<Classes>
{
new Classes { ClassName = "A", StudentName = "A" },
new Classes { ClassName = "A", StudentName ="B" },
new Classes { ClassName = "B", StudentName = "C" },
new Classes { ClassName = "B", StudentName = "D" },
};
it shows
and finally this
viewModel.ReportsTable = new List<Cars>
{
new Cars { Mileage = 100, CarType = "BMW", StudentName = "A" },
new Cars { Mileage = 200, CarType = "BMW", StudentName = "B" },
new Cars { Mileage = 300, CarType = "BMW", StudentName = "C" },
new Cars { Mileage = 400, CarType = "BMW", StudentName = "D" },
};
results in
UPDATE: The above requires modifying the model to return concrete List<T> instances. If you want to keep the model as it is (i.e. returning List<IReports>), then you'll need a different solution, this time utilizing the ITypedList. In order to do that, we'll create a simple list wrapper using the System.Collections.ObjectModel.Collection<T> base class:
public class ReportsList : Collection<IReports>, ITypedList
{
public ReportsList(IList<IReports> source) : base(source) { }
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
return TypeDescriptor.GetProperties(Count > 0 ? this[0].GetType() : typeof(IReports));
}
public string GetListName(PropertyDescriptor[] listAccessors) { return null; }
}
then change the bindable property to
private IList<IReports> _reportsTable;
public IList<IReports> ReportsTable
{
get { return _reportsTable; }
set { SetProperty(ref _reportsTable, value as ReportsList ?? new ReportsList(value)); }
}
and you are done.
As I understand it, you want a datagrid to show the various columns of various classes that implement an interface. If you hook the DataGrid's LoadingRow event, you can see what types of objects you are dealing with at runtime. You can use reflection to get the properties off the row's datacontext and then check the datagrid to see if there is a column for that property. If not, add it.
An issue will be if there are different types in the list and a type doesn't have a property that is in another type (like Cars doesn't have a Name property and both Students and Cars are in the list). If you edit a column for a property that doesn't exist on the object, you'll throw an exception. To get around this, you'll need a converter and style that applies it to the datagridcells. For fun, I also added a datatrigger that changes the background of the cell to Silver if it is disabled. One issue will be if you need to change the cell's style then you have to do it in the code (or change the style in the code to be based on your style).
XAML:
<DataGrid ItemsSource="{Binding ReportsTable}" AutoGenerateColumns="True" LoadingRow="DataGrid_LoadingRow" />
CS
private void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
var dg = sender as DataGrid;
var pis = e.Row.DataContext.GetType().GetProperties();
foreach (var pi in pis)
{
// Check if this property already has a column in the datagrid
string name = pi.Name;
var q = dg.Columns.Where(_ => _.SortMemberPath == name);
if (!q.Any())
{
// No column matches, so add one
DataGridTextColumn c = new DataGridTextColumn();
c.Header = name;
c.SortMemberPath = name;
System.Windows.Data.Binding b = new Binding(name);
c.Binding = b;
// All columns don't apply to all items in the list
// So, we need to disable the cells that aren't applicable
// We'll use a converter on the IsEnabled property of the cell
b = new Binding();
b.Converter = new ReadOnlyConverter();
b.ConverterParameter = name;
// Can't apply it directly, so we have to make a style that applies it
Style s = new Style(typeof(DataGridCell));
s.Setters.Add(new Setter(DataGridCell.IsEnabledProperty, b));
// Add a trigger to the style to color the background when disabled
var dt = new DataTrigger() { Binding = b, Value = false };
dt.Setters.Add(new Setter(DataGridCell.BackgroundProperty, Brushes.Silver));
s.Triggers.Add(dt);
c.CellStyle = s;
// Add the column to the datagrid
dg.Columns.Add(c);
}
}
}
CS for the converter:
public class ReadOnlyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
var prop = value.GetType().GetProperty(parameter as string);
if (prop != null)
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And, just to be complete, this is what I used to setup the data for the screenshot:
public List<IReports> ReportsTable { get; set; }
public MainWindow()
{
InitializeComponent();
ReportsTable = new List<IReports>() {
new Students() { Name = "Student 1" },
new Students() { Name = "Student 2" },
new Classes() { ClassName="CS 101", StudentName = "Student 3" },
new Cars() { CarType = "Truck", Mileage=12345, StudentName = "Student 4" }
};
this.DataContext = this;
}
Screenshot:
Instead of a converter option to display a given string value, why not add a getter to the base interface. Then, each class just returns its own, almost like every object can override its "ToString()" method Since you would create a list such as for display, or picking, the value would be read-only anyhow, Make it just a getter...
public interface IReports
{
string ShowValue {get;}
}
public class Students:IReports
{
public string Name { get; set; }
public string ShowValue { get { return Name; } }
}
public class Classes : IReports
{
public string ClassName { get; set; }
public string StudentName { get; set; }
public string ShowValue { get { return ClassName + " - " + StudentName ; } }
}
public class Cars : IReports
{
public int Mileage { get; set; }
public string CarType { get; set; }
public string StudentName { get; set; }
public string ShowValue { get { return CarType + "(" + Mileage + ") - " + StudentName; } }
}
Then in your view model manager...
public class YourMVVMClass
{
public YourMVVMClass()
{
SelectedRptRow = null;
ReportsTable = new List<IReports>()
{
new Students() { Name = "Student 1" },
new Students() { Name = "Student 2" },
new Classes() { ClassName="CS 101", StudentName = "Student 3" },
new Cars() { CarType = "Truck", Mileage=12345, StudentName = "Student 4" }
};
}
// This get/set for binding your data grid to
public List<IReports> ReportsTable { get; set; }
// This for the Selected Row the data grid binds to
public IReports SelectedRptRow { get; set; }
// This for a user double-clicking to select an entry from
private void Control_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Now, you can look directly at the SelectedRptRow
// as in the data-grid binding declaration.
if (SelectedRptRow is Classes)
MessageBox.Show("User selected a class item");
else if( SelectedRptRow is Cars)
MessageBox.Show("User selected a car item");
else if( SelectedRptRow is Students)
MessageBox.Show("User selected a student item");
else
MessageBox.Show("No entry selected");
}
}
Finally in your form/view
<DataGrid Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding ReportsTable}"
SelectedItem="{Binding SelectedRptRow}"
MouseDoubleClick="Control_OnMouseDoubleClick"
AutoGenerateColumns="False"
Width="200" Height ="140"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<DataGrid.Columns>
<DataGridTextColumn
Header="Report Item"
Width="180"
IsReadOnly="True"
CanUserSort="False"
Binding="{Binding Path=ShowValue}" />
</DataGrid.Columns>
</DataGrid>
The other answers using the converters are just another path, but this avenue to me is easier as you can change each individual class and expand / adjust as needed. The exposed "ShowValue" getter is common to all instances of the "IReports", so the binding is direct without going through the converter. If you remove a class, or extend in the future, your underlying is all self-contained.
Now don't get me wrong, I do use converters and typically do with Boolean type fields to respectively show, hide, collapse controls as needed. This is nice as I have different boolean converters such as
BoolToVisibleHidden = if True, make visible vs Hidden
BoolToHiddenVisible = if True, make Hidden vs Visible
BoolToVisibleCollapse = if True, make visible vs Collapsed
BoolToCollapseVisible = if True, make Collapsed vs visible.
So, with one boolean property on my MVVM, I can both show AND hide different controls... maybe such as an admin vs standard user option.
I've also used converters dealing with dates for alternate formatting purposes.
You could abuse IValueConverter for that. Create one for each column.
In the ValueConverter you can test for the type and return the correct property. An example of what I mean:
public class NameValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Students)
{
return (value as Students).Name;
}
if (value is Classes)
{
return (value as Classes).ClassName;
}
if (value is Cars)
{
return (value as Cars).CarType;
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
To use it add it as resource to the DataGrid:
<DataGrid.Resources>
<local:NameValueConverter x:Key="NameValueConverter"></local:NameValueConverter>
</DataGrid.Resources>
And specify it in the binding like this:
{Binding Path=., Converter={StaticResource NameValueConverter}}
This solution would only work for read-only DataGrids though (editing throws a NotImplementedException).
I have a (string, object) dictionary, object (class) has some values including data type which is defined by enum. I need a GetItemValue method that should return dictionary item's value. So return type must be the type which is defined in item object.
Class Item
{
String Name;
DataValueType DataType;
Object DataValue;
}
private Dictionary<string, Item> ItemList = new Dictionary<string, Item>();
void Main()
{
int value;
ItemList.Add("IntItem", new Item("IntItem", DataValueType.TInt, 123));
value = GetItemValue("IntItem"); // value = 123
}
What kind of solution can overcome this problem?
Best Regards,
You can use Generic Classes
Class Item<T>
{
String Name;
T DataTypeObject;
Object DataValue;
public T GetItemValue()
{
//Your code
return DataTypeObject;
}
}
A better solution would be to introduce an interface that you make all the classes implement. Note that the interface doesn't necessarily have to specify any behavior:
public interface ICanBePutInTheSpecialDictionary {
}
public class ItemTypeA : ICanBePutInTheSpecialDictionary {
// code for the first type
}
public class ItemTypeB : ICanBePutInTheSpecialDictionary {
// code for the second type
}
// etc for all the types you want to put in the dictionary
To put stuff in the dictionary:
var dict = new Dictionary<string, ICanBePutInTheSpecialDictionary>();
dict.add("typeA", new ItemTypeA());
dict.add("typeB", new ItemTypeB());
When you need to cast the objects to their specific types, you can either use an if-elseif-block, something like
var obj = dict["typeA"];
if (obj is ItemTypeA) {
var a = obj as ItemTypeA;
// Do stuff with an ItemTypeA.
// You probably want to call a separate method for this.
} elseif (obj is ItemTypeB) {
// do stuff with an ItemTypeB
}
or use reflection. Depending on how many choices you have, either might be preferrable.
If you have a 'mixed bag' you could do something like this...
class Item<T>
{
public String Name { get; set; }
public DataValueType DataType { get; set; }
public T DataValue { get; set; }
}
class ItemRepository
{
private Dictionary<string, object> ItemList = new Dictionary<string, object>();
public void Add<T>(Item<T> item) { ItemList[item.Name] = item; }
public T GetItemValue<T>(string key)
{
var item = ItemList[key] as Item<T>;
return item != null ? item.DataValue : default(T);
}
}
and use it like...
var repository = new ItemRepository();
int value;
repository.Add(new Item<int> { Name = "IntItem", DataType = DataValueType.TInt, DataValue = 123 });
value = repository.GetItemValue<int>("IntItem");
If you have just a couple types - you're better off with Repository<T>.
I found a solution exactly what I want. Thanks to uncle Google.
Thanks all of you for your kind interest.
public dynamic GetValue(string name)
{
if (OpcDataList[name].IsChanged)
{
OpcReflectItem tmpItem = OpcDataList[name];
tmpItem.IsChanged = false;
OpcDataList[name] = tmpItem;
}
return Convert.ChangeType(OpcDataList[name].ItemValue.Value, OpcDataList[name].DataType);
}
I am working with DataGrids but I am struggling to binding my data since the number of columns varies depending of the info that has to be showed.
So, what I have tried to do is to create and object which contains all the columns and rows that I need at some point and binding this object to the ItemsSource property. Since I have worked with DataGridViews in WindowsForms I have in mind something like this:
DataTable myTable = new DataTable();
DataColumn col01 = new DataColumn("col 01");
myTable.Columns.Add(col01);
DataColumn col02 = new DataColumn("col 02");
myTable.Columns.Add(col02);
DataRow row = myTable.NewRow();
row[0] = "data01";
row[1] = "data02";
myTable.Rows.Add(row);
row = myTable.NewRow();
row[0] = "data01";
row[1] = "data02";
myTable.Rows.Add(row);
But I haven't been able to find a way to do the same thing in WPF since I need some columns to be DataGridComboBoxColumns for example.
Actually I have read many post about it in this site, but none of them helped to me. I am really lost.
Could anyone help me? I just need to be able to create a table which may contain DataGridTextColumns or `DataGridComboBoxColumns, etc, In order to bind this final object to the DataGrid's ItemsSource property.
Hope someone can help me.
Okay, let me try to take an example which is similar to your needs
Let's assume we use this class:
public class MyObject
{
public int MyID;
public string MyString;
public ICommand MyCommand;
}
And we are willing to display a DataGrid listing the ID, and having as a second column a Button, with the property MyString as content, which, when clicked, launches the ICommand MyCommand which opens in a new window whatever you want.
Here is what you should have on the View side:
<DataGrid ItemsSource="{Binding MyList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding MyID}" />
<DataGridTemplateColumn Header="Buttons">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="{Binding MyString}" Command="{Binding MyCommand}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This will show a DataGrid taking all the content in an IEnumerable<MyObject> named 'MyList', and shows two columns as defined before.
Now if you need to define the command.
First, I recommend you read this introductory link to MVVM and take the RelayCommand class (that's what we're gonna use for your problem)
So, in your ViewModel, the one which defines the MyList, here is how you should define some of the useful objects:
public ObservableCollection<MyObject> MyList { get; set; }
// blah blah blah
public void InitializeMyList()
{
MyList = new ObservableCollection<MyObject>();
for (int i = 0; i < 5; i++)
{
MyList.Add(InitializeMyObject(i));
}
}
public MyObject InitializeMyObject(int i)
{
MyObject theObject = new MyObject();
theObject.MyID = i;
theObject.MyString = "The object " + i;
theObject.MyCommand = new RelayCommand(param =< this.ShowWindow(i));
return theObject
}
private void ShowWindow(int i)
{
// Just as an exammple, here I just show a MessageBox
MessageBox.Show("You clicked on object " + i + "!!!");
}
A simple example of binding to a ObservableCollection of a custom object. Add more properties to the custom object to match what you want your rows to look like.
using System.Collections.ObjectModel;
public MyClass
{
public ObservableCollection<MyObject> myList { get; set; }
public MyClass()
{
this.DataContext = this;
myList = new ObservableCollection<MyObject>();
myList.Add(new MyObject() { MyProperty = "foo", MyBool = false };
myList.Add(new MyObject() { MyProperty = "bar", MyBool = true };
}
}
public MyObject
{
public string MyProperty { get; set; }
// I believe will result in checkbox in the grid
public bool MyBool { get; set; }
//...as many properties as you want
}
with xaml
<DataGrid ItemsSource= "{Binding myList}" />
Might be some small syntax errors, I wrote that entirely within the SO window.
I am new to WPF and used Damascus's example to learn binding of a List to a datagrid. But when I used his answer I found that my datagrid would populate with the correct number of rows but not with any of the properties from the MyObject class. I did a bit more searching then stumbled across what I had to do by accident.
I had to encapsulate the MyObject class properties to have them show. It wasn't enough to have them be public.
Before:
public class MyObject
{
public int MyID;
public string MyString;
public ICommand MyCommand;
}
After:
public class MyObject
{
private int _myID;
public int MyID
{
get { return _myID; }
set { _myID = value; }
}
private string _myString;
public string MyString
{
get { return _myString; }
set { _myString = value; }
}
private ICommand _myCommand;
public ICommand MyCommand
{
get { return _myCommand; }
set { _myCommand = value; }
}
}
Thank you Damascus for a great example and thank you Dante for a great question. I don't know if this is due to a change in version since your post but hopefully this will help others new to WPF like me.