Browse Source

Added basic implementation of MultiBinding.

Just enough to allow TextBox's floating watermark logic to work.
Steven Kirk 10 years ago
parent
commit
e6351e8269

+ 10 - 2
samples/XamlTestApplicationPcl/TextBox.paml

@@ -20,8 +20,16 @@
               <TextBlock Name="floatingWatermark"
                          Foreground="#ff007ACC"
                          FontSize="10"
-                         Text="{TemplateBinding Watermark}"
-                         IsVisible="{TemplateBinding UseFloatingWatermark}">
+                         Text="{TemplateBinding Watermark}">
+                <TextBlock.IsVisible>
+                  <MultiBinding Converter="{Static BoolConverters.And}">
+                    <Binding RelativeSource="{RelativeSource TemplatedParent}"
+                             SourcePropertyPath="UseFloatingWatermark"/>
+                    <Binding RelativeSource="{RelativeSource TemplatedParent}"
+                             SourcePropertyPath="Text" 
+                             Converter="{Static StringConverters.NotNullOrEmpty}"/>
+                  </MultiBinding>
+                </TextBlock.IsVisible>
               </TextBlock>
               <Panel>
                 <TextBlock Name="watermark"

+ 5 - 5
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs

@@ -39,9 +39,9 @@ namespace Perspex.Markup.Xaml.Context
 
         public override void SetValue(object instance, object value)
         {
-            if (value is Data.Binding)
+            if (value is IBinding)
             {
-                HandleXamlBindingDefinition(instance, (Data.Binding)value);
+                HandleBinding(instance, (IBinding)value);
             }
             else if (IsPerspexProperty)
             {
@@ -68,9 +68,9 @@ namespace Perspex.Markup.Xaml.Context
             po.SetValue(pp, value);
         }
 
-        private void HandleXamlBindingDefinition(object instance, Data.Binding binding)
+        private void HandleBinding(object instance, IBinding binding)
         {
-            if (_xamlMember.XamlType.UnderlyingType == typeof(Data.Binding))
+            if (typeof(IBinding).GetTypeInfo().IsAssignableFrom(_xamlMember.XamlType.UnderlyingType.GetTypeInfo()))
             {
                 var property = instance.GetType().GetRuntimeProperty(_xamlMember.Name);
 
@@ -88,7 +88,7 @@ namespace Perspex.Markup.Xaml.Context
             }                
         }
 
-        private void ApplyBinding(object instance, Data.Binding binding)
+        private void ApplyBinding(object instance, IBinding binding)
         {
             var perspexObject = instance as PerspexObject;
             var attached = _xamlMember as PerspexAttachableXamlMember;

+ 1 - 1
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@@ -10,7 +10,7 @@ using Perspex.Markup.Data;
 
 namespace Perspex.Markup.Xaml.Data
 {
-    public class Binding
+    public class Binding : IBinding
     {
         private readonly ITypeConverterProvider _typeConverterProvider;
 

+ 7 - 0
src/Markup/Perspex.Markup.Xaml/Data/IBinding.cs

@@ -0,0 +1,7 @@
+namespace Perspex.Markup.Xaml.Data
+{
+    public interface IBinding
+    {
+        void Bind(IObservablePropertyBag instance, PerspexProperty property);
+    }
+}

+ 90 - 0
src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs

@@ -0,0 +1,90 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using OmniXaml.TypeConversion;
+using Perspex.Controls;
+using Perspex.Markup.Data;
+using Perspex.Metadata;
+
+namespace Perspex.Markup.Xaml.Data
+{
+    public class MultiBinding : IBinding
+    {
+        private readonly ITypeConverterProvider _typeConverterProvider;
+
+        public MultiBinding()
+        {
+        }
+
+        public MultiBinding(ITypeConverterProvider typeConverterProvider)
+        {
+            _typeConverterProvider = typeConverterProvider;
+        }
+
+        [Content]
+        public IList<Binding> Bindings { get; } = new List<Binding>();
+        public IMultiValueConverter Converter { get; set; }
+        public BindingMode Mode { get; set; }
+        public BindingPriority Priority { get; set; }
+        public RelativeSource RelativeSource { get; set; }
+        public string SourcePropertyPath { get; set; }
+
+        public void Bind(IObservablePropertyBag instance, PerspexProperty property)
+        {
+            var subject = CreateSubject(instance, property);
+
+            if (subject != null)
+            {
+                Bind(instance, property, subject);
+            }
+        }
+
+        public ISubject<object> CreateSubject(
+            IObservablePropertyBag instance, 
+            PerspexProperty property)
+        {
+            if (Converter == null)
+            {
+                throw new NotSupportedException("MultiBinding without Converter not currently supported.");
+            }
+
+            var result = new Subject<object>();
+            var children = Bindings.Select(x => x.CreateExpressionSubject(instance, property));
+            var input = Observable.CombineLatest(children).Select(x =>
+                Converter.Convert(x, property.PropertyType, null, CultureInfo.CurrentUICulture));
+            input.Subscribe(result);
+            return result;
+        }
+
+        internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
+        {
+            var mode = Mode == BindingMode.Default ?
+                property.DefaultBindingMode : Mode;
+
+            switch (mode)
+            {
+                case BindingMode.Default:
+                case BindingMode.OneWay:
+                    target.Bind(property, subject, Priority);
+                    break;
+                case BindingMode.TwoWay:
+                    throw new NotSupportedException("TwoWay MultiBinding not currently supported.");
+                case BindingMode.OneTime:
+                    target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
+                    {
+                        subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
+                    });                    
+                    break;
+                case BindingMode.OneWayToSource:
+                    target.GetObservable(property).Subscribe(subject);
+                    break;
+            }
+        }
+    }
+}

+ 2 - 0
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@@ -38,6 +38,8 @@
     <Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
       <Link>Properties\SharedAssemblyInfo.cs</Link>
     </Compile>
+    <Compile Include="Data\IBinding.cs" />
+    <Compile Include="Data\MultiBinding.cs" />
     <Compile Include="Data\RelativeSource.cs" />
     <Compile Include="Data\SourceBindingEndpoint.cs" />
     <Compile Include="Data\TargetBindingEndpoint.cs" />

+ 19 - 0
src/Markup/Perspex.Markup/BoolConverters.cs

@@ -0,0 +1,19 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Linq;
+
+namespace Perspex.Markup
+{
+    /// <summary>
+    /// Provides a set of useful <see cref="IValueConverter"/>s for working with string values.
+    /// </summary>
+    public static class BoolConverters
+    {
+        /// <summary>
+        /// A multi-value converter that returns true if all inputs are true.
+        /// </summary>
+        public static readonly IMultiValueConverter And =
+            new FuncMultiValueConverter<bool, bool>(x => x.All(y => y));
+    }
+}

+ 36 - 0
src/Markup/Perspex.Markup/FuncMultiValueConverter.cs

@@ -0,0 +1,36 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace Perspex.Markup
+{
+    /// <summary>
+    /// A general purpose <see cref="IValueConverter"/> that uses a <see cref="Func{T1, TResult}"/>
+    /// to provide the converter logic.
+    /// </summary>
+    /// <typeparam name="TIn">The type of the inputs.</typeparam>
+    /// <typeparam name="TOut">The output type.</typeparam>
+    public class FuncMultiValueConverter<TIn, TOut> : IMultiValueConverter
+    {
+        private Func<IEnumerable<TIn>, TOut> _convert;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FuncValueConverter{TIn, TOut}"/> class.
+        /// </summary>
+        /// <param name="convert">The convert function.</param>
+        public FuncMultiValueConverter(Func<IEnumerable<TIn>, TOut> convert)
+        {
+            _convert = convert;
+        }
+
+        /// <inheritdoc/>
+        public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
+        {
+            return _convert(values.OfType<TIn>());
+        }
+    }
+}

+ 2 - 2
src/Markup/Perspex.Markup/FuncValueConverter.cs

@@ -11,8 +11,8 @@ namespace Perspex.Markup
     /// A general purpose <see cref="IValueConverter"/> that uses a <see cref="Func{T1, TResult}"/>
     /// to provide the converter logic.
     /// </summary>
-    /// <typeparam name="TIn"></typeparam>
-    /// <typeparam name="TOut"></typeparam>
+    /// <typeparam name="TIn">The input type.</typeparam>
+    /// <typeparam name="TOut">The output type.</typeparam>
     public class FuncValueConverter<TIn, TOut> : IValueConverter
     {
         private Func<TIn, TOut> _convert;

+ 30 - 0
src/Markup/Perspex.Markup/IMultiValueConverter.cs

@@ -0,0 +1,30 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Perspex.Markup
+{
+    /// <summary>
+    /// Converts multi-binding inputs to a final value.
+    /// </summary>
+    public interface IMultiValueConverter
+    {
+        /// <summary>
+        /// Converts multi-binding inputs to a final value.
+        /// </summary>
+        /// <param name="values">The values to convert.</param>
+        /// <param name="targetType">The type of the target.</param>
+        /// <param name="parameter">A user-defined parameter.</param>
+        /// <param name="culture">The culture to use.</param>
+        /// <returns>The converted value.</returns>
+        /// <remarks>
+        /// This method should not throw exceptions. If the value is not convertible, return
+        /// <see cref="PerspexProperty.UnsetValue"/>. Any exception thrown will be treated as
+        /// an application exception.
+        /// </remarks>
+        object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture);
+    }
+}

+ 3 - 0
src/Markup/Perspex.Markup/Perspex.Markup.csproj

@@ -52,7 +52,10 @@
     <Compile Include="Data\PropertyAccessorNode.cs" />
     <Compile Include="Data\ExpressionNode.cs" />
     <Compile Include="Data\ExpressionObserver.cs" />
+    <Compile Include="FuncMultiValueConverter.cs" />
     <Compile Include="FuncValueConverter.cs" />
+    <Compile Include="IMultiValueConverter.cs" />
+    <Compile Include="BoolConverters.cs" />
     <Compile Include="StringConverters.cs" />
     <Compile Include="DefaultValueConverter.cs" />
     <Compile Include="IValueConverter.cs" />

+ 6 - 0
src/Markup/Perspex.Markup/StringConverters.cs

@@ -17,5 +17,11 @@ namespace Perspex.Markup
         /// </summary>
         public static readonly IValueConverter NullOrEmpty =
             new FuncValueConverter<string, bool>(x => string.IsNullOrEmpty(x));
+
+        /// <summary>
+        /// A value converter that returns true if the input string is not null or empty.
+        /// </summary>
+        public static readonly IValueConverter NotNullOrEmpty =
+            new FuncValueConverter<string, bool>(x => !string.IsNullOrEmpty(x));
     }
 }