diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index c782786e42..cfe10d3ab4 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -10,7 +10,7 @@ - + diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs b/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs index 2ea6a54599..18b464a211 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs @@ -10,6 +10,7 @@ using OmniXaml.TypeConversion; using OmniXaml.Typing; using Perspex.Controls; using Perspex.Data; +using Perspex.Markup.Xaml.Data; using Perspex.Styling; namespace Perspex.Markup.Xaml.Context @@ -116,25 +117,28 @@ namespace Perspex.Markup.Xaml.Context IValueContext context, IBinding binding) { - if (property != null) + if (property == null) { - IPerspexObject treeAnchor = null; + return false; + } - if (!(instance is IControl)) - { - // HACK: StoredInstances not exposed on ITopDownValueContext. - var tdvc = (TopDownValueContext)context.TopDownValueContext; - treeAnchor = (IControl)tdvc.StoredInstances - .Select(x => x.Instance) - .OfType() - .LastOrDefault(); - } + var control = instance as IControl; + + if (control != null) + { + DelayedBinding.Add(control, property, binding); + } + else + { + IPerspexObject treeAnchor = context.TopDownValueContext.StoredInstances + .Select(x => x.Instance) + .OfType() + .LastOrDefault(); ((IPerspexObject)instance).Bind(property, binding, treeAnchor); - return true; } - return false; + return true; } } } diff --git a/src/Markup/Perspex.Markup.Xaml/Data/DelayedBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/DelayedBinding.cs new file mode 100644 index 0000000000..95e09497a2 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Data/DelayedBinding.cs @@ -0,0 +1,95 @@ +// 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.Runtime.CompilerServices; +using Perspex.Controls; +using Perspex.Data; + +namespace Perspex.Markup.Xaml.Data +{ + /// + /// Provides delayed bindings for controls. + /// + /// + /// The XAML engine applies its bindings in a delayed manner where bindings are only applied + /// when a control is added to the visual tree. This was done because applying bindings as soon + /// as controls are created means that long-form bindings (i.e. bindings that don't use the + /// `{Binding}` markup extension) don't work as the binding is applied to the property before + /// the binding properties are set, and looking at WPF it uses a similar mechanism for bindings + /// that come from XAML. + /// + public static class DelayedBinding + { + private static ConditionalWeakTable> _entries = + new ConditionalWeakTable>(); + + /// + /// Adds a delayed binding to a control. + /// + /// The control. + /// The property on the control to bind to. + /// The binding. + public static void Add(IControl target, PerspexProperty property, IBinding binding) + { + if (target.IsAttachedToVisualTree) + { + target.Bind(property, binding); + } + else + { + List bindings; + + if (!_entries.TryGetValue(target, out bindings)) + { + bindings = new List(); + _entries.Add(target, bindings); + + // TODO: Make this a weak event listener. + target.AttachedToVisualTree += ApplyBindings; + } + + bindings.Add(new Entry(binding, property)); + } + } + + /// + /// Applies any delayed bindings to a control. + /// + /// The control. + public static void ApplyBindings(IControl control) + { + List bindings; + + if (_entries.TryGetValue(control, out bindings)) + { + foreach (var binding in bindings) + { + control.Bind(binding.Property, binding.Binding); + } + + _entries.Remove(control); + } + } + + private static void ApplyBindings(object sender, VisualTreeAttachmentEventArgs e) + { + var target = (IControl)sender; + ApplyBindings(target); + target.AttachedToVisualTree -= ApplyBindings; + } + + private class Entry + { + public Entry(IBinding binding, PerspexProperty property) + { + Binding = binding; + Property = property; + } + + public IBinding Binding { get; } + public PerspexProperty Property { get; } + } + } +} diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index c2b86b9d1a..43698917d0 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit c2b86b9d1ae638c788f44bc63d17911986d766fb +Subproject commit 43698917d00c5aaf789605fd7434798a748fba89 diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 54ac88b2dd..f59483a22e 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -76,6 +76,7 @@ + diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index 8ecd552284..e1c93ff3ac 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -323,7 +323,8 @@ namespace Perspex.Controls { if (_initCount == 0) { - throw new InvalidOperationException("BeginInit was not called."); + ++_initCount; + //throw new InvalidOperationException("BeginInit was not called."); } if (--_initCount == 0 && !_styled) diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index ffefacfbc3..48fcc7b698 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -25,6 +25,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Xaml var button = window.FindControl