I have a simple scenario with a View, a ViewModel and a custom type class.
The model class is something like:
public class Person : Validation.DataError, INotifyPropertyChanged
{
#region INotifyProperty
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public global::System.String name
{
get
{
return _name;
}
set
{
_name= value;
RaisePropertyChanged("name");
}
}
private global::System.String _name;
}
In the ViewModel I have a Person property:
private Model.Person person;
public Model.Person Person
{
get
{
return person;
}
set
{
this.person= value;
this.RaisePropertyChanged("Person");
this.SavePersonCommand.OnCanExecuteChanged();
}
}
In my View I have a textbox that is bound to Person.name
So the ViewModel is not executing the set method because the Person object is still the same... it is executing the set method in the Model property.
I want to let the user change the person name and make a call to another method (search through a web service and other stuff...) and I think this functionality should be in the ViewModel.
I'm using Messenger from MVVM Light toolkit to communicate between different viewmodels and between views and viewmodels.
Now I don't know if I should use a mediator too for this or if I should know another way to solve this.
Just subscribe to the PropertyChanged event of the Person in your ViewModel and check for the "Name" property, or whatever you wanna do:
Person.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Person_PropertyChanged);
void Person_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == "Name")
{
//do something
}
}
Related
I am using MVVM light in WPF. The Model class properties are constantly changed due to changes of the underlying data-access layer.
Model:
public class SBE_V1_Model : ViewModelBase
{
public SBE_V1_Model(String name)
{
Name = "MAIN." + name;
SetupClient();
}
private void SetupClient()
{
client = new ConnectionHelper(this);
client.Connect(Name);
}
public Boolean Output
{
get
{
return _Output;
}
set
{
if (value != this._Output)
{
Boolean oldValue = _Output;
_Output = value;
RaisePropertyChanged("Output", oldValue, value, true);
}
}
}
}
If Output property changes, then bindings will be notified, so this works. But what is the correct way to update the property from the data-access source, which knows the new value?
public class ConnectionHelper : ViewModelBase
{
public Boolean Connect(String name)
{
Name = name;
tcClient = new TcAdsClient();
try
{
dataStream = new AdsStream(4);
binReader = new AdsBinaryReader(dataStream);
tcClient.Connect(851);
SetupADSNotifications();
return true;
}
catch (Exception ee)
{
return false;
}
}
private void tcClient_OnNotification(object sender, AdsNotificationEventArgs e)
{
String prop;
notifications.TryGetValue(e.NotificationHandle, out prop);
switch (prop)
{
case "Output":
Boolean b = binReader.ReadBoolean();
RaisePropertyChanged("Output", false,b, true);
break;
}
}
}
Why doesnt the RaisePropertyChanged call in connectionhelper update the property of the model? If this is the wrong way, should I set up some kind of listener?
In your SBE_V1_Model class you should subscribe to receive PropertyChange notifications from the ConnectionHelper ViewModel.
// Attach EventHandler
ConnectionHelper.PropertyChanged += OnConnectionHelperPropertyChanged;
...
// When property gets changed, raise the PropertyChanged
// event of the ViewModel copy of the property
OnConnectionHelperPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Something") //your ConnectionHelper property name
RaisePropertyChanged("Ouput");
}
Also look into MVVM light messenger. Here is a link you might be interested from StackOverflow.
You should only use PropertyChanged in the ViewModel, not in the Model.
You can use PropertyChanged in Models only in special times.
RaisePropertyChanged("Output", false,b, true);
In that PropertyChanged you are always saying that Output Property was changed.
I recommend you implement INotifyPropertyChanged
class MyClass : INotifyPropertyChanged
{
public bool MyProperty{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
To notify any property change you have to use:
OnPropertyChanged("MyProperty");
I'm using ObservableCollection<MyItemViewModel> myItemVMList as the ItemsSouce. I am able to bind Command perfectly but the INotifyPropertyChanged isn't working. Here's my code:
public class MyItemViewModel: INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
public MyItem MyItem { set; get; }
private RelayCommand _ChangeMyItemPropertyValue;
public ICommand ChangeMyItemPropertyValueCommand {
get {
if (_ChangeMyItemPropertyValue == null) _ChangeMyItemPropertyValue = new RelayCommand(o => ChangeMyItemPropertyValue());
return _ChangeMyItemPropertyValue;
}
}
private ChangeMyItemPropertyValue() {
MyItem.SomeProperty = someDifferentValue;
// NEITHER OF THESE CALLS WORK
OnPropertyChanged("MyItem.SomeProperty");
OnPropertyChagned("SomeProperty");
}
}
Needless to say, the binding is set as Content="{Binding MyItem.SomeProperty}" inside the DataTemplate, and it shows the correct value. Problem is it isn't updated when I run the function.
SideNote: If I implement the INotifyPropertyChanged inside MyItem it works, but I want it on the ViewModel.
If I implement the INotifyPropertyChanged inside MyItem it works, but I want it on the ViewModel
Yes, because that's how it's designed. How it's supposed to know it should listen to your ViewModel's property changed event? It doesn't bind to it, it binds to the model, so it listens to the changes on the model.
You have two choices basically:
Implement INotifyPropertyChanged on MyItem
Bind to the ViewModel
Content="{Binding SomeProperty}"
And add a wrapper property:
public string SomeProperty
{
get { return MyItem.SomeProperty; }
set
{
MyItem.SomeProperty = value;
OnPropertyChanged("SomeProperty");
}
}
You should prefer binding to the ViewModel if you want to follow MVVM practices.
Side note: If you add [CallerMemberName] to OnPropertyChanged like this:
protected void OnPropertyChanged([CallerMemberName] string name = null) {
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
You'll be able to skip the property name altogether:
public string SomeProperty
{
get { return MyItem.SomeProperty; }
set
{
MyItem.SomeProperty = value;
OnPropertyChanged(); // <-- no need for property name anymore
}
}
I want use MVVM pattern in the WP app. I have a some idea about this pattern. But I do not understand some things. I do not know whether it's good practice or not to do so.
So, I have Model.
Model is a data structure. The set of fields and properties.
Model
public class Person : INotifyPropertyChanged
{
private string name;
private GeoCoordinate coordinate;
public string Name
{
get
{
return name;
}
set
{
if (this.name != value)
{
this.name = value;
this.RaisePropertyChanged("Name");
}
}
}
public GeoCoordinate Coordinate
{
get
{
return this.coordinate;
}
set
{
if (this.coordinate != value)
{
this.coordinate = value;
this.RaisePropertyChanged("Coordinate");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModel initializes the fields of the model.
ViewModel
public class PersonViewModel : INotifyPropertyChanged
{
public Person User
{
get;
private set;
}
public PersonViewModel()
{
this.User = new Person();
}
public LoadData()
{
Service.GetUser((result) =>
{
this.User.Name = result.Name;
this.User.Coordinate = result.Coordinate;
});
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View
PersonViewModel _viewModel;
this.DataContext = _viewModel;
_viewModel.LoadData();
Here are the moments that I would like to clarify:
How can ViewModel notify View about error in loading data, end loading?
Can I pass part of date to View (without databinding, technically it is possible, I mean, this is permissible under the pattern)?
For exaple, in ViewModel:
public LoadData(Action<Person, Exception> act)
{
Service.GetUser((result, error) =>
{
if (error != null)
{
act.Invoke(null, error);
}
else
{
this.User.Name = result.Name;
this.User.Coordinate = result.Coordinate;
act.Invoke(result, null);
}
});
}
In View:
_viewModel.LoadData((result, error) =>
{
if (error != null)
{
//error data loading
}
else
{
//successfully loading
}
});
This is terrible, probably this approach destroys the whole concept. But, for example, I work with Jeff Wilcox static map.
<jwMaps:StaticMap
Provider="Bing"
Visibility="Visible">
<jwMaps:StaticMap.MapCenter>
<geo:GeoCoordinate
Latitude ="50"
Longitude="50" />
</jwMaps:StaticMap.MapCenter>
</jwMaps:StaticMap>
I can't binding coordinate to this control. I tried, dont't work. If use
StaticMap.MapCenter =
new GeoCoordinate() { Latitude = user.Latitude, Longitude = user.Longitude };
then works.
In the case of a delegate I could do it in a successfully branch...
Please help advice.
you can send messages from your viewmodel to your view for that you can use Messenger class of mvvm light.
And i will not broke the MVVM pattern Because What MVVM pattern is that you have to do your logic part in ViewModel but it does not mean that you can not use Your Page.xaml.cs Code behind.you can make decisions on visibility or any UI related Code in your Code behind.Also it is not a Hard Coded Pattern actually it is made to organize your project in much better and simpler way but if you have to communicate between view and your viewModel for some Reason that you can not solve from viewmodel than you can use you codebehind.
I am not an expert but this is what i think..hope some updates on this topic..
So I have a class with 40 or so properties that are updated from communication with a micro controller. This class implements INotifyPropertyChanged.
Loose Example:
private int _Example;
public int Example
{
get
{
return _Example;
}
set
{
_Example = value;
OnPropertyChange("Example");
}
}
And the OnPropertyChange function:
protected void OnPropertyChange(string p_Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p_Property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
Binding (many of these)
Second_Class_Control.DataBindings.Clear();
Second_Class_Control.DataBindings.Add("My_Property", FirstClass, "Example");
In the main form I've set up binds to display and react to these values. One of those happens to land on another property in a another class. I happened to place a breakpoint in the set function of this property, and noticed it was being called any time any property from the first class changed.
Is this the correct behavior? I don't notice any performance hits but I plan on having many instances of these classes running together and wasn't expecting this.
Thanks
Hmm.. I noticed that you have the your OnPropertyChange virtual. Why is this, are you making a override somewhere?
I usually creates it like this :
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
then for the usage :
public class MainWindowViewModel : ViewModelBase
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
}
I have created WPF MVVM application, and set WPFToolkit DataGrid binding to DataTable so I want to know how to implement DataTable property to notify changed. Currently my code is like below.
public DataTable Test
{
get { return this.testTable; }
set
{
...
...
base.OnPropertyChanged("Test");
}
}
public void X()
{
this.Test.Add(...); // I suppose this line will call to getter first (this.Test = get Test) and then it will call add letter, this mean that setter scope will never fire.
base.OnPropertyChanged("Test"); // my solution is here :) but I hope it has better ways.
}
Is it has another solution for this problem?
There are 2 ways your Table data could change: Either an element could be added/removed from the collection, or some properties from within an element could change.
The first scenario is easy to handle: make your collection an ObservableCollection<T>. Invoking .Add(T item) or .Remove(item) on your table will fire a change notification through to the View for you (and the table will update accordingly)
The second scenario is where you need your T object to implement INotifyPropertyChanged...
Ultimately your code should look something like this:
public class MyViewModel
{
public ObservableCollection<MyObject> MyData { get; set; }
}
public class MyObject : INotifyPropertyChanged
{
public MyObject()
{
}
private string _status;
public string Status
{
get { return _status; }
set
{
if (_status != value)
{
_status = value;
RaisePropertyChanged("Status"); // Pass the name of the changed Property here
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Now set the datacontext of your View to be an instance of your ViewModel, and bind to the collection, like:
<tk:DataGrid
ItemsSource="{Binding Path=MyData}"
... />
Hope this helps :)
Ian