DataGrid data binding and MVVM in WPF
step one: you created a window, its view model and connected them via DataContext. Already done
View.MainWindow mainWin = new View.MainWindow();
ViewModel.BookViewModel bookViewModel = new ViewModel.BookViewModel();
mainWin.DataContext = bookViewModel;
step two: fix (add) binding in xaml:
<DataGrid Name="BooksDataGrid" ItemsSource="{Binding Books}">
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Width="200" Binding="{Binding title}"/>
<DataGridTextColumn Header="isbn" Width="200" Binding="{Binding isbn}"/>
</DataGrid.Columns>
</DataGrid>
step three: fix view model for Binding to work - Binding works with properties:
class BookViewModel
{
public ObservableCollection<Book> Books { get; private set; }
public BookViewModel()
{
Books = new ObservableCollection<Book>();
}
}
step four: implement load data method in the view model amd call it from command, and after mainWin.Show();
to load initial data
Double binding on a WPF DataGrid?
The DataSource of the DataGrid should point to a collection of items.
Each row will be a single entity of your collection and it's properties can then be bound to a column (using templates).
See:
Datagrid binding in WPF
Bind WPF DataGrid to one-dimensional array
You have a one-dimensional array, so how are you expecting it to bind to 3 columns? Think about it, the DataGrid
is like a display for a two-dimensional array, the columns are the x axis, and the rows are the y axis. So your one-dimensional array must go in the rows of one column.
EDIT To represent a more complex type as you mentioned in your comments, you can use DataTable
if you bind the DataGrid
directly to the data (only for very simple projets) or better List<>
if you bind to business objects. Here is an example:
Change your class to have the three properties that you want (give them more meaningful name than this example):
public class Cylinder {
public float Vector1 = { get; set; };
public float Vector2 = { get; set; };
public float Vector3 = { get; set; };
}
Now you can bind your DataGrid
directly to this class for testing, but in a real application where data is coming from some source like a database, you create a list of this class:
var cylinders = new List<Cylinder>();
And then populate it with the data coming from the database:
foreach(var row in myTable) {
var c = new Cylinder();
c.Vector1 = 4;
c.Vector2 = 5;
c.Vector3 = 6;
cylinders.Add(c);
}
And now you can bind your DataGrid
to cylinders
. The grid will have three columns representing the three properties of the Cylinder
class, and as many rows as you have in myTable
.
WPF binding command to Datacontext inside Datagrid
As stated in my comment you need to bind to (ViewModel) Parent/Ancestor.DataContext
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.PreviousPageCommand}"
You'll also need to bind the SelectedItem
of your DataGrid
so the ViewModel knows which row the button was in when the user clicks. Or bind the DataContext
of the button to the CommandParameter
. like @Andy mentioned in the comment
DataGridView Column binding in WPF
You can set AutoGenerateColumns to False
and take responsibility in your hand to provide the list of columns you want to avoid auto generation of columns when DataSource or DataMember properties are set.
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding SourceCollection}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Index}"/>
<DataGridTextColumn Binding="{Binding Colour}"/>
<DataGridTextColumn Binding="{Binding Location}"/>
<DataGridTextColumn Binding="{Binding Srno}"/>
</DataGrid.Columns>
</DataGrid>
Binding WPF DataGrid to ListInterface
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.
Data Binding in WPF Data Grid not working
Property IsReadOnly does not see the IsControlUnit field.
You can also use the template for editing and reading.
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>Control Unit</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ControllingControlUnit}" Width="200" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding ControllingControlUnit}" IsReadOnly="{Binding IsControlUnit, UpdateSourceTrigger=PropertyChanged}" Width="200" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
You can also add IsEnabled, then you cannot select text
Related Topics
Creating a Very Simple Linked List
How to Get the Index of an Item in a List in a Single Step
Upload Files and JSON in ASP.NET Core Web API
Performance of Static Methods VS Instance Methods
How to Save Console.Writeline Output to Text File
What's Better: Dataset or Datareader
Can a Unit Test Project Load the Target Application's App.Config File
Serializing an Object as Utf-8 Xml in .Net
Distinct in Linq Based on Only One Field of the Table
Httpwebrequest Is Extremely Slow!
Save and Retrieve Image (Binary) from SQL Server Using Entity Framework 6
Differencebetween Linq to Xml Descendants and Elements
How to Get the Time Difference Between Two Datetime Objects Using C#
Using ASP.NET Identity Database First Approach
Converting from Ienumerable to List