WPF / MVVM / Model does not update viewmodel [duplicate] - c#

This question already has an answer here:
Notifying ViewModel of Property change from Model class
(1 answer)
Closed 3 years ago.
Context:
I had a problem with binding property in my MVVM app. Now I've made a little project to test this case. It includes just window, viewmodel, model, BindableObject(abstract class with INotifyProperyChanged) and Command classes. All classes are in same namespace, datacontext is set in view, model and vm have INotifyProperyChanged, text in view is binded to vm property that is binded to model property. Constructor sets model property and it affects property in viewmodel and view.
Problem:
When I change property in model it does not change property in viewmodel and view.
Here are BaseModel and Command classes:
abstract class BindableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertieChanged ([CallerMemberName]string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
class Command : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public Command(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
Model:
public class Model : BindableObject
{
private int modelValue;
public int ModelValue { get => modelValue; set { modelValue = value; OnPropertieChanged(); } }
public Model ()
{
ModelValue = 111;
}
public void ChangeValue ()
{
ModelValue = 777;
}
}
ViewModel:
class MainVM : BindableObject
{
private int myValue;
public int MyValue { get => myValue; set { myValue = value; OnPropertieChanged(); } }
public ICommand Command1 { get; set; }
public MainVM()
{
var model = new Model();
MyValue = model.ModelValue;
Command1 = new Command( (obj) => model.ChangeValue() );
}
}
View:
<Window>
...
<Window.DataContext>
<local:MainVM/>
</Window.DataContext>
<StackPanel>
<TextBlock Text="{Binding MyValue}"/>
<Button Command="{Binding Command1}"/>
</StackPanel>
</Window>

The problem is your command in your view model is invoking a method on an instance of your model, but your xaml is binding to a separate property in your view model of type int (MyValue). That's why MyValue is getting set once in the vm constructor but never getting updated with the command.
Make the instance of your Model class a property in the view model:
private Model _myModel;
public Model MyModel
{
get
{
return _myModel;
}
set
{
_myModel = value;
OnPropertieChanged();
}
}
public ICommand Command1 { get; set; }
public MainVM()
{
MyModel = new Model();
Command1 = new Command((obj) => MyModel.ChangeValue());
}
And bind to it's ModelValue property in the xaml.
<StackPanel>
<TextBlock Text="{Binding MyModel.ModelValue}"/>
<Button Height="50" Width="100" Command="{Binding Command1}"/>
</StackPanel>
In general, though, a model class doesn't typically perform logic; rather, logic is either performed on instances of your model in a service class, or client-side in the view model. But that's a separate discussion.

Related

Binding command on button doesn't work wpf mvvm

I'm trying to create simple add entity to database form, but binding command doesn't work and I can't figure out why. Here is XAML
<DockPanel Margin="30">
<StackPanel DockPanel.Dock="Top">
<Label>Manufacturer</Label>
<TextBox Text="{Binding Manufacturer, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label>Type</Label>
<TextBox Text="{Binding Type, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Label>Serial number</Label>
<TextBox Text="{Binding SerialNumber, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding AddScaleCommand}">Add Scale</Button>
</StackPanel>
<ListBox ItemsSource="{Binding Scales}" DockPanel.Dock="Bottom"></ListBox>
</DockPanel>
And here is ScaleViewModel where the command is located
public class ScaleViewModel : ViewModel
{
public ScaleViewModel()
{
Scales = new ObservableCollection<Scale>();
}
public ICollection<Scale> Scales { get; private set; }
public string Manufacturer { get; set; }
public string Type { get; set; }
public string SerialNumber { get; set; }
public bool IsValid
{
get
{
return !string.IsNullOrWhiteSpace(SerialNumber);
}
}
public ActionCommand AddScaleCommand
{
get
{
return new ActionCommand(p => AddScale(Manufacturer, Type, SerialNumber),
p => IsValid);
}
}
private void AddScale(string manufacturer, string type, string serialNumber)
{
using (var api = new BusinessContext())
{
var scale = new Scale
{
Manifacturer = manufacturer,
Type = type,
SerialNumber = serialNumber
};
try
{
api.AddNewScale(scale);
}
catch (Exception ex)
{
//TODO kasnije
return;
}
Scales.Add(scale);
};
}
}
Scale is simple class with 3 properties (Manufacturer, type and serial number), and ViewModel class implements INotifyPropertyChanged and IDataErrorInfo and implemented necessary methods. ActionCommand class implements ICommand and implements ICommand methods.
UPDATE added ActionCommand class
public class ActionCommand : ICommand
{
private readonly Action<Object> action;
private readonly Predicate<Object> predicate;
public ActionCommand(Action<Object> action) : this(action, null)
{
}
public ActionCommand(Action<Object> action, Predicate<Object> predicate)
{
if (action == null)
{
throw new ArgumentNullException("Action", "Yout must specify an Action<T>");
}
this.action = action;
this.predicate = predicate;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (predicate == null)
{
return true;
}
return predicate(parameter);
}
public void Execute(object parameter)
{
action(parameter);
}
public void Execute()
{
Execute(null);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
#endregion
}
Your ViewModel class needs to implement both IDataErrorInfo and INotifyPropertyChanged for the validation to work.
Also, there doesn't appear any way for the ActionCommand to re-evaluate IsValid() after SerialNumber has changed.
For more detail on data validation in WPF / MVVM using IDataErrorInfo, check out my blog post.
The problem was I didn't add DataContext to MainWindow in App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var window = new MainWindow
{
DataContext = new ScaleViewModel()
};
window.ShowDialog();
}
}

Data Binding Issue: How do I get the Binding value to work both ways?

I am working on a MVVM WPF application and I have a CheckBox which I am trying to work on. What I want is for the value to be binding to a model property (which I have done). However, when I click it in a debugging session it never actually changes my IsChecked property to true from its default false. Please see code below:
Model
public class MyModel:INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked == value)
return;
_isChecked = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
View
<StackPanel Orientation="Horizontal" Height="51" Width="667" Canvas.Left="10" Canvas.Top="45">
<CheckBox IsChecked="{Binding IsChecked}" Command="{Binding CheckBoxClickCommand}" Content="We're in the matrix" VerticalAlignment="Center" Margin="10,10,200,10"/>
</StackPanel>
ViewModel
public class MyViewModel
{
private MyModel _myModel = new MyModel();
public ObservableCollection<MyModel> UrlsList { get; } = new ObservableCollection<MyModel>();
public ICommand CheckBoxClickCommand { get; private set; }
public MyViewModel()
{
CheckBoxClickCommand = new RelayCommand(CheckBoxOnClick);
}
public void CheckBoxOnClick()
{
var newList = new List<MyModel>();
if (_myModel.IsChecked)
{
foreach (var url in UrlsList)
{
if (!url.ExistsInDb)
newList.Add(url);
}
}
}
}
When I debug and get to the if statement in CheckBoxClickCommand it obviously goes to the model to get the property value, but it does not change from the default false to true. Any help is much appreciated, thanks!.
Bind to the model's property:
<CheckBox IsChecked="{Binding Model.IsChecked}" ...>
For this to work, the model has to be returned from a public property of the view model:
private MyModel _myModel = new MyModel();
public MyModel Model { get { return _myModel; }}

Binding ICommand to button?

I am new to the MVVM pattern and things are coming to me ever so slowly, I want to be able to click a button on my form and then it dynamically create a textbox at runtime. I have a 'Add Title' and also 'Add Question' which both add textboxes but at different locations, you can add as many questions under one title. I have Created a class called Standard in this class it holds:
public class Standard
{
string _title;
ObservableCollection<string> _questions;
public event PropertyChangedEventHandler PropertyChanged;
#region NofiftyPropChnage
protected void NotifyOfPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void NotifyOfPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
NotifyOfPropertyChanged(property.GetMemberInfo().Name);
}
#endregion
#region Properties
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyOfPropertyChanged(() => Title);
}
}
public ObservableCollection<string> Questions
{
get { return _questions; }
set
{
_questions = value;
NotifyOfPropertyChanged(() => Questions);
}
}
#endregion
}
This class holds a Title property and also a list of Questions property because you can add Questions under a Title.
I also have a ViewModel class which holds:
class ViewModel :INotifyPropertyChanged
{
#region NotifyPropertyChange
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyOfPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void NotifyOfPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
NotifyOfPropertyChanged(property.GetMemberInfo().Name);
}
#endregion
private ObservableCollection<Standard> _standardCollection;
public ObservableCollection<Standard> StandardCollection
{
get
{
return _standardCollection;
}
set
{
_standardCollection = value;
NotifyOfPropertyChanged(() => StandardCollection);
}
}
}
This class holds a list of standards, a standard is when you click save with the text boxes and information in the text boxes done. It saves as a Standard
Finally my XAML code:
<Grid>
<button Content="Add Title"/>
<button Content="Add Question"/>
<StackPanel>
<ItemsControl ItemsSource="{Binding StandardCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Standard}">
<Grid>
<TextBox Text="{Binding Title}"/>
<ItemsControl ItemsSource="{Binding Questions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Questions}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Everything runs and there are no errors but when I click 'Add Title' or 'Add Question' no textbox appears, any help?
Ok, I'll have another shot at this one. I've stripped out the Title part and just concentrated on the Questions in order to keep this as a minimal example. First you'll need a base class that implements INotifyPropertyChanged for your view models:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpresion)
{
var property = (MemberExpression)propertyExpresion.Body;
this.OnPropertyChanged(property.Member.Name);
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Next you'll need a class that implements ICommand for your buttons to bind to which causes handlers to get called when those buttons are pressed:
// by Josh Smith, http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
Those two classes were written by others, if you add MVVM Lite project to your project you'll get them provided for you.
Next we need to create a view model with an ObservableCollection of Questions and a handler that gets called when the user presses the button:
public class MyViewModel : ObservableObject
{
public ICommand AddQuestionCommand {get; private set;}
ObservableCollection<string> _questions = new ObservableCollection<string>();
public ObservableCollection<string> Questions
{
get { return _questions; }
set
{
_questions = value;
OnPropertyChanged(() => Questions);
}
}
public MyViewModel()
{
this.AddQuestionCommand = new RelayCommand(new Action<object>((o) => OnAddQuestion()));
}
private void OnAddQuestion()
{
this.Questions.Add("new item");
}
}
Obviously you'll need to create an instance of this and set it as your window's DataContext. When the command gets triggerd the handler gets called and it in turn adds a new string to the collection. The XAML now needs to bind a button to that command and use the Questions collection to create a list of TextBlocks that display them all:
<StackPanel>
<Button Content="Add Question" Command="{Binding AddQuestionCommand}" HorizontalAlignment="Left"/>
<ItemsControl ItemsSource="{Binding Questions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding .}" Width="200" HorizontalAlignment="Left"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Hopefully this should give you a starting point. If I've missed something or you need clarification on anything then pls post a follow-up and I'll do my best.
Standard needs to implement the INotifyPropertyChanged interface. Generally you shouldn't do this more than once though, just declare one base class that implements that stuff and inherit all your view models from that. Also if you use package manager to add MVVM Lite to your project then you'll get a lot of this stuff provided for you.
I have no idea why these other guys are banging on about the INotifyPropertyChanged interface, as that has so very little to do with ICommand, although it does appear that you have tried to use it without adding it to the Standard class definition.
Either way, it sounds to me like you need to use the RelayCommand, or similar. This is a class that extends the ICommand interface... you can think of it as a delegate command. Instead of defining a separate class for each command, you can simply define the command logic and the canExecute handler inline. Here is a simplified example:
public ICommand SaveCommand
{
get { return new RelayCommand(execute => Save(), canExecute => CanSave()); }
}
...
<Button Content="Save" Command="{Binding SaveCommand}" />
You can find an implementation of it in the RelayCommand.cs page on GitHub and a description of it in the Commands, RelayCommands and EventToCommand page on MDSN Magazine.
You will need to change your code heavily to make it work. Do the following:
Step 1. Add Class RelayCommand:
public class RelayCommand : ICommand
{
public Func<bool> CanExecute { get; set; }
public Action Execute { get; set; }
public RelayCommand()
{
}
public RelayCommand(Action execute)
{
Execute = execute;
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (this.CanExecute == null)
{
return true;
}
else
{
return this.CanExecute();
}
}
event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
void ICommand.Execute(object parameter)
{
this.Execute();
}
#endregion
}
Step 2. Add Commands in ViewModel
public ICommand AddTitle { get; private set; }
public ICommand AddQuestion { get; private set; }
public ViewModel()
{
_standardCollection = new ObservableCollection<Standard>();
AddTitle = new RelayCommand(OnAddTitle);
AddQuestion = new RelayCommand(OnAddQuestion);
}
void OnAddTitle()
{
_standardCollection.Add(new Standard());
}
void OnAddQuestion()
{
_standardCollection.Last().Questions.Add(new Question("Some Question"));
}
Step 3. Bind buttons
<Button Content="Add Title" Command="{Binding AddTitle}"/>
<Button Content="Add Question" Command="{Binding AddQuestion}"/>
You will also have to fix you layount in XAML.
Since the user can change the question text, you should create a separate class Question.
Try implementing INotifyPropertyChanged on class Standard.
public class Standard : INotifyPropertyChanged
{
string _title;
ObservableCollection<string> _questions;
public event PropertyChangedEventHandler PropertyChanged;
#region NofiftyPropChnage
protected void NotifyOfPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void NotifyOfPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
NotifyOfPropertyChanged(property.GetMemberInfo().Name);
}
#endregion
#region Properties
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyOfPropertyChanged(() => Title);
}
}
public ObservableCollection<string> Questions
{
get { return _questions; }
set
{
_questions = value;
NotifyOfPropertyChanged(() => Questions);
}
}
#endregion
}

Using commands in the Data Model scope

In my program I am trying to write commands for a User Control that will toggle the isEnabled and isChecked property of a few controls. Attached to my User Control is a View Model and a Data Model. My commands and properties are in my Data Model (First of all, is this correct implementation?), and there is a property for my Data Model inside my View Model.
The commands are not working. I do not get any binding errors, and when I debug my code, the values are changed correctly. However, there is no visual feedback.
My View Model is set as the DataContext of the User Control in it's constructor.
My data is bound like this:
<CheckBox Command="{Binding Model.myCommand}" ... />
This is an example of what one of my commands looks like:
public Command myCommand { get { return _myCommand; } }
private void MyCommand_C()
{
if (_myCommand== true) //Checked
{
_checkBoxEnabled = true;
}
else //UnChecked
{
_checkBoxEnabled = false;
_checkBox = false;
}
}
Why aren't these commands functioning?
Commands should be implemented in the ViewModel.
There or in your Models, you should have Properties binded to the IsChecked and IsEnabled properties of your controls, and in the command, changing the properties will trigger PropertyChanged event which will update your views.
Example:
In your view :
<StackPanel>
<Button Command="{Binding ToggleCommand}"/>
<CheckBox IsChecked="{Binding Path=Model.IsChecked}"/>
<CheckBox IsEnabled="{Binding Path=Model.IsEnabled}"/>
</StackPanel>
ViewModel:
public class MainWindowViewModel : NotificationObject
{
public MainWindowViewModel()
{
Model = new MyModel();
ToggleCommand = new DelegateCommand(() =>
{
Model.IsChecked = !Model.IsChecked;
Model.IsEnabled = !Model.IsEnabled;
});
}
public DelegateCommand ToggleCommand { get; set; }
public MyModel Model { get; set; }
}
Model:
public class MyModel : INotifyPropertyChanged
{
private bool _isChecked;
private bool _isEnabled;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
_isChecked = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public bool IsEnabled
{
get
{
return _isEnabled;
}
set
{
_isEnabled = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsEnabled"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Hope this helps
First, your Command properties should be in your ViewModel, not your data model.
Setting that aside, you should not bind a CheckBox to a Command - commands are for elements that trigger actions (such as clicking a Button). A CheckBox should be bound to a bool property. Where the property should reside can be debated, but my opinion is that it should be in the ViewModel so you can keep the Property Changed Notification logic out of your data model.
A quick example:
public class MyViewModel : INotifyPropertyChanged
{
private bool _myCheckValue;
public bool MyCheckValue
{
get { return _myCheckValue; }
set
{
_myCheckValue = value;
this.RaisePropertyChanged("MyCheckValue");
}
}
//INotifyPropertyChange implementation not included...
}
And then in your XAML (assuming the ViewModel is the DataContext):
<CheckBox IsChecked="{Binding MyCheckValue}" ... />

WPF MVVM two-way updates

I'm trying to setup a working two-way update by using this example.
These are the relevant code snippets:
XAML:
<Button Click="clkInit">Initialize</Button>
<Button Click="clkStudent">Add student</Button>
<Button Click="clkChangeStudent">Change students</Button>
(...)
<TabControl Name="tabControl1" ItemsSource="{Binding StudentViewModels}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=StudentFirstName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Label Content="First Name" Name="label1" />
<TextBox Name="textBoxFirstName" Text="{Binding Path=StudentFirstName}" />
<Label Content="Last Name" Name="label2" />
<TextBox Name="textBoxLastName" Text ="{Binding Path=StudentLastName}" />
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Main Window:
public partial class MainWindow : Window
{
internal MainWindowViewModel myMWVM;
public MainWindow()
{
InitializeComponent();
}
private void clkInit(object sender, RoutedEventArgs e)
{
myMWVM= new MainWindowViewModel();
DataContext = myMWVM;
}
private void clkStudent(object sender, RoutedEventArgs e)
{
myMWVM.StudentViewModels.Add(new StudentViewModel());
}
// For testing - call a function out of the student class to make changes there
private void clkChangeStudent(object sender, RoutedEventArgs e)
{
for (Int32 i = 0; i < test.StudentViewModels.Count; i++)
{
myMWVM.StudentViewModels.ElementAt((int)i).changeStudent();
}
}
}
Main view:
class MainWindowViewModel : INotifyPropertyChanged
{
ObservableCollection<StudentViewModel> _studentViewModels =
new ObservableCollection<StudentViewModel>();
// Collection for WPF.
public ObservableCollection<StudentViewModel> StudentViewModels
{
get { return _studentViewModels; }
}
// Constructor. Add two stude
public MainWindowViewModel()
{
_studentViewModels.Add(new StudentViewModel());
_studentViewModels.Add(new StudentViewModel());
}
// Property change.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Student view:
class StudentViewModel : INotifyPropertyChanged
{
Lazy<Student> _model;
string _studentFirstName;
public string StudentFirstName
{
get { return _studentFirstName; }
set
{
if (_studentFirstName != value)
{
_studentFirstName = value;
_model.Value.StudentFirstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
string _studentLastName;
public string StudentLastName
{
get { return _studentLastName; }
set
{
if (_studentLastName != value)
{
_studentLastName = value;
_model.Value.StudentLastName = value;
OnPropertyChanged("StudentLastName");
}
}
}
public void changeStudent()
{
_model.Value.changeStudent();
}
public StudentViewModel()
{
_studentFirstName = "Default";
_model = new Lazy<Student>(() => new Student());
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
THE student:
class Student
{
public string StudentFirstName { get; set; }
public string StudentLastName { get; set; }
public Student()
{
MessageBox.Show("Student constructor called");
}
public Student(string nm)
{
StudentLastName = nm;
}
public void changeStudent()
{
StudentLastName = "McDonald";
}
}
If you read until here I already thank you :) Still, by calling "clkChangeStudent" I don't see the changes in the textbox. I guess it's because I don't call the set-method of the StudentViewModel. The project I'm working on is a bit complex and a lot of things happen in the class (here Student) itself.
How can I get a textbox update by settings values in the Student-class itself?
Your actual code clearly won't notify changes to the interface. The reason is simple. Your method that changes the student name is in the Student model and that model does not implement the INotifyPropertyChanged.
There is 2 solutions to fix this issue depending on one question, does the changeStudent() method has to stick with the object model, that is to say, can your requirements allows you to move the changeStudent() method to the view model?
If yes then, first solution, simply remove the changeStudent method from the model and move it to the view model like this:
class StudentViewModel : INotifyPropertyChanged
{
...
public void changeStudent()
{
this.StudentLastName = "McDonald";
}
}
In the other case, second solution, you have to raise events whenever a model property changes and then get your view model to suscribe to these changes. You can proceed like this in the model:
class Student : INotifyPropertyChanged
{
...
private string studentLastName;
public string StudentLastName
{
get
{
return this.studentLastName;
}
set
{
if(this.studentLastname != value)
{
this.studentLastName = value;
this.OnPropertyChanged("StudentLastName");
}
}
}
}
And for the view model:
class StudentViewModel : INotifyPropertyChanged
{
...
public StudentViewModel(Student model)
{
this._model = model;
this._model.PropertyChanged += (sender, e) =>
{
if(e.PropertyName == "StudentLastName")
{
this.OnPropertyChanged("StudentLastName");
}
};
}
}
Both solution will work. It is really import that you understand that your code explicitely needs to notifies the interface whenever a value changes.
ChangeStudent doesn't call any of the methods that trigger a property notify event in the view model, it alters the underlying model instead. It's these events that trigger the view to update itself.
As an aside you should also look at command binding from the view instead of using click handlers in the code-behind. That way your view doesn't need to know anything about the view model that's attached and can be pure presentation.
First you should use commands instead of events.
In your current structure you have to add an
OnPropertyChanged("StudentLastName");
call to your ChangedStudent() Method in StudentViewModel.
After that you have to set the UpdateSourceTrigger of the Bindings to PropertyChanged
Text="{Binding Path=StudentFirstName, UpdateSourceTrigger=PropertyChanged}"

Categories

Resources