How to update Datagrid using MVVM? - c#

I have searched and have come to a brick wall. There seems to be plenty of questions and answers on how to do this, but I can't find anything specifically that I can implement(Obviously an issue with my understanding).
I was hoping to be able to update several Datagrids and going off the premise that you should not be naming you controls, I am lost as to how to get this to work.
So what I have been able to come up with thus far is using the System.Windows.Interactivity assembly. But I have no Idea how to implement the code behind (I certainly could show all the code I have tried to get it to work, but unfortunately it would just clutter the post, so I am not including it). I have research as much as I can on implementing ICommand.
So I have the XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnding">
<i:InvokeCommandAction Command="{Binding CanExecuteChanged}" />
</i:EventTrigger>
</i:Interaction.Triggers>
But I can't seem to get the code behind to be able to notify when the RowEditEnding has finished and be able to update the database with the new data.
So with the MVVM model in mind, how do I go about getting an event to fire so I can update the database?

EDIT2: Changed People to an ObservableCollection instead of List. Added CollectionChanged event to handle attaching/removing PropertyChanged events from Person objects in the collection.
EDIT: changed foreach in ViewModel for consistency.
First, Model-View-ViewModel(MVVM) means that you should have as little code behind as possible, preferably none in fact. Instead, logic for the UI should be done in xaml (the View), while logic for the data should be done in the Model and the organization of the data into presentable form should be done via the View Model, which is a separate file that knows nothing about WPF or the UI.
It also seems you have a misunderstanding as to how data binding works. You are referring to stuff you have done in code behind, but binding expressions usually point to a property on your view's DataContext, which in MVVM should be set to your ViewModel. There is a good tutorial here that can get you started with binding. I also recommend the followup posts to that one, they helped me a great deal when I was starting out WPF.
Now for the situation with the DataGrid. First, here is a good tutorial on the WPF DataGrid.
Next, you state that you want to update the database when the row has been updated. Here is an example of how to do that in MVVM style:
Suppose you have a view with a DataGrid like this:
<UserControl x:Class="MyProject.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding People, Mode=OneWay}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
<DataGridTextColumn Header="Age" Binding="{Binding Age}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
Code behind:
namespace TestWPFApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
}
Note that the code behind is almost empty. Just the default code, plus DataContext = new MyViewModel();. As I mentioned earlier, the DataContext of your view should be your View Model.
MyViewModel looks like this:
public class MyViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Impl
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private ObservableCollection<Person> m_people;
public ObservableCollection<Person> People
{
get { return m_people; }
private set
{
if (value == m_people)
return;
m_people = value;
OnPropertyChanged();
}
}
public MyViewModel()
{
m_people = new ObservableCollection<Person>();
m_people.CollectionChanged += m_people_CollectionChanged;
m_people.Add(new Person() { FirstName = "Bob", LastName = "Brown", Age = 45 });
m_people.Add(new Person() { FirstName = "Sarah", LastName = "Smith", Age = 25 });
}
private void m_people_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.NewItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged += people_PropertyChanged;
}
}
if (e.OldItems != null && e.OldItems.Count > 0)
{
foreach (INotifyPropertyChanged item in e.OldItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged -= people_PropertyChanged;
}
}
}
//Property Changed will be called whenever a property of one of the 'Person'
//objects is changed.
private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var row = sender as Person;
SaveData(row);
}
private void SaveData(Person row)
{
//Save the row to the database here.
}
}
I have a property of type List<Person> in my view model. Person looks like this:
public class Person : INotifyPropertyChanged
{
#region INotifyPropertyChanged Impl
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string m_firstName;
public string FirstName
{
get { return m_firstName; }
set
{
if (value == m_firstName)
return;
m_firstName = value;
OnPropertyChanged();
}
}
private string m_lastName;
public string LastName
{
get { return m_lastName; }
set
{
if (value == m_lastName)
return;
m_lastName = value;
OnPropertyChanged();
}
}
private int m_age;
public int Age
{
get { return m_age; }
set
{
if (value == m_age)
return;
m_age = value;
OnPropertyChanged();
}
}
}
The important thing to note here is INotifyPropertyChanged, this interface is very important to MVVM and WPF data binding in general. When implemented properly, it causes an object to publish a PropertyChanged event whenever one of its properties is changed. This tells any bound WPF controls that they should get the new value, and also allows your ViewModel to watch them for changes. So in the view model, we attach an event handler to the CollectionChanged event on People, which then takes care of attaching the PropertyChanged event handler for each item added to the collection.
The CollectionChanged event will be called whenever an item is added, removed, or replaced in the collection and will remove the PropertyChanged handler from the old items and add the handler to the new ones. It is important to remember to remove the handlers, or items removed from the collection may not be properly garbage collected.
The person_PropertyChanged method will be called each time a property of one of the Person objects changes. In person_PropertyChanged we then call the methods to update the database, passing the updated row (Person in this case), like below:
//Property Changed will be called whenever a property of one of the 'Person'
//objects is changed.
private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var row = sender as Person;
SaveData(row);
}
private void SaveData(Person row)
{
//Save the row to the database here.
}
Each row in the grid I displayed above represents one person object. Whenever the user changes a value of one of the cells in the grid, the corresponding property of the Person object that the row represents will be updated as well, which will trigger a PropertyChanged event, and call person_PropertyChanged.
Suppose the user changes the Age column of "Bob" to 37. After the user hits enter or moves off the cell for Age the Age property of the Person object representing "Bob" will be changed to 37 from 45. This will cause that Person object to raise PropertyChanged, which will call the person_PropertyChanged method in MyViewModel. person_PropertyChanged will then call SaveData which is where you would put the code to save the updated Person row to the database.
If you have any questions, let me know!

Related

WPF TwoWay binding in multiple UserControl

I have multiple UserControl which contain a shared ViewModel.
It's a DataGrid where the user click on a row to see the detail of the row (the actual structure is more complex).
The problem is when I handle the SelectionChanged in the grid, I update the shared ViewModel to update the ContactDetail but it doesn't update the value in the TextBoxes (the object is updated in ContactDetail but values are not displayed).
ListContact.xaml.cs
public void contactsTable_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
contacts.current_identity = //Get the associated `IdentityViewModel`
}
ContactDetail.xaml.cs
public partial class ContactDetail : UserControl
{
public ContactsViewModel contacts;
public DetailContact(ContactsViewModel contacts)
{
InitializeComponent();
this.contacts = contacts;
this.DataContext = contacts;
}
}
ContactDetail.xaml
<UserControl x:Class="ContactDetail">
<TextBox Name='address' Text="{Binding Path=contacts.current_identity.address, Mode=TwoWay}"/>
<TextBox Name='phone' Text="{Binding Path=contacts.current_identity.phone, Mode=TwoWay}"/>
<TextBox Name='email' Text="{Binding Path=contacts.current_identity.email, Mode=TwoWay}"/>
</UserControl>
ContactsViewModel.cs (IdentityViewModel uses the same structure)
public class ContactsViewModel : INotifyPropertyChanged
{
private List<Contact> _contacts;
public List<Contact> contacts;
{
get { return _contacts; }
set { _contacts = value; OnPropertyChanged("contacts"); }
}
private IdentityViewModel _current_identity;
public IdentityViewModel current_identity
{
get { return _current_identity; }
set { _current_identity = value; OnPropertyChanged("current_identity"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The question is, why doesn't this work and how to notify ContactDetail so that it displays the new value ?
Your data for contacts changes but the original reference location Binding Path=contacts.current_identity.address is still being referred to in the binding. I.E. address is still valid and has not changed. What changed was contacts.current but you are not binding to that.
Remember that binding is simply reflection to a location reference. If the original address changes you would see a change because that is what is being looked for to have a change. But instead the parent instance is what changed.
You need to refactor your bindings to allow for proper update when the current_identity changes.

Silverlight: ComboboxItem text not updated when binding changes

I have an issue whereby when I update the text on a Comboboxitem it isnt instantly reflected on the UI. One has to click the Combobox to show the items (which have the correct text). Any ideas why? Note this exact code works perfectly in WPF
The property that defines the string to show
public string NormallyOpenString
{
get
{
if (this.IsInput)
{
return "High";
}
else if (this.IsRelay)
{
return "Open";
}
else
{
return "Open (High)";
}
}
}
Which is bound to a Combobox like so
<ComboBox SelectedIndex="{Binding Normally, Mode=TwoWay}" >
<ComboBoxItem Content="{Binding NormallyOpenString}" />
<ComboBoxItem Content="{Binding NormallyClosedString}" />
</ComboBox>
When another combobox changes I want to update the text as it changes what IsInput / IsRelay is. I do this via NotifyPropertyChanged like so
this.NotifyPropertyChanged("NormallyOpenString");
this.NotifyPropertyChanged("NormallyClosedOpenString");
this.NotifyPropertyChanged("Normally");
I've never done it that way, so I can't vouch for it. This is how I do property change notification:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
private string normallyOpenString = "I'm an open string!";
public string NormallyOpenString
{
get { return normallyOpenString; }
set
{
normallyOpenString = value;
RaisePropertyChanged("NormallyOpenString");
}
}
}
So now, whenever anyone calls your setter, anything bound to your property will be updated. So if it gets set from one binding, all other bindings that are also bound to it will be updated.
I think you should rather use the SelectedItem property.

Binding to member of Listbox item

I don't know if I would be informative enough, but I'm having a problem.
I bound an ObservableCollection to a normal Listbox, everything is working fine, but ImageInfo has a member (Source) which contains the place where the image is, and I need the Source member of the current selected item in the Listbox. However, I don't seem to have a clue where to start.
Maybe you need in your xaml something like <Image Source="{Binding ElementName=myListbox, Path=SelectedItem.Source}"> . Other examples and explanations related to binding here https://stackoverflow.com/a/1069389/1606534
Are you binding in normal mode to a property like: EG: < combobox itemssource={Binding Listing}/>? If so you really just need to have a public property exposed for the 'selecteditem' if memory serves. The real power in Observable Collection from my understanding of WPF is how things can change in real time and you can notice those changes when implementing INotifyPropertyChanged or INotifyCollectionChanged.
<combobox x:Name="mycombo" itemssource="{Binding itemsource}"
selecteditem="{Binding SelectedItem}" />
ViewModel property:
public string SelectedItem { get; set; }
However if you want your property to be noticed when it changes you need to implement INotifyPropertyChanged. Typically then in studios I have worked in they set a private variable at the top of the class and then use it in the get set and then use the public property in bindings.
public class example : INotifyPropertyChanged
{
private string _SelectedItem;
public string SelectedItem
{
get { return _SelectedItem; }
set
{
_SelectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
public void DoSomething()
{
Messagebox.Show("I selected: " + SelectedItem);
}
}

Bind Button to DataGrid Content

My WPF-Application contains a DataGrid with 3 Columns and a disabled Button.
One Column of my DataGrid is a DataGridCheckBoxColumn. Now I want to bind Button's IsEnabled-Property, to be enable if at least one of them is checked, else remain disabled.
Actually I've following implementation:
<Button x:Name="buttonStart" Content="Start" IsEnabled="{Binding Converter={StaticResource ButtonEnableConverter}, ElementName=gridTestCaseChooser}" />
But the Converter is only called once at startup the application. If the DataGrid content change, or I check/uncheck a CheckBox, there isn't a new call to my converter.
How could I do this?
Using MVVM can be very easy:
Declare an ICommand property to execute the logic of the button with a RelayCommand.
The items you add must implement INotifyPropertyChanged interface and have a bool IsChecked property firing PropertyChanged event.
Every time a new item is added, subscribe to its PropertyChanged event and when removed, remove the hook as well.
Every time the hooking method is called, fire the PropertyChanged for the ICommand property.
In XAML just bind the button to the ICommand property.
ViewModels
public class Item: INotifyPropertyChanged
{
private bool isChecked;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsChecked
{
get
{
return this.isChecked;
}
set
{
this.isChecked = value;
var propertyChangedEvent = PropertyChanged;
if(propertyChangedEvent != null)
{
propertyChangedEvent(this, new PropertyChangedEventArgs("UpdateSomething");
}
}
}
}
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand UpdateSomething { get; private set; }
public ObservableCollection<Item> MyItems { get; set; }
public MyViewModel()
{
UpdateSomething = new RelayCommand(MyCommandExecute, MyCommandCanExecute);
MyItems = new ObservableCollection<Item>();
}
private void MyCommandExecute(object parameter)
{
// Your logic here.
}
private void MyCommandCanExecute(object parameter)
{
return MyItems.Any(item => item.IsChecked);
}
private void AddItem(Item item)
{
item.PropertyChanged += ItemsPropertyChanged;
MyItems.Add(item);
}
private void ItemsPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var propertyChangedEvent = PropertyChanged;
if(propertyChangedEvent != null &&
e.PropertyName == "IsChecked")
{
propertyChangedEvent(this, new PropertyChangedEventArgs("UpdateSomething");
}
}
You need to bind not to visual element like grid, but rather to some DependencyProperty, which value is changing. Grid itself is not changing usually, that's why converter is only called one time on startup. I would create a UserControl, which has the grid with check-boxes and the button. That UserControl would define a DependencyProperty of type bool, which would be set to true or false when one of the check-boxes is checked or unchecked. Then IsEnabled property of Button would bind to that DependencyProperty.
for your button:
IsEnabled="{Binding ElementName=NN, Path=IsChecked}">
Update after question was updated
the same situation you have is discussed here:
What causes a Value Converter to fire?
The idea is to choose something that is really change its own state
Updated
try something like this:
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Header="First Name" Width="50">
<dg:DataGridTextColumn.DataFieldBinding>
<Binding Path="FirstName" />
</dg:DataGridTextColumn.DataFieldBinding>
</dg:DataGridTextColumn>
<dg:DataGridCheckBoxColumn Header="Active" Width="50" DataFieldBinding="{Binding Path= IsActive}" />
</dg:DataGrid.Columns>

Bind a control to a single value in a collection/array in WPF

In WPF I have a collection of bool? values and I want to bind each of these to a separate checkbox programmatically. I want the bindings to be TwoWay so that changing the value of the individual item in the collection in code updates the check box and vice versa.
I have spent ages trying to figure out how to do this and I am completely stuck. With the following code the checkbox only gets the right value when the window is loaded and that's it. Changing the check box doesn't even update the value in the collection. (UPDATE: this appears to be a bug in .NET4 as the collection does get updated in an identical .NET3.5 project. UPDATE: Microsoft have confirmed the bug and that it will be fixed in the .NET4 release.)
Many thanks in advance for your help!
C#:
namespace MyNamespace
{
public partial class MyWindow : Window, INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<bool?> myCollection = new List<bool?>
{ true, false, true, false, true, false };
public List<bool?> MyCollection
{
get { return myCollection; }
set { myCollection = value; }
}
}
}
XAML:
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}">
There are a few things that need changing here to get this to work. Firstly you'll need to wrap your boolean value in an object that implements the INotifyPropertyChanged interface in order to get the change notification that you are looking for. Currently you are binding to boolean values in your collection which do not implement the interface. To do this you could create a wrapper class like so :
public class Wrapper: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool val = false;
public bool Val
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Val");
}
}
public Wrapper(bool val)
{
this.val = val;
}
}
You'll then want to create these objects in your form instead of a list of booleans. You may also want to use an observable collection instead of a list so that notification of items being added and removed are sent. This is shown below:
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Wrapper> myCollection = new ObservableCollection<Wrapper>()
{new Wrapper(true), new Wrapper(false), new Wrapper(true)};
public ObservableCollection<Wrapper> MyCollection
{
get { return myCollection; }
}
The next thing to do is to display a list of check boxes in your ui. To do this WPF provides itemscontrols. ListBox is an itemscontrol so we can use this as a starting point. Set the itemssource of a listbox to be MyCollection. We then need to define how each Wrapper object is going to be displayed in the list box and this can be done with a datatemplate which is created in the windows resources. This is shown below :
<Window.Resources>
<DataTemplate x:Key="myCollectionItems">
<CheckBox IsChecked="{Binding Path=Val, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=MyCollection}" ItemTemplate="{StaticResource myCollectionItems}"></ListBox>
</Grid>
This should get you up and running with a simple demo of checkboxes that have values bound to a list of booleans.
What makes you think it's not working? It's working for me :)
Here's my test XAML:
<UniformGrid>
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}"/>
<ListBox ItemsSource="{Binding MyCollection}"/>
<Button Content="Test" Click="Button_Click"/>
</UniformGrid>
Here's my code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
(the rest is the same as yours)
I placed a breakpoint on Button_Click and checked MyCollection[0] it was updated according to the IsChecked value of the CheckBox.
Try changing your collection type from List<bool?> to ObservableCollection<bool?> perhaps that is the reason you think it's not working for you (the fact that changes to the collection are not reflected anywhere else in your view).
Change your List<bool?> to an ObservableCollection<bool?>. A List does not raise the change notifications that WPF needs to update the UI. An ObservableCollection does. This handles the case where the list entry is changed and the CheckBox needs to update accordingly.
In the other direction, it works for me even with a List<bool?> -- i.e. toggling the checkbox modifies the value in the collection. Your binding syntax is certainly correct.

Categories

Resources