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..
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 have the visibility of a progress bar bound to The following property within my viewmodel:
public string CalcProgVisibility
{
get
{
return Calculation.CalcProgVisibility;
}
set
{
}
}
Calculation is my model, which can change the value. When the value changes within the model, what do I need to do to make sure the view is aware of this change?
EDIT:
Here is the property within my model too. I am using onpropertychanged but its not making it to the view.
I am changing the value within the model, the view is bound to my viewmodel and the viewmodel si trying to return a value taken from the model. I am updating the value on the model, and cannot push the fact that it has updated the value all the way down to the view, I can only get the viewmodel to see it has changed...
I updated the entire code. I hope it's clear now.
Define your control BindingMode = TwoWay
<TextBox Visibility="{Binding Path=CalcProgVisibility, Mode=TwoWay}"...
and call the OnPropertyChanged method on the setter of the property in your view model and also in your model
//Model
public class Calculation : INotifyPropertyChanged
{
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
//ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Calculation model)
{
this.CalcProgVisibility = model.CalcProgVisibility;
model.PropertyChanged += (s, e) => UpdateEntity(s as Calculation);
}
private void UpdateEntity(Calculation source)
{
CalcProgVisibility = source.CalcProgVisibility;
}
private string _calcProgVisibility;
public string CalcProgVisibility
{
get { return _calcProgVisibility; }
set
{
_calcProgVisibility = value;
OnPropertyChanged("CalcProgVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
RaisePropertyChanged(propertyName);
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Your Viewmodel has to implement the INotifyPropertyChanged Interface. To fire it in your case your viewmodel must also be aware of changes in your model object. So your model object could also implement INotifyPropertyChanged, or you use some form of the observer pattern.
If your model implements INotifyPropertyChanged, your viewmodel must manually register for this event and implement an handler. This could in turn trigger the PropertyChange event of the viewmodel then.
Another but in my opinion ugly way would be to scan (per timer or background thread) through your viemodel and check if a value changed since the last scan and then trigger a property changed event.
The first solution could look like this:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace StackOverflow
{
[TestClass]
public class IntegrationTest
{
[TestMethod]
public void NotifyPropertyChangeShouldFireOnViewModelWhenModelChanges()
{
//Arrange
Model model = new Model();
ViewModel sut = new ViewModel(model);
bool notifyPropertyChangeOnViewModelWasCalled = false;
sut.PropertyChanged += (sender, e) => { notifyPropertyChangeOnViewModelWasCalled = true; };
//Act
model.CalcValue = 4711;
//Assert
Assert.IsTrue(notifyPropertyChangeOnViewModelWasCalled, "NotifyPropertyChange was not fired on ViewModel");
}
}
public class ObjectWithNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Model : ObjectWithNotifyPropertyChanged
{
private double calcValue;
public double CalcValue
{
get
{
return calcValue;
}
set
{
if (calcValue != value)
{
calcValue = value;
RaisePropertyChanged();
}
}
}
}
public class ViewModel : ObjectWithNotifyPropertyChanged
{
public ViewModel(Model model)
{
this.model = model;
model.PropertyChanged += model_PropertyChanged;
}
void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CalcValue":
RaisePropertyChanged("CalcValue");
break;
}
}
private Model model;
public double CalcValue
{
get
{
return model.CalcValue;
}
}
}
}
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
}
}
So I am trying to implement the MVVM pattern in a simple sample app. Essentially my app allows a user to choose from a list of search providers in a SettingsPage, and then in the MainPage when the user clicks the 'search' button he or she will be navigated to the search provider's website. Everything seems to work ok, no errors, except when navigating directly back to MainPage from SettingsPage the search property does not seem to be updated. Everything is fine though when the application is completely exited and launched fresh. What I have is as follows
MainPage.xaml.cs
void search_Click(object sender, EventArgs e)
{
TheBrowser.Navigate(App.ViewModel.SearchProvider.Address);
}
App.xaml.cs
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
MainViewMode.cs
public ListItem SearchProvider { get; private set; }
public MainViewModel()
{
SearchProvider = Settings.SearchProvider.Value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and in my SettingsPage is where I am allowin ga user to select a search provider
SettingsPage.xaml.cs
private void PopulateSearchProviderList()
{
searchProviderList = new ObservableCollection<ListItem>();
searchProviderList.Add(new ListItem { Name = "Bing", Address = "http://www.bing.com" });
searchProviderList.Add(new ListItem { Name = "Google", Address = "http://www.google.com" });
SearchProviderListPicker.ItemsSource = searchProviderList;
}
private void stk_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
if (SearchProviderListPicker.SelectedIndex != -1)
{
var selectedItem = (sender as StackPanel).DataContext as TestApp.Classes.ListItem;
Settings.SearchProvider.Value = selectedItem; //Setting the search provider
}
}
and finally my ListItem class which is fairly straightforward
ListItem.cs
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
So essentially I am not updating the ViewModel correctly based on the SettingsPage, but I am unsure of how to go about this properly.
You have to call the OnNotifyPropertyChanged("propertyName") for the item to update in the UI.
For example (assuming the Name and Address properties are bound to your UI elements.)
private string name;
private string address;
public string Name
{
get { return name;}
set {
name = value;
OnNotifyPropertyChanged("Name");
}
}
public string Address
{
get { return address; }
set {
address = value ;
OnNotifyPropertyChanged("Address");
}
}
There are a few issues I can see. We'll start from there.
Your MainViewModel needs to implement INotifyPropertyChanged see here
Your SearchProvider setter needs to raise PropertyChanged
You need to set the value of the SearchProvider. Currently that is only performed in the constructor which is probably why you are seeing things working on app startup only.
You need to make sure you are correctly binding the value of SearchProvider in your xaml. If you post your xaml we can check that out too.
In your ViewModel, add:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Update the SearchProvider property to something like:
private ListItem searchProvider;
public ListItem SearchProvider
{
get { return searchProvider; }
set
{
searchProvider = value;
OnPropertyChanged();
}
}
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
}
}