From 907505f1941842ff3e92da56df745f0ebb475c85 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 2 Oct 2015 12:12:40 +0200 Subject: [PATCH] Get simple two-way bindings working. --- samples/BindingTest/App.cs | 9 +++ samples/BindingTest/BindingTest.csproj | 8 +++ samples/BindingTest/packages.config | 1 + .../DataBinding/XamlBinding.cs | 3 +- .../Binding/ExpressionSubject.cs | 2 +- src/Perspex.Base/PerspexObject.cs | 62 ++++++++++++++++--- src/Perspex.Controls/TextBlock.cs | 2 +- .../Perspex.Markup.Xaml.UnitTests.csproj | 7 ++- .../packages.config | 2 +- 9 files changed, 82 insertions(+), 14 deletions(-) diff --git a/samples/BindingTest/App.cs b/samples/BindingTest/App.cs index df2ba5a586..79222817cd 100644 --- a/samples/BindingTest/App.cs +++ b/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("Property", x => x == "Text")) + .MinimumLevel.Verbose() + .WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}") + .CreateLogger(); } public static void AttachDevTools(Window window) diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index 0e0925adb2..f98cc1dd08 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -36,6 +36,14 @@ + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.dll + True + + + ..\..\packages\Serilog.1.5.9\lib\net45\Serilog.FullNetFx.dll + True + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll True diff --git a/samples/BindingTest/packages.config b/samples/BindingTest/packages.config index 85a1a3cdc5..6a1eddec98 100644 --- a/samples/BindingTest/packages.config +++ b/samples/BindingTest/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs b/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs index c23a5bce22..8bf09ec63c 100644 --- a/src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs +++ b/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: diff --git a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs index e2e94daf7c..cd076c9ab7 100644 --- a/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs +++ b/src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs @@ -42,7 +42,7 @@ namespace Perspex.Markup.Binding /// public IDisposable Subscribe(IObserver observer) { - return _inner.Select(x => x.Value).Distinct().Subscribe(observer); + return _inner.Select(x => x.Value).Subscribe(observer); } } } diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 7a0e211f2c..0c27fe9453 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/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); } /// @@ -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 } /// - /// Initialites a two-way bind between s. + /// Initiates a two-way binding between s. /// /// The property on this object. /// The source object. @@ -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))); } + /// + /// Initiates a two-way binding between a and an + /// . + /// + /// The property on this object. + /// The subject to bind to. + /// The priority of the binding. + /// + /// A disposable which can be used to terminate the binding. + /// + /// + /// The binding is first carried out from to this. + /// + public IDisposable BindTwoWay( + PerspexProperty property, + ISubject 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)); + } + /// /// Forces the specified property to be revalidated. /// @@ -931,6 +964,21 @@ namespace Perspex return string.Format("{0}.{1}", GetType().Name, property.Name); } + /// + /// Logs a property set message. + /// + /// The property. + /// The new value. + /// The priority. + private void LogPropertySet(PerspexProperty property, object value, BindingPriority priority) + { + _propertyLog.Verbose( + "Set {Property} to {$Value} with priority {Priority}", + property, + value, + priority); + } + /// /// Throws an exception indicating that the specified property is not registered on this /// object. diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index 607754dcb2..904e2d81d7 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/src/Perspex.Controls/TextBlock.cs @@ -65,7 +65,7 @@ namespace Perspex.Controls /// Defines the property. /// public static readonly PerspexProperty TextProperty = - PerspexProperty.Register(nameof(Text)); + PerspexProperty.Register(nameof(Text), defaultBindingMode: BindingMode.TwoWay); /// /// Defines the property. diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj index b74704c7fa..6c851a1e87 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -46,13 +46,14 @@ ..\..\packages\Octokit.0.14.0\lib\net45\Octokit.dll + + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll + True + ..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll - - ..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll - ..\..\packages\Sprache.2.0.0.47\lib\portable-net4+netcore45+win8+wp8+sl5+MonoAndroid1+MonoTouch1\Sprache.dll diff --git a/tests/Perspex.Markup.Xaml.UnitTests/packages.config b/tests/Perspex.Markup.Xaml.UnitTests/packages.config index c7f996e816..f0bea65dab 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/packages.config +++ b/tests/Perspex.Markup.Xaml.UnitTests/packages.config @@ -6,7 +6,7 @@ - +