Browse Source

Determine binding target at time of bind.

This is needed in order to be able to use bindings as selectors, e.g.
for TreeDataTemplate.ItemsSelector.
pull/237/head
Steven Kirk 11 years ago
parent
commit
9d88f91852
  1. 1
      samples/BindingTest/App.cs
  2. 10
      samples/XamlTestApplication/Views/MainWindow.paml
  3. 51
      src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs
  4. 27
      src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs
  5. 6
      src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs
  6. 26
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
  7. 6
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  8. 27
      tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs
  9. 20
      tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs
  10. 36
      tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs
  11. 2
      tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

1
samples/BindingTest/App.cs

@ -18,6 +18,7 @@ namespace BindingTest
Log.Logger = new LoggerConfiguration()
.Filter.ByIncludingOnly(Matching.WithProperty("Area", "Property"))
.Filter.ByIncludingOnly(Matching.WithProperty("Property", "Text"))
.MinimumLevel.Verbose()
.WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}")
.CreateLogger();

10
samples/XamlTestApplication/Views/MainWindow.paml

@ -59,7 +59,7 @@
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
<DropDown VerticalAlignment="Center">
<DropDown VerticalAlignment="Center" SelectedIndex="0">
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />
<TextBlock Text="Item 1 Value" />
@ -69,16 +69,16 @@
<TextBlock Text="Item 2 Value" />
</StackPanel>
</DropDown>
<!--<TreeView Items="{Binding Nodes}">
<TreeView.DataTemplates>
<TreeView Items="{Binding Nodes}">
<!--<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TestNode" ItemsSource="{Binding Children}">
<StackPanel>
<TextBlock Text="{Binding Header}" FontSize="24"/>
<TextBlock Text="{Binding SubHeader}"/>
</StackPanel>
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView>-->
</TreeView.DataTemplates>-->
</TreeView>
</StackPanel>
</TabItem>
<TabItem Header="Layout">

51
src/Markup/Perspex.Markup.Xaml/Binding/PerspexPropertyBinder.cs

@ -22,38 +22,41 @@ namespace Perspex.Markup.Xaml.Binding
public XamlBinding GetBinding(PerspexObject po, PerspexProperty pp)
{
return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp);
throw new NotImplementedException();
//return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp);
}
public IEnumerable<XamlBinding> GetBindings(PerspexObject source)
{
return from binding in _bindings
where binding.Target == source
select binding;
throw new NotImplementedException();
//return from binding in _bindings
// where binding.Target == source
// select binding;
}
public XamlBinding Create(XamlBindingDefinition xamlBinding)
{
if (xamlBinding.Target == null)
{
throw new InvalidOperationException();
}
if (xamlBinding.TargetProperty == null)
{
throw new InvalidOperationException();
}
var binding = new XamlBinding(TypeConverterProvider)
{
BindingMode = xamlBinding.BindingMode,
SourcePropertyPath = xamlBinding.SourcePropertyPath,
Target = xamlBinding.Target,
TargetProperty = xamlBinding.TargetProperty
};
_bindings.Add(binding);
return binding;
throw new NotImplementedException();
//if (xamlBinding.Target == null)
//{
// throw new InvalidOperationException();
//}
//if (xamlBinding.TargetProperty == null)
//{
// throw new InvalidOperationException();
//}
//var binding = new XamlBinding(TypeConverterProvider)
//{
// BindingMode = xamlBinding.BindingMode,
// SourcePropertyPath = xamlBinding.SourcePropertyPath,
// Target = xamlBinding.Target,
// TargetProperty = xamlBinding.TargetProperty
//};
//_bindings.Add(binding);
//return binding;
}
}
}

27
src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs

@ -23,49 +23,46 @@ namespace Perspex.Markup.Xaml.Binding
_typeConverterProvider = typeConverterProvider;
}
public IObservablePropertyBag Target { get; set; }
public PerspexProperty TargetProperty { get; set; }
public string SourcePropertyPath { get; set; }
public BindingMode BindingMode { get; set; }
public void Bind()
public void Bind(IObservablePropertyBag instance, PerspexProperty property)
{
Bind(new ExpressionSubject(CreateExpressionObserver()));
var subject = new ExpressionSubject(CreateExpressionObserver(instance));
Bind(instance, property, subject);
}
public ExpressionObserver CreateExpressionObserver()
public ExpressionObserver CreateExpressionObserver(IObservablePropertyBag instance)
{
var result = new ExpressionObserver(null, SourcePropertyPath);
var dataContext = Target.GetObservable(Control.DataContextProperty);
var dataContext = instance.GetObservable(Control.DataContextProperty);
dataContext.Subscribe(x => result.Root = x);
return result;
}
internal void Bind(ISubject<object> subject)
internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
{
var mode = BindingMode == BindingMode.Default ?
TargetProperty.DefaultBindingMode : BindingMode;
property.DefaultBindingMode : BindingMode;
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
Target.Bind(TargetProperty, subject);
target.Bind(property, subject);
break;
case BindingMode.TwoWay:
Target.BindTwoWay(TargetProperty, subject);
target.BindTwoWay(property, subject);
break;
case BindingMode.OneTime:
Target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
{
subject.Take(1).Subscribe(x => Target.SetValue(TargetProperty, x));
subject.Take(1).Subscribe(x => target.SetValue(property, x));
});
break;
case BindingMode.OneWayToSource:
Target.GetObservable(TargetProperty).Subscribe(subject);
target.GetObservable(property).Subscribe(subject);
break;
}
}

6
src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs

@ -8,19 +8,13 @@ namespace Perspex.Markup.Xaml.Binding
public class XamlBindingDefinition
{
public XamlBindingDefinition(
Control target,
PerspexProperty targetProperty,
string sourcePropertyPath,
BindingMode bindingMode)
{
Target = target;
TargetProperty = targetProperty;
SourcePropertyPath = sourcePropertyPath;
BindingMode = bindingMode;
}
public Control Target { get; }
public PerspexProperty TargetProperty { get; }
public string SourcePropertyPath { get; }
public BindingMode BindingMode { get; }
}

26
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using Glass;
using OmniXaml.ObjectAssembler;
@ -27,7 +28,7 @@ namespace Perspex.Markup.Xaml.Context
{
if (value is XamlBindingDefinition)
{
HandleXamlBindingDefinition((XamlBindingDefinition)value);
HandleXamlBindingDefinition(instance, (XamlBindingDefinition)value);
}
else if (IsPerspexProperty)
{
@ -54,17 +55,32 @@ namespace Perspex.Markup.Xaml.Context
po.SetValue(pp, value);
}
private void HandleXamlBindingDefinition(XamlBindingDefinition def)
private void HandleXamlBindingDefinition(object instance, XamlBindingDefinition def)
{
var perspexObject = instance as PerspexObject;
if (perspexObject == null)
{
throw new InvalidOperationException(
$"Cannot bind to an object of type '{instance.GetType()}");
}
var property = perspexObject.GetRegisteredProperties()
.FirstOrDefault(x => x.Name == _xamlMember.Name);
if (property == null)
{
throw new InvalidOperationException(
$"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}");
}
var binding = new XamlBinding(_propertyBinder.TypeConverterProvider)
{
BindingMode = def.BindingMode,
SourcePropertyPath = def.SourcePropertyPath,
Target = def.Target,
TargetProperty = def.TargetProperty,
};
binding.Bind();
binding.Bind(perspexObject, property);
}
private void BindToDataContextWhenItsSet(XamlBindingDefinition definition)

6
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -21,11 +21,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public override object ProvideValue(MarkupExtensionContext extensionContext)
{
var target = extensionContext.TargetObject as Control;
var targetProperty = extensionContext.TargetProperty;
var targetPropertyName = targetProperty.Name;
var perspexProperty = target.GetRegisteredProperties().First(property => property.Name == targetPropertyName);
return new XamlBindingDefinition(target, perspexProperty, Path, Mode);
return new XamlBindingDefinition(Path, Mode);
}
public string Path { get; set; }

27
tests/Perspex.Markup.Xaml.UnitTests/BinderTest.cs

@ -1,27 +0,0 @@
// 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 Moq;
using Perspex.Markup.Xaml.Binding;
using OmniXaml.TypeConversion;
using Xunit;
namespace Perspex.Xaml.Base.UnitTest
{
public class BinderTest
{
[Fact]
public void NullTarget_Throws()
{
var typeConverter = new Mock<ITypeConverterProvider>();
var perspexPropertyBinder = new PerspexPropertyBinder(typeConverter.Object);
var bindingDefinitionBuilder = new BindingDefinitionBuilder();
var binding = bindingDefinitionBuilder
.WithNullTarget()
.Build();
var exception = Assert.Throws<InvalidOperationException>(() => perspexPropertyBinder.Create(binding));
}
}
}

20
tests/Perspex.Markup.Xaml.UnitTests/Binding/XamlBindingTests.cs

@ -19,13 +19,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding
var target = CreateTarget();
var binding = new XamlBinding
{
Target = target.Object,
TargetProperty = TextBox.TextProperty,
SourcePropertyPath = "Foo",
BindingMode = BindingMode.OneWay,
};
binding.Bind();
binding.Bind(target.Object, TextBox.TextProperty);
target.Verify(x => x.Bind(
TextBox.TextProperty,
@ -39,13 +37,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding
var target = CreateTarget();
var binding = new XamlBinding
{
Target = target.Object,
TargetProperty = TextBox.TextProperty,
SourcePropertyPath = "Foo",
BindingMode = BindingMode.TwoWay,
};
binding.Bind();
binding.Bind(target.Object, TextBox.TextProperty);
target.Verify(x => x.BindTwoWay(
TextBox.TextProperty,
@ -61,13 +57,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding
var target = CreateTarget(dataContext: dataContext);
var binding = new XamlBinding
{
Target = target.Object,
TargetProperty = TextBox.TextProperty,
SourcePropertyPath = "Foo",
BindingMode = BindingMode.OneTime,
};
binding.Bind(expression);
binding.Bind(target.Object, TextBox.TextProperty, expression);
target.Verify(x => x.SetValue(
(PerspexProperty)TextBox.TextProperty,
@ -92,13 +86,11 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding
var target = CreateTarget(text: textObservable.Object);
var binding = new XamlBinding
{
Target = target.Object,
TargetProperty = TextBox.TextProperty,
SourcePropertyPath = "Foo",
BindingMode = BindingMode.OneWayToSource,
};
binding.Bind(expression.Object);
binding.Bind(target.Object, TextBox.TextProperty, expression.Object);
textObservable.Verify(x => x.Subscribe(expression.Object));
}
@ -109,12 +101,10 @@ namespace Perspex.Markup.Xaml.UnitTests.Binding
var target = CreateTarget(null);
var binding = new XamlBinding
{
Target = target.Object,
TargetProperty = TextBox.TextProperty,
SourcePropertyPath = "Foo",
};
binding.Bind();
binding.Bind(target.Object, TextBox.TextProperty);
// Default for TextBox.Text is two-way.
target.Verify(x => x.BindTwoWay(

36
tests/Perspex.Markup.Xaml.UnitTests/BindingDefinitionBuilder.cs

@ -1,36 +0,0 @@
// 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 Perspex.Controls;
using Perspex.Markup.Xaml.Binding;
namespace Perspex.Xaml.Base.UnitTest
{
public class BindingDefinitionBuilder
{
private readonly BindingMode _bindingMode;
private readonly string _sourcePropertyPath;
private Control _target;
public BindingDefinitionBuilder()
{
_bindingMode = BindingMode.Default;
_sourcePropertyPath = string.Empty;
}
public BindingDefinitionBuilder WithNullTarget()
{
_target = null;
return this;
}
public XamlBindingDefinition Build()
{
return new XamlBindingDefinition(
bindingMode: _bindingMode,
sourcePropertyPath: _sourcePropertyPath,
target: _target,
targetProperty: null);
}
}
}

2
tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj

@ -88,7 +88,6 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="BindingDefinitionBuilder.cs" />
<Compile Include="Binding\XamlBindingTests.cs" />
<Compile Include="Converters\PerspexPropertyConverterTest.cs" />
<Compile Include="Parsers\SelectorGrammarTests.cs" />
@ -103,7 +102,6 @@
<Compile Include="SampleModel\UserRepositoriesViewModel.cs" />
<Compile Include="SamplePerspexObject.cs" />
<Compile Include="TypeProviderMock.cs" />
<Compile Include="BinderTest.cs" />
<Compile Include="XamlBindingTest.cs" />
<Compile Include="ViewModelMock.cs" />
</ItemGroup>

Loading…
Cancel
Save