1
0
Эх сурвалжийг харах

Get simple two-way bindings working.

Steven Kirk 10 жил өмнө
parent
commit
907505f194

+ 9 - 0
samples/BindingTest/App.cs

@@ -3,6 +3,8 @@ using Perspex;
 using Perspex.Controls;
 using Perspex.Diagnostics;
 using Perspex.Themes.Default;
+using Serilog;
+using Serilog.Filters;
 
 namespace BindingTest
 {
@@ -13,6 +15,13 @@ namespace BindingTest
             RegisterServices();
             InitializeSubsystems((int)Environment.OSVersion.Platform);
             Styles = new DefaultTheme();
+
+            Log.Logger = new LoggerConfiguration()
+                .Filter.ByIncludingOnly(Matching.WithProperty("Area", "Property"))
+                .Filter.ByIncludingOnly(Matching.WithProperty<string>("Property", x => x == "Text"))
+                .MinimumLevel.Verbose()
+                .WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}")
+                .CreateLogger();
         }
 
         public static void AttachDevTools(Window window)

+ 8 - 0
samples/BindingTest/BindingTest.csproj

@@ -36,6 +36,14 @@
     <StartupObject />
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Serilog.1.5.9\lib\net45\Serilog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Serilog.FullNetFx, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Serilog.1.5.9\lib\net45\Serilog.FullNetFx.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll</HintPath>
       <Private>True</Private>

+ 1 - 0
samples/BindingTest/packages.config

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="Serilog" version="1.5.9" targetFramework="net46" />
   <package id="Splat" version="1.6.2" targetFramework="net46" />
 </packages>

+ 2 - 1
src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs

@@ -53,7 +53,8 @@ namespace Perspex.Markup.Xaml.DataBinding
                     Target.Bind(TargetProperty, observable.Select(x => x.Value));
                     break;
                 case BindingMode.TwoWay:
-                    throw new NotImplementedException();
+                    Target.BindTwoWay(TargetProperty, new ExpressionSubject(observable));
+                    break;
                 case BindingMode.OneTime:
                     throw new NotImplementedException();
                 case BindingMode.OneWayToSource:

+ 1 - 1
src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs

@@ -42,7 +42,7 @@ namespace Perspex.Markup.Binding
         /// <inheritdoc/>
         public IDisposable Subscribe(IObserver<object> observer)
         {
-            return _inner.Select(x => x.Value).Distinct().Subscribe(observer);
+            return _inner.Select(x => x.Value).Subscribe(observer);
         }
     }
 }

+ 55 - 7
src/Perspex.Base/PerspexObject.cs

@@ -7,6 +7,7 @@ using System.ComponentModel;
 using System.Linq;
 using System.Reactive.Disposables;
 using System.Reactive.Linq;
+using System.Reactive.Subjects;
 using System.Reflection;
 using Perspex.Reactive;
 using Perspex.Utilities;
@@ -492,6 +493,7 @@ namespace Perspex
                     throw new ArgumentException($"The property {property.Name} is readonly.");
                 }
 
+                LogPropertySet(property, value, priority);
                 property.Setter(this, value);
             }
             else
@@ -524,14 +526,9 @@ namespace Perspex
                     _values.Add(property, v);
                 }
 
+                LogPropertySet(property, value, priority);
                 v.SetValue(value, (int)priority);
             }
-
-            _propertyLog.Verbose(
-                "Set {Property} to {$Value} with priority {Priority}",
-                property,
-                value,
-                priority);
         }
 
         /// <summary>
@@ -557,6 +554,7 @@ namespace Perspex
                     throw new ArgumentException($"The property {property.Name} is readonly.");
                 }
 
+                LogPropertySet(property, value, priority);
                 property.Setter(this, value);
             }
             else
@@ -658,7 +656,7 @@ namespace Perspex
         }
 
         /// <summary>
-        /// Initialites a two-way bind between <see cref="PerspexProperty"/>s.
+        /// Initiates a two-way binding between <see cref="PerspexProperty"/>s.
         /// </summary>
         /// <param name="property">The property on this object.</param>
         /// <param name="source">The source object.</param>
@@ -676,11 +674,46 @@ namespace Perspex
             PerspexProperty sourceProperty,
             BindingPriority priority = BindingPriority.LocalValue)
         {
+            _propertyLog.Verbose(
+                "Bound two way {Property} to {Binding} with priority {Priority}",
+                property,
+                source,
+                priority);
+
             return new CompositeDisposable(
                 Bind(property, source.GetObservable(sourceProperty)),
                 source.Bind(sourceProperty, GetObservable(property)));
         }
 
+        /// <summary>
+        /// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an 
+        /// <see cref="ISubject{Object}"/>.
+        /// </summary>
+        /// <param name="property">The property on this object.</param>
+        /// <param name="source">The subject to bind to.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>
+        /// A disposable which can be used to terminate the binding.
+        /// </returns>
+        /// <remarks>
+        /// The binding is first carried out from <paramref name="source"/> to this.
+        /// </remarks>
+        public IDisposable BindTwoWay(
+            PerspexProperty property,
+            ISubject<object> source,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            _propertyLog.Verbose(
+                "Bound two way {Property} to {Binding} with priority {Priority}",
+                property,
+                source,
+                priority);
+
+            return new CompositeDisposable(
+                Bind(property, source),
+                GetObservable(property).Subscribe(source));
+        }
+
         /// <summary>
         /// Forces the specified property to be revalidated.
         /// </summary>
@@ -931,6 +964,21 @@ namespace Perspex
             return string.Format("{0}.{1}", GetType().Name, property.Name);
         }
 
+        /// <summary>
+        /// Logs a property set message.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The new value.</param>
+        /// <param name="priority">The priority.</param>
+        private void LogPropertySet(PerspexProperty property, object value, BindingPriority priority)
+        {
+            _propertyLog.Verbose(
+                "Set {Property} to {$Value} with priority {Priority}",
+                property,
+                value,
+                priority);
+        }
+
         /// <summary>
         /// Throws an exception indicating that the specified property is not registered on this
         /// object.

+ 1 - 1
src/Perspex.Controls/TextBlock.cs

@@ -65,7 +65,7 @@ namespace Perspex.Controls
         /// Defines the <see cref="Text"/> property.
         /// </summary>
         public static readonly PerspexProperty<string> TextProperty =
-            PerspexProperty.Register<TextBlock, string>(nameof(Text));
+            PerspexProperty.Register<TextBlock, string>(nameof(Text), defaultBindingMode: BindingMode.TwoWay);
 
         /// <summary>
         /// Defines the <see cref="TextAlignment"/> property.

+ 4 - 3
tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

@@ -46,13 +46,14 @@
     <Reference Include="Octokit">
       <HintPath>..\..\packages\Octokit.0.14.0\lib\net45\Octokit.dll</HintPath>
     </Reference>
+    <Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="Moq, Version=4.2.1409.1722, Culture=neutral, PublicKeyToken=69f491c39445e920">
       <HintPath>..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll</HintPath>
     </Reference>
-    <Reference Include="Splat, Version=1.6.1.0, Culture=neutral, PublicKeyToken=null">
-      <HintPath>..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll</HintPath>
-    </Reference>
     <Reference Include="Sprache, Version=2.0.0.47, Culture=neutral, PublicKeyToken=null">
       <HintPath>..\..\packages\Sprache.2.0.0.47\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid1+MonoTouch1\Sprache.dll</HintPath>
     </Reference>

+ 1 - 1
tests/Perspex.Markup.Xaml.UnitTests/packages.config

@@ -6,7 +6,7 @@
   <package id="Rx-Linq" version="2.2.5" targetFramework="net451" />
   <package id="Rx-Main" version="2.2.5" targetFramework="net451" />
   <package id="Rx-PlatformServices" version="2.2.5" targetFramework="net451" />
-  <package id="Splat" version="1.6.1" targetFramework="net451" />
+  <package id="Splat" version="1.6.2" targetFramework="net451" />
   <package id="Sprache" version="2.0.0.47" targetFramework="net451" />
   <package id="xunit" version="2.0.0" targetFramework="net451" />
   <package id="xunit.abstractions" version="2.0.0" targetFramework="net451" />