diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index a3c3015a38..41c2ad6e54 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -350,14 +350,15 @@ namespace Avalonia.Collections
public void MoveRange(int oldIndex, int count, int newIndex)
{
var items = _inner.GetRange(oldIndex, count);
+ var modifiedNewIndex = newIndex;
_inner.RemoveRange(oldIndex, count);
if (newIndex > oldIndex)
{
- newIndex -= count;
+ modifiedNewIndex -= count;
}
- _inner.InsertRange(newIndex, items);
+ _inner.InsertRange(modifiedNewIndex, items);
if (_collectionChanged != null)
{
diff --git a/src/Avalonia.Base/Collections/IAvaloniaList.cs b/src/Avalonia.Base/Collections/IAvaloniaList.cs
index 0233cee7a9..48c36976a5 100644
--- a/src/Avalonia.Base/Collections/IAvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/IAvaloniaList.cs
@@ -36,6 +36,21 @@ namespace Avalonia.Collections
/// The items.
void InsertRange(int index, IEnumerable items);
+ ///
+ /// Moves an item to a new index.
+ ///
+ /// The index of the item to move.
+ /// The index to move the item to.
+ void Move(int oldIndex, int newIndex);
+
+ ///
+ /// Moves multiple items to a new index.
+ ///
+ /// The first index of the items to move.
+ /// The number of items to move.
+ /// The index to move the items to.
+ void MoveRange(int oldIndex, int count, int newIndex);
+
///
/// Removes multiple items from the collection.
///
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 8c79e5dce5..59281c5ad0 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -549,7 +549,7 @@ namespace Avalonia.Controls
}
else
{
- if (addedDate.HasValue && !(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
+ if (!(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
{
foreach (DateTime item in SelectedDates)
{
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index d4777b2f8a..6e1e1a05f1 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -621,7 +621,6 @@ namespace Avalonia.Controls
Contract.Requires(property != null);
Contract.Requires(selector != null);
Contract.Requires(className != null);
- Contract.Requires(property != null);
if (string.IsNullOrWhiteSpace(className))
{
diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs
index fa2e0c1e16..6b27c479ba 100644
--- a/src/Avalonia.Controls/DropDown.cs
+++ b/src/Avalonia.Controls/DropDown.cs
@@ -96,6 +96,16 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(this.SelectedItem);
}
+ protected override void OnGotFocus(GotFocusEventArgs e)
+ {
+ base.OnGotFocus(e);
+
+ if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
+ {
+ e.Handled = UpdateSelectionFromEventSource(e.Source);
+ }
+ }
+
///
protected override void OnKeyDown(KeyEventArgs e)
{
@@ -104,7 +114,7 @@ namespace Avalonia.Controls
if (!e.Handled)
{
if (e.Key == Key.F4 ||
- (e.Key == Key.Down && ((e.Modifiers & InputModifiers.Alt) != 0)))
+ ((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
@@ -114,6 +124,27 @@ namespace Avalonia.Controls
IsDropDownOpen = false;
e.Handled = true;
}
+
+ if (!IsDropDownOpen)
+ {
+ if (e.Key == Key.Down)
+ {
+ if (SelectedIndex == -1)
+ SelectedIndex = 0;
+
+ if (++SelectedIndex >= ItemCount)
+ SelectedIndex = 0;
+
+ e.Handled = true;
+ }
+ else if (e.Key == Key.Up)
+ {
+ if (--SelectedIndex < 0)
+ SelectedIndex = ItemCount - 1;
+
+ e.Handled = true;
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index aa209e0462..4366de1cd6 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
+using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
@@ -106,6 +107,12 @@ namespace Avalonia.Controls
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
+ public int ItemCount
+ {
+ get;
+ private set;
+ }
+
///
/// Gets or sets the panel used to display the items.
///
@@ -352,6 +359,10 @@ namespace Avalonia.Controls
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
+
+ int? count = (Items as IList)?.Count;
+ if (count != null)
+ ItemCount = (int)count;
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs
index 3272d3779b..6448f11491 100644
--- a/src/Avalonia.Controls/Panel.cs
+++ b/src/Avalonia.Controls/Panel.cs
@@ -115,6 +115,11 @@ namespace Avalonia.Controls
VisualChildren.AddRange(e.NewItems.OfType());
break;
+ case NotifyCollectionChangedAction.Move:
+ LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+ VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+ break;
+
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType().ToList();
LogicalChildren.RemoveAll(controls);
@@ -132,11 +137,7 @@ namespace Avalonia.Controls
break;
case NotifyCollectionChangedAction.Reset:
- controls = e.OldItems.OfType().ToList();
- LogicalChildren.Clear();
- VisualChildren.Clear();
- VisualChildren.AddRange(_children);
- break;
+ throw new NotSupportedException();
}
InvalidateMeasure();
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index ab09a4701d..563d394919 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -297,7 +297,7 @@ namespace Avalonia.Controls.Primitives
.OfType()
.FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
- return item as IControl;
+ return item;
}
///
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index de02c10764..8cf6b149cb 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -28,9 +28,6 @@ namespace Avalonia.Controls
{
ValueProperty.Changed.AddClassHandler(x => x.ValueChanged);
- HorizontalAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Left);
- VerticalAlignmentProperty.OverrideDefaultValue(VerticalAlignment.Top);
-
IsIndeterminateProperty.Changed.AddClassHandler(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler(
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 079e571d29..fa3ecdedef 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -176,10 +176,7 @@ namespace Avalonia.Controls
SelectedItem = item;
- if (SelectedItem != null)
- {
- MarkContainerSelected(container, true);
- }
+ MarkContainerSelected(container, true);
}
}
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 834f6d218b..409dd231ad 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -134,12 +134,14 @@ namespace Avalonia.Controls
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
+ if (from == null)
+ return null;
+
var logicalScrollable = Parent as ILogicalScrollable;
- var fromControl = from as IControl;
- if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
+ if (logicalScrollable?.IsLogicalScrollEnabled == true)
{
- return logicalScrollable.GetControlInDirection(direction, fromControl);
+ return logicalScrollable.GetControlInDirection(direction, from);
}
else
{
diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
index fbc189546c..9e4a3cbeae 100644
--- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs
+++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
@@ -320,7 +320,7 @@ namespace Avalonia.Media
if (c == 'E')
{
readSign = false;
- readExponent = c == 'E';
+ readExponent = true;
}
}
else
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 90ef78de37..b5ac7e4077 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -192,7 +192,7 @@ namespace Avalonia.Rendering.SceneGraph
UpdateLayer(node, scene.Layers[node.LayerRoot]);
}
}
- else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
+ else if (node.LayerRoot == node.Visual && node.Parent != null)
{
ClearLayer(scene, node);
}
diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs
index bc65d4f69f..3662fe50be 100644
--- a/src/Avalonia.Visuals/Visual.cs
+++ b/src/Avalonia.Visuals/Visual.cs
@@ -537,6 +537,19 @@ namespace Avalonia
v.SetVisualParent(null);
}
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ foreach (Visual v in e.OldItems)
+ {
+ v.SetVisualParent(null);
+ }
+
+ foreach (Visual v in e.NewItems)
+ {
+ v.SetVisualParent(this);
+ }
+
break;
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
index eb5f87ea2a..7f750144df 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
@@ -66,7 +66,7 @@ namespace Avalonia.Markup.Xaml.Data
///
/// Gets or sets the binding path.
///
- public string Path { get; set; }
+ public string Path { get; set; } = "";
///
/// Gets or sets the binding priority.
@@ -93,53 +93,53 @@ namespace Avalonia.Markup.Xaml.Data
bool enableDataValidation = false)
{
Contract.Requires(target != null);
-
anchor = anchor ?? DefaultAnchor?.Target;
-
- var pathInfo = ParsePath(Path);
- ValidateState(pathInfo);
+
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
-
+
ExpressionObserver observer;
- if (pathInfo.ElementName != null || ElementName != null)
+ if (ElementName != null)
{
observer = CreateElementObserver(
(target as IControl) ?? (anchor as IControl),
- pathInfo.ElementName ?? ElementName,
- pathInfo.Path);
+ ElementName,
+ Path,
+ enableDataValidation);
}
else if (Source != null)
{
- observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation);
+ observer = CreateSourceObserver(Source, Path, enableDataValidation);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContexObserver(
target,
- pathInfo.Path,
+ Path,
targetProperty == Control.DataContextProperty,
anchor,
enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
- observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
+ observer = CreateSourceObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
- observer = CreateTemplatedParentObserver(target, pathInfo.Path);
+ observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
- if (RelativeSource.AncestorType == null)
+ if (RelativeSource.Tree == TreeType.Visual && RelativeSource.AncestorType == null)
{
- throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor.");
+ throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
observer = CreateFindAncestorObserver(
(target as IControl) ?? (anchor as IControl),
- pathInfo.Path);
+ RelativeSource,
+ Path,
+ enableDataValidation);
}
else
{
@@ -168,53 +168,6 @@ namespace Avalonia.Markup.Xaml.Data
return new InstancedBinding(subject, Mode, Priority);
}
- private static PathInfo ParsePath(string path)
- {
- var result = new PathInfo();
-
- if (string.IsNullOrWhiteSpace(path) || path == ".")
- {
- result.Path = string.Empty;
- }
- 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 CreateDataContexObserver(
IAvaloniaObject target,
string path,
@@ -256,7 +209,11 @@ namespace Avalonia.Markup.Xaml.Data
}
}
- private ExpressionObserver CreateElementObserver(IControl target, string elementName, string path)
+ private ExpressionObserver CreateElementObserver(
+ IControl target,
+ string elementName,
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
@@ -264,35 +221,39 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
path,
- false,
+ enableDataValidation,
description);
return result;
}
private ExpressionObserver CreateFindAncestorObserver(
IControl target,
- string path)
+ RelativeSource relativeSource,
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
return new ExpressionObserver(
- ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1),
- path);
+ ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
+ path,
+ enableDataValidation);
}
private ExpressionObserver CreateSourceObserver(
object source,
string path,
- bool enabledDataValidation)
+ bool enableDataValidation)
{
Contract.Requires(source != null);
- return new ExpressionObserver(source, path, enabledDataValidation);
+ return new ExpressionObserver(source, path, enableDataValidation);
}
private ExpressionObserver CreateTemplatedParentObserver(
IAvaloniaObject target,
- string path)
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
@@ -303,7 +264,8 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
path,
- update);
+ update,
+ enableDataValidation);
return result;
}
@@ -328,6 +290,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
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
index f77df6853b..825d3b8ba5 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
@@ -87,5 +87,7 @@ namespace Avalonia.Markup.Xaml.Data
/// Gets or sets a value that describes the type of relative source lookup.
///
public RelativeSourceMode Mode { get; set; }
+
+ public TreeType Tree { get; set; } = TreeType.Visual;
}
}
\ No newline at end of file
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
index 8984498393..c6705cbb4b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
@@ -29,20 +29,167 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
+ var descriptorContext = (ITypeDescriptorContext)serviceProvider;
+
+ var pathInfo = ParsePath(Path, descriptorContext);
+ ValidateState(pathInfo);
+
return new Binding
{
Converter = Converter,
ConverterParameter = ConverterParameter,
- ElementName = ElementName,
+ ElementName = pathInfo.ElementName ?? ElementName,
FallbackValue = FallbackValue,
Mode = Mode,
- Path = Path,
+ Path = pathInfo.Path,
Priority = Priority,
- RelativeSource = RelativeSource,
+ RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
}
+ private class PathInfo
+ {
+ public string Path { get; set; }
+ public string ElementName { get; set; }
+ public RelativeSource RelativeSource { get; set; }
+ }
+
+ 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.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) &&
+ (pathInfo.RelativeSource != null || RelativeSource != null))
+ {
+ throw new InvalidOperationException(
+ "ElementName property cannot be set with a RelativeSource.");
+ }
+ }
+
+ private static PathInfo ParsePath(string path, ITypeDescriptorContext context)
+ {
+ var result = new PathInfo();
+
+ if (string.IsNullOrWhiteSpace(path) || path == ".")
+ {
+ result.Path = string.Empty;
+ }
+ 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 if (path.StartsWith("$"))
+ {
+ var relativeSource = new RelativeSource
+ {
+ Tree = TreeType.Logical
+ };
+ 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;
+ relativeSource.AncestorLevel = 1;
+ }
+ else if (relativeSourceMode.StartsWith("parent["))
+ {
+ relativeSource.Mode = RelativeSourceMode.FindAncestor;
+ var parentConfigStart = relativeSourceMode.IndexOf('[');
+ if (!relativeSourceMode.EndsWith("]"))
+ {
+ throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['.");
+ }
+ var parentConfigParams = relativeSourceMode.Substring(parentConfigStart + 1).TrimEnd(']').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 = null;
+ relativeSource.AncestorLevel = level + 1;
+ }
+ else
+ {
+ relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+ }
+ }
+ else
+ {
+ relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+ relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]) + 1;
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}");
+ }
+ }
+ else
+ {
+ result.Path = path;
+ }
+
+ return result;
+ }
+
+ private static Type LookupAncestorType(string ancestorTypeName, ITypeDescriptorContext context)
+ {
+ var parts = ancestorTypeName.Split(':');
+ if (parts.Length == 0 || parts.Length > 2)
+ {
+ throw new InvalidOperationException("Invalid type name");
+ }
+
+ if (parts.Length == 1)
+ {
+ return context.ResolveType(string.Empty, parts[0]);
+ }
+ else
+ {
+ return context.ResolveType(parts[0], parts[1]);
+ }
+ }
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
index 4664947b8e..c5fe83977f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
@@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
ElementName = ElementName,
Mode = Mode,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
- Path = Path,
+ Path = Path ?? string.Empty,
Priority = Priority,
};
}
diff --git a/src/Markup/Avalonia.Markup/ControlLocator.cs b/src/Markup/Avalonia.Markup/ControlLocator.cs
index de8415d6db..1a82c0a4fd 100644
--- a/src/Markup/Avalonia.Markup/ControlLocator.cs
+++ b/src/Markup/Avalonia.Markup/ControlLocator.cs
@@ -11,6 +11,21 @@ using Avalonia.VisualTree;
namespace Avalonia.Markup
{
+ ///
+ /// The type of tree via which to track a control.
+ ///
+ public enum TreeType
+ {
+ ///
+ /// The visual tree.
+ ///
+ Visual,
+ ///
+ /// The logical tree.
+ ///
+ Logical,
+ }
+
///
/// Locates controls relative to other controls.
///
@@ -27,13 +42,13 @@ namespace Avalonia.Markup
{
var attached = Observable.FromEventPattern(
x => relativeTo.AttachedToLogicalTree += x,
- x => relativeTo.DetachedFromLogicalTree += x)
+ x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => ((IControl)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern(
x => relativeTo.DetachedFromLogicalTree += x,
- x => relativeTo.DetachedFromLogicalTree += x)
+ x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
@@ -68,37 +83,75 @@ namespace Avalonia.Markup
///
/// The control relative from which the other control should be found.
///
- /// The type of the ancestor to find.
+ /// The tree via which to track the control.
///
/// The level of ancestor control to look for. Use 0 for the first ancestor of the
/// requested type.
///
- public static IObservable Track(IControl relativeTo, Type ancestorType, int ancestorLevel)
+ /// The type of the ancestor to find.
+ public static IObservable Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
+ {
+ return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
+ {
+ if (isAttachedToTree)
+ {
+ if (tree == TreeType.Visual)
+ {
+ return relativeTo.GetVisualAncestors()
+ .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+ .ElementAtOrDefault(ancestorLevel) as IControl;
+ }
+ else
+ {
+ return relativeTo.GetLogicalAncestors()
+ .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+ .ElementAtOrDefault(ancestorLevel) as IControl;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ });
+ }
+
+ private static IObservable TrackAttachmentToTree(IControl relativeTo, TreeType tree)
+ {
+ return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
+ }
+
+ private static IObservable TrackAttachmentToVisualTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern(
x => relativeTo.AttachedToVisualTree += x,
- x => relativeTo.DetachedFromVisualTree += x)
+ x => relativeTo.AttachedToVisualTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToVisualTree);
var detached = Observable.FromEventPattern(
x => relativeTo.DetachedFromVisualTree += x,
- x => relativeTo.DetachedFromVisualTree += x)
+ x => relativeTo.DetachedFromVisualTree -= x)
.Select(x => false);
- return attached.Merge(detached).Select(isAttachedToVisualTree =>
- {
- if (isAttachedToVisualTree)
- {
- return relativeTo.GetVisualAncestors()
- .Where(x => ancestorType.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()))
- .ElementAtOrDefault(ancestorLevel) as IControl;
- }
- else
- {
- return null;
- }
- });
+ var attachmentStatus = attached.Merge(detached);
+ return attachmentStatus;
+ }
+
+ private static IObservable TrackAttachmentToLogicalTree(IControl relativeTo)
+ {
+ var attached = Observable.FromEventPattern(
+ x => relativeTo.AttachedToLogicalTree += x,
+ x => relativeTo.AttachedToLogicalTree -= x)
+ .Select(x => true)
+ .StartWith(relativeTo.IsAttachedToLogicalTree);
+
+ var detached = Observable.FromEventPattern(
+ x => relativeTo.DetachedFromLogicalTree += x,
+ x => relativeTo.DetachedFromLogicalTree -= x)
+ .Select(x => false);
+
+ var attachmentStatus = attached.Merge(detached);
+ return attachmentStatus;
}
}
}
diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
index a824a38867..563b372c78 100644
--- a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
+++ b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
@@ -51,15 +51,7 @@ namespace Avalonia.Markup.Data.Parsers
}
}
- if (!r.End)
- {
- r.Take();
- return result;
- }
- else
- {
- throw new ExpressionParseException(r.Position, "Expected ']'.");
- }
+ throw new ExpressionParseException(r.Position, "Expected ']'.");
}
return null;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 6a72923ce3..b1bfdcbfeb 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -54,7 +54,6 @@ namespace Avalonia.Direct2D1.Media
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
- _swapChain = swapChain;
_renderTarget.BeginDraw();
}
diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
index 587816b07b..fd731455d8 100644
--- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
@@ -83,6 +83,28 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(new[] { 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 }, target);
}
+ [Fact]
+ public void MoveRange_Raises_Correct_CollectionChanged_Event()
+ {
+ var target = new AvaloniaList(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ var raised = false;
+
+ target.CollectionChanged += (s, e) =>
+ {
+ Assert.Equal(NotifyCollectionChangedAction.Move, e.Action);
+ Assert.Equal(0, e.OldStartingIndex);
+ Assert.Equal(10, e.NewStartingIndex);
+ Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.OldItems);
+ Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.NewItems);
+ raised = true;
+ };
+
+ target.MoveRange(0, 9, 10);
+
+ Assert.True(raised);
+ Assert.Equal(new[] { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, target);
+ }
+
[Fact]
public void Adding_Item_Should_Raise_CollectionChanged()
{
diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
index fb1ae3ba1a..29acdaa8d4 100644
--- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
@@ -4,6 +4,7 @@
using System.Linq;
using Avalonia.Collections;
using Avalonia.LogicalTree;
+using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Controls.UnitTests
@@ -18,8 +19,9 @@ namespace Avalonia.Controls.UnitTests
panel.Children.Add(child);
- Assert.Equal(child.Parent, panel);
- Assert.Equal(child.GetLogicalParent(), panel);
+ Assert.Same(child.Parent, panel);
+ Assert.Same(child.GetLogicalParent(), panel);
+ Assert.Same(child.GetVisualParent(), panel);
}
[Fact]
@@ -45,6 +47,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child.Parent);
Assert.Null(child.GetLogicalParent());
+ Assert.Null(child.GetVisualParent());
}
[Fact]
@@ -60,8 +63,10 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child1.Parent);
Assert.Null(child1.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
Assert.Null(child2.Parent);
Assert.Null(child2.GetLogicalParent());
+ Assert.Null(child2.GetVisualParent());
}
[Fact]
@@ -77,24 +82,32 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child1.Parent);
Assert.Null(child1.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
Assert.Null(child2.Parent);
Assert.Null(child2.GetLogicalParent());
+ Assert.Null(child2.GetVisualParent());
}
[Fact]
- public void Setting_Children_Should_Make_Controls_Appear_In_Panel_Children()
+ public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent()
{
var panel = new Panel();
- var child = new Control();
+ var child1 = new Control();
+ var child2 = new Control();
- panel.Children = new Controls { child };
+ panel.Children.Add(child1);
+ panel.Children[0] = child2;
- Assert.Equal(new[] { child }, panel.Children);
- Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Null(child1.Parent);
+ Assert.Null(child1.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
+ Assert.Same(child2.Parent, panel);
+ Assert.Same(child2.GetLogicalParent(), panel);
+ Assert.Same(child2.GetVisualParent(), panel);
}
[Fact]
- public void Child_Control_Should_Appear_In_Panel_Children()
+ public void Child_Control_Should_Appear_In_Panel_Logical_And_Visual_Children()
{
var panel = new Panel();
var child = new Control();
@@ -103,10 +116,11 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child }, panel.Children);
Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child }, panel.GetVisualChildren());
}
[Fact]
- public void Removing_Child_Control_Should_Remove_From_Panel_Children()
+ public void Removing_Child_Control_Should_Remove_From_Panel_Logical_And_Visual_Children()
{
var panel = new Panel();
var child = new Control();
@@ -115,7 +129,36 @@ namespace Avalonia.Controls.UnitTests
panel.Children.Remove(child);
Assert.Equal(new Control[0], panel.Children);
- Assert.Equal(new ILogical[0], panel.GetLogicalChildren());
+ Assert.Empty(panel.GetLogicalChildren());
+ Assert.Empty(panel.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Moving_Panel_Children_Should_Reoder_Logical_And_Visual_Children()
+ {
+ var panel = new Panel();
+ var child1 = new Control();
+ var child2 = new Control();
+
+ panel.Children.Add(child1);
+ panel.Children.Add(child2);
+ panel.Children.Move(1, 0);
+
+ Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
+ }
+
+ [Fact]
+ public void Setting_Children_Should_Make_Controls_Appear_In_Logical_And_Visual_Children()
+ {
+ var panel = new Panel();
+ var child = new Control();
+
+ panel.Children = new Controls { child };
+
+ Assert.Equal(new[] { child }, panel.Children);
+ Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child }, panel.GetVisualChildren());
}
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
index 230e61f300..9a08073920 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
@@ -13,6 +13,7 @@ using Moq;
using Xunit;
using System.ComponentModel;
using System.Runtime.CompilerServices;
+using Avalonia.UnitTests;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
@@ -337,6 +338,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal("foo", target.Content);
}
+ [Fact]
+ public void Binding_With_Null_Path_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = window.FindControl("textBlock");
+
+ window.DataContext = "foo";
+ window.ApplyTemplate();
+
+ Assert.Equal("foo", textBlock.Text);
+ }
+ }
+
private class TwoWayBindingTest : Control
{
public static readonly StyledProperty TwoWayProperty =
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
index 197afe46ee..ccb13039f1 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
@@ -11,6 +11,9 @@ using Avalonia.Markup.Xaml.Data;
using Avalonia.Styling;
using Xunit;
using System.Reactive.Disposables;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using System.Linq;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
@@ -56,6 +59,35 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
BindingPriority.TemplatedParent));
}
+ [Fact]
+ public void TemplateBinding_With_Null_Path_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl