Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Make the programming model for binding hooks easier

The data that ExecuteHook gives you made it a total pain to actually
write useful hooks. Give the hook producer way more data, but only
produce it if asked, since it's (relatively) expensive.
  • Loading branch information...
commit 49c59f4901c4a51a1166f53e330169f6e743407a 1 parent ed9f58b
@paulcbetts paulcbetts authored
View
8 ReactiveUI.Routing/AutoDataTemplateBindingHook.cs
@@ -36,12 +36,14 @@ public class AutoDataTemplateBindingHook : IPropertyBindingHook
#endif
);
- public bool ExecuteHook(object source, object target, string sourceProperty, string targetProperty, BindingDirection direction)
+ public bool ExecuteHook(object source, object target, Func<IObservedChange<object, object>[]> getCurrentViewModelProperties, Func<IObservedChange<object, object>[]> getCurrentViewProperties, BindingDirection direction)
{
- var itemsControl = target as ItemsControl;
+ var viewProperties = getCurrentViewProperties();
+
+ var itemsControl = viewProperties.Last().Sender as ItemsControl;
if (itemsControl == null) return true;
- if (!targetProperty.EndsWith("ItemsSource", StringComparison.OrdinalIgnoreCase)) return true;
+ if (viewProperties.Last().PropertyName != "ItemsSource") return true;
if (itemsControl.ItemTemplate != null) return true;
View
14 ReactiveUI.Tests/PropertyBindingTest.cs
@@ -72,6 +72,7 @@ public class PropertyBindView : Control, IViewFor<PropertyBindViewModel>
public ListBox SomeListBox;
public TextBox Property2;
public PropertyBindFakeControl FakeControl;
+ public ItemsControl FakeItemsControl;
public PropertyBindView()
{
@@ -79,6 +80,7 @@ public PropertyBindView()
SomeListBox = new ListBox();
Property2 = new TextBox();
FakeControl = new PropertyBindFakeControl();
+ FakeItemsControl = new ItemsControl();
}
}
@@ -242,5 +244,17 @@ public void ViewModelNullableToViewNullable()
view.FakeControl.NullableDouble = 0.0;
Assert.Equal(0.0, vm.NullableDouble);
}
+
+ [Fact]
+ public void ItemsControlShouldGetADataTemplate()
+ {
+ var vm = new PropertyBindViewModel();
+ var view = new PropertyBindView() {ViewModel = vm};
+
+ Assert.Null(view.FakeItemsControl.ItemTemplate);
+ view.OneWayBind(vm, x => x.SomeCollectionOfStrings, x => x.FakeItemsControl.ItemsSource);
+
+ Assert.NotNull(view.FakeItemsControl.ItemTemplate);
+ }
}
}
View
2  ReactiveUI/Interfaces.cs
@@ -398,7 +398,7 @@ public enum BindingDirection
public interface IPropertyBindingHook
{
- bool ExecuteHook(object source, object target, string sourceProperty, string targetProperty, BindingDirection direction);
+ bool ExecuteHook(object source, object target, Func<IObservedChange<object, object>[]> getCurrentViewModelProperties, Func<IObservedChange<object, object>[]> getCurrentViewProperties, BindingDirection direction);
}
public interface IViewFor
View
113 ReactiveUI/PropertyBinding.cs
@@ -294,16 +294,10 @@ class PropertyBinderImplementation : IPropertyBinderImplementation
}
});
- var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
- var shouldBind = hooks.Aggregate(true, (acc, x) =>
- acc && x.ExecuteHook(viewModel, view, vmString, vString, BindingDirection.TwoWay));
+ var ret = evalBindingHooks(viewModel, view, vmPropChain, viewPropChain);
+ if (ret != null) return ret;
- if (!shouldBind) {
- this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
- return Disposable.Empty;
- }
-
- var ret = changeWithValues.Subscribe(isVmWithLatestValue => {
+ ret = changeWithValues.Subscribe(isVmWithLatestValue => {
if (isVmWithLatestValue == null) return;
if (isVmWithLatestValue.Item2) {
@@ -320,6 +314,7 @@ class PropertyBinderImplementation : IPropertyBinderImplementation
return ret;
}
+
public IDisposable OneWayBind<TViewModel, TView, TVMProp, TVProp>(
TViewModel viewModel,
TView view,
@@ -343,16 +338,8 @@ class PropertyBinderImplementation : IPropertyBinderImplementation
throw new ArgumentException(String.Format("Can't convert {0} to {1}. To fix this, register a IBindingTypeConverter", typeof (TVMProp), viewType));
}
- var vString = String.Format("{0}.{1}", viewType.Name, String.Join(".", viewPropChain));
-
- var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
- var shouldBind = hooks.Aggregate(true, (acc, x) =>
- acc && x.ExecuteHook(viewModel, view, vmString, vString, BindingDirection.OneWay));
-
- if (!shouldBind) {
- this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
- return Disposable.Empty;
- }
+ var ret = evalBindingHooks(viewModel, view, vmPropChain, viewPropChain);
+ if (ret != null) return ret;
return Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty)
.SelectMany(x => {
@@ -369,16 +356,9 @@ class PropertyBinderImplementation : IPropertyBinderImplementation
}
var viewPropChain = Reflection.ExpressionToPropertyNames(viewProperty);
- var vString = String.Format("{0}.{1}", typeof(TView), String.Join(".", viewPropChain));
- var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
- var shouldBind = hooks.Aggregate(true, (acc, x) =>
- acc && x.ExecuteHook(viewModel, view, vmString, vString, BindingDirection.OneWay));
-
- if (!shouldBind) {
- this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
- return Disposable.Empty;
- }
+ var ret = evalBindingHooks(viewModel, view, vmPropChain, viewPropChain);
+ if (ret != null) return ret;
return Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty)
.SelectMany(x => {
@@ -408,32 +388,17 @@ class PropertyBinderImplementation : IPropertyBinderImplementation
if (viewProperty == null) {
var viewPropChain = Reflection.getDefaultViewPropChain(view, Reflection.ExpressionToPropertyNames(vmProperty));
- var vString = String.Format("{0}.{1}", typeof (TView), String.Join(".", viewPropChain));
- var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
- var shouldBind = hooks.Aggregate(true, (acc, x) =>
- acc && x.ExecuteHook(viewModel, view, vmString, vString, BindingDirection.OneWay));
-
- if (!shouldBind) {
- this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
- return Disposable.Empty;
- }
+ var ret = evalBindingHooks(viewModel, view, vmPropChain, viewPropChain);
+ if (ret != null) return ret;
return Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty)
.Select(selector)
.Subscribe(x => Reflection.SetValueToPropertyChain(view, viewPropChain, x, false));
} else {
var viewPropChain = Reflection.ExpressionToPropertyNames(viewProperty);
- var vString = String.Format("{0}.{1}", typeof(TView), String.Join(".", viewPropChain));
-
- var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
- var shouldBind = hooks.Aggregate(true, (acc, x) =>
- acc && x.ExecuteHook(viewModel, view, vmString, vString, BindingDirection.OneWay));
-
- if (!shouldBind) {
- this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
- return Disposable.Empty;
- }
+ var ret = evalBindingHooks(viewModel, view, vmPropChain, viewPropChain);
+ if (ret != null) return ret;
return Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty)
.Select(selector)
@@ -455,34 +420,19 @@ class PropertyBinderImplementation : IPropertyBinderImplementation
var vmString = String.Format("{0}.{1}", typeof (TViewModel).Name, String.Join(".", vmPropChain));
if (viewProperty == null) {
- var viewPropChain = Reflection.getDefaultViewPropChain(view,
- Reflection.ExpressionToPropertyNames(vmProperty));
- var vString = String.Format("{0}.{1}", typeof (TView), String.Join(".", viewPropChain));
-
- var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
- var shouldBind = hooks.Aggregate(true, (acc, x) =>
- acc && x.ExecuteHook(viewModel, view, vmString, vString, BindingDirection.AsyncOneWay));
+ var viewPropChain = Reflection.getDefaultViewPropChain(view, Reflection.ExpressionToPropertyNames(vmProperty));
- if (!shouldBind) {
- this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
- return Disposable.Empty;
- }
+ var ret = evalBindingHooks(viewModel, view, vmPropChain, viewPropChain);
+ if (ret != null) return ret;
return Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty)
.SelectMany(selector)
.Subscribe(x => Reflection.SetValueToPropertyChain(view, viewPropChain, x, false));
} else {
var viewPropChain = Reflection.ExpressionToPropertyNames(viewProperty);
- var vString = String.Format("{0}.{1}", typeof(TView), String.Join(".", viewPropChain));
-
- var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
- var shouldBind = hooks.Aggregate(true, (acc, x) =>
- acc && x.ExecuteHook(viewModel, view, vmString, vString, BindingDirection.OneWay));
- if (!shouldBind) {
- this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
- return Disposable.Empty;
- }
+ var ret = evalBindingHooks(viewModel, view, vmPropChain, viewPropChain);
+ if (ret != null) return ret;
return Reflection.ViewModelWhenAnyValue(viewModel, view, vmProperty)
.SelectMany(selector)
@@ -490,6 +440,35 @@ class PropertyBinderImplementation : IPropertyBinderImplementation
}
}
+ IDisposable evalBindingHooks<TViewModel, TView>(TViewModel viewModel, TView view, string[] vmPropChain, string[] viewPropChain)
+ where TViewModel : class
+ where TView : IViewFor
+ {
+ var hooks = RxApp.GetAllServices<IPropertyBindingHook>();
+ var vmFetcher = new Func<IObservedChange<object, object>[]>(() => {
+ IObservedChange<object, object>[] fetchedValues;
+ Reflection.TryGetAllValuesForPropertyChain(out fetchedValues, viewModel, vmPropChain);
+ return fetchedValues;
+ });
+ var vFetcher = new Func<IObservedChange<object, object>[]>(() => {
+ IObservedChange<object, object>[] fetchedValues;
+ Reflection.TryGetAllValuesForPropertyChain(out fetchedValues, view, viewPropChain);
+ return fetchedValues;
+ });
+ var shouldBind = hooks.Aggregate(true, (acc, x) =>
+ acc && x.ExecuteHook(viewModel, view, vmFetcher, vFetcher, BindingDirection.TwoWay));
+
+ if (!shouldBind) {
+ var vmString = String.Format("{0}.{1}", typeof (TViewModel).Name, String.Join(".", vmPropChain));
+ var vString = String.Format("{0}.{1}", typeof (TView).Name, String.Join(".", viewPropChain));
+ this.Log().Warn("Binding hook asked to disable binding {0} => {1}", vmString, vString);
+ return Disposable.Empty;
+ }
+
+ return null;
+ }
+
+
MemoizingMRUCache<Tuple<Type, Type>, IBindingTypeConverter> typeConverterCache = new MemoizingMRUCache<Tuple<Type, Type>, IBindingTypeConverter>(
(types, _) =>
RxApp.GetAllServices<IBindingTypeConverter>()
View
33 ReactiveUI/Reflection.cs
@@ -262,6 +262,39 @@ public static bool TryGetValueForPropertyChain<TValue>(out TValue changeValue, o
return true;
}
+ public static bool TryGetAllValuesForPropertyChain(out IObservedChange<object, object>[] changeValues, object current, string[] propNames)
+ {
+ int currentIndex = 0;
+ changeValues = new IObservedChange<object,object>[propNames.Length];
+
+ foreach (var propName in propNames.SkipLast(1)) {
+ if (current == null) {
+ changeValues[currentIndex] = null;
+ return false;
+ }
+
+ var box = new ObservedChange<object, object> { Sender = current, PropertyName = propName };
+ current = GetValueFetcherOrThrow(current.GetType(), propName)(current);
+ box.Value = current;
+
+ changeValues[currentIndex] = box;
+ currentIndex++;
+ }
+
+ if (current == null) {
+ changeValues[currentIndex] = null;
+ return false;
+ }
+
+ changeValues[currentIndex] = new ObservedChange<object, object> {
+ Sender = current,
+ PropertyName = propNames.Last(),
+ Value = GetValueFetcherOrThrow(current.GetType(), propNames.Last())(current)
+ };
+
+ return true;
+ }
+
public static bool SetValueToPropertyChain<TValue>(object target, string[] propNames, TValue value, bool shouldThrow = true)
{
foreach (var propName in propNames.SkipLast(1)) {
Please sign in to comment.
Something went wrong with that request. Please try again.