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>
/// Gets or sets the binding path.
/// </summary>
public string SourcePropertyPath { get; set; }
public string Path { get; set; }
/// <summary>
/// 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>(targetType != null);
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
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)
{
observer = CreateDataContextSubject(target, targetIsDataContext);
observer = CreateDataContextSubject(
target,
pathInfo.Path,
targetIsDataContext);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
observer = CreateTemplatedParentSubject(target);
observer = CreateTemplatedParentSubject(
target,
pathInfo.Path);
}
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(
IObservablePropertyBag target,
string path,
bool targetIsDataContext)
{
Contract.Requires<ArgumentNullException>(target != null);
@ -156,7 +215,7 @@ namespace Perspex.Markup.Xaml.Data
{
var result = new ExpressionObserver(
() => dataContextHost.GetValue(Control.DataContextProperty),
GetExpression());
path);
dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x =>
result.UpdateRoot());
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);
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
GetExpression());
path);
if (target.GetValue(Control.TemplatedParentProperty) == null)
{
@ -189,13 +250,16 @@ namespace Perspex.Markup.Xaml.Data
return result;
}
private ExpressionObserver CreateElementSubject(IControl target)
private ExpressionObserver CreateElementSubject(
IControl target,
string elementName,
string path)
{
Contract.Requires<ArgumentNullException>(target != null);
var result = new ExpressionObserver(
ControlLocator.Track(target, ElementName),
GetExpression());
ControlLocator.Track(target, elementName),
path);
return result;
}
@ -214,10 +278,10 @@ namespace Perspex.Markup.Xaml.Data
return nameScope.Find<IControl>(ElementName);
}
private string GetExpression()
private class PathInfo
{
return SourcePropertyPath == null || SourcePropertyPath == "." ?
string.Empty : SourcePropertyPath;
public string Path { get; set; }
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,
ElementName = ElementName,
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,
Priority = BindingPriority.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\TemplateHostingObjectAssembler.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\DoubleTypeConverter.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)
{
var obs = new ExpressionObserver(item, ItemsSource.SourcePropertyPath);
var obs = new ExpressionObserver(item, ItemsSource.Path);
return obs.Take(1).Wait() as IEnumerable;
}

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

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

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

@ -403,13 +403,13 @@ namespace Perspex.Controls.UnitTests.Primitives
var itemsBinding = new Binding
{
SourcePropertyPath = "Items",
Path = "Items",
Mode = BindingMode.OneWay,
};
var selectedItemsBinding = new Binding
{
SourcePropertyPath = "SelectedItems",
Path = "SelectedItems",
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 binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.OneWay,
};
@ -38,7 +38,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget();
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.TwoWay,
};
@ -58,7 +58,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(dataContext: dataContext);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.OneTime,
};
@ -87,7 +87,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.OneWayToSource,
};
@ -102,7 +102,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
@ -127,7 +127,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding
{
SourcePropertyPath = "Header",
Path = "Header",
};
binding.Bind(parent.Child, Control.DataContextProperty);
@ -145,7 +145,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(null);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
};
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -161,7 +161,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding
{
Converter = converter.Object,
SourcePropertyPath = "Foo",
Path = "Foo",
};
var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType);
@ -189,13 +189,13 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var fooBinding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
Mode = BindingMode.TwoWay,
};
var barBinding = new Binding
{
SourcePropertyPath = "Bar",
Path = "Bar",
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
{
ElementName = "source",
SourcePropertyPath = "Text",
Path = "Text",
};
binding.Bind(target, TextBlock.TextProperty);
@ -66,7 +66,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var binding = new Binding
{
ElementName = "source",
SourcePropertyPath = "Text",
Path = "Text",
};
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,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo",
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
@ -43,7 +43,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
Mode = BindingMode.TwoWay,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Priority = BindingPriority.TemplatedParent,
SourcePropertyPath = "Foo",
Path = "Foo",
};
binding.Bind(target.Object, TextBox.TextProperty);
@ -62,7 +62,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Data
var target = CreateTarget(text: textObservable.Object);
var binding = new Binding
{
SourcePropertyPath = "Foo",
Path = "Foo",
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(),
Bindings = new[]
{
new Binding { SourcePropertyPath = "A" },
new Binding { SourcePropertyPath = "B" },
new Binding { SourcePropertyPath = "C" },
new Binding { Path = "A" },
new Binding { Path = "B" },
new Binding { Path = "C" },
}
};

Loading…
Cancel
Save