Browse Source

Bind to other controls with #control syntax.

Rather than specifying ElementName separately, allow binding using a "#control.Property" property path.
pull/325/head
Steven Kirk 11 years ago
parent
commit
1044154279
  1. 92
      src/Markup/Perspex.Markup.Xaml/Data/Binding.cs
  2. 2
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  3. 2
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  4. 2
      src/Markup/Perspex.Markup.Xaml/OmniXAML
  5. 1
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  6. 2
      src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
  7. 4
      src/Perspex.Themes.Default/TextBox.paml
  8. 4
      tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  9. 20
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs
  10. 4
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
  11. 6
      tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
  12. 6
      tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs

92
src/Markup/Perspex.Markup.Xaml/Data/Binding.cs

@ -42,7 +42,7 @@ namespace Perspex.Markup.Xaml.Data
/// <summary> /// <summary>
/// Gets or sets the binding path. /// Gets or sets the binding path.
/// </summary> /// </summary>
public string SourcePropertyPath { get; set; } public string Path { get; set; }
/// <summary> /// <summary>
/// Applies the binding to a property on an instance. /// Applies the binding to a property on an instance.
@ -82,19 +82,30 @@ namespace Perspex.Markup.Xaml.Data
Contract.Requires<ArgumentNullException>(target != null); Contract.Requires<ArgumentNullException>(target != null);
Contract.Requires<ArgumentNullException>(targetType != null); Contract.Requires<ArgumentNullException>(targetType != null);
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
ExpressionObserver observer; ExpressionObserver observer;
if (ElementName != null) if (pathInfo.ElementName != null || ElementName != null)
{ {
observer = CreateElementSubject((IControl)target); observer = CreateElementSubject(
(IControl)target,
pathInfo.ElementName ?? ElementName,
pathInfo.Path);
} }
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext) else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{ {
observer = CreateDataContextSubject(target, targetIsDataContext); observer = CreateDataContextSubject(
target,
pathInfo.Path,
targetIsDataContext);
} }
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{ {
observer = CreateTemplatedParentSubject(target); observer = CreateTemplatedParentSubject(
target,
pathInfo.Path);
} }
else else
{ {
@ -143,8 +154,56 @@ namespace Perspex.Markup.Xaml.Data
} }
} }
private static PathInfo ParsePath(string path)
{
var result = new PathInfo();
if (string.IsNullOrWhiteSpace(path) || path == ".")
{
result.Path = path;
}
else if (path.StartsWith("#"))
{
var dot = path.IndexOf('.');
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
result.ElementName = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
result.ElementName = path.Substring(1);
}
}
else
{
result.Path = path;
}
return result;
}
private void ValidateState(PathInfo pathInfo)
{
if (pathInfo.ElementName != null && ElementName != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when an #elementName path is provided.");
}
if ((pathInfo.ElementName != null || ElementName != null) &&
RelativeSource != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set with a RelativeSource.");
}
}
private ExpressionObserver CreateDataContextSubject( private ExpressionObserver CreateDataContextSubject(
IObservablePropertyBag target, IObservablePropertyBag target,
string path,
bool targetIsDataContext) bool targetIsDataContext)
{ {
Contract.Requires<ArgumentNullException>(target != null); Contract.Requires<ArgumentNullException>(target != null);
@ -156,7 +215,7 @@ namespace Perspex.Markup.Xaml.Data
{ {
var result = new ExpressionObserver( var result = new ExpressionObserver(
() => dataContextHost.GetValue(Control.DataContextProperty), () => dataContextHost.GetValue(Control.DataContextProperty),
GetExpression()); path);
dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x => dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x =>
result.UpdateRoot()); result.UpdateRoot());
return result; return result;
@ -168,13 +227,15 @@ namespace Perspex.Markup.Xaml.Data
} }
} }
private ExpressionObserver CreateTemplatedParentSubject(IObservablePropertyBag target) private ExpressionObserver CreateTemplatedParentSubject(
IObservablePropertyBag target,
string path)
{ {
Contract.Requires<ArgumentNullException>(target != null); Contract.Requires<ArgumentNullException>(target != null);
var result = new ExpressionObserver( var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty), () => target.GetValue(Control.TemplatedParentProperty),
GetExpression()); path);
if (target.GetValue(Control.TemplatedParentProperty) == null) if (target.GetValue(Control.TemplatedParentProperty) == null)
{ {
@ -189,13 +250,16 @@ namespace Perspex.Markup.Xaml.Data
return result; return result;
} }
private ExpressionObserver CreateElementSubject(IControl target) private ExpressionObserver CreateElementSubject(
IControl target,
string elementName,
string path)
{ {
Contract.Requires<ArgumentNullException>(target != null); Contract.Requires<ArgumentNullException>(target != null);
var result = new ExpressionObserver( var result = new ExpressionObserver(
ControlLocator.Track(target, ElementName), ControlLocator.Track(target, elementName),
GetExpression()); path);
return result; return result;
} }
@ -214,10 +278,10 @@ namespace Perspex.Markup.Xaml.Data
return nameScope.Find<IControl>(ElementName); return nameScope.Find<IControl>(ElementName);
} }
private string GetExpression() private class PathInfo
{ {
return SourcePropertyPath == null || SourcePropertyPath == "." ? public string Path { get; set; }
string.Empty : SourcePropertyPath; public string ElementName { get; set; }
} }
} }
} }

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

@ -24,7 +24,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
Converter = Converter, Converter = Converter,
ElementName = ElementName, ElementName = ElementName,
Mode = Mode, Mode = Mode,
SourcePropertyPath = Path, Path = Path,
}; };
} }

2
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@ -26,7 +26,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
Mode = Mode, Mode = Mode,
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
SourcePropertyPath = Path, Path = Path,
}; };
} }

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

@ -1 +1 @@
Subproject commit 55fc2abc715885d3686f8c8605f34714445cd72f Subproject commit 85f2814e7a0daa76d9b1e63b1a329cb291be30a9

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

@ -185,7 +185,6 @@
<Compile Include="OmniXAML\Source\OmniXaml\Sequence.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\Sequence.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TemplateHostingObjectAssembler.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TemplateHostingObjectAssembler.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeContext.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeContext.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeContextBuilder.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\BooleanConverter.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\BooleanConverter.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\DoubleTypeConverter.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\DoubleTypeConverter.cs" />
<Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\IntTypeConverter.cs" /> <Compile Include="OmniXAML\Source\OmniXaml\TypeConversion\BuiltInConverters\IntTypeConverter.cs" />

2
src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -35,7 +35,7 @@ namespace Perspex.Markup.Xaml.Templates
{ {
if (ItemsSource != null) if (ItemsSource != null)
{ {
var obs = new ExpressionObserver(item, ItemsSource.SourcePropertyPath); var obs = new ExpressionObserver(item, ItemsSource.Path);
return obs.Take(1).Wait() as IEnumerable; return obs.Take(1).Wait() as IEnumerable;
} }

4
src/Perspex.Themes.Default/TextBox.paml

@ -22,9 +22,9 @@
<TextBlock.IsVisible> <TextBlock.IsVisible>
<MultiBinding Converter="{Static BoolConverters.And}"> <MultiBinding Converter="{Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" <Binding RelativeSource="{RelativeSource TemplatedParent}"
SourcePropertyPath="UseFloatingWatermark"/> Path="UseFloatingWatermark"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" <Binding RelativeSource="{RelativeSource TemplatedParent}"
SourcePropertyPath="Text" Path="Text"
Converter="{Static StringConverters.NotNullOrEmpty}"/> Converter="{Static StringConverters.NotNullOrEmpty}"/>
</MultiBinding> </MultiBinding>
</TextBlock.IsVisible> </TextBlock.IsVisible>

4
tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -403,13 +403,13 @@ namespace Perspex.Controls.UnitTests.Primitives
var itemsBinding = new Binding var itemsBinding = new Binding
{ {
SourcePropertyPath = "Items", Path = "Items",
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
}; };
var selectedItemsBinding = new Binding var selectedItemsBinding = new Binding
{ {
SourcePropertyPath = "SelectedItems", Path = "SelectedItems",
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
}; };

20
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs

@ -20,7 +20,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(); var target = CreateTarget();
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
}; };
@ -38,7 +38,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(); var target = CreateTarget();
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
}; };
@ -58,7 +58,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(dataContext: dataContext); var target = CreateTarget(dataContext: dataContext);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneTime, Mode = BindingMode.OneTime,
}; };
@ -87,7 +87,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object); var target = CreateTarget(text: textObservable.Object);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneWayToSource, Mode = BindingMode.OneWayToSource,
}; };
@ -102,7 +102,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null); var target = CreateTarget(null);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
}; };
binding.Bind(target.Object, TextBox.TextProperty); binding.Bind(target.Object, TextBox.TextProperty);
@ -127,7 +127,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Header", Path = "Header",
}; };
binding.Bind(parent.Child, Control.DataContextProperty); binding.Bind(parent.Child, Control.DataContextProperty);
@ -145,7 +145,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null); var target = CreateTarget(null);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
}; };
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -161,7 +161,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding var binding = new Binding
{ {
Converter = converter.Object, Converter = converter.Object,
SourcePropertyPath = "Foo", Path = "Foo",
}; };
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -189,13 +189,13 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var fooBinding = new Binding var fooBinding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
}; };
var barBinding = new Binding var barBinding = new Binding
{ {
SourcePropertyPath = "Bar", Path = "Bar",
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
}; };

4
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs

@ -35,7 +35,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding var binding = new Binding
{ {
ElementName = "source", ElementName = "source",
SourcePropertyPath = "Text", Path = "Text",
}; };
binding.Bind(target, TextBlock.TextProperty); binding.Bind(target, TextBlock.TextProperty);
@ -66,7 +66,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding var binding = new Binding
{ {
ElementName = "source", ElementName = "source",
SourcePropertyPath = "Text", Path = "Text",
}; };
binding.Bind(target, TextBlock.TextProperty); binding.Bind(target, TextBlock.TextProperty);

6
tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs

@ -23,7 +23,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo", Path = "Foo",
}; };
binding.Bind(target.Object, TextBox.TextProperty); binding.Bind(target.Object, TextBox.TextProperty);
@ -43,7 +43,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Mode = BindingMode.TwoWay, Mode = BindingMode.TwoWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent, Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo", Path = "Foo",
}; };
binding.Bind(target.Object, TextBox.TextProperty); binding.Bind(target.Object, TextBox.TextProperty);
@ -62,7 +62,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object); var target = CreateTarget(text: textObservable.Object);
var binding = new Binding var binding = new Binding
{ {
SourcePropertyPath = "Foo", Path = "Foo",
Mode = BindingMode.OneWayToSource, Mode = BindingMode.OneWayToSource,
}; };

6
tests/Perspex.Markup.Xaml.UnitTests/Data/MultiBindingTests.cs

@ -25,9 +25,9 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Converter = new ConcatConverter(), Converter = new ConcatConverter(),
Bindings = new[] Bindings = new[]
{ {
new Binding { SourcePropertyPath = "A" }, new Binding { Path = "A" },
new Binding { SourcePropertyPath = "B" }, new Binding { Path = "B" },
new Binding { SourcePropertyPath = "C" }, new Binding { Path = "C" },
} }
}; };

Loading…
Cancel
Save