@@ -539,6 +539,26 @@ public static IDisposable AsyncOneWayBind<TViewModel, TView, TProp, TOut>(
539539 {
540540 return binderImplementation . AsyncOneWayBind ( viewModel , view , vmProperty , null , x => selector ( x ) . ToObservable ( ) , fallbackValue ) ;
541541 }
542+
543+ /// <summary>
544+ /// BindTo takes an Observable stream and applies it to a target
545+ /// property. Conceptually it is similar to "Subscribe(x =>
546+ /// target.property = x)", but allows you to use child properties
547+ /// without the null checks.
548+ /// </summary>
549+ /// <param name="target">The target object whose property will be set.</param>
550+ /// <param name="property">An expression representing the target
551+ /// property to set. This can be a child property (i.e. x.Foo.Bar.Baz).</param>
552+ /// <returns>An object that when disposed, disconnects the binding.</returns>
553+ public static IDisposable BindTo < TValue , TTarget , TTValue > (
554+ this IObservable < TValue > This ,
555+ TTarget target ,
556+ Expression < Func < TTarget , TTValue > > property ,
557+ Func < TValue > fallbackValue = null ,
558+ object conversionHint = null )
559+ {
560+ return binderImplementation . BindTo ( This , target , property , fallbackValue ) ;
561+ }
542562 }
543563
544564 /// <summary>
@@ -739,6 +759,23 @@ IDisposable AsyncOneWayBind<TViewModel, TView, TProp, TOut>(
739759 Func < TOut > fallbackValue = null )
740760 where TViewModel : class
741761 where TView : IViewFor ;
762+
763+ /// <summary>
764+ /// BindTo takes an Observable stream and applies it to a target
765+ /// property. Conceptually it is similar to "Subscribe(x =>
766+ /// target.property = x)", but allows you to use child properties
767+ /// without the null checks.
768+ /// </summary>
769+ /// <param name="target">The target object whose property will be set.</param>
770+ /// <param name="property">An expression representing the target
771+ /// property to set. This can be a child property (i.e. x.Foo.Bar.Baz).</param>
772+ /// <returns>An object that when disposed, disconnects the binding.</returns>
773+ IDisposable BindTo < TValue , TTarget , TTValue > (
774+ IObservable < TValue > This ,
775+ TTarget target ,
776+ Expression < Func < TTarget , TTValue > > property ,
777+ Func < TValue > fallbackValue = null ,
778+ object conversionHint = null ) ;
742779 }
743780
744781 public class PropertyBinderImplementation : IPropertyBinderImplementation
@@ -870,7 +907,7 @@ public IDisposable Bind<TViewModel, TView, TVMProp, TVProp, TDontCare>(
870907 }
871908 } ) ;
872909
873- var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain ) ;
910+ var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain , BindingDirection . TwoWay ) ;
874911 if ( ret != null ) return ret ;
875912
876913 ret = changeWithValues . Subscribe ( isVmWithLatestValue => {
@@ -944,6 +981,8 @@ public IDisposable OneWayBind<TViewModel, TView, TVMProp, TVProp>(
944981 {
945982 var vmPropChain = Reflection . ExpressionToPropertyNames ( vmProperty ) ;
946983 var vmString = String . Format ( "{0}.{1}" , typeof ( TViewModel ) . Name , String . Join ( "." , vmPropChain ) ) ;
984+ var source = default ( IObservable < TVProp > ) ;
985+ var fallbackWrapper = default ( Func < TVProp > ) ;
947986
948987 if ( viewProperty == null ) {
949988 var viewPropChain = Reflection . getDefaultViewPropChain ( view , Reflection . ExpressionToPropertyNames ( vmProperty ) ) ;
@@ -955,16 +994,20 @@ public IDisposable OneWayBind<TViewModel, TView, TVMProp, TVProp>(
955994 throw new ArgumentException ( String . Format ( "Can't convert {0} to {1}. To fix this, register a IBindingTypeConverter" , typeof ( TVMProp ) , viewType ) ) ;
956995 }
957996
958- var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain ) ;
997+ var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain , BindingDirection . OneWay ) ;
959998 if ( ret != null ) return ret ;
960999
961- return Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
1000+ source = Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
9621001 . SelectMany ( x => {
9631002 object tmp ;
964- if ( ! converter . TryConvert ( x , viewType , conversionHint , out tmp ) ) return Observable . Empty < object > ( ) ;
965- return Observable . Return ( tmp ) ;
966- } )
967- . Subscribe ( x => Reflection . SetValueToPropertyChain ( view , viewPropChain , x , false ) ) ;
1003+ if ( ! converter . TryConvert ( x , viewType , conversionHint , out tmp ) ) return Observable . Empty < TVProp > ( ) ;
1004+ return Observable . Return ( ( TVProp ) tmp ) ;
1005+ } ) ;
1006+
1007+ fallbackWrapper = ( ) => {
1008+ object tmp ;
1009+ return converter . TryConvert ( fallbackValue ( ) , typeof ( TVProp ) , conversionHint , out tmp ) ? ( TVProp ) tmp : default ( TVProp ) ;
1010+ } ;
9681011 } else {
9691012 var converter = getConverterForTypes ( typeof ( TVMProp ) , typeof ( TVProp ) ) ;
9701013
@@ -974,20 +1017,23 @@ public IDisposable OneWayBind<TViewModel, TView, TVMProp, TVProp>(
9741017
9751018 var viewPropChain = Reflection . ExpressionToPropertyNames ( viewProperty ) ;
9761019
977- var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain ) ;
1020+ var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain , BindingDirection . OneWay ) ;
9781021 if ( ret != null ) return ret ;
9791022
980- return Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
1023+ source = Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
9811024 . SelectMany ( x => {
9821025 object tmp ;
9831026 if ( ! converter . TryConvert ( x , typeof ( TVProp ) , conversionHint , out tmp ) ) return Observable . Empty < TVProp > ( ) ;
984- return Observable . Return ( tmp == null ? default ( TVProp ) : ( TVProp ) tmp ) ;
985- } )
986- . BindTo ( view , viewProperty , ( ) => {
987- object tmp ;
988- return converter . TryConvert ( fallbackValue ( ) , typeof ( TVProp ) , conversionHint , out tmp ) ? ( TVProp ) tmp : default ( TVProp ) ;
1027+ return Observable . Return ( tmp == null ? default ( TVProp ) : ( TVProp ) tmp ) ;
9891028 } ) ;
1029+
1030+ fallbackWrapper = ( ) => {
1031+ object tmp ;
1032+ return converter . TryConvert ( fallbackValue ( ) , typeof ( TVProp ) , conversionHint , out tmp ) ? ( TVProp ) tmp : default ( TVProp ) ;
1033+ } ;
9901034 }
1035+
1036+ return bindToDirect ( source , view , viewProperty , fallbackWrapper ) ;
9911037 }
9921038
9931039 /// <summary>
@@ -1040,25 +1086,24 @@ public IDisposable OneWayBind<TViewModel, TView, TProp, TOut>(
10401086 {
10411087 var vmPropChain = Reflection . ExpressionToPropertyNames ( vmProperty ) ;
10421088 var vmString = String . Format ( "{0}.{1}" , typeof ( TViewModel ) . Name , String . Join ( "." , vmPropChain ) ) ;
1089+ var source = default ( IObservable < TOut > ) ;
10431090
10441091 if ( viewProperty == null ) {
10451092 var viewPropChain = Reflection . getDefaultViewPropChain ( view , Reflection . ExpressionToPropertyNames ( vmProperty ) ) ;
10461093
1047- var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain ) ;
1094+ var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain , BindingDirection . OneWay ) ;
10481095 if ( ret != null ) return ret ;
10491096
1050- return Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
1051- . Select ( selector )
1052- . Subscribe ( x => Reflection . SetValueToPropertyChain ( view , viewPropChain , x , false ) ) ;
1097+ source = Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty ) . Select ( selector ) ;
10531098 } else {
10541099 var viewPropChain = Reflection . ExpressionToPropertyNames ( viewProperty ) ;
1055- var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain ) ;
1100+ var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain , BindingDirection . OneWay ) ;
10561101 if ( ret != null ) return ret ;
10571102
1058- return Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
1059- . Select ( selector )
1060- . BindTo ( view , viewProperty , fallbackValue ) ;
1103+ source = Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty ) . Select ( selector ) ;
10611104 }
1105+
1106+ return bindToDirect ( source , view , viewProperty , fallbackValue ) ;
10621107 }
10631108
10641109 /// <summary>
@@ -1117,45 +1162,120 @@ public IDisposable AsyncOneWayBind<TViewModel, TView, TProp, TOut>(
11171162 {
11181163 var vmPropChain = Reflection . ExpressionToPropertyNames ( vmProperty ) ;
11191164 var vmString = String . Format ( "{0}.{1}" , typeof ( TViewModel ) . Name , String . Join ( "." , vmPropChain ) ) ;
1165+ var source = default ( IObservable < TOut > ) ;
11201166
11211167 if ( viewProperty == null ) {
11221168 var viewPropChain = Reflection . getDefaultViewPropChain ( view , Reflection . ExpressionToPropertyNames ( vmProperty ) ) ;
11231169
1124- var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain ) ;
1170+ var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain , BindingDirection . AsyncOneWay ) ;
11251171 if ( ret != null ) return ret ;
11261172
1127- return Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
1128- . SelectMany ( selector )
1129- . Subscribe ( x => Reflection . SetValueToPropertyChain ( view , viewPropChain , x , false ) ) ;
1173+ source = Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty ) . SelectMany ( selector ) ;
11301174 } else {
11311175 var viewPropChain = Reflection . ExpressionToPropertyNames ( viewProperty ) ;
11321176
1133- var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain ) ;
1177+ var ret = evalBindingHooks ( viewModel , view , vmPropChain , viewPropChain , BindingDirection . AsyncOneWay ) ;
11341178 if ( ret != null ) return ret ;
11351179
1136- return Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty )
1137- . SelectMany ( selector )
1138- . BindTo ( view , viewProperty , fallbackValue ) ;
1180+ source = Reflection . ViewModelWhenAnyValue ( viewModel , view , vmProperty ) . SelectMany ( selector ) ;
11391181 }
1182+
1183+ return bindToDirect ( source , view , viewProperty , fallbackValue ) ;
11401184 }
11411185
1142- IDisposable evalBindingHooks < TViewModel , TView > ( TViewModel viewModel , TView view , string [ ] vmPropChain , string [ ] viewPropChain )
1186+ public IDisposable BindTo < TValue , TTarget , TTValue > (
1187+ IObservable < TValue > This ,
1188+ TTarget target ,
1189+ Expression < Func < TTarget , TTValue > > property ,
1190+ Func < TValue > fallbackValue = null ,
1191+ object conversionHint = null )
1192+ {
1193+ var viewPropChain = Reflection . ExpressionToPropertyNames ( property ) ;
1194+ var ret = evalBindingHooks ( This , target , null , viewPropChain , BindingDirection . OneWay ) ;
1195+ if ( ret != null ) return ret ;
1196+
1197+ var converter = getConverterForTypes ( typeof ( TValue ) , typeof ( TTValue ) ) ;
1198+
1199+ if ( converter == null ) {
1200+ throw new ArgumentException ( String . Format ( "Can't convert {0} to {1}. To fix this, register a IBindingTypeConverter" , typeof ( TValue ) , typeof ( TTValue ) ) ) ;
1201+ }
1202+
1203+ var source = This . SelectMany ( x => {
1204+ object tmp ;
1205+ if ( ! converter . TryConvert ( x , typeof ( TTValue ) , conversionHint , out tmp ) ) return Observable . Empty < TTValue > ( ) ;
1206+ return Observable . Return ( tmp == null ? default ( TTValue ) : ( TTValue ) tmp ) ;
1207+ } ) ;
1208+
1209+ return bindToDirect ( source , target , property , fallbackValue == null ? default ( Func < TTValue > ) : new Func < TTValue > ( ( ) => {
1210+ object tmp ;
1211+ if ( ! converter . TryConvert ( fallbackValue ( ) , typeof ( TTValue ) , conversionHint , out tmp ) ) return default ( TTValue ) ;
1212+ return tmp == null ? default ( TTValue ) : ( TTValue ) tmp ;
1213+ } ) ) ;
1214+ }
1215+
1216+ IDisposable bindToDirect < TTarget , TValue > (
1217+ IObservable < TValue > This ,
1218+ TTarget target ,
1219+ Expression < Func < TTarget , TValue > > property ,
1220+ Func < TValue > fallbackValue = null )
1221+ {
1222+ var types = new [ ] { typeof ( TTarget ) } . Concat ( Reflection . ExpressionToPropertyTypes ( property ) ) . ToArray ( ) ;
1223+ var names = Reflection . ExpressionToPropertyNames ( property ) ;
1224+
1225+ var setter = Reflection . GetValueSetterOrThrow ( types . Reverse ( ) . Skip ( 1 ) . First ( ) , names . Last ( ) ) ;
1226+ if ( names . Length == 1 ) {
1227+ return This . Subscribe (
1228+ x => setter ( target , x ) ,
1229+ ex => {
1230+ this . Log ( ) . ErrorException ( "Binding recieved an Exception!" , ex ) ;
1231+ if ( fallbackValue != null ) setter ( target , fallbackValue ( ) ) ;
1232+ } ) ;
1233+ }
1234+
1235+ var bindInfo = Observable . CombineLatest (
1236+ This , target . WhenAnyDynamic ( names . SkipLast ( 1 ) . ToArray ( ) , x => x . Value ) ,
1237+ ( val , host ) => new { val , host } ) ;
1238+
1239+ return bindInfo
1240+ . Where ( x => x . host != null )
1241+ . Subscribe (
1242+ x => setter ( x . host , x . val ) ,
1243+ ex => {
1244+ this . Log ( ) . ErrorException ( "Binding recieved an Exception!" , ex ) ;
1245+ if ( fallbackValue != null ) setter ( target , fallbackValue ( ) ) ;
1246+ } ) ;
1247+ }
1248+
1249+ IDisposable evalBindingHooks < TViewModel , TView > ( TViewModel viewModel , TView view , string [ ] vmPropChain , string [ ] viewPropChain , BindingDirection direction )
11431250 where TViewModel : class
1144- where TView : IViewFor
11451251 {
11461252 var hooks = RxApp . GetAllServices < IPropertyBindingHook > ( ) ;
1147- var vmFetcher = new Func < IObservedChange < object , object > [ ] > ( ( ) => {
1148- IObservedChange < object , object > [ ] fetchedValues ;
1149- Reflection . TryGetAllValuesForPropertyChain ( out fetchedValues , viewModel , vmPropChain ) ;
1150- return fetchedValues ;
1151- } ) ;
1253+
1254+ var vmFetcher = default ( Func < IObservedChange < object , object > [ ] > ) ;
1255+ if ( vmPropChain != null ) {
1256+ vmFetcher = ( ) => {
1257+ IObservedChange < object , object > [ ] fetchedValues ;
1258+ Reflection . TryGetAllValuesForPropertyChain ( out fetchedValues , viewModel , vmPropChain ) ;
1259+ return fetchedValues ;
1260+ } ;
1261+ } else {
1262+ vmFetcher = ( ) => {
1263+ return new [ ] {
1264+ new ObservedChange < object , object > ( ) {
1265+ Sender = null , PropertyName = null , Value = viewModel ,
1266+ }
1267+ } ;
1268+ } ;
1269+ }
1270+
11521271 var vFetcher = new Func < IObservedChange < object , object > [ ] > ( ( ) => {
11531272 IObservedChange < object , object > [ ] fetchedValues ;
11541273 Reflection . TryGetAllValuesForPropertyChain ( out fetchedValues , view , viewPropChain ) ;
11551274 return fetchedValues ;
11561275 } ) ;
1276+
11571277 var shouldBind = hooks . Aggregate ( true , ( acc , x ) =>
1158- acc && x . ExecuteHook ( viewModel , view , vmFetcher , vFetcher , BindingDirection . TwoWay ) ) ;
1278+ acc && x . ExecuteHook ( viewModel , view , vmFetcher , vFetcher , direction ) ) ;
11591279
11601280 if ( ! shouldBind ) {
11611281 var vmString = String . Format ( "{0}.{1}" , typeof ( TViewModel ) . Name , String . Join ( "." , vmPropChain ) ) ;
@@ -1167,56 +1287,22 @@ IDisposable evalBindingHooks<TViewModel, TView>(TViewModel viewModel, TView view
11671287 return null ;
11681288 }
11691289
1170-
11711290 MemoizingMRUCache < Tuple < Type , Type > , IBindingTypeConverter > typeConverterCache = new MemoizingMRUCache < Tuple < Type , Type > , IBindingTypeConverter > (
1172- ( types , _ ) =>
1173- RxApp . GetAllServices < IBindingTypeConverter > ( )
1174- . Aggregate ( Tuple . Create ( - 1 , default ( IBindingTypeConverter ) ) , ( acc , x ) => {
1291+ ( types , _ ) => {
1292+ return RxApp . GetAllServices < IBindingTypeConverter > ( )
1293+ . Aggregate ( Tuple . Create ( - 1 , default ( IBindingTypeConverter ) ) , ( acc , x ) =>
1294+ {
11751295 var score = x . GetAffinityForObjects ( types . Item1 , types . Item2 ) ;
1176- return score > acc . Item1 && score > 0 ?
1296+ return score > acc . Item1 && score > 0 ?
11771297 Tuple . Create ( score , x ) : acc ;
1178- } ) . Item2
1179- , 25 ) ;
1298+ } ) . Item2 ;
1299+ } , 25 ) ;
11801300
1181- IBindingTypeConverter getConverterForTypes ( Type lhs , Type rhs )
1301+ internal IBindingTypeConverter getConverterForTypes ( Type lhs , Type rhs )
11821302 {
11831303 lock ( typeConverterCache ) {
11841304 return typeConverterCache . Get ( Tuple . Create ( lhs , rhs ) ) ;
11851305 }
11861306 }
11871307 }
1188-
1189- public static class ObservableBindingMixins
1190- {
1191- /// <summary>
1192- /// BindTo takes an Observable stream and applies it to a target
1193- /// property. Conceptually it is similar to "Subscribe(x =>
1194- /// target.property = x)", but allows you to use child properties
1195- /// without the null checks.
1196- /// </summary>
1197- /// <param name="target">The target object whose property will be set.</param>
1198- /// <param name="property">An expression representing the target
1199- /// property to set. This can be a child property (i.e. x.Foo.Bar.Baz).</param>
1200- /// <returns>An object that when disposed, disconnects the binding.</returns>
1201- public static IDisposable BindTo < TTarget , TValue > (
1202- this IObservable < TValue > This ,
1203- TTarget target ,
1204- Expression < Func < TTarget , TValue > > property ,
1205- Func < TValue > fallbackValue = null )
1206- {
1207- var pn = Reflection . ExpressionToPropertyNames ( property ) ;
1208- var bn = pn . Take ( pn . Length - 1 ) ;
1209-
1210- var lastValue = default ( TValue ) ;
1211-
1212- var o = target . SubscribeToExpressionChain < TTarget , object > ( bn , false , true )
1213- . Select ( x => lastValue ) ;
1214-
1215- return Observable . Merge ( o , This )
1216- . Subscribe ( x => {
1217- lastValue = x ;
1218- Reflection . SetValueToPropertyChain ( target , pn , x ) ;
1219- } ) ;
1220- }
1221- }
12221308}
0 commit comments