I am building a WP7 client app that talk to a web service (SOAP like) using Mvvm-Light.
I have a ViewModel that both implements INotifyPropertyChanged
and calls RaisePropertryChanged
with the broadcast flag set.
Both my view (XAML) and my model (which does HTTP requests to the web service) subscribe to property changes. XAML, obviously, because of INotifyPropertyChanged
, and my model by calling
Messenger.Default.Register<SysObjectCreatedMessage>(this, (action) => SysObjectCreatedHandler(action.SysObject));
This pattern is not going to work, i'm afraid, because of the following:
When I get data back from the web service I set properties on my ViewModel (using DispatcherHelper.CheckBeginInvokeUI
). I actually use Reflection and my call looks like this:
GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() => pinfo.SetValue(this, e.Response, null));
Here's the problem: The resultant property set caused by that call to SetValue causes my property set
to call RaisePropertryChanged
causing me to send the data I just got from the server right back to it.
EDIT - Adding more context Per Jon's suggestion
Here's some of my XAML. My GarageDoorOpener class has a GarageDoorOpened property
On the home control server there are a bunch of garage door objects that have a boolean property representing whether they are open or not. I can access these using HTTP POSTs of the form:
http://server/sys/Home/Upstairs/Garage/West Garage Door?f??GarageDoorOpened
The the resultant HTTP body will contain either True or False.
The same model applies to other objects in the home with other types (Strings, Integers, etc...).
For now I'm just focusing on the garage door openers.
The View Model for the garage door looks like this:
public class GarageDoorSensor : SysObject
{
public static new string SysType = "Garage Door Sensor";
public const string GarageDoorOpenedPropertyName = "GarageDoorOpened";
public Boolean _GarageDoorOpened = false;
[SysProperty]
public Boolean GarageDoorOpened
{
get
{
return _GarageDoorOpened;
}
set
{
if (_GarageDoorOpened == value)
{
return;
}
var oldValue = _GarageDoorOpened;
_GarageDoorOpened = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(GarageDoorOpenedPropertyName, oldValue, value, true);
}
}
}
The SysObject class this inherits from looks like this (simplified):
public class SysObject : ViewModelBase
{
public static string SysType = "Object";
public SysObject()
{
Messenger.Default.Send<SysObjectCreatedMessage>(new SysObjectCreatedMessage(this));
}
}
protected override void RaisePropertyChanged<T>(string propertyName, T oldValue, T newValue, bool broadcast)
{
// When we are initilizing, do not send updates to the server
// if (UpdateServerWithChange == true)
// ****************************
// ****************************
//
// HERE IS THE PROBLEM
//
// This gets called whenever a property changes (called from set())
// It both notifies the "server" AND the view
//
// I need a pattern for getting the "SendPropertyChangeToServer" out
// of here so it is only called when properties change based on
// UI input.
//
// ****************************
// ****************************
SendPropertyChangeToServer(propertyName, newValue.ToString());
// Check if we are on the UI thread or not
if (App.Current.RootVisual == null || App.Current.RootVisual.CheckAccess())
{
base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast);
}
else
{
// Invoke on the UI thread
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() =>
base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast));
}
}
private void SendPropertyChangeToServer(String PropertyName, String Value)
{
Messenger.Default.Send<SysObjectPropertyChangeMessage>(new SysObjectPropertyChangeMessage(this, PropertyName, Value));
}
// Called from PremiseServer when a result has been returned from the server.
// Uses reflection to set the appropriate property's value
public void PropertySetCompleteHandler(HttpResponseCompleteEventArgs e)
{
// BUGBUG: this is wonky. there is no guarantee these operations will modal. In fact, they likely
// will be async because we are calling CheckBeginInvokeUI below to wait on the UI thread.
Type type = this.GetType();
PropertyInfo pinfo = type.GetProperty((String)e.context);
// TODO: Genericize this to parse not string property types
//
if (pinfo.PropertyType.Name == "Boolean")
{
GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() => pinfo.SetValue(this, Boolean.Parse(e.Response), null));
//pinfo.SetValue(this, Boolean.Parse(e.Response), null);
}
else
{
GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() => pinfo.SetValue(this, e.Response, null));
//pinfo.SetValue(this, e.Response, null);
}
}
}
My "model" is called PremiseServer. It wraps Http POSTs and handles causing the server to be "queried" for the latest data every once and a while. I plan on eventually implementing notifications, but for now i am polling. It uses a bit of Reflection to dynamically translate the HTTP results into property sets. At it's core it looks like this (i'm pretty proud of myself for this, although I probably should be ashamed instead).
protected virtual void OnRequery()
{
Debug.WriteLine("OnRequery");
Type type;
foreach (SysObject so in sysObjects)
{
type = so.GetType();
PropertyInfo[] pinfos = type.GetProperties();
foreach (PropertyInfo p in pinfos)
{
if (p.IsDefined(typeof(SysProperty),true))
SendGetProperty(so.Location, p.Name, so, so.PropertySetCompleteHandler);
}
}
}
protected delegate void CompletionMethod(HttpResponseCompleteEventArgs e);
protected void SendGetProperty(string location, string property, SysObject sysobject, CompletionMethod cm)
{
String url = GetSysUrl(location.Remove(0, 5));
Uri uri = new Uri(url + "?f??" + property);
HttpHelper helper = new HttpHelper(uri, "POST", null, true, property);
Debug.WriteLine("SendGetProperty: url = <" + uri.ToString() + ">");
helper.ResponseComplete += new HttpResponseCompleteEventHandler(cm);
helper.Execute();
}
Note that OnRequery is not the only place I'll eventually be calling SendGetProperty from; it's just there for initialization scaffolding for now. The idea is that I can have a generic piece of code that gets a "message from the server" and translates it into a SysObject.Property.SetValue() call...
END EDIT
I need a pattern that will let me both bind to my data on the XAML side but also on my model side in a thread safe way.
Suggestions?
Thanks!