wpf itemlist not updating values - c#

So I have three things:
A ListBox in a Window
A DataBase class granting me access to an ObservableList
Contact implements INotifyChanged
In my main Window, I have three Buttons (One for new List Entry, one for editing, one for deleting an item)
I fill the list like this:
lbKontakte.ItemsSource = DB.GetInstance().Kontakte;
whereas Kontakte is a ObservableCollection
I can create a new Entry using
DB.GetInstance().Kontakte.Add(New Kontakt(...));
or remove an entry using
DB.GetInstance().Kontakte.Remove(...);
Boh actions are immediately visible in the ListBox.
If I modify a value however, I'm not using any Code. I have a TextBox which is bound to the Name field of a contact. If I make changes to it, the changes should theoretically be carried out immediately to the bound Contact Object.
However, if I do modify the text, the changes do not become visible in the ListBox. If I pause the code and take a look at the object, I can see its Name Field has correctly been changed.
How come my ListBox is not updated?
PS:
Contact does implement INotifyChanged using following Code:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
and
public String Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(new PropertyChangedEventArgs("Name")); }
}
Edit: The Textbox is NOT part of the main Window but a Window showed as a dialog if the user clicks the edit button. The Window is then given the selectedItem casted as Kontakt in the Constructor. Bound to the Field like this:
<TextBox Name="txtName" Grid.Column="1" Grid.Row="0" Margin="4" Text="{Binding Path=Name}"></TextBox>
and
public KontaktAddUI(Kontakt kontaktToEdit)
{
InitializeComponent();
this.kontaktToEdit = kontaktToEdit;
this.MainGrid.DataContext = kontaktToEdit;
}
Correct Answer in the Comments, thanks again!

Your problem is ObservableCollection doesn't get notified if your Item Property Changed that is a known issue To fix this problem you need to wire up your INotifyPropertyChanged event to the CollectionChanged event from your ObservableCollection
Here you can see an example how you could do it.

Related

Change property in PropertyChanged method not updating view

Under certain conditions if the user selects an item in a combobox, it automatically must be changed to another item
ViewModel
public class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private string selected;
public string Selected
{
get { return selected; }
set
{
if (selected != value)
{
selected = value;
OnPropertyChanged("Selected");
}
}
}
private ObservableCollection<string> collection;
public ObservableCollection<string> Collection
{
get { return collection; }
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
public VM()
{
this.Collection = new ObservableCollection<string>(new string[] { "A", "B", "C" });
this.Selected = "A";
this.PropertyChanged += VM_PropertyChanged;
}
void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.Selected = "C";
}
}
View
<Window x:Class="WpfApplication1.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>
<Grid>
<ComboBox ItemsSource="{Binding Collection}" SelectedValue="{Binding Selected}"/>
</Grid>
<Label Content="{Binding Selected, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Window>
So, in this example, no matter what do I select, it should show "C" both, on the combobox and Label, but "C" only shows on Label, it means that the ViewModel is updated but not the view.
It seems the problem here is to try to change the property from the PropertyChanged method.
What could be wrong?
Here's how I would most likely do it, but the BeginInvoke() call that does the magic could just as easily be called from your PropertyChanged handler.
What it's doing is essentially queueing the action to happen after the entire property-set business has fully completed. The DispatcherPriority.ApplicationIdle flag is a key point.
As you've found, it's useless for the PropertyChanged handler and the property setter to raise PropertyChanged while the ComboBox is still in the process of changing its selection. This code lets that whole thing finish, and then immediately changes Selected to something else. At that point, the ComboBox will be at leisure to take notice of your PropertyChanged event and update its own selection.
private string selected;
public string Selected
{
get { return selected; }
set
{
if (selected != value)
{
// Don't let them select "B".
if (value == "B")
{
Dispatcher.CurrentDispatcher.
BeginInvoke(new Action(() => this.Selected = "C"),
DispatcherPriority.ApplicationIdle);
return;
}
selected = value;
OnPropertyChanged("Selected");
}
}
}
Under certain conditions if the user selects an item in a combobox, it automatically must be changed to another item
For what it's worth, I think it would be a good idea to revisit that design choice. It is likely to be confusing to users, and there is probably a better way to present that state of affairs to the user, than to ignore input they give the program. There's not enough context in your question to fully understand how you got into this situation in the first place, so I can't offer anything more than to suggest it's likely better to fix the design, than to finagle the code into doing what you want.
That said…
The issue you are running into is that WPF ignores property-changed events for the source of a binding it is currently already updating. In your scenario, the binding is updating the Selected value from its binding, and so changes to that property will be ignored until that binding update is completed.
There are a variety of ways to get the code to work the way you want. Probably the easiest is to simply defer the update of the source property until the handling of the user input has completed. You can do that by using the Dispatcher.InvokeAsync() method:
void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeAsync(() => this.Selected = "C");
}
I'm not a big fan of the above, because of the fact that it takes what ideally should be a view-agnostic object, the view model, and injects knowledge of your specific view API, i.e. use of the Dispatcher object. That said, there are other mechanisms you could use which are similar, and which don't rely on the Dispatcher object (e.g. using an asynchronous timer).
There are a variety of other examples of ways to address this on Stack Overflow. For example, you might look at this answer for inspiration. I don't think it will do exactly what you want "straight out of the box", but the attached property approach might be something you find more appropriate, by moving the logic from view model to view code, and thus a place where it's more appropriate to use Dispatcher. (And arguably, if you are going to do something like this, the logic probably belongs in the view anyway…it's weird enough there, but I see no compelling reason this should be inherent in the view model.)
Another approach can be seen in the question Coerce a WPF TextBox not working anymore in .NET 4.0. I.e. manually force the view's state to be updated after the fact.

Transferring Textbox data to my ViewModel

I have two textboxes with userinput, of which I need to transfer the data to my ViewModel. I tried looking around how to do this by binding it to a button (as the transfer is supposed to take place upon a buttonclick), but most advice to use bindings. However, to use bindings you have to declare properties in the ViewModel (afaik), but as these strings are used to create a new object, holding properties for them would be all but ideal because the two textboxes might expand to over 10 in the future. I've also tried messing around with CommandParameter but I only seem to be able to declare one.
So for clarification:
How do I transfer the contents of two (or more) textboxes to the corresponding ViewModel so I can create a new Object with them?
Edit:
In addition I'd also like to be able to reset the Text= field to be empty once the method handling the data has succesfully completed.
The View
<TextBox Name="UI1"/>
<TextBox Name="UI2"/>
<Button Source="*ImageSource*" Command="{Binding CallCreateObject}"/>
and the ModelView
private void OnCallCreateObject()
{
Object newObject = new Object(UI1, UI2, false)
}
This is a general example of what I'm trying to achieve
If you want to insert data from UI to ViewModel on Button Click than there is no reason to use binding. Binding is mainly used to sync data between UI and underlying models.
Still if you want only that then on button_click event you can do something like this.
private void button_Click(object sender, RoutedEventArgs e)
{
Model model = new Model();
model.Property1 = textBox1.Text;
model.Property2 = textBox2.Text;
textBox1.Text = string.Empty;
textBox2.Text = string.Empty;
}
That will solve your issue. But this approach is not recommended when you have a better thing that is called 'Binding'
If you want to bind your view with a viewmodel then try this:
Your view model:
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Great, you have set up your view model. Now the view:
XML PersonView.xml:
<Grid Name="MyContainer">
<TextBox Text="{Binding PersonName}" />
<Button Name="SaveInfoButton" OnClick="SaveInfoButton_Click">Save info</Button>
</Grid>
Now that we have indicated with which property the textbox will be bind, lets indicate to the view the model that will use to update the property named PersonName. The idea is that when you click over the button, the property PersonName of our model Person gets updated with the value of the TextBox.
The xml class:
public partial class PersonView : UserControl
{
private readonly Person Model;
public PersonView()
{
//Components initialization, etc. etc...
this.Model = new Person();
this.DataContext = this.Model; // Here we are binding the model with our view.
}
private void SaveInfoButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this.Model.PersonName); // this will print the value of your textbox.
}
}
Dont know if you noticed, but we didnt have the need of creating a new object when the user click the button. We just use our model and update the model properpies. If you add more textbox to your view, you'll have to added to our viewmodel as well as the given example.
Here is some post that can help you a little bit more(dont have enough time)
http://blog.scottlogic.com/2012/04/20/everything-you-wanted-to-know-about-databinding-in-wpf-silverlight-and-wp7-part-two.html
http://www.tutorialspoint.com/wpf/wpf_data_binding.htm
You could use bindings like this:
<TextBox Name="UI1" Text="{Binding Path=Ut1Value}"/>
<TextBox Name="UI2" Text="{Binding Path=Ut2Value}"/>
<Button Source="*ImageSource*" Command="{Binding CreateTheThingCommand}"/>
Then in your viewmodel you'll need to have the properties and command for those:
private string _ut1Value;
private string _ut2Value;
public string Ut1Value
{
get
{
return _ut1Value;
}
set
{
if (_ut1Value!= value)
{
_ut1Value= value;
OnPropertyChanged("Ut1Value");
}
}
}
public string Ut2Value
{
get
{
return _ut2Value;
}
set
{
if (_ut2Value!= value)
{
_ut2Value= value;
OnPropertyChanged("Ut2Value");
}
}
}
public ICommand CreateTheThingCommand
{
get { return new RelayCommand(CreateTheThing); }
}
private void CreateTheThing()
{
Object newObject = new Object(_ut1Value, _ut2Value, false);
// Do whatever with your new object
}
It sounds as if you need at least two ViewModel objects:
One to present the data from an existing object. This would be, essentially, what you have already.
A container ViewModel. This encapsulates the behaviours of the IEnumerable collection of objects, including the functionality required to Add a new object.
The container ViewModel would have the properties that you are struggling with, plus the CreateObject command, along with an IEnumerable (ObservableCollection) property to hold the existing ViewModel objects.
In your View, you would have one control to present the data in an existing ViewModel object, and a second control with a ListView (or similar) control to display the existing view controls and the set of TextBox controls, plus the button to create a new object (and add it to the list).
This would also allow you to add 'remove', 'sort', etc. functionality to the container ViewModel, without having to change the existing ViewModel.
A way to accomplish a scalable solution with minimal lines of code, would be to create hold a list of items you bind to in the view model.
This way you can use an ItemsControl in the UI to display a textbox for each item:
public class ViewModel
{
public List<Item> Items {get;} = new List<Item>
{
new Item { Value = "UI1" },
new Item { Value = "UI2" },
};
public class Item
{
public string Value {get;set;}
}
}
View:
<ItemsControl ItemsSource="{Binding Test}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}" Margin="5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Commit" Margin="5" Click="ButtonBase_OnClick"/>
You can then create the object either from a click event or command:
private void OnCallCreateObject()
{
Object newObject = new Object(Items[0], Items[1], false);
}
The downside is that the order of the items is not explicit, so either you need to assume that the indexed order is correct, or order them manually.

How to refresh a window in C#/WPF?

I want to change a value (textBlock) according to an event. Then, I want to refresh my window, but I couldn't. I used invalidateVisual as well as solutions of other posts, but nothing worked.
Thank you in advance
Several solutions (the first and second one does not make use of databinding).
txtMyControl.text = "New value";
If not on the main thread, you could use the dispatcher to update the value.
Application.Current.Dispatcher.BeginInvoke(() => txtMyControl.text == "New Value")
However, the most WPF friendly way to do it is to use the databinding.
Any change made to the value in code will be instantly reflected in the UI.
XAML
<TextBox x:Name="txtExample" Text="{Binding MyTextProperty,Mode=TwoWay}" Height="24" Width="120"/>
In your code, you have to declare a variable that will be persistent.
private ExampleModel _ExampleModel = new ExmampleModel();
When you load your code, you associate that variable to your textbox data context.
txtExample.DataContext = _ExampleModel
Then, you have the class that will contains all the editable properties on screen (textboxes, radio boxes, etc...)
public class ExampleModel : INotifyPropertyChanged
{
private string _MyTextProperty = "test";
public string MyTextProperty {
get { return _MyTextProperty; }
set {
if (string.Compare(_MyTextProperty, value) != 0) {
_MyTextProperty = value;
RaisePropertyChanged("MyTextProperty");
}
}
}
public void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
}
Whenever you handle your event, you just have to change the value of the property containing the information and the UI will refresh accordingly. Also, since we use a two-way binding, the value from your textbox will always be the same than the one contained by MyTextProperty property in ExampleModel class, which make value retrieval very easy.
ex:
_ExampleModel.MyTextProperty = "New value";
If you were already using databinding, make sure the class used implements INotifyPropertyChanged and that the propertyChanged event is called when the property value change or otherwise it won't update the UI.
The best approach to what you're trying to do would be to use Data Binding.
You need to have a string object that will always hold the value of your textblock. Next you need to bind that object to your textblock and then use the event provided by the INotifyPropertyChanged interface and each time the value changes its representation (the textblock) will change to, no need to refresh the window.
More information here
If your event updates the textblock and the textblock you are using is bound to a string property and that property issues a NotifyPropertyChanged() in it's set method, that will cause the display to refresh as you desire.
There are other ways, but this is the easiest given my understanding of your question.
(this is similar to the other answer, but I tried to word so it is easier to understand/implement.)

bind a combo-box to a List with "static" content and show content from database on WPF with MVVM

i am having a combo box and i want to show when you click on it the Gender "Female" or "Male" , the same time i want to read from my database which "Gender" has the selected Employee. So, generally i want to read and show on my combo-box the "sex" from my database but when you click on it i want also to show the two different options that you will have ("Female" or "Male"). The problem is now that i know how to bind to a combo-box so i can show the content on it from an Observable collection, but i do not know first how i can bind a property on it and how i can show also the same time the two different choices that some one can choose.
Thanks in advance!
Keep your ObservableCollection binding. It sounds as if that's working fine and it is responsible for providing all the options you want exposed in the dropdown of the ComboBox.
It sounds like you want to then show by default the Sex property that was obtained from your database.
Presumably, you will have some Sex property on your DataContext that is implementing INotifyPropertyChanged
private string _sex;
public string Sex
{
get { return _sex; }
set
{
if (_sex != value)
{
_sex = value;
OnPropertyChanged("Sex");
}
}
}
private void WhateverMethodYouHaveGettingDataFromDB()
{
//... do whatever it needs ...
Sex = // get sex from database...
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Now you have a bindable property that represents the Sex that you obtained from your database.
To push this onto the default exposed value in your ComboBox, simply bind it to the SelectedValue property.
<ComboBox ItemsSource="{Binding --your observable collection--}"
SelectedValue="{Binding Sex}"/>

How do I use INotifyPropertyChanged in WinRT?

I'm a total newbie, just learning the basics of DataContext and the MVVM model. I've now got a grid bound to a view model object which implements INotifyPropertyChanged, however it appears that UpdateSourceTrigger (which all the WPF tutorials tell me to use) is not available for WinRT / Metro Style apps!
How do I implement INotifyPropertyChanged then?
I'm at the end of my tether here. I've spend nearly the whole day on the most basic of app examples, simply trying to get a grid to update after I click something. The only way I've managed to do this so far is to create an entirely new instance of the view model and reassign the DataContext which I know is wrong
UPDATE:
I have made some progress, but things have gotten very weird. I have a view model, with a generic list of items. The items list is wired up with a PropertyChangedEventHandler. If I replace the entire collection with a new one, the listview updates.
model.Items = new List<DataItem>{ new DataItem{ Title = "new item" }};
This results in a one item list with the above item. However, if I try adding an item, nothing happens
model.Items.Add(new DataItem{ Title = "added item" });
I also tried creating a method which added an item and specifically fired PropertyChanged, but that also doesn't work
Here's where it gets weird. Next I tried this code.
model.Items.Add(new DataItem { Title = "added item" });
model.Items = new List<DataItem> { new DataItem { Title = "new item" }};
This results in a two item list:
- new item
- added item
How can this be? The code says, "add one item" then "replace the whole list" but it executes in the reverse order?
UPDATE 2:
I've switched to ObservableCollection as suggested, which has actually solved the original problem. I can now add an item and it shows up on the list.
However, the new weird behaviour is still in effect. Items added before the collection is reset are appended to the end of the new collection. Why is my code executing in reverse order?
You need to implement the interface and send out the notification once the given property you care about changes.
public event PropertyChangedEventHandler PropertyChanged;
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CustomerName"));
}
}
}
}
Keep in mind that for a collection, you should use an ObservableCollection as it will take care of the INotifyCollectionChanged being fired when an item is added or removed.
I would suggest to scale your sample back as far as possible. Don't start with a DataGrid but rather a simple TextBoxand Button, where the Button forces a change in your ViewModel which will then reflect on the UI.
Code taken from here.
It's best to implement a parent class which implements it like this:
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And then in your subclass (i.e. ViewModel) in your property do something like this:
public class MyViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name {
get{ return _name; }
set{
_name = value;
RaisePropertyChanged("Name");
}
}
}

Categories