I have started learning MVVM with some basic applications and I just encountered below issue with binding.
I have 2 textboxes in my View say- Student_name and Student_year. I have a Student class implemented in my viewmodel with its properties. But, the actual Student class is in my Model layer.
<TextBox x:Name="StuName"
Text="{Binding Path=MyStudent.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<TextBox x:Name="StuYear"
Text="{Binding Path=MyStudent.Year, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
ViewModel:
private Student _myStudent = new Student();
public Student MyStudent
{
get { return _myStudent ; }
set
{
if (value != _myStudent )
{
_myStudent = value;
OnPropertyChanged("MyStudent");
}
}
}
Model (Student Class):
public string Name
{
get { return _name; }
set
{
if (_name!= value)
{
_name= value;
OnPropertyChanged("Name");
}
}
}
I can see everything working fine on binding the values from VM to View. But, the other way is behaving little tricky here..
Wheneven I change Name/Year in the textbox, the control has to land on Viewmodel's Set property? Rather, it straight away goes to Model's Set property.
For Instance, When I modify txtbox 'StuName', SET method of Student class is invoked. But not SET method of Viewmodel(MyStudent object).
I am not sure why this behaves in such a way. Is it because I have directly bounded Student.Name to the textbox? What are the alternatives to handle this SET operation in Viewmodel class..
Thanks in advance.
PS: I have implemented INotifyPropertyChanged interface properly and rest other bindings(of primitive data type) are working fine with other controls.
As Philip Stuyck correctly pointed out in his answer, the ViewModel only has a setter for the Student instance, which never changes. So the setter on the ViewModel is never invoked. The binding goes to the name property of that instance.
A different approach would be to wrap the name property in you ViewModel explicitly. This allows for a clearer separation of concerns between Model and ViewModel. I.e. right now your Model implements INotifyPropertyChanged which IMO belongs into the ViewModel, because in general it is only used for triggering View updates. Your ViewModel would look like this:
class StudentViewModel
{
private Student _myStudent = new Student();
public string Name
{
get { return _myStudent.Name ; }
set
{
if (value != _myStudent.Name )
{
_myStudent.Name = value;
OnPropertyChanged("Name");
}
}
}
}
Your Model on the other hand becomes simpler, because it doesn't have to implement INotifyPropertyChanged anymore.
That is normal behavior because your binding is to MyStudent.Name.
So the Mystudent setter is never called because the instance never changes.
The setter of the name is called because in fact that is where your binding is going to.
Related
I experiment with the order of setting the DataContext property in the default constructor in WPF.
<StackPanel>
<ListBox ItemsSource="{Binding MyItems, PresentationTraceSources.TraceLevel=High}"></ListBox>
<TextBlock Text="{Binding SomeText}"></TextBlock>
<TextBlock Text="{Binding SomeNum}"></TextBlock>
<TextBlock Text="{Binding Path=Person.Name}"></TextBlock>
<ListBox ItemsSource="{Binding Path=PersonList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
1) With DataContext set before the InitializeComponent method
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string someText = "Default text";
public List<string> MyItems { get; set; }
public List<Person> PersonList { get; set; }
public Person Person { get; set; }
public int SomeNum { get; set; }
public string SomeText
{
get
{
return someText;
}
set
{
someText = value;
OnPropertyChanged("SomeText");
}
}
public MainWindow()
{
this.DataContext = this;
MyItems = new List<string>();
PersonList = new List<Person>();
Person = new Person();
InitializeComponent();
/*These changes are not reflected in the UI*/
SomeNum = 7;
Person.Name = "Andy";
/*Changes reflected with a help of INotifyPropertyChanged*/
SomeText = "Modified Text";
/* Changes to the Lists are reflected in the UI */
MyItems.Add("Red");
MyItems.Add("Blue");
MyItems.Add("Green");
MyItems[0] = "Golden";
PersonList.Add(new Person() { Name = "Xavier" });
PersonList.Add(new Person() { Name = "Scott" });
PersonList[0].Name = "Jean";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class Person
{
public string Name { get; set; } = "Default Name";
}
After the call to the InitializeComponent method changes to the values of properties are not reflected in the UI except for those properties which use INotifyPropertyChanged. Everything is clear so far.
However I noticed that changes to the list items are also reflected in the UI. How come?
I always thought that in order to reflect adding/removing from the collection I need ObservableCollection and to implement INotifyPropertyChanged on list object to detect modifications of these objects. What is the meaning of this?
2) With DataContext set after the InitializeComponent method
Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM? Could you describe it more thoroughly or give a simple code example?
I always thought that in order to reflect adding/removing from the collection I need ObservableCollection<T> and to implement INotifyPropertyChanged on list object to detect modifications of these objects.
You do, if you want reliable updating of the UI during changes in the view model.
What is the meaning of this?
The "meaning" is that in your particular scenario, you are making assumptions that aren't valid. WPF components go through a variety of initialization steps, only some of which occur as part of the InitializeComponent() method.
If, for example, you were to move the code for your value updates into a handler for the Loaded event, you'd find some of the updates reflected in the UI, but not all.
If you move that same code into a method invoked via Dispatcher.InvokeAsync() using a priority of DispatcherPriority.SystemIdle, you'd find that none of the updates would be observed, except for the one backed by INotifyPropertyChanged. In that case, you're explicitly waiting until every aspect of initialization has completed, and there are no longer opportunities for the initialization code to observe your updated values.
It's all about timing. Any code that sets a value before the UI winds up observing it, can do so successfully without INotifyPropertyChanged or equivalent. But you're entirely at the mercy of the current implementation of the framework in that case. Different parts of the initialization happen at different times, and these are not all documented, so you're relying on undocumented behavior. It probably won't change, but you have no way to know for sure.
Why setting a DataContext property after the InitializeComponent is a bad practice with MVVM?
It's not. Don't believe everything you read, even (or especially!) on the Internet.
If you want to forego implementation of INotifyPropertyChanged, then it will be important that you initialize all of your view model data before assigning the DataContext. But, even if you assign the DataContext after calling InitializeComponent, that assignment will be observed (because DataContext is a dependency property and so provides property changed notification to the framework), and the UI will retrieve all of the bound data from your view model data.
What's important is that the view model data be initialized before the assignment of DataContext. Where that happens relative to InitializeComponent() is not important.
When a view model property does not fire the PropertyChanged event, its value must of course be set before assigning the view model instance to the view's DataContext.
It does however not matter if you assign the DataContext before or after calling InitializeComponent:
Given a Binding like
<TextBlock Text="{Binding SomeText}"/>
these two sequence will both result in showing the property value in the view:
DataContext = new { SomeText = "Hello, World." };
InitializeComponent();
and
InitializeComponent();
DataContext = new { SomeText = "Hello, World." };
As the ViewModel has the job to "prepare" the Model's properties to get displayed in the View, what is the best way of referring to the underlying Models properties from the ViewModel?
I could think about two solutions by now:
Option 1 - Duplicate the Model's properties in the ViewModel (wrapper-approach)
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model-instance for this ViewModel
private Model M;
public string p1
{
get { return M.p1; }
set
{
M.p1 = value;
// assuming View controls are bound to the ViewModel's properties
RaisePropertyChanged("p1");
}
}
// let's say, I only want to check a Checkbox in the View,
// if the value of p2 exceeds 10.
// Raising the property changed notification would get handled
// in the modifying code instead of the missing setter of this property.
public bool p2
{
get
{
if (M.p2 > 10)
{ return true; }
else
{ return false; }
}
}
// Initialize the Model of the ViewModel instance in its c'tor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding p2, Mode=OneWay}"/>
Advantages
Full control about how the Model's properties are displayed on the View as shown in p2: int gets converted to bool on demand.
Changes of the properties of the ViewModel could be raised individual, might be a little performance increase compared to option 2.
Disadvantages
Violation of DRY.
More Code to write/maintain.
Modifications to the Model/ViewModel could easily become shotgun surgery.
Option 2 - Treat the whole Model as property of the ViewModel
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model instance for this ViewModel (private field with public property)
private Model _M;
public Model M
{
get { return _M; }
set
{
_M = value;
// Raising the changing notification for the WHOLE Model-instance.
// This should cause ALL bound View-controls to update their values,
// even if only a single property actually got changed
RaisePropertyChanged("M");
}
}
// Initialize the Model of the ViewModel instance in its ctor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding M.p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding M.p2, Mode=OneWay, Converter={StaticResource InverseBooleanConverter}"/>
Advantages
Can save a lot of code.
Reduces complexity.
Increases maintainability.
Disadvantages
In this approach, the ViewModel is nothing more than a continuous-flow water heater for the Models properties, except for some possible interaction logic for the View.
No control about how the Model's properties are displayed in the View - which ultimately leads to total needlessness of the ViewModel and implementation of conversion logic in the View.
It is the responsibility of your ViewModel to expose the Model to the View, you should not expose the Model's properties as additional properties in the ViewModel, instead, your View should bind directly to the model.
Additionally, it isn't wrong to have logic in your Model, in fact, it makes more sense to contain model related code within the model, as opposed to the ViewModel.
Here is an example:
public class Movie
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
//Notify property changed stuff (if required)
//This is obviously a stupid example, but the idea
//is to contain model related logic inside the model.
//It makes more sense inside the model.
MyFavourite = value == "My Movie";
}
}
private bool _MyFavourite;
public bool MyFavourite
{
get { return _MyFavourite; }
set
{
_MyFavourite = value;
//Notify property changed stuff.
}
}
}
So to answer your question a little more directly, you should expose your model in the view model as a property.
public class ViewModel
{
private Movie _Model;
public Movie Model
{
get { return _Model; }
set
{
_Model = value;
//Property changed stuff (if required)
}
}
...
}
Therefore, your View will bind to the Model property, like you have already done so.
EDIT
In the example for casting to the type, you can implement a read-only property in your Model, like so:
public bool MyBool
{
get
{
return MyInt > 10; }
}
}
Now the magic here would be that you will need to call the INotifyPropertyChanged for this property whenever MyInt changes. So your other property would look something like this:
public int MyInt
{
get { ... }
set
{
_MyInt = value;
//Notify property changed for the read-only property too.
OnPropertyChanged();
OnPropertyChanged("MyBool");
}
}
In my view, Model should not have RaisePropertyChanged stuff. Some view models (e.g. Blazor) might not need it, others (e.g. WPF) might use other mechanisms like DependencyProperty. Thus, to me Model is a POCO class. Hence, it becomes ViewModel responsibility to report changes to the data up to the View. Consequently, ViewModel is bound to wrap Model's properties (OA's option 1).
You might want to look at AutoMapper to centralize the mappings.
In my WPF application, I have some properties which I have bound to the XAML counterpart, but for some reason do not get set whenever their values change. I have implemented the INotifyPropertyChanged interface as well as set my DataContext for this View too, and it is still not picking up any changes.
I have this same pattern for other properties within this ViewModel which do work, while others don't.
Here is a snippet of my current code:
ViewModel
public class TestViewModel : INotifyPropertyChanged
{
private string testString;
public TestViewModel()
{
.....
this.RunCommand = new RelayCommand(this.RunAction);
}
public string TestString
{
get
{
return this.testString;
}
set
{
this.testString = value;
this.OnPropertyChanged("TestString");
}
}
private void RunAction()
{
.....
this.testString = "Running.";
}
}
View
<StatusBarItem>
<TextBlock Text="{Binding Path=TestString, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
</StatusBarItem>
DataContext (set in code-behind of another MainWindow class)
var testViewModel = SimpleIoc.Default.GetInstance<TestViewModel>();
var testWindow = new TestWindow() { DataContext = testViewModel };
testingWindow.Show();
If it helps, this is part of a multi-windowed application which uses MVVM-Light to pass properties between classes.
You are not changing the value of the TestString, you are assigning a command to change the value but you do not seem to be executing it.
this.RunCommand = new RelayCommand(this.RunAction);
Bind that command to something or execute it manually from somewhere.
Also you need to assign the property not the field
this.TestString = "Running.";
I found the problem. You are only updating the private property testString. But you do not update the property TestString so the notify is never called.
Try this:
this.TestString = "Running";
I have a TextBox which I'm trying to bind to a element of a table property 'regimeAlias' is a column with the tbRegimes table which I have mapped with Entity Framework:
<TextBox Text="{Binding NewRegime.regimeAlias, Mode=TwoWay}"/>
Exposed property in my ViewModel:
private tbRegime _NewRegime;
public tbRegime NewRegime
{
get { return _NewRegime; }
set
{
_NewRegime = value;
OnPropertyChanged("NewRegime");
}
}
Lastly, here's the WCF Service Reference auto-generated code class:
public partial class tbRegime : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
//blah blah blah
[System.Runtime.Serialization.DataMemberAttribute()]
public string regimeAlias {
get {
return this.regimeAliasField;
}
set {
if ((object.ReferenceEquals(this.regimeAliasField, value) != true)) {
this.regimeAliasField = value;
this.RaisePropertyChanged("regimeAlias");
}
}
}
The setter never gets hit. Is this because each element within the NewRegime object needs to raise PropertyChanged and if so is there an easy workaround without adding a further DTO layer to my code?
Edit3: with the post from your regimeAlias code. i have to say your binding should work. but of course if you wanna debug you have to set the breakpoint in your regimeAlias setter
<TextBox Text="{Binding NewRegime.regimeAlias, Mode=TwoWay}"/>
this code means, you bind to a Public Property regimeAlias in your class tbRegime.
your setter for NewRegime will never hit because you dont bind to it.
so check your tbRegime class property setter for regimeAlias.
EDIT: the DataContext of the TextBox is of course an object with the Public Property NewRegime, but like i said if you use dot notation in your binding the last property is the one you bind to :)
EDIT: you dont have much ways to workaround:) if you let the binding like you did, you need a model with a public property regimeAlias and it should implement INotifyPropertyChanged.
if you wanna wrap the regimeAlias Property then you have the problem the you have to raise OnPropertyChanged("MyRegimeAlias") at the right point.
public string MyRegimeAlias
{
get { return _NewRegime.regimeAlias; }
set
{
_NewRegime.regimeAlias = value;
OnPropertyChanged("MyRegimeAlias");
}
}
xaml
<TextBox Text="{Binding MyRegimeAlias, Mode=TwoWay}"/>
MVVM methodology of WPF for problem specified. I have a combo box say in a 'MainView' of XAML. Its code partner is 'MainViewModel' and exposes a property of a 'Person' which is basically just a seperate class(POCO class) for exposing a string and an int to represent a name and a seed in the database. It sets up a property of ReadOnlyCollection that binds to a combobox like so: (referencing the viewmodel at the top of the xaml like: xmlns:vm="clr-namespace:(mylocationforviewmodelnamespace))
ItemsSource="{Binding Path=People}"
DisplayMemberPath="FirstName"
SelectedValuePath="PersonId"
This works great but I am then setting up a User Control View and it's respective View Model code. What I am not getting about the ViewModel method of binding is how you bind the passed in values for the constructor? Or can you even do that? Or should I be setting up an intermediary class not just for my 'model' but for the 'DataAccess' ?
My end goal is to select a value in a combobox, which is already bound properly and works great, and pass it to the viewmodel code that then associates with the view when built and docks in the parent form. I can make a constructor just fine and set a static value to make a name appear when it builds. I don't know how you pass the value from the parent view object of a combobox which is bound to the resulting creation of usercontrol. I am up for doing lots of things but I really want to stick to the MVVM method and not do this in code behind of which I already know how to do this.
MVVM method I am following loosely is here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Okay I got it. The MainViewModel in the example contains private methods which are the constructors of the subsequent user controls. The main window binds to the combobox fine but needs more detail on the member of the combobox it needs to relate to a new constructor. This needs to be set and bound to a property bound to the element in XAML 'selected value' of the combobox. Once this property knows it is bound it can later on be used in an internal method in the ViewModelCode that is databound to inform the constructor of what the passed in Person object needs to be.
I see a lot of people with situations similar to mine but different so I thought I would post this if anyone may find it useful later. The only word I would add is that I believe you need to inherit "INotifyPropertyChanged" class but in my example it is in an abstract class two classes inheritance levels down so I felt it was not necessary to redo everything to show a simpler example as I got what I needed.
XAML:
<ComboBox Height="30" Width="170" Margin="10" x:Name="combopersons"
FontSize="20"
ItemsSource="{Binding Path=People}"
DisplayMemberPath="FirstName"
SelectedValuePath="PersonId"
SelectedValue="{Binding Path=CurrentUser}" />
Fields:
Person _currentPerson;
ReadOnlyCollection<Person> _people;
ObservableCollection<WorkspaceViewModel> _workspaces;
string _curuser;
string u = WindowsIdentity.GetCurrent().Name.Split('\\')[1];
public string CurrentUser { get; set; }
ExpensesEntities ee = new ExpensesEntities();
public ReadOnlyCollection<Person> People
{
get
{
if (_people == null)
{
List<Person> persns = this.GetPeople();
_people = new ReadOnlyCollection<Person>(persns);
}
return _people;
}
}
Constructor:
public MainWindowViewModel()
{
_curuser = ee.tePersons.Where(n => n.FirstName == u)
.Select(x => x.PersonID).FirstOrDefault().ToString();
CurrentUser = _curuser;
}
Helper Methods:
List<Person> GetPeople()
{
//ExpensesEntities ee = new ExpensesEntities();
return ee.tePersons.Select(x => new Person
{
PersonId = x.PersonID,
FirstName = x.FirstName
}).ToList();
}
int ConvertToNumber(string s)
{
try
{
return Convert.ToInt32(s);
}
catch (FormatException e)
{
return 0;
}
}
void SetCurrentUser()
{
int currentID = ConvertToNumber(CurrentUser);
_currentPerson = ee.tePersons
.Where(i => i.PersonID == currentID)
.Select(p => new Person
{
PersonId = p.PersonID,
FirstName = p.FirstName
}).FirstOrDefault();
}
Constructor in MainViewModel of CHILD View Model:
void MoneyEntry()
{
SetCurrentUser();
MoneyEntryViewModel money = new MoneyEntryViewModel(_currentPerson);
this.Workspaces.Add(money);
this.SetActiveWorkspace(money);
}