diff --git a/Directory.Build.props b/Directory.Build.props
index b52a8875..eb4ed89d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,14 +3,17 @@
enable
- true
- nullable
- net6.0
12
false
false
+
+
+ true
+ nullable
+
+
11.0.0
diff --git a/src/Avalonia.Controls.TreeDataGrid/Avalonia.Controls.TreeDataGrid.csproj b/src/Avalonia.Controls.TreeDataGrid/Avalonia.Controls.TreeDataGrid.csproj
index e1ca2c40..07408080 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Avalonia.Controls.TreeDataGrid.csproj
+++ b/src/Avalonia.Controls.TreeDataGrid/Avalonia.Controls.TreeDataGrid.csproj
@@ -1,6 +1,6 @@
- net5.0
+ netstandard2.0;net5.0;net6.0
True
Avalonia.Controls
@@ -12,4 +12,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/AnonymousRow.cs b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/AnonymousRow.cs
index 6829da91..acd7aa11 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/AnonymousRow.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/AnonymousRow.cs
@@ -17,6 +17,10 @@ internal class AnonymousRow : IRow, IModelIndexableRow
private int _modelIndex;
[AllowNull] private TModel _model;
+#if NETSTANDARD2_0
+ object? IRow.Model => _model;
+#endif
+
public object? Header => _modelIndex;
public TModel Model => _model;
public int ModelIndex => _modelIndex;
diff --git a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/HierarchicalRow.cs b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/HierarchicalRow.cs
index 9aa76da2..4c45dad3 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/HierarchicalRow.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/HierarchicalRow.cs
@@ -22,6 +22,10 @@ public class HierarchicalRow : NotifyingBase,
private bool _isExpanded;
private bool? _showExpander;
+#if NETSTANDARD2_0
+ object? IRow.Model => Model;
+#endif
+
public HierarchicalRow(
IExpanderRowController controller,
IExpanderColumn expanderColumn,
diff --git a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IRow`1.cs b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IRow`1.cs
index d48b2c0e..78244e0a 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IRow`1.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Models/TreeDataGrid/IRow`1.cs
@@ -11,10 +11,12 @@ public interface IRow : IRow
///
new TModel Model { get; }
+#if !NETSTANDARD2_0
///
/// Gets the untyped row model.
///
object? IRow.Model => Model;
+#endif
///
/// Updates the model index due to a change in the data source.
diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs
index 11e88c52..448a682a 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridPresenterBase.cs
@@ -649,8 +649,8 @@ private Rect EstimateViewport(Size availableSize)
return new Rect(
0,
0,
- double.IsFinite(availableSize.Width) ? availableSize.Width : 0,
- double.IsFinite(availableSize.Height) ? availableSize.Height : 0);
+ Double.IsFinite(availableSize.Width) ? availableSize.Width : 0,
+ Double.IsFinite(availableSize.Height) ? availableSize.Height : 0);
}
private void RecycleElement(Control element, int index)
diff --git a/src/Avalonia.Controls.TreeDataGrid/Selection/ITreeDataGridSelectionInteraction.cs b/src/Avalonia.Controls.TreeDataGrid/Selection/ITreeDataGridSelectionInteraction.cs
index e0bdd0df..d02618ea 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Selection/ITreeDataGridSelectionInteraction.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Selection/ITreeDataGridSelectionInteraction.cs
@@ -12,15 +12,15 @@ public interface ITreeDataGridSelectionInteraction
{
public event EventHandler? SelectionChanged;
- bool IsCellSelected(int columnIndex, int rowIndex) => false;
- bool IsRowSelected(IRow rowModel) => false;
- bool IsRowSelected(int rowIndex) => false;
- public void OnKeyDown(TreeDataGrid sender, KeyEventArgs e) { }
- public void OnPreviewKeyDown(TreeDataGrid sender, KeyEventArgs e) { }
- public void OnKeyUp(TreeDataGrid sender, KeyEventArgs e) { }
- public void OnTextInput(TreeDataGrid sender, TextInputEventArgs e) { }
- public void OnPointerPressed(TreeDataGrid sender, PointerPressedEventArgs e) { }
- public void OnPointerMoved(TreeDataGrid sender, PointerEventArgs e) { }
- public void OnPointerReleased(TreeDataGrid sender, PointerReleasedEventArgs e) { }
+ bool IsCellSelected(int columnIndex, int rowIndex);
+ bool IsRowSelected(IRow rowModel);
+ bool IsRowSelected(int rowIndex);
+ void OnKeyDown(TreeDataGrid sender, KeyEventArgs e);
+ void OnPreviewKeyDown(TreeDataGrid sender, KeyEventArgs e);
+ void OnKeyUp(TreeDataGrid sender, KeyEventArgs e);
+ void OnTextInput(TreeDataGrid sender, TextInputEventArgs e);
+ void OnPointerPressed(TreeDataGrid sender, PointerPressedEventArgs e);
+ void OnPointerMoved(TreeDataGrid sender, PointerEventArgs e);
+ void OnPointerReleased(TreeDataGrid sender, PointerReleasedEventArgs e);
}
}
diff --git a/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridCellSelectionModel.cs b/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridCellSelectionModel.cs
index 19d709ef..5bbda6c4 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridCellSelectionModel.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridCellSelectionModel.cs
@@ -121,8 +121,14 @@ sender.Rows is null ||
};
var anchor = shift ? _rangeAnchor : GetAnchor();
+
+#if NETSTANDARD
+ var columnIndex = Math.Min(Math.Max(anchor.x + x, 0), sender.Columns.Count - 1);
+ var rowIndex = Math.Min(Math.Max(anchor.y + y, 0), sender.Rows.Count - 1);
+#else
var columnIndex = Math.Clamp(anchor.x + x, 0, sender.Columns.Count - 1);
var rowIndex = Math.Clamp(anchor.y + y, 0, sender.Rows.Count - 1);
+#endif
if (!shift)
Select(columnIndex, rowIndex);
@@ -171,6 +177,18 @@ e.Source is Control source &&
}
}
+ bool ITreeDataGridSelectionInteraction.IsRowSelected(IRow rowModel) => false;
+
+ bool ITreeDataGridSelectionInteraction.IsRowSelected(int rowIndex) => false;
+
+ void ITreeDataGridSelectionInteraction.OnPreviewKeyDown(TreeDataGrid sender, KeyEventArgs e) { }
+
+ void ITreeDataGridSelectionInteraction.OnKeyUp(TreeDataGrid sender, KeyEventArgs e) { }
+
+ void ITreeDataGridSelectionInteraction.OnTextInput(TreeDataGrid sender, TextInputEventArgs e) { }
+
+ void ITreeDataGridSelectionInteraction.OnPointerMoved(TreeDataGrid sender, PointerEventArgs e) { }
+
private void BeginBatchUpdate()
{
_selectedColumns.BeginBatchUpdate();
diff --git a/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridRowSelectionModel.cs b/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridRowSelectionModel.cs
index ec112459..93dae8bc 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridRowSelectionModel.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Selection/TreeDataGridRowSelectionModel.cs
@@ -356,6 +356,12 @@ void ITreeDataGridSelectionInteraction.OnTextInput(TreeDataGrid sender, TextInpu
HandleTextInput(e.Text, sender, _source.Rows.ModelIndexToRowIndex(AnchorIndex));
}
+ bool ITreeDataGridSelectionInteraction.IsCellSelected(int columnIndex, int rowIndex) => false;
+
+ void ITreeDataGridSelectionInteraction.OnKeyUp(TreeDataGrid sender, KeyEventArgs e) { }
+
+ void ITreeDataGridSelectionInteraction.OnPointerMoved(TreeDataGrid sender, PointerEventArgs e) { }
+
protected internal override IEnumerable? GetChildren(TModel node)
{
if (_source is HierarchicalTreeDataGridSource treeSource)
diff --git a/src/Avalonia.Controls.TreeDataGrid/Selection/TreeSelectionModelBase.cs b/src/Avalonia.Controls.TreeDataGrid/Selection/TreeSelectionModelBase.cs
index e3525f64..32e571cd 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Selection/TreeSelectionModelBase.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Selection/TreeSelectionModelBase.cs
@@ -519,8 +519,10 @@ private int CommitSelect(IndexRanges selectedRanges)
{
var result = 0;
- foreach (var (parent, ranges) in selectedRanges.Ranges)
+ foreach (var row in selectedRanges.Ranges)
{
+ var parent = row.Key;
+ var ranges = row.Value;
var node = GetOrCreateNode(parent);
if (node is not null)
@@ -537,8 +539,10 @@ private int CommitDeselect(IndexRanges selectedRanges)
{
var result = 0;
- foreach (var (parent, ranges) in selectedRanges.Ranges)
+ foreach (var row in selectedRanges.Ranges)
{
+ var parent = row.Key;
+ var ranges = row.Value;
var node = GetOrCreateNode(parent);
if (node is not null)
diff --git a/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/BitOperations.cs b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/BitOperations.cs
new file mode 100644
index 00000000..4a44232c
--- /dev/null
+++ b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/BitOperations.cs
@@ -0,0 +1,107 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Some routines inspired by the Stanford Bit Twiddling Hacks by Sean Eron Anderson:
+// http://graphics.stanford.edu/~seander/bithacks.html
+
+namespace System.Numerics
+{
+ ///
+ /// Utility methods for intrinsic bit-twiddling operations.
+ /// The methods use hardware intrinsics when available on the underlying platform,
+ /// otherwise they use optimized software fallbacks.
+ ///
+ internal static class BitOperations
+ {
+ // C# no-alloc optimization that directly wraps the data section of the dll (similar to string constants)
+ // https://github.com/dotnet/roslyn/pull/24621
+
+ private static ReadOnlySpan Log2DeBruijn => // 32
+ [
+ 00, 09, 01, 10, 13, 21, 02, 29,
+ 11, 14, 16, 18, 22, 25, 03, 30,
+ 08, 12, 20, 28, 15, 17, 24, 07,
+ 19, 27, 23, 06, 26, 05, 04, 31
+ ];
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since log(0) is undefined.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Log2(uint value)
+ {
+ // The 0->0 contract is fulfilled by setting the LSB to 1.
+ // Log(1) is 0, and setting the LSB for values > 1 does not change the log2 result.
+ value |= 1;
+
+ // Fallback contract is 0->0
+ return Log2SoftwareFallback(value);
+ }
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since log(0) is undefined.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Log2(ulong value)
+ {
+ value |= 1;
+
+ uint hi = (uint)(value >> 32);
+
+ if (hi == 0)
+ {
+ return Log2((uint)value);
+ }
+
+ return 32 + Log2(hi);
+ }
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since log(0) is undefined.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Log2(nuint value)
+ {
+#if TARGET_64BIT
+ return Log2((ulong)value);
+#else
+ return Log2((uint)value);
+#endif
+ }
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since Log(0) is undefined.
+ /// Does not directly use any hardware intrinsics, nor does it incur branching.
+ ///
+ /// The value.
+ private static int Log2SoftwareFallback(uint value)
+ {
+ // No AggressiveInlining due to large method size
+ // Has conventional contract 0->0 (Log(0) is undefined)
+
+ // Fill trailing zeros with ones, eg 00010010 becomes 00011111
+ value |= value >> 01;
+ value |= value >> 02;
+ value |= value >> 04;
+ value |= value >> 08;
+ value |= value >> 16;
+
+ // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
+ return Unsafe.AddByteOffset(
+ // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u
+ ref MemoryMarshal.GetReference(Log2DeBruijn),
+ // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
+ (IntPtr)(int)((value * 0x07C4ACDDu) >> 27));
+ }
+ }
+}
diff --git a/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/Double.cs b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/Double.cs
new file mode 100644
index 00000000..40a4d399
--- /dev/null
+++ b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/Double.cs
@@ -0,0 +1,10 @@
+namespace Avalonia;
+
+
+internal static class Double
+{
+ public static bool IsFinite(double value)
+ {
+ return !double.IsNaN(value) && !double.IsInfinity(value);
+ }
+}
diff --git a/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/IsExternalInit.cs b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/IsExternalInit.cs
new file mode 100644
index 00000000..2822f9d1
--- /dev/null
+++ b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/IsExternalInit.cs
@@ -0,0 +1,7 @@
+namespace System.Runtime.CompilerServices;
+
+///
+/// A class which allows to use C# 9's init and record features
+/// in older target frameworks like .NET Standard 2.0
+///
+internal static class IsExternalInit { }
diff --git a/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/Range.cs b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/Range.cs
new file mode 100644
index 00000000..35a9bdcb
--- /dev/null
+++ b/src/Avalonia.Controls.TreeDataGrid/StandardExtensions/Range.cs
@@ -0,0 +1,271 @@
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ /// Represent a type can be used to index a collection either from the start or the end.
+ ///
+ /// Index is used by the C# compiler to support the new index syntax
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
+ /// int lastElement = someArray[^1]; // lastElement = 5
+ ///
+ ///
+ internal readonly struct Index : IEquatable
+ {
+ private readonly int _value;
+
+ /// Construct an Index using a value and indicating if the index is from the start or from the end.
+ /// The index value. it has to be zero or positive number.
+ /// Indicating if the index is from the start or from the end.
+ ///
+ /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Index(int value, bool fromEnd = false)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ if (fromEnd)
+ _value = ~value;
+ else
+ _value = value;
+ }
+
+ // The following private constructors mainly created for perf reason to avoid the checks
+ private Index(int value)
+ {
+ _value = value;
+ }
+
+ /// Create an Index pointing at first element.
+ public static Index Start => new Index(0);
+
+ /// Create an Index pointing at beyond last element.
+ public static Index End => new Index(~0);
+
+ /// Create an Index from the start at the position indicated by the value.
+ /// The index value from the start.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromStart(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(value);
+ }
+
+ /// Create an Index from the end at the position indicated by the value.
+ /// The index value from the end.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromEnd(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(~value);
+ }
+
+ /// Returns the index value.
+ public int Value
+ {
+ get
+ {
+ if (_value < 0)
+ {
+ return ~_value;
+ }
+ else
+ {
+ return _value;
+ }
+ }
+ }
+
+ /// Indicates whether the index is from the start or the end.
+ public bool IsFromEnd => _value < 0;
+
+ /// Calculate the offset from the start using the giving collection length.
+ /// The length of the collection that the Index will be used with. length has to be a positive value
+ ///
+ /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
+ /// we don't validate either the returned offset is greater than the input length.
+ /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
+ /// then used to index a collection will get out of range exception which will be same affect as the validation.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetOffset(int length)
+ {
+ var offset = _value;
+ if (IsFromEnd)
+ {
+ // offset = length - (~value)
+ // offset = length + (~(~value) + 1)
+ // offset = length + value + 1
+
+ offset += length + 1;
+ }
+ return offset;
+ }
+
+ /// Indicates whether the current Index object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value;
+
+ /// Indicates whether the current Index object is equal to another Index object.
+ /// An object to compare with this object
+ public bool Equals(Index other) => _value == other._value;
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode() => _value;
+
+ /// Converts integer number to an Index.
+ public static implicit operator Index(int value) => FromStart(value);
+
+ /// Converts the value of the current Index object to its equivalent string representation.
+ public override string ToString()
+ {
+ if (IsFromEnd)
+ return "^" + ((uint)Value).ToString();
+
+ return ((uint)Value).ToString();
+ }
+ }
+
+ /// Represent a range has start and end indexes.
+ ///
+ /// Range is used by the C# compiler to support the range syntax.
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
+ /// int[] subArray1 = someArray[0..2]; // { 1, 2 }
+ /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
+ ///
+ ///
+ internal readonly struct Range : IEquatable
+ {
+ /// Represent the inclusive start index of the Range.
+ public Index Start { get; }
+
+ /// Represent the exclusive end index of the Range.
+ public Index End { get; }
+
+ /// Construct a Range object using the start and end indexes.
+ /// Represent the inclusive start index of the range.
+ /// Represent the exclusive end index of the range.
+ public Range(Index start, Index end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ /// Indicates whether the current Range object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) =>
+ value is Range r &&
+ r.Start.Equals(Start) &&
+ r.End.Equals(End);
+
+ /// Indicates whether the current Range object is equal to another Range object.
+ /// An object to compare with this object
+ public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode()
+ {
+ return Start.GetHashCode() * 31 + End.GetHashCode();
+ }
+
+ /// Converts the value of the current Range object to its equivalent string representation.
+ public override string ToString()
+ {
+ return Start + ".." + End;
+ }
+
+ /// Create a Range object starting from start index to the end of the collection.
+ public static Range StartAt(Index start) => new Range(start, Index.End);
+
+ /// Create a Range object starting from first element in the collection to the end Index.
+ public static Range EndAt(Index end) => new Range(Index.Start, end);
+
+ /// Create a Range object starting from first element to the end.
+ public static Range All => new Range(Index.Start, Index.End);
+
+ /// Calculate the start offset and length of range object using a collection length.
+ /// The length of the collection that the range will be used with. length has to be a positive value.
+ ///
+ /// For performance reason, we don't validate the input length parameter against negative values.
+ /// It is expected Range will be used with collections which always have non negative length/count.
+ /// We validate the range is inside the length scope though.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public (int Offset, int Length) GetOffsetAndLength(int length)
+ {
+ int start;
+ var startIndex = Start;
+ if (startIndex.IsFromEnd)
+ start = length - startIndex.Value;
+ else
+ start = startIndex.Value;
+
+ int end;
+ var endIndex = End;
+ if (endIndex.IsFromEnd)
+ end = length - endIndex.Value;
+ else
+ end = endIndex.Value;
+
+ if ((uint)end > (uint)length || (uint)start > (uint)end)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ return (start, end - start);
+ }
+ }
+}
+
+namespace System.Runtime.CompilerServices
+{
+ internal static class RuntimeHelpers
+ {
+ ///
+ /// Slices the specified array using the specified range.
+ ///
+ public static T[] GetSubArray(T[] array, Range range)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ (int offset, int length) = range.GetOffsetAndLength(array.Length);
+
+ if (default(T) != null || typeof(T[]) == array.GetType())
+ {
+ // We know the type of the array to be exactly T[].
+
+ if (length == 0)
+ {
+ return [];
+ }
+
+ var dest = new T[length];
+ Array.Copy(array, offset, dest, 0, length);
+ return dest;
+ }
+ else
+ {
+ // The array is actually a U[] where U:T.
+ var dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length);
+ Array.Copy(array, offset, dest, 0, length);
+ return dest;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs b/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs
index 1483405a..7ea7994e 100644
--- a/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs
@@ -605,7 +605,9 @@ private void AutoScroll(bool direction)
_autoScrollTimer.Start();
}
+#if !NETSTANDARD2_0
[MemberNotNullWhen(true, nameof(_source))]
+#endif
private bool CalculateAutoDragDrop(
TreeDataGridRow targetRow,
DragEventArgs e,
diff --git a/src/Avalonia.Controls.TreeDataGrid/Utils/StableSort.cs b/src/Avalonia.Controls.TreeDataGrid/Utils/StableSort.cs
index 5ff0c1f3..127c489c 100644
--- a/src/Avalonia.Controls.TreeDataGrid/Utils/StableSort.cs
+++ b/src/Avalonia.Controls.TreeDataGrid/Utils/StableSort.cs
@@ -19,8 +19,13 @@ public static List SortedMap(IReadOnlyList elements, Comparison
map.Add(i);
}
+#if NETSTANDARD2_0
+ map.Sort(compare);
+#else
var span = CollectionsMarshal.AsSpan(map);
SortHelper.Sort(span, compare);
+#endif
+
return map;
}
}