WPF Datagrid using MVVM.. is two way binding to DataTable possible? - wpf

I have a datagrid in my View with it's ItemSource bound to a DataTable in my ViewModel. When I update the DataTable programmatically (adding a column through a command) the changes are not populated to the View. Also, if I invalidate the View, by switching to another tab and then switching back, the changes made are shown.
My ViewModel inherits from INotifyPropertyChanged, and I am raising the PropertyChanged event correctly since I use the same process for other bound properties in the ViewModel and they work as expected.
Is it possible to get the datagrid to reflect changes I've made to the bound DataTable using the MVVM pattern?
Is there a datagrid event I can use to refresh the datagrid in the view's code behind?
Thanks for your help!
-Steven

While modifying rows and editing cell contents in the DataTable get reflected in the DataGrid (works for me) you're right that ColumnChanges don't seem to be. If you're using the AutoGenerateColumns option then I imagine it does so at initialization but doesn't watch for changes afterwards.
If you can find an event which fires (I haven't noticed one) when a column is added to the DataTable you could then add it manually in the code behind. Another hack which may or may not be practical would be to set your DataTable propety to null, and then re-set your property to the DataTable, with OnPropertyChanged being called each time. That should force the rebuilding of the DataGrid.
private DataTable _myDataTable;
public DataTable MyDataTable
{
get { return _myDataTable; }
set
{
_myDataTable = value;
OnPropertyChanged("MyDataTable");
}
}
void SomeMethod()
{
....results in column changes
DataTable holder;
holder = MyDataTable
MyDataTable = null;
MyDataTable = holder;
}

You must use ObservableCollection<> type to binding with DataGrid. Until do this, your DataGrid can update the change of DataTable in ViewModel.
Raising the PropertyChanged event seem useless when you bind with the type rather than ObservableCollection<>.

Related

Silverlight MVVM binding DataGrid to DynamicObject, reevaluating dynamic members

I have a ViewModel which exposes a DataSource which is an ObservableCollection of DynamicObjects. Upon binding, the DataGrid calls GetDynamicMemberNames() on the first DataSource item to obtain the columns it needs to autogenerate and bind to. So far so good.
However, when I then change the DataSource to contain items with completely different properties and raise PropertyChanged for the DataSource, the Grid does not re-evaluate the dynamic members!
My question is, how do I get the DataGrid to re-evaluate the DynamicObject's members? How do I force it to call GetDynamicMemberNames after the initial binding?
Some code:
private ObservableCollection<dynamic> _dataSource;
public ObservableCollection<dynamic> DataSource
{
get
{
if(_dataSource == null)
{
_dataSource = new ObservableCollection<dynamic>();
foreach(var model in SourceModels)
{
var row = new DynamicDataRow() // Inherits from DynamicObject ...
row["SomeProperty"] = model.GetType().GetProperty("SomeProperty").GetValue(model, null);
_dataSource.Add(row);
}
}
return _dataSource;
}
}
This works if I fill the SourceModels collection in the ViewModel constructor.
What I'm looking for is some way to rebind the grid in a way that calls GetDynamicMemberNames() after I change the SourceModels collection. Preferably in an MVVM manner...
Can anybody help me out?

Observable Collection is not updating the datagrid

I am using a Dim All_PriceLists As System.Collections.ObjectModel.ObservableCollection(Of BSPLib.PriceLists.PriceListPrime) where PriceListPrime implements Inotify for all properties in it.
I bound the All_PriceList to a datagrid as DataGrid1.ItemsSource = All_PriceLists but when I do All_PriceLists=Getall() where Getall reads and gets the data from the DB, the datagrid is not updating.
It updates only when I hack it this way:
DataGrid1.ItemsSource = Nothing
DataGrid1.ItemsSource = All_PriceLists
Could you please tell me where I have gone wrong or what I should implement. Thank you.
You have several solutions to your problem
Update the ItemsSource directly (instead of replacing the local member variable)
DataGrid1.ItemsSource = new ObservableCollection(Of PriceListPrime)(GetAll())
Update the ObservableCollection (as mentioned in another answer)
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
Set your DataContext to a view model and bind to a property of the view model
Dim vm as new MyViewModel()
DataContext = vm
vm.Items = new ObservableCollection(Of PriceListPrime)(GetAll())
The view model will implement INotifyPropertyChanged and raised the PropertyChanged event when the Items property is changed. In the Xaml your DataGrid's ItemsSource will bind to the Items property.
The problem is that you are not updating the collection, you are replacing it, which is different.
The datagrid remains bound to the old list and the updated data is stored in a new unbound collection. So, you are not hacking a solution, your are binding the datagrid to the new collection, which is correct.
If you want a more automatic solution, you should bind your datagrid to a dataset/datatable, which is totally different code.
You should update ObservableCollection instead of creating new one if you want the app to react on your changes.
So, clear All_PriceList collection and add new items into it. Example:
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
ObservableCollection doesn't support AddRange, so you have to add items one by one or implement INotifyCollectionChanged in your own collection.

INotifyPropertyChanged or INotifyCollectionChanged with DataTable?

Hi i am having some troube with DataTables. So What i need is to detect whenever i change any cell in the DataGrid of the DataTable that is binded.
How to do it? With INotifyPropertyChanged or with INotifyCollectionChanged?
Note: I am trying with INotifyPropertyChanged but it only detects when i set some value in the DataTable, and never when i change any value of any cell in the DataGrid, i already have tried OneWay and TwoWay but nothing happens.
Thanks in advance!
The datagrid would be bound to a list of objects. If you want the grid to update when individual object properties change, than each contained object must implement the INotifyPropertyChanged interface.
The INotifyCollectionChanged is an interface that the collection should implement, and are for notifications of addition and removal events.
See the section "How to implement collections" on this page.
Here's a way to approach your problem:
Create a new class that exposes the properties currently held in each DataRow. On this class implement INotifyPropertyChanged.
Instead of a DataTable, use an ObservableCollection<T> or your new class.
ObservableCollection already implements INotifyCollectionChanged, so this saves you the effort of implementing it yourself.
if you set the itemssource of your datagrid to a datatable then wpf create a IBindingListView wich is bound to the datagrid.
what you can do now is edit,add and delete items to your datatable via datagrid. if you wanna know when a cell in your datatable is changed you can subscribe to DataTable.ColumnChanged event.
why do you want do know when a cell is changed?
The answer to the title of your question is : Neither. Actually you do not need to bind a DataTable to a DataGrid. You bind a DataView. "The ADO.NET DataView implements the IBindingList interface, which provides change notifications that the binding engine listens for."(Binding Sources Overview)
One answer to your question is :
You modify the datagrid cell with a TextBox (usually). Do this with a new textbox inheriting from TextBox and override the OnGotFocus and OnLostFocus methods of it.

Silverlight DataGrid binding issues after refreshing or setting selectedIndex=-1

I have a datagrid and a combobox on the form. The combobox is bound to the selectedItem of the datagrid.
I load things fine and if i select different rows the combobox is updated correcly.
If however I set datagrid.selectedIndex=-1 after it loads (so that the first row is not selected) the combobox binding no longer works. This is a problem.
I also have another scenario where the exact thing occurs. If i filter the datagrid, the binding to the combobox also stops working.
I am binding the datagrid to a CollectionViewSource like the following where _codes is an ObservableCollection
_ocvsCode = (CollectionViewSource)this.Resources["cvsCode"];
_ocvsCode.Source = _codes;
dataGrid1.ItemsSource = _ocvsCode.View;
I don't know why the binding to the combobox is failing after some operation on the datagrid.
The appropriate solution in this case is to bind the datagrid selecteditem to some variable, and to then bind the other controls to that variable as well. It is generally bad practice to bind UIElement properties directly to other UIElement properties. This will also make debugging the problem you seem to be having with coercing the selecteditem property to the combo-box.
I have come across the same problem, where a ComboBox is bound to a the value of the SelectedItem of a DataGrid.
The ComboBox control breaks when the data it is binding becomes null, and never recovers. I'm not sure why that is, but it seems to me to be a bug. When the DataGrid sorts a column, it first sets its SelectedItem to null, performs the sort, and then resets SelectedItem to the original value. When the SelectedItem becomes null, the ComboBox breaks.
Here's my work around:
Create a SelectedItem property on your class that is being used for the DataContext. Perform a check on the setter that prevents it from being set to null. Bind against this property with your DataGrid and ComboBox.
public YourItem SelectedItem
{
get { return _selectedItem; }
set
{
if (value == _selectedItem || value == null)
return;
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}

After adding a new row why aren't my bound controls updating?

I'm using wpf drag and drop databinding to datasets. I generate a new datatable row using the AddNew method of the BindingListCollectionView. I set values on the new row and call CommitNew on the BindingListCollectionView. I expect to see my assigned values in the bound controls but they are all blank. If I save the changes to the database the controls are then updated, but I want the user to see my assigned values before calling UpdateAll on the TableAdapterManager.
Background:
I've created a strongly typed dataset in a project separate from my wpf application. My wpf app references the dataset application. I added an object datasource in the wpf app pointing to the typed dataset. I dragged fields/controls from the datasources window to the wpf designer window. The generated xaml includes a Window.Resources section with a CollectionViewSource accurately bound to my dataset and datatable. This CollectionViewSource is the DataContext for the controls that I dragged to the design surface. All of the controls use TwoWay databinding.
When the window loads I grab a reference to the xaml's CollectionViewSource (using FindResource). I then grab a reference to the view property of the CollectionViewSource and cast it to a BindingListCollectionView. Now I use the AddNew method of the BindingListCollectionView to generate a new row (which AddNew returns as an object). I cast the object to a DataRowView and access it's Row property. I then cast the row to a strongly typed datatable row (generated by the DataSet designer). Now I assign values to some of the datatable row columns. I call CommitNew on the BindingListCollectionView. Finally I call MoveCurrentToFirst on the CollectionViewSource.
Problem:
Using a watch expression I can see the data is in the SourceCollection of both the CollectionView and the BindingListCollectionView. Can anyone explain why the bound controls do not show the data unless I save the changes to the database?
Code (generated XAML not shown):
Private WithEvents _cvsScanData As System.Windows.Data.CollectionViewSource
Private WithEvents _blcvScanData As System.Windows.Data.BindingListCollectionView
_cvsScanData = CType(Me.FindResource("Dt_tblScanDataViewSource"), System.Windows.Data.CollectionViewSource)
_blcvScanData = CType(_cvsScanData.View, BindingListCollectionView)
Dim newRow As LabDataSet.dt_tblScanDataRow = CType(CType(_blcvScanData.AddNew, System.Data.DataRowView).Row, LabDataSet.dt_tblScanDataRow)
newRow.SampleID = "testSampleID"
newRow.MachineID = "testMachineID"
_blcvScanData.CommitNew()
_cvsScanData.View.MoveCurrentToFirst()
The simple fix is to call the Refresh method of the BindingListCollectionView after calling CommitNew.
_blcvScanData.Refresh()
I stumbled across this answer to my own question via intellisense. If anyone can explain why refresh is necessary I'd appreciate it. I expected the INotifyPropertyChange interface to update the bound controls obviating the need to call refresh.

Resources