diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 603308ef9a..accad63faa 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -34,9 +34,17 @@ jobs:
pool:
vmImage: 'macOS-10.14'
steps:
- - task: DotNetCoreInstaller@0
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core SDK 3.0.x'
inputs:
- version: '2.1.403'
+ packageType: sdk
+ version: 3.0.x
+
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core Runtime 2.1.x'
+ inputs:
+ packageType: runtime
+ version: 2.1.x
- task: CmdLine@2
displayName: 'Install Mono 5.18'
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 958729e2e8..52a9591c94 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -1,6 +1,4 @@
-using System;
using Avalonia;
-using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
@@ -19,7 +17,7 @@ namespace ControlCatalog
desktopLifetime.MainWindow = new MainWindow();
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView();
-
+
base.OnFrameworkInitializationCompleted();
}
}
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 59c6c47ed9..9158ac7038 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -32,7 +32,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
///
- public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
+ public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
{
///
/// The application-global data templates.
@@ -45,6 +45,12 @@ namespace Avalonia
private Styles _styles;
private IResourceDictionary _resources;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty
TreeContainerIndex Index { get; }
+
+ ///
+ /// Updates the index based on the parent .
+ ///
+ void UpdateIndex();
}
}
diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
index c06a64443c..9200490668 100644
--- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
@@ -3,8 +3,10 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Avalonia.Controls.Templates;
using Avalonia.Data;
+using Avalonia.LogicalTree;
namespace Avalonia.Controls.Generators
{
@@ -15,6 +17,8 @@ namespace Avalonia.Controls.Generators
public class TreeItemContainerGenerator : ItemContainerGenerator, ITreeItemContainerGenerator
where T : class, IControl, new()
{
+ private TreeView _treeView;
+
///
/// Initializes a new instance of the class.
///
@@ -23,31 +27,28 @@ namespace Avalonia.Controls.Generators
/// The container's ContentTemplate property.
/// The container's Items property.
/// The container's IsExpanded property.
- /// The container index for the tree
public TreeItemContainerGenerator(
IControl owner,
AvaloniaProperty contentProperty,
AvaloniaProperty contentTemplateProperty,
AvaloniaProperty itemsProperty,
- AvaloniaProperty isExpandedProperty,
- TreeContainerIndex index)
+ AvaloniaProperty isExpandedProperty)
: base(owner, contentProperty, contentTemplateProperty)
{
Contract.Requires(owner != null);
Contract.Requires(contentProperty != null);
Contract.Requires(itemsProperty != null);
Contract.Requires(isExpandedProperty != null);
- Contract.Requires(index != null);
ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty;
- Index = index;
+ UpdateIndex();
}
///
/// Gets the container index for the tree.
///
- public TreeContainerIndex Index { get; }
+ public TreeContainerIndex Index { get; private set; }
///
/// Gets the item container's Items property.
@@ -70,7 +71,7 @@ namespace Avalonia.Controls.Generators
}
else if (container != null)
{
- Index.Add(item, container);
+ Index?.Add(item, container);
return container;
}
else
@@ -92,7 +93,7 @@ namespace Avalonia.Controls.Generators
result.DataContext = item;
}
- Index.Add(item, result);
+ Index?.Add(item, result);
return result;
}
@@ -101,24 +102,50 @@ namespace Avalonia.Controls.Generators
public override IEnumerable Clear()
{
var items = base.Clear();
- Index.Remove(0, items);
+ Index?.Remove(0, items);
return items;
}
public override IEnumerable Dematerialize(int startingIndex, int count)
{
- Index.Remove(startingIndex, GetContainerRange(startingIndex, count));
+ Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.Dematerialize(startingIndex, count);
}
public override IEnumerable RemoveRange(int startingIndex, int count)
{
- Index.Remove(startingIndex, GetContainerRange(startingIndex, count));
+ Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.RemoveRange(startingIndex, count);
}
public override bool TryRecycle(int oldIndex, int newIndex, object item) => false;
+ public void UpdateIndex()
+ {
+ if (Owner is TreeView treeViewOwner && Index == null)
+ {
+ Index = new TreeContainerIndex();
+ _treeView = treeViewOwner;
+ }
+ else if (Owner.IsAttachedToLogicalTree)
+ {
+ var treeView = Owner.GetSelfAndLogicalAncestors().OfType().FirstOrDefault();
+
+ if (treeView != _treeView)
+ {
+ Clear();
+ Index = treeView?.ItemContainerGenerator?.Index;
+ _treeView = treeView;
+ }
+ }
+ else
+ {
+ Clear();
+ Index = null;
+ _treeView = null;
+ }
+ }
+
class WrapperTreeDataTemplate : ITreeDataTemplate
{
private readonly IDataTemplate _inner;
diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs
index f1ee7c0e1a..baa51f92ec 100644
--- a/src/Avalonia.Controls/Primitives/RangeBase.cs
+++ b/src/Avalonia.Controls/Primitives/RangeBase.cs
@@ -75,7 +75,10 @@ namespace Avalonia.Controls.Primitives
set
{
- ValidateDouble(value, "Minimum");
+ if (!ValidateDouble(value))
+ {
+ return;
+ }
if (IsInitialized)
{
@@ -102,7 +105,10 @@ namespace Avalonia.Controls.Primitives
set
{
- ValidateDouble(value, "Maximum");
+ if (!ValidateDouble(value))
+ {
+ return;
+ }
if (IsInitialized)
{
@@ -129,7 +135,10 @@ namespace Avalonia.Controls.Primitives
set
{
- ValidateDouble(value, "Value");
+ if (!ValidateDouble(value))
+ {
+ return;
+ }
if (IsInitialized)
{
@@ -164,16 +173,12 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Throws an exception if the double value is NaN or Inf.
+ /// Checks if the double value is not inifinity nor NaN.
///
/// The value.
- /// The name of the property being set.
- private static void ValidateDouble(double value, string property)
+ private static bool ValidateDouble(double value)
{
- if (double.IsInfinity(value) || double.IsNaN(value))
- {
- throw new ArgumentException($"{value} is not a valid value for {property}.");
- }
+ return !double.IsInfinity(value) || !double.IsNaN(value);
}
///
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 293809bf51..a0df186eb7 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -269,6 +269,12 @@ namespace Avalonia.Controls
///
protected virtual void HandleClosed()
{
+ var logicalArgs = new LogicalTreeAttachmentEventArgs(this);
+ ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
+
+ var visualArgs = new VisualTreeAttachmentEventArgs(this, this);
+ OnDetachedFromVisualTreeCore(visualArgs);
+
(this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null;
OnClosed(EventArgs.Empty);
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 59844be8a6..738d9d0b51 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -393,8 +393,7 @@ namespace Avalonia.Controls
TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty,
- TreeViewItem.IsExpandedProperty,
- new TreeContainerIndex());
+ TreeViewItem.IsExpandedProperty);
result.Index.Materialized += ContainerMaterialized;
return result;
}
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 07d5497c14..4d24337c3a 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -98,17 +98,18 @@ namespace Avalonia.Controls
TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty,
- TreeViewItem.IsExpandedProperty,
- _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex());
+ TreeViewItem.IsExpandedProperty);
}
///
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
+
_treeView = this.GetLogicalAncestors().OfType().FirstOrDefault();
-
+
Level = CalculateDistanceFromLogicalParent(this) - 1;
+ ItemContainerGenerator.UpdateIndex();
if (ItemTemplate == null && _treeView?.ItemTemplate != null)
{
@@ -119,7 +120,7 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
- ItemContainerGenerator.Clear();
+ ItemContainerGenerator.UpdateIndex();
}
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)
diff --git a/src/Avalonia.Styling/IDataContextProvider.cs b/src/Avalonia.Styling/IDataContextProvider.cs
new file mode 100644
index 0000000000..31639c5784
--- /dev/null
+++ b/src/Avalonia.Styling/IDataContextProvider.cs
@@ -0,0 +1,13 @@
+namespace Avalonia
+{
+ ///
+ /// Defines an element with a data context that can be used for binding.
+ ///
+ public interface IDataContextProvider : IAvaloniaObject
+ {
+ ///
+ /// Gets or sets the element's data context.
+ ///
+ object DataContext { get; set; }
+ }
+}
diff --git a/src/Avalonia.Styling/IStyledElement.cs b/src/Avalonia.Styling/IStyledElement.cs
index bcf1898c4c..d4d0f179c3 100644
--- a/src/Avalonia.Styling/IStyledElement.cs
+++ b/src/Avalonia.Styling/IStyledElement.cs
@@ -10,7 +10,8 @@ namespace Avalonia
IStyleHost,
ILogical,
IResourceProvider,
- IResourceNode
+ IResourceNode,
+ IDataContextProvider
{
///
/// Occurs when the control has finished initialization.
@@ -27,11 +28,6 @@ namespace Avalonia
///
new Classes Classes { get; set; }
- ///
- /// Gets or sets the control's data context.
- ///
- object DataContext { get; set; }
-
///
/// Gets the control's logical parent.
///
diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs
index 19bd17f123..5e1bcde2f6 100644
--- a/src/Avalonia.Styling/StyledElement.cs
+++ b/src/Avalonia.Styling/StyledElement.cs
@@ -24,7 +24,7 @@ namespace Avalonia
/// - Implements to form part of a logical tree.
/// - A collection of class strings for custom styling.
///
- public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent
+ public class StyledElement : Animatable, IDataContextProvider, IStyledElement, ISetLogicalParent, ISetInheritanceParent
{
///
/// Defines the property.
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
index a466714136..20f68df820 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
@@ -52,6 +52,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
// the context.
object anchor = context.GetFirstParent();
+ if(anchor is null)
+ {
+ // Try to find IDataContextProvider, this was added to allow us to find
+ // a datacontext for Application class when using NativeMenuItems.
+ anchor = context.GetFirstParent();
+ }
+
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs
index 61d0f7c83b..b4545f792e 100644
--- a/src/Markup/Avalonia.Markup/Data/Binding.cs
+++ b/src/Markup/Avalonia.Markup/Data/Binding.cs
@@ -231,9 +231,9 @@ namespace Avalonia.Data
{
Contract.Requires(target != null);
- if (!(target is IStyledElement))
+ if (!(target is IDataContextProvider))
{
- target = anchor as IStyledElement;
+ target = anchor as IDataContextProvider;
if (target == null)
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
index e2eb628512..34e6b228d0 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
@@ -82,22 +82,6 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(50, target.Value);
}
- [Fact]
- public void Properties_Should_Not_Accept_Nan_And_Inifinity()
- {
- var target = new TestRange();
-
- Assert.Throws(() => target.Minimum = double.NaN);
- Assert.Throws(() => target.Minimum = double.PositiveInfinity);
- Assert.Throws(() => target.Minimum = double.NegativeInfinity);
- Assert.Throws(() => target.Maximum = double.NaN);
- Assert.Throws(() => target.Maximum = double.PositiveInfinity);
- Assert.Throws(() => target.Maximum = double.NegativeInfinity);
- Assert.Throws(() => target.Value = double.NaN);
- Assert.Throws(() => target.Value = double.PositiveInfinity);
- Assert.Throws(() => target.Value = double.NegativeInfinity);
- }
-
[Theory]
[InlineData(true)]
[InlineData(false)]
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index a91b7a0701..ed8a39d063 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -10,6 +10,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
+using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
@@ -33,6 +34,8 @@ namespace Avalonia.Controls.UnitTests
Items = CreateTestTreeData(),
};
+ var root = new TestRoot(target);
+
CreateNodeDataTemplate(target);
ApplyTemplates(target);
@@ -77,6 +80,8 @@ namespace Avalonia.Controls.UnitTests
Items = CreateTestTreeData(),
};
+ var root = new TestRoot(target);
+
CreateNodeDataTemplate(target);
ApplyTemplates(target);
@@ -527,6 +532,8 @@ namespace Avalonia.Controls.UnitTests
Items = data,
};
+ var root = new TestRoot(target);
+
CreateNodeDataTemplate(target);
ApplyTemplates(target);
@@ -893,6 +900,37 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(2, GetItem(target, 0, 1, 0).Level);
}
+ [Fact]
+ public void Adding_Node_To_Removed_And_ReAdded_Parent_Should_Not_Crash()
+ {
+ // Issue #2985
+ var tree = CreateTestTreeData();
+ var target = new TreeView
+ {
+ Template = CreateTreeViewTemplate(),
+ Items = tree,
+ };
+
+ var visualRoot = new TestRoot();
+ visualRoot.Child = target;
+
+ CreateNodeDataTemplate(target);
+ ApplyTemplates(target);
+ ExpandAll(target);
+
+ var parent = tree[0];
+ var node = parent.Children[1];
+
+ parent.Children.Remove(node);
+ parent.Children.Add(node);
+
+ var item = target.ItemContainerGenerator.Index.ContainerFromItem(node);
+ ApplyTemplates(new[] { item });
+
+ // #2985 causes ArgumentException here.
+ node.Children.Add(new Node());
+ }
+
[Fact]
public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
{