From 12e32dc08e9ac77c51860df9f6fe0f16d2ea5a6e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 9 Oct 2017 15:31:38 -0500 Subject: [PATCH] Implement RelativeSource binding syntax sugar except for parsing AncestorType. --- .../Avalonia.Markup.Xaml/Data/Binding.cs | 96 +++++++++++++++++-- 1 file changed, 87 insertions(+), 9 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs index 3f12593197..9d911999c6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs @@ -96,20 +96,23 @@ namespace Avalonia.Markup.Xaml.Data ValidateState(pathInfo); enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue; + var elementName = pathInfo.ElementName; + var relativeSource = RelativeSource ?? pathInfo.RelativeSource; + ExpressionObserver observer; - if (pathInfo.ElementName != null || ElementName != null) + if (elementName != null) { observer = CreateElementObserver( (target as IControl) ?? (anchor as IControl), - pathInfo.ElementName ?? ElementName, + elementName, pathInfo.Path); } else if (Source != null) { observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation); } - else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext) + else if (relativeSource == null || relativeSource.Mode == RelativeSourceMode.DataContext) { observer = CreateDataContexObserver( target, @@ -118,23 +121,24 @@ namespace Avalonia.Markup.Xaml.Data anchor, enableDataValidation); } - else if (RelativeSource.Mode == RelativeSourceMode.Self) + else if (relativeSource.Mode == RelativeSourceMode.Self) { observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation); } - else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent) + else if (relativeSource.Mode == RelativeSourceMode.TemplatedParent) { observer = CreateTemplatedParentObserver(target, pathInfo.Path); } - else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor) + else if (relativeSource.Mode == RelativeSourceMode.FindAncestor) { - if (RelativeSource.AncestorType == null) + if (relativeSource.AncestorType == null) { throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor."); } observer = CreateFindAncestorObserver( (target as IControl) ?? (anchor as IControl), + relativeSource, pathInfo.Path); } else @@ -187,6 +191,66 @@ namespace Avalonia.Markup.Xaml.Data result.ElementName = path.Substring(1); } } + else if (path.StartsWith("$")) + { + var relativeSource = new RelativeSource(); + result.RelativeSource = relativeSource; + var dot = path.IndexOf('.'); + string relativeSourceMode; + if (dot != -1) + { + result.Path = path.Substring(dot + 1); + relativeSourceMode = path.Substring(1, dot - 1); + } + else + { + result.Path = string.Empty; + relativeSourceMode = path.Substring(1); + } + + if (relativeSourceMode == "self") + { + relativeSource.Mode = RelativeSourceMode.Self; + } + else if(relativeSourceMode == "parent") + { + relativeSource.Mode = RelativeSourceMode.FindAncestor; + var parentConfigStart = relativeSourceMode.IndexOf('['); + if (parentConfigStart != -1) + { + if (!relativeSourceMode.EndsWith("]")) + { + throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['."); + } + var parentConfigParams = relativeSourceMode.Substring(0, relativeSourceMode.Length - 1).Split(','); + if (parentConfigParams.Length > 2 || parentConfigParams.Length == 0) + { + throw new InvalidOperationException("Expected either 1 or 2 parameters for RelativeSource binding syntax"); + } + else if (parentConfigParams.Length == 1) + { + if (int.TryParse(parentConfigParams[0], out int level)) + { + relativeSource.AncestorType = typeof(IControl); + relativeSource.AncestorLevel = level; + } + else + { + relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0]); + } + } + else + { + relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0]); + relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]); + } + } + } + else + { + throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}"); + } + } else { result.Path = path; @@ -195,6 +259,12 @@ namespace Avalonia.Markup.Xaml.Data return result; } + private static Type LookupAncestorType(string ancestorTypeName) + { + //TODO: What is our syntax for type lookup here? + throw new NotImplementedException(); + } + private void ValidateState(PathInfo pathInfo) { if (pathInfo.ElementName != null && ElementName != null) @@ -203,8 +273,14 @@ namespace Avalonia.Markup.Xaml.Data "ElementName property cannot be set when an #elementName path is provided."); } + if (pathInfo.RelativeSource != null && RelativeSource != null) + { + throw new InvalidOperationException( + "ElementName property cannot be set when a $self or $parent path is provided."); + } + if ((pathInfo.ElementName != null || ElementName != null) && - RelativeSource != null) + (pathInfo.RelativeSource != null || RelativeSource != null)) { throw new InvalidOperationException( "ElementName property cannot be set with a RelativeSource."); @@ -267,12 +343,13 @@ namespace Avalonia.Markup.Xaml.Data private ExpressionObserver CreateFindAncestorObserver( IControl target, + RelativeSource relativeSource, string path) { Contract.Requires(target != null); return new ExpressionObserver( - ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1), + ControlLocator.Track(target, relativeSource.AncestorType, relativeSource.AncestorLevel -1), path); } @@ -324,6 +401,7 @@ namespace Avalonia.Markup.Xaml.Data { public string Path { get; set; } public string ElementName { get; set; } + public RelativeSource RelativeSource { get; set; } } } } \ No newline at end of file