I'm trying to bind a datagrid in WPF to my ViewModel so that it will update any cell changes to the database as well as allow the user to delete rows and add new rows. I've got part of it working but can't find a ELEGANT solution for the ADD and modify. here is the xaml
<DataGrid AutoGenerateColumns="false" HorizontalAlignment="Left" Margin="26,41,0,0" Name="dataGrid1"
ItemsSource="{Binding Path=GetAllItems}" Height="200" VerticalAlignment="Top" Width="266" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=ItemListID}" Header="ID" Visibility="Hidden"/>
<DataGridTextColumn Binding="{Binding Path=ItemName}" Header="Name" Width="4*" />
<DataGridCheckBoxColumn Binding="{Binding Path=IsActive}" Header="Active" Width="*" />
</DataGrid.Columns>
then in my view model method
private ObservableCollection< ItemsList> getAllItems()
{
using (var context = new InspectorGeneralEntities())
{
var query = from I in context.ItemsLists
select I;
var item = new ObservableCollection<Ite开发者_如何学PythonmsList>(query.ToList());
return item;
}
}
Deleting a row or modifying a row on the datagrid does not flow onto the database.
a) what other binding do i need to create in the xaml code that will detect these events
b) How do i detect a removed record or modified item in the view model so that I can update the datacontext if it won't automatically.
Just subscribe to the CollectionChanged event of your ObservableCollection. The event handler receives an instance of the NotifyCollectionChangedEventArgs class which contains a property 'Action' describing if rows have been added or removed. It also contains lists of the rows that have been added ('NewItems') or removed ('OldItems'). That should give you enough information to update your database.
You could implement INotifyPropertyChanged in your row ViewModel (class ItemsList
I guess) and then subscribe to it, to find out if a row is dirty and needs to be updated in the database. The interface consists of a single event PropertyChanged
that is to be raised in the property setters of your ViewModel whenever a value changes.
You're right the NotifyCollectionChanged event comes too early for an immediate insert to the database. But you can flag the row as 'inserted' in the event handler and insert it as soon as the last property changed event (see above) required to complete the row occurs.
I see a couple of problems with the code in your question. But the reason why deleting a row is not reflected in the database is the .ToList() -- essentially you're creating a new list which is a copy of query and the grid is removing elements from that copy.
You should use a ListCollectionView and use a Filter instead of a linq statement.
Here's a sample showing how to do it:
1) Create a new WPF project called ListCollectionViewTest
2) In the MainWindow.xaml.cs cut&paste the following (should be in ViewModel but I'm too lazy)
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace ListCollectionViewTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private List<Employee> equivalentOfDatabase = new List<Employee>()
{
new Employee() { FirstName = "John", LastName = "Doe", IsWorthyOfAttention = true },
new Employee() { FirstName = "Jane", LastName = "Doe", IsWorthyOfAttention = true },
new Employee() { FirstName = "Mr.", LastName = "Unsignificant", IsWorthyOfAttention = false },
};
public ListCollectionView TestList { get; set; }
public MainWindow()
{
DataContext = this;
// This is all the magic you need -------
TestList = new ListCollectionView(equivalentOfDatabase);
TestList.Filter = x => (x as Employee).IsWorthyOfAttention;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(equivalentOfDatabase.Aggregate("Employees are: \n\r", (acc, emp) => acc + string.Format(" - {0} {1}\n\r", emp.FirstName, emp.LastName), x => x));
}
}
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsWorthyOfAttention { get; set; }
}
}
3) In MainWindow.xaml cut&paste this:
<Window x:Class="ListCollectionViewTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<DataGrid ItemsSource="{Binding TestList}"
RowHeight="22"
AutoGenerateColumns="True">
</DataGrid>
<Button Content="Show All Employees in DB" Click="Button_Click" />
</StackPanel>
</Window>
Your data context doesn't exist at the time you would be making udpates. You are using a 'using' statement which destroys the context. The only reason you are seeing data is that you have forced a query to run against your DB (via the .ToList() statement). To answer your questions:
a) nothing more is needed specifically for binding b) Calling SaveChanges() on your InspectorGeneralEntities() context will update any changes in your db.
You could create a new ViewModel CLass:ItemGridVM to represent each row object ie ItemGridVM exposes the Item class properties you are binding in your datagrid. eg:
public class ItemGridVM : BaseViewModel
{
private Item _item;//an instance of Item class
public int ItemId
{
get
{
return _item.ItemId;
}
set
{
_item.ItemId = value;
//if you want UI changes : raise PropertyChanged Notification and binding in UI should be Update SourceTrigger:PropertyChanged
}
}
//Contains Commands :UpdateItem,EditItem,DeleteItem
}
Now in yourMainViewModel you can create an Observable COllection of ItemGridVM as:
private ObservableCollection<ItemGridVM> _getAllItems;
public ObservableCollection<ItemGridVM> GetAllItems
{
get
{
return _getAllItems;
}
set
{
_getAllItems = value;
//if u want Observable Collection to get updated on edit either
RaisePropertyChanged("GetAllItems");
}
}
Now in case of any action on any row the command binds to ItemGridVM.So u get the row details as exposed properties. I resolved the same issue using this.(Pure MVVM)
精彩评论