Bound TextBox Text Does not Update - c#

I am having a problem with the textbox text updating in my view. I have seen some other threads but cannot find one that is close to my setup.
I have a model with a class of
interface IPerson : INotifyPropertyChanged
{
string FirstName { get; set;}
}
public class Person
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _firstName;
public string FirstName
{
get {return _firstName;}
set
{
_firstName = value;
PropertyChanged(this , new PropertChangedEventArgs("FirstName"));
}
}
}
This is what my viewModel Looks Like
interface IViewModel : INotifyPropertyChanged
{
string FirstName { get; set;}
IPerson Person { get; set; }
}
public class viewModel : IViewModel
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private IPerson _person;
public Person Person
{
get {return _person;}
set
{
_person = value;
PropertyChanged(this , new PropertyChangedEventArgs("Person"));
}
}
public string FirstName
{
get {return Person.FirstName;}
Set
{
Person.FirstName = value;
PropertyChanged(this , new PropertChangedEventArgs("FirstName"));
}
}
}
Xaml Binding Setup
// If i use this binding my model will get updated but my textbox text will never show what the value is in the model
Text="{Binding Path=FirstName ,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,diag:PresentationTraceSources.TraceLevel=High}"
// This binding works fine both ways
Text="{Binding Path=Person.FirstName ,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay,diag:PresentationTraceSources.TraceLevel=High}"
So if i bind to the Person.First Name everything works great. If I bind to FirstName then it will update my model fine but will not get the data for The TextBox Text. This becomes apparent when i select an item in my list.
Does anyone have an idea why this would happen. I want to be able to bind to the ViewModel Firstname and just have it pass that to my person object.

If your model changes the FirsName value it triggers an PropertyChanged event but that is not extended (relayed) by the ViewModel.
To make the first binding work your ViewModel should subscribe to Person.PropertyChanged and then raise its own event when the nested FirstName changes.
But of course the Path=Person.FirstName binding is preferable anyway.

With this binding "Binding Path=Person.FirstName" the View is listening to your viewModel for the PropertyChanged event. The View is not listening to your model (i.e. Person).
If you are setting the DataContext of your View to the viewModel, then in order for the View to know about changes to your model (i.e. person) you are going to have to have your viewModel listen for the PropertyChanged event of your model. Then, raise the PropertyChanged event from within your viewModel.

Related

DataGrid not updating when rebinding a new datasoure

I bind an ObservableCollection CustomerList to a datagrid, it works fine when it is opened initially, but when I use a buttion to call the fuction to give a new object to CustomerList the datagrid is not refreshed, I am aware that this is the way how the datagrid update works, as the original CustomerList is not upadted, so what can I do to solve the problem in this case? I am using MVVM pattern
class CustomerViewModel
{
public ObservableCollection<Customer> CustomerList { get; set; }
public RelayCommand SearchCommand { get; set; }
public CustomerViewModel()
{
CustomerList = new ObservableCollection<Customer>(customerDAL.GetAllCustomers());
SearchCommand = new RelayCommand(SearchCustomersByKeyWords);
}
void SearchCustomersByKeyWords(object parameter)
{
CustomerList = new ObservableCollection<Customer>(customerDAL.SearchByKeywords(keyWords));
}
}
CustomerViewModel is not actually a viewmodel. It's just a regular class. To be a proper viewmodel, it needs to implement INotifyPropertyChanged.
When you change the value of CustomerList, you must raise the PropertyChanged event of INotifyPropertyChanged. Otherwise, the UI will never know that the value of CustomerList has changed. The binding on DataGrid.ItemsSource has no idea that you updated the source property, so it isn't updating the target property.
CustomerList should look something like this:
public class CustomerViewModel : ViewModelBase
{
private ObservableCollection<Customer> _customerList = new ObservableCollection<Customer>();
public ObservableCollection<Customer> CustomerList {
get { return _customerList; }
set {
if (_customerList != value) {
_customerList != value;
// Member of ViewModelBase that raises PropertyChanged
OnPropertyChanged(nameof(CustomerList));
}
}
}
And write a ViewModelBase class that implements INotifyPropertyChanged; you'll find many examples of that online.
A poor workaround would be to keep the collection you've got, but Clear() it and add the new items to it one by one in a loop.

MVVM binding many properties

Is there a simpeler way to bind many properties?
So if you have a Person class with properties: lastname, firstname, birthday, gender, title, ...
Now I do this for every property on the ViewModel:
public string _LastName;
public string LastName
{
get { return _LastName; }
set { _LastName = value;
RaisePropertyChanged("LastName"); }
}
And on the XAML page this binding:
<TextBlock Text="{Binding FirstName}" />
Now image if Person object has like 20 properties..
So my question is can I do this in a simpeler way?
You only need to raise the PropertyChanged event from the setter of a data-bound property if you actually intend to update the property dynamically at runtime. Otherwise you could use auto-implemented properties without any custom logic:
public FirstName { get; set; }
There is also a NuGet package called Fody that can turn simple public properties into full INotifyPropertyChanged implementations for you automatically: https://github.com/Fody/PropertyChanged
If you use a third party MVVM framework, it also might have a code snippet to create property with INotifyPropertyChanged.
If you use Catel, you can download templates and snippets here:
http://catelproject.com/downloads/general-files/
And here's implementation for Caliburn.Micro:
https://github.com/winterdouglas/propc
The simplest solution is to use the POCO mechanism provided by a free DevExpress MVVM Framework.
POCO will automatically implement INotifyPropertyChanged and raise the PropertyChanged event for all public virtual properties in your view model.
All magic happens when you use the ViewModelSource class to create your view model. You can create your view model in XAML:
<UserControl ...
DataContext="{dxmvvm:ViewModelSource Type=local:MyViewModel}">
Or in code-behind:
this.DataContext = ViewModelSource.Create(() => new MyViewModel());
PREMISE
In a default MVVM scenario, your ViewModel don't have to raise notifications on every property.
Typical case: you get some Person from a database, show them on a View, modify them via TextBoxes and other controls, and click "Save" re-sending them to the database. You can do this by setting the DataContext on the View every time you call the database. This action raises a first update on the bound properties of the control and of every sub-control, so all the getters of the ViewModel's bound properties are called one time and the View get populated with the ViewModel's values. When you modify something on the View, that binding carries the modification to the corresponding ViewModel's property (even a simple plain get-set property).
In this case, you're just fine with something like:
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
//and so on...
}
You need to raise notifications for the ViewModel's properties only if the View must listen to some property's change. For example, this feature: the Button "Save" is enabled if and only if the Name on the Person is not empty. Here, clearly the Button must be able to see when the Name property changes, so that property setter must raise the PropertyChanged event.
A possible implementation:
Use this as base class for ViewModels:
protected abstract BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetAndNotifyIfChanged<T>(
ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
NotifyPropertyChanged(propertyName);
}
}
}
In a derived class you can write every get-set property like this:
class MyViewModel : BaseViewModel
{
public string MyProp
{
get { return _MyProp; }
set { SetAndNotifyIfChanged(ref _MyProp, value); }
}
private string _MyProp;
}
The type T and the parameter propertyName are automatically inferred.
This is the shortest piece of code you could write, and is not so different from a normal full-property:
public string NormalProp
{
get { return _ NormalProp; }
set { _NormalProp = value; }
}
private string _MyProp;
If you don't want to write all this code every time, use a code snippet.

Using windows forms viewmodel in other class

I try to read properties from ViewModel that is binded to some controls on windows form. I made ViewModel as singleton, so I'm sure there is only one instance of it. The problem is, when I go to other class and get singleton instance of ViewModel to read it's properties, they are not equal with these in ViewModel class. Here is ViewModel class:
class EditorToolboxViewModel : INotifyPropertyChanged
{
static EditorToolboxViewModel instance;
public static EditorToolboxViewModel GetSingleton()
{
if (instance == null)
instance = new EditorToolboxViewModel();
return instance;
}
public event PropertyChangedEventHandler PropertyChanged;
public int BrushRadius {get; set;}
public int BrushSensitivity {get; set;}
public bool TerrainUp { get; set; }
public bool TerrainDown { get; set; }
public bool AddTerrainSlot { get; set; }
public bool RemoveTerrainSlot { get; set; }
public bool FlattenTerrain { get; set; }
public float FlattenTerrainTarget { get; set; }
private EditorToolboxViewModel()
{
BrushRadius = 10;
BrushSensitivity = 1;
}
}
And this is how I bind it to controls on windows form:
this.viewModel = EditorToolboxViewModel.GetSingleton();
this.trackBarBrushRadius.DataBindings.Add(new Binding("Value", viewModel, "BrushRadius"));
this.trackBarTerrainBrushSensitivity.DataBindings.Add(new Binding("Value", viewModel, "BrushSensitivity"));
this.radioButtonIncreaseHeight.DataBindings.Add("Checked", viewModel, "TerrainUp");
this.radioButtonDecreaseHeight.DataBindings.Add("Checked", viewModel, "TerrainDown");
this.radioButtonAddTerrainSlot.DataBindings.Add(new Binding("Checked", viewModel, "AddTerrainSlot"));
this.radioButtonRemoveTerrainSlot.DataBindings.Add(new Binding("Checked", viewModel, "RemoveTerrainSlot"));
this.radioButtonFlattenTerrain.DataBindings.Add(new Binding("Checked", viewModel, "FlattenTerrain"));
this.textBoxTerrainFlattenTarget.DataBindings.Add(new Binding("Text", viewModel, "FlattenTerrainTarget"));
And now, when I click TerrainUp radio button and try to read value from viewmodel in other class, it remains false:
bool b = EditorToolboxViewModel.GetSingleton().TerrainUp;
In ViewModel class, everything is exactly as it should, but accessing it elsewhere causes data mismatch.
Any ideas?
1) Check if the objects are the same by doing a == comparison. Properties might have different values at different time because of the binding of one windows. If you confirm that the object is the same, you know that the Singleton is working and there's probably a binding changing the property values.
2) Singleton should have a private field, not an internal field.
3) Your Singleton is not thread safe so check that.
I eventually fixed my problem.
It looks like that's known bug, that radio buttons lost bindings during state change, so that's why viewmodel wasn't behaving correctly. Look here:
http://www.abhisheksur.com/2011/03/issue-with-radiobuttons-and-binding-for.html
One of possible solutions was to replace radio buttons with check boxes. But doing this, remember two things:
Set all checkbox bindings to false in viewmodel, then set true where it's supposed to be (if you want to make checkboxes behave like radio buttons).
IMPORTANT: ViewModel is being validated not when you click checkbox, but when checkbox LOSES FOCUS. So to force viewmodel validation after single click, just programmatically change focus to other control on the form, like this:
void CheckBoxes_CheckedChanged(object sender, EventArgs e)
{
CheckBox c = (CheckBox)sender;
var otherCheckBoxName = typeof(EditorToolbox).GetRuntimeFields().Where(x => x.FieldType == typeof(CheckBox) && !x.Name.Equals(c.Name)).Select(x => x.Name).FirstOrDefault();
CheckBox c2 = (CheckBox)this.Controls.Find(otherCheckBoxName, true).FirstOrDefault();
c2.Focus();
}

Binding text box with property inside an object doesn't trigger NotifyPropertyChanged

I have a Property Student which has Name and SchoolName. I have bound the Student.Name property to a textbox
<TextBox Text="{Binding Student.Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
My datacontext is
public class MyDataContext : INotifyPropertyChanged
{
private Student _student;
public Student Student
{
get { return _student; }
set
{
if (value != null)
{
_student = value;
NotifyPropertyChanged("Student");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Student
{
public string Name { get; set; }
public string SchoolName { get; set; }
}
When I change the text in the textbox NotifyPropertyChanged event is not firing.
What am I doing wrong here? How can I achieve this?
You need to implement INotifyPropertyChanged on the Student class to get this to work.
The XAML binding framework is monitoring the 'Name' property on the Student class not the Student class.
The event isn't being raised because changing the textbox is not actually modifying the Student Reference. It is modifying the value of a property within the student. The way this is written it would only fire if the entire Student property were replaced with a new instance.
To get the behavior you want you should make Student implement INotifyPropertyChanged and update the properties within it to raise the event similar to the way you raise the event on the context.
An alternate solution would be to add proxy properties to your datacontext for the Student properties that just forward the calls to the Student instance.

Capturing RaisePropertyChanged from MVVM Light?

I currently have a Model with a boolean property that stores a checkbox value. When this value changes(checked/unchecked) I want to show or hide a textbox.
Now my Visibility property for the textbox is in my ViewModel and not in my Model. I am not sure how to tell my Visibility property that it should show/hide because the value of the checkbox changed.
I know in all the properties I have RaisePropertyChanged and I thinking this would be something I could use but I don't know how to capture it in my ViewModel.
Or am I approaching this all wrong?
Your ViewModel should act as a gate between your Model and your View. It looks like your checkbox is bound directly to the model. It should be bound to the corresponding View Model property that would act as a conduct to the appropriate value to your model. For example (disclaimer: I haven't used MVVM Light, but it should be self explanatory for most MVVM frameworks):
public class Chobo2
{
public bool IsChecked {get;set;}
}
public class Chobo2ViewModel // Your base class and interfaces
{
private Chobo2 model;
public bool IsChecked
{
get { return model.IsChecked; }
set
{
if(model.IsChecked == value) return;
model.IsChecked = value;
RaisePropertyChanged("IsChecked");
RaisePropertyChanged("Visibility");
}
}
public System.Windows.Visibility Visibility
{
get
{
return IsChecked
? System.Windows.Visibility.Visible
: System.Windows.Visibility.Collapsed;
}
}
}
If your model itself implements the INotifyPropertyChanged interface and changing your view model logic isn't an option (IE your view directly binds to the model's property), all you can do is listen to the change on the PropertyChanged event.
// Assume the Chobo2 class implements INPC
public class Chobo2ViewModel // Your base class and interfaces
{
private Chobo2 model;
public Chobo2ViewModel(Chobo2 model)
{
// Should check for null here
this.model = model;
this.model.PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "IsChecked")
RaisePropertyChanged("Visibility")
}
}
public System.Windows.Visibility Visibility
{
get
{
return model.IsChecked
? System.Windows.Visibility.Visible
: System.Windows.Visibility.Collapsed;
}
}
}

Categories