Я создаю клиентское приложение WP7, которое взаимодействует с веб-службой (например, SOAP) с использованием Mvvm-Light.
У меня есть ViewModel, которая реализует INotifyPropertyChanged
и вызывает RaisePropertryChanged
с установленным широковещательным флагом.
И мое представление (XAML), и моя модель (которая выполняет HTTP-запросы к веб-службе) подписываются на изменения свойств. XAML, очевидно, из-за INotifyPropertyChanged
и моей модели, позвонив
Messenger.Default.Register<SysObjectCreatedMessage>(this, (action) => SysObjectCreatedHandler(action.SysObject));
Боюсь, этот шаблон не сработает из-за следующего:
Когда я получаю данные из веб-службы, я устанавливаю свойства своей ViewModel (используя DispatcherHelper.CheckBeginInvokeUI
). На самом деле я использую Reflection, и мой вызов выглядит так:
GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() => pinfo.SetValue(this, e.Response, null));
Вот в чем проблема: результирующий набор свойств, вызванный этим вызовом SetValue, заставляет мое свойство set
вызывать RaisePropertryChanged
, заставляя меня отправлять данные, которые я только что получил с сервера, обратно на него.
EDIT — Добавление дополнительного контекста по предложению Пера Джона
Вот некоторые из моих XAML. Мой класс GarageDoorOpener имеет свойство GarageDoorOpened.
На сервере управления домом есть куча объектов гаражных ворот, у которых есть логическое свойство, определяющее, открыты они или нет. Я могу получить к ним доступ, используя HTTP POST в форме:
http://server/sys/Home/Upstairs/Garage/West Дверь гаража? f??GarageDoorOpened
Результирующее тело HTTP будет содержать значение True или False.
Та же модель применяется к другим объектам в доме с другими типами (строки, целые числа и т. д.).
На данный момент я просто сосредотачиваюсь на открывателях гаражных ворот.
Модель просмотра гаражных ворот выглядит так:
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);
}
}
}
Класс SysObject, от которого он наследуется, выглядит так (упрощенно):
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);
}
}
}
Моя «модель» называется PremiseServer. Он обертывает Http POST и обрабатывает, заставляя сервер время от времени «запрашивать» последние данные. Я планирую в конечном итоге реализовать уведомления, но пока я голосую. Он использует немного отражения для динамического преобразования результатов HTTP в наборы свойств. По сути это выглядит так (я очень горжусь собой за это, хотя, наверное, мне должно быть стыдно).
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();
}
Обратите внимание, что OnRequery — не единственное место, из которого я в конечном итоге буду вызывать SendGetProperty; это просто для инициализации лесов на данный момент. Идея состоит в том, что у меня может быть общий фрагмент кода, который получает "сообщение от сервера" и переводит его в вызов SysObject.Property.SetValue()...
ЗАВЕРШИТЬ РЕДАКТИРОВАНИЕ
Мне нужен шаблон, который позволит мне привязываться к моим данным на стороне XAML, а также на стороне моей модели потокобезопасным способом.
Предложения?
Спасибо!