Browse Source

Implemented delayed bindings.

pull/464/head
Steven Kirk 10 years ago
parent
commit
80ecc8002b
  1. 2
      samples/BindingTest/MainWindow.paml
  2. 30
      src/Markup/Perspex.Markup.Xaml/Context/PropertyAccessor.cs
  3. 95
      src/Markup/Perspex.Markup.Xaml/Data/DelayedBinding.cs
  4. 2
      src/Markup/Perspex.Markup.Xaml/OmniXAML
  5. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  6. 3
      src/Perspex.Controls/Control.cs
  7. 2
      tests/Perspex.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  8. 5
      tests/Perspex.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

2
samples/BindingTest/MainWindow.paml

@ -10,7 +10,7 @@
<TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/>
<TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<TextBox Watermark="One Way To Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
<!--<TextBox Watermark="One Way To Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>-->
</StackPanel>
<StackPanel Margin="18" Gap="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/>

30
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<IControl>()
.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<IControl>()
.LastOrDefault();
((IPerspexObject)instance).Bind(property, binding, treeAnchor);
return true;
}
return false;
return true;
}
}
}

95
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
{
/// <summary>
/// Provides delayed bindings for controls.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public static class DelayedBinding
{
private static ConditionalWeakTable<IControl, List<Entry>> _entries =
new ConditionalWeakTable<IControl, List<Entry>>();
/// <summary>
/// Adds a delayed binding to a control.
/// </summary>
/// <param name="target">The control.</param>
/// <param name="property">The property on the control to bind to.</param>
/// <param name="binding">The binding.</param>
public static void Add(IControl target, PerspexProperty property, IBinding binding)
{
if (target.IsAttachedToVisualTree)
{
target.Bind(property, binding);
}
else
{
List<Entry> bindings;
if (!_entries.TryGetValue(target, out bindings))
{
bindings = new List<Entry>();
_entries.Add(target, bindings);
// TODO: Make this a weak event listener.
target.AttachedToVisualTree += ApplyBindings;
}
bindings.Add(new Entry(binding, property));
}
}
/// <summary>
/// Applies any delayed bindings to a control.
/// </summary>
/// <param name="control">The control.</param>
public static void ApplyBindings(IControl control)
{
List<Entry> 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; }
}
}
}

2
src/Markup/Perspex.Markup.Xaml/OmniXAML

@ -1 +1 @@
Subproject commit c2b86b9d1ae638c788f44bc63d17911986d766fb
Subproject commit 43698917d00c5aaf789605fd7434798a748fba89

1
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -76,6 +76,7 @@
<Compile Include="Converters\TimeSpanTypeConverter.cs" />
<Compile Include="Converters\UriTypeConverter.cs" />
<Compile Include="Data\Binding.cs" />
<Compile Include="Data\DelayedBinding.cs" />
<Compile Include="Data\MultiBinding.cs" />
<Compile Include="Data\RelativeSource.cs" />
<Compile Include="Data\SourceBindingEndpoint.cs" />

3
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)

2
tests/Perspex.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@ -25,6 +25,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Xaml
var button = window.FindControl<Button>("button");
button.DataContext = new { Foo = "foo" };
window.ApplyTemplate();
Assert.Equal("foo", button.Content);
}
@ -50,6 +51,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Xaml
var button = window.FindControl<Button>("button");
button.DataContext = new { Foo = "foo" };
window.ApplyTemplate();
Assert.Equal("foo", button.Content);
}

5
tests/Perspex.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Controls;
using Perspex.Markup.Xaml.Data;
using Perspex.Media;
using Perspex.Styling;
using Perspex.UnitTests;
@ -79,8 +80,10 @@ namespace Perspex.Markup.Xaml.UnitTests.Xaml
var loader = new PerspexXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}

Loading…
Cancel
Save