Browse Source

Merge branch 'master' into refactor/value-store

pull/3255/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
527bac1ab3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  2. 4
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  3. 2
      src/Avalonia.Controls/TopLevel.cs
  4. 14
      src/Avalonia.Input/DragDropDevice.cs
  5. 23
      src/Avalonia.Input/DragEventArgs.cs
  6. 7
      src/Avalonia.Input/Raw/RawDragEvent.cs
  7. 5
      src/Avalonia.Native/Avalonia.Native.csproj
  8. 14
      src/Avalonia.Native/CallbackBase.cs
  9. 59
      src/Avalonia.Native/PlatformThreadingInterface.cs
  10. 32
      src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
  11. 43
      src/Avalonia.Styling/StyledElement.cs
  12. 4
      src/Avalonia.X11/X11Framebuffer.cs
  13. 8
      src/Avalonia.X11/X11Window.cs
  14. 2
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  15. 25
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  16. 34
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  17. 86
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

8
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -127,7 +127,7 @@ namespace Avalonia.Controls.Generators
Index = new TreeContainerIndex(); Index = new TreeContainerIndex();
_treeView = treeViewOwner; _treeView = treeViewOwner;
} }
else if (Owner.IsAttachedToLogicalTree) else
{ {
var treeView = Owner.GetSelfAndLogicalAncestors().OfType<TreeView>().FirstOrDefault(); var treeView = Owner.GetSelfAndLogicalAncestors().OfType<TreeView>().FirstOrDefault();
@ -138,12 +138,6 @@ namespace Avalonia.Controls.Generators
_treeView = treeView; _treeView = treeView;
} }
} }
else
{
Clear();
Index = null;
_treeView = null;
}
} }
class WrapperTreeDataTemplate : ITreeDataTemplate class WrapperTreeDataTemplate : ITreeDataTemplate

4
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -8,7 +8,7 @@ namespace Avalonia.Controls.Primitives
{ {
private const int AdornerZIndex = int.MaxValue - 100; private const int AdornerZIndex = int.MaxValue - 100;
private const int OverlayZIndex = int.MaxValue - 99; private const int OverlayZIndex = int.MaxValue - 99;
private IStyleHost _styleRoot; private IStyleRoot _styleRoot;
private readonly List<Control> _layers = new List<Control>(); private readonly List<Control> _layers = new List<Control>();
@ -53,7 +53,7 @@ namespace Avalonia.Controls.Primitives
layer.ZIndex = zindex; layer.ZIndex = zindex;
VisualChildren.Add(layer); VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree) if (((ILogical)this).IsAttachedToLogicalTree)
((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot)); ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot, layer, this));
InvalidateArrange(); InvalidateArrange();
} }

2
src/Avalonia.Controls/TopLevel.cs

@ -266,7 +266,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
protected virtual void HandleClosed() protected virtual void HandleClosed()
{ {
var logicalArgs = new LogicalTreeAttachmentEventArgs(this); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
var visualArgs = new VisualTreeAttachmentEventArgs(this, this); var visualArgs = new VisualTreeAttachmentEventArgs(this, this);

14
src/Avalonia.Input/DragDropDevice.cs

@ -19,7 +19,7 @@ namespace Avalonia.Input
return null; return null;
} }
private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, InputModifiers modifiers) private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers)
{ {
if (target == null) if (target == null)
return DragDropEffects.None; return DragDropEffects.None;
@ -38,13 +38,13 @@ namespace Avalonia.Input
return args.DragEffects; return args.DragEffects;
} }
private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers) private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{ {
_lastTarget = GetTarget(inputRoot, point); _lastTarget = GetTarget(inputRoot, point);
return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers); return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers);
} }
private DragDropEffects DragOver(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers) private DragDropEffects DragOver(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{ {
var target = GetTarget(inputRoot, point); var target = GetTarget(inputRoot, point);
@ -77,7 +77,7 @@ namespace Avalonia.Input
} }
} }
private DragDropEffects Drop(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers) private DragDropEffects Drop(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{ {
try try
{ {
@ -100,16 +100,16 @@ namespace Avalonia.Input
switch (e.Type) switch (e.Type)
{ {
case RawDragEventType.DragEnter: case RawDragEventType.DragEnter:
e.Effects = DragEnter(e.Root, e.Location, e.Data, e.Effects, e.Modifiers); e.Effects = DragEnter(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break; break;
case RawDragEventType.DragOver: case RawDragEventType.DragOver:
e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.Modifiers); e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break; break;
case RawDragEventType.DragLeave: case RawDragEventType.DragLeave:
DragLeave(e.Root); DragLeave(e.Root);
break; break;
case RawDragEventType.Drop: case RawDragEventType.Drop:
e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.Modifiers); e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break; break;
} }
} }

23
src/Avalonia.Input/DragEventArgs.cs

@ -13,8 +13,11 @@ namespace Avalonia.Input
public IDataObject Data { get; private set; } public IDataObject Data { get; private set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; private set; } public InputModifiers Modifiers { get; private set; }
public KeyModifiers KeyModifiers { get; private set; }
public Point GetPosition(IVisual relativeTo) public Point GetPosition(IVisual relativeTo)
{ {
var point = new Point(0, 0); var point = new Point(0, 0);
@ -32,13 +35,25 @@ namespace Avalonia.Input
return point; return point;
} }
[Obsolete("Use constructor taking KeyModifiers")]
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, InputModifiers modifiers) public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, InputModifiers modifiers)
: base(routedEvent) : base(routedEvent)
{ {
this.Data = data; Data = data;
this._target = target; _target = target;
this._targetLocation = targetLocation; _targetLocation = targetLocation;
this.Modifiers = modifiers; Modifiers = modifiers;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xF);
}
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
: base(routedEvent)
{
Data = data;
_target = target;
_targetLocation = targetLocation;
Modifiers = (InputModifiers)keyModifiers;
KeyModifiers = keyModifiers;
} }
} }

7
src/Avalonia.Input/Raw/RawDragEvent.cs

@ -1,4 +1,6 @@
namespace Avalonia.Input.Raw using System;
namespace Avalonia.Input.Raw
{ {
public class RawDragEvent : RawInputEventArgs public class RawDragEvent : RawInputEventArgs
{ {
@ -6,7 +8,9 @@
public IDataObject Data { get; } public IDataObject Data { get; }
public DragDropEffects Effects { get; set; } public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; } public RawDragEventType Type { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; } public InputModifiers Modifiers { get; }
public KeyModifiers KeyModifiers { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type, public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
IInputRoot root, Point location, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers) IInputRoot root, Point location, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
@ -17,6 +21,7 @@
Data = data; Data = data;
Effects = effects; Effects = effects;
Modifiers = (InputModifiers)modifiers; Modifiers = (InputModifiers)modifiers;
KeyModifiers = KeyModifiersUtils.ConvertToKey(modifiers);
} }
} }
} }

5
src/Avalonia.Native/Avalonia.Native.csproj

@ -19,8 +19,9 @@
<ItemGroup> <ItemGroup>
<SharpGenMapping Include="Mappings.xml" /> <SharpGenMapping Include="Mappings.xml" />
<PackageReference Include="SharpGenTools.Sdk" Version="1.1.2" PrivateAssets="all" /> <PackageReference Include="SharpGenTools.Sdk" Version="1.2.1" PrivateAssets="all" />
<PackageReference Include="SharpGen.Runtime.COM" Version="1.1.0" /> <PackageReference Include="SharpGen.Runtime" Version="1.2.1" />
<PackageReference Include="SharpGen.Runtime.COM" Version="1.2.0" />
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" /> <ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
</ItemGroup> </ItemGroup>

14
src/Avalonia.Native/CallbackBase.cs

@ -2,11 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Runtime.ExceptionServices;
using SharpGen.Runtime; using SharpGen.Runtime;
using Avalonia.Platform;
namespace Avalonia.Native namespace Avalonia.Native
{ {
public class CallbackBase : SharpGen.Runtime.IUnknown public class CallbackBase : SharpGen.Runtime.IUnknown, IExceptionCallback
{ {
private uint _refCount; private uint _refCount;
private bool _disposed; private bool _disposed;
@ -76,5 +78,15 @@ namespace Avalonia.Native
{ {
} }
public void RaiseException(Exception e)
{
if (AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>() is PlatformThreadingInterface threadingInterface)
{
threadingInterface.TerminateNativeApp();
threadingInterface.DispatchException(ExceptionDispatchInfo.Capture(e));
}
}
} }
} }

59
src/Avalonia.Native/PlatformThreadingInterface.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Runtime.ExceptionServices;
using System.Threading; using System.Threading;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform; using Avalonia.Platform;
@ -43,6 +44,8 @@ namespace Avalonia.Native
} }
readonly IAvnPlatformThreadingInterface _native; readonly IAvnPlatformThreadingInterface _native;
private ExceptionDispatchInfo _exceptionDispatchInfo;
private CancellationTokenSource _exceptionCancellationSource;
public PlatformThreadingInterface(IAvnPlatformThreadingInterface native) public PlatformThreadingInterface(IAvnPlatformThreadingInterface native)
{ {
@ -57,32 +60,48 @@ namespace Avalonia.Native
public void RunLoop(CancellationToken cancellationToken) public void RunLoop(CancellationToken cancellationToken)
{ {
if (cancellationToken.CanBeCanceled == false) var l = new object();
_native.RunLoop(null); _exceptionCancellationSource = new CancellationTokenSource();
else
var compositeCancellation = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, _exceptionCancellationSource.Token).Token;
var cancellation = _native.CreateLoopCancellation();
compositeCancellation.Register(() =>
{ {
var l = new object(); lock (l)
var cancellation = _native.CreateLoopCancellation();
cancellationToken.Register(() =>
{ {
lock (l) cancellation?.Cancel();
{
cancellation?.Cancel();
}
});
try
{
_native.RunLoop(cancellation);
} }
finally });
try
{
_native.RunLoop(cancellation);
}
finally
{
lock (l)
{ {
lock(l) cancellation?.Dispose();
{ cancellation = null;
cancellation?.Dispose();
cancellation = null;
}
} }
} }
if (_exceptionDispatchInfo != null)
{
_exceptionDispatchInfo.Throw();
}
}
public void DispatchException (ExceptionDispatchInfo exceptionInfo)
{
_exceptionDispatchInfo = exceptionInfo;
}
public void TerminateNativeApp()
{
_exceptionCancellationSource?.Cancel();
} }
public void Signal(DispatcherPriority priority) public void Signal(DispatcherPriority priority)

32
src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs

@ -16,16 +16,44 @@ namespace Avalonia.LogicalTree
/// Initializes a new instance of the <see cref="LogicalTreeAttachmentEventArgs"/> class. /// Initializes a new instance of the <see cref="LogicalTreeAttachmentEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="root">The root of the logical tree.</param> /// <param name="root">The root of the logical tree.</param>
public LogicalTreeAttachmentEventArgs(IStyleHost root) /// <param name="source">The control being attached/detached.</param>
/// <param name="parent">The <see cref="Parent"/>.</param>
public LogicalTreeAttachmentEventArgs(
IStyleRoot root,
ILogical source,
ILogical parent)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(source != null);
Root = root; Root = root;
Source = source;
Parent = parent;
} }
/// <summary> /// <summary>
/// Gets the root of the logical tree that the control is being attached to or detached from. /// Gets the root of the logical tree that the control is being attached to or detached from.
/// </summary> /// </summary>
public IStyleHost Root { get; } public IStyleRoot Root { get; }
/// <summary>
/// Gets the control that was attached or detached from the logical tree.
/// </summary>
/// <remarks>
/// Logical tree attachment events travel down the attached logical tree from the point of
/// attachment/detachment, so this control may be different from the control that the
/// event is being raised on.
/// </remarks>
public ILogical Source { get; }
/// <summary>
/// Gets the control that <see cref="Source"/> is being attached to or detached from.
/// </summary>
/// <remarks>
/// For logical tree attachment, holds the new logical parent of <see cref="Source"/>. For
/// detachment, holds the old logical parent of <see cref="Source"/>. If the detachment event
/// was caused by a top-level control being closed, then this property will be null.
/// </remarks>
public ILogical Parent { get; }
} }
} }

43
src/Avalonia.Styling/StyledElement.cs

@ -59,7 +59,7 @@ namespace Avalonia
private int _initCount; private int _initCount;
private string _name; private string _name;
private readonly Classes _classes = new Classes(); private readonly Classes _classes = new Classes();
private bool _isAttachedToLogicalTree; private IStyleRoot _styleRoot;
private IAvaloniaList<ILogical> _logicalChildren; private IAvaloniaList<ILogical> _logicalChildren;
private IResourceDictionary _resources; private IResourceDictionary _resources;
private Styles _styles; private Styles _styles;
@ -81,7 +81,7 @@ namespace Avalonia
/// </summary> /// </summary>
public StyledElement() public StyledElement()
{ {
_isAttachedToLogicalTree = this is IStyleRoot; _styleRoot = this as IStyleRoot;
} }
/// <summary> /// <summary>
@ -307,7 +307,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree. /// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary> /// </summary>
bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree; bool ILogical.IsAttachedToLogicalTree => _styleRoot != null;
/// <summary> /// <summary>
/// Gets the styled element's logical parent. /// Gets the styled element's logical parent.
@ -367,7 +367,7 @@ namespace Avalonia
throw new InvalidOperationException("BeginInit was not called."); throw new InvalidOperationException("BeginInit was not called.");
} }
if (--_initCount == 0 && _isAttachedToLogicalTree) if (--_initCount == 0 && _styleRoot != null)
{ {
InitializeStylesIfNeeded(); InitializeStylesIfNeeded();
@ -435,19 +435,6 @@ namespace Avalonia
throw new InvalidOperationException("The Control already has a parent."); throw new InvalidOperationException("The Control already has a parent.");
} }
if (_isAttachedToLogicalTree)
{
var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null)
{
throw new AvaloniaInternalException("Was attached to logical tree but cannot find root.");
}
var e = new LogicalTreeAttachmentEventArgs(oldRoot);
OnDetachedFromLogicalTreeCore(e);
}
if (InheritanceParent == null || parent == null) if (InheritanceParent == null || parent == null)
{ {
InheritanceParent = parent as AvaloniaObject; InheritanceParent = parent as AvaloniaObject;
@ -455,6 +442,12 @@ namespace Avalonia
Parent = (IStyledElement)parent; Parent = (IStyledElement)parent;
if (_styleRoot != null)
{
var e = new LogicalTreeAttachmentEventArgs(_styleRoot, this, old);
OnDetachedFromLogicalTreeCore(e);
}
if (old != null) if (old != null)
{ {
old.ResourcesChanged -= ThisResourcesChanged; old.ResourcesChanged -= ThisResourcesChanged;
@ -474,7 +467,7 @@ namespace Avalonia
throw new AvaloniaInternalException("Parent is attached to logical tree but cannot find root."); throw new AvaloniaInternalException("Parent is attached to logical tree but cannot find root.");
} }
var e = new LogicalTreeAttachmentEventArgs(newRoot); var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent);
OnAttachedToLogicalTreeCore(e); OnAttachedToLogicalTreeCore(e);
} }
@ -708,15 +701,21 @@ namespace Avalonia
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{ {
if (this.GetLogicalParent() == null && !(this is IStyleRoot))
{
throw new InvalidOperationException(
$"AttachedToLogicalTreeCore called for '{GetType().Name}' but control has no logical parent.");
}
// This method can be called when a control is already attached to the logical tree // This method can be called when a control is already attached to the logical tree
// in the following scenario: // in the following scenario:
// - ListBox gets assigned Items containing ListBoxItem // - ListBox gets assigned Items containing ListBoxItem
// - ListBox makes ListBoxItem a logical child // - ListBox makes ListBoxItem a logical child
// - ListBox template gets applied; making its Panel get attached to logical tree // - ListBox template gets applied; making its Panel get attached to logical tree
// - That AttachedToLogicalTree signal travels down to the ListBoxItem // - That AttachedToLogicalTree signal travels down to the ListBoxItem
if (!_isAttachedToLogicalTree) if (_styleRoot == null)
{ {
_isAttachedToLogicalTree = true; _styleRoot = e.Root;
InitializeStylesIfNeeded(true); InitializeStylesIfNeeded(true);
@ -732,9 +731,9 @@ namespace Avalonia
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e) private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{ {
if (_isAttachedToLogicalTree) if (_styleRoot != null)
{ {
_isAttachedToLogicalTree = false; _styleRoot = null;
_styleDetach.OnNext(this); _styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e); OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e); DetachedFromLogicalTree?.Invoke(this, e);

4
src/Avalonia.X11/X11Framebuffer.cs

@ -14,6 +14,10 @@ namespace Avalonia.X11
public X11Framebuffer(IntPtr display, IntPtr xid, int depth, int width, int height, double factor) public X11Framebuffer(IntPtr display, IntPtr xid, int depth, int width, int height, double factor)
{ {
// HACK! Please fix renderer, should never ask for 0x0 bitmap.
width = Math.Max(1, width);
height = Math.Max(1, height);
_display = display; _display = display;
_xid = xid; _xid = xid;
_depth = depth; _depth = depth;

8
src/Avalonia.X11/X11Window.cs

@ -102,7 +102,7 @@ namespace Avalonia.X11
valueMask |= SetWindowValuemask.ColorMap; valueMask |= SetWindowValuemask.ColorMap;
} }
int defaultWidth = 300, defaultHeight = 200; int defaultWidth = 0, defaultHeight = 0;
if (!_popup && Screen != null) if (!_popup && Screen != null)
{ {
@ -117,6 +117,10 @@ namespace Avalonia.X11
} }
} }
// check if the calculated size is zero then compensate to hardcoded resolution
defaultWidth = Math.Max(defaultWidth, 300);
defaultHeight = Math.Max(defaultHeight, 200);
_handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0, _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, defaultWidth, defaultHeight, 0,
depth, depth,
(int)CreateWindowArgs.InputOutput, (int)CreateWindowArgs.InputOutput,
@ -133,7 +137,7 @@ namespace Avalonia.X11
_renderHandle = _handle; _renderHandle = _handle;
Handle = new PlatformHandle(_handle, "XID"); Handle = new PlatformHandle(_handle, "XID");
_realSize = new PixelSize(300, 200); _realSize = new PixelSize(defaultWidth, defaultHeight);
platform.Windows[_handle] = OnEvent; platform.Windows[_handle] = OnEvent;
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
| XEventMask.ResizeRedirectMask | XEventMask.ResizeRedirectMask

2
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -75,7 +75,7 @@ namespace Avalonia.Shared.PlatformSupport
lock (_btlock) lock (_btlock)
Backtraces.Remove(_backtrace); Backtraces.Remove(_backtrace);
#endif #endif
_plat.Free(_address, Size); _plat?.Free(_address, Size);
GC.RemoveMemoryPressure(Size); GC.RemoveMemoryPressure(Size);
IsDisposed = true; IsDisposed = true;
_address = IntPtr.Zero; _address = IntPtr.Zero;

25
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -166,6 +166,31 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Impl_Close_Should_Raise_DetachedFromLogicalTree_Event()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
var raised = 0;
target.DetachedFromLogicalTree += (s, e) =>
{
Assert.Same(target, e.Root);
Assert.Same(target, e.Source);
Assert.Null(e.Parent);
++raised;
};
impl.Object.Closed();
Assert.Equal(1, raised);
}
}
[Fact] [Fact]
public void Impl_Input_Should_Pass_Input_To_InputManager() public void Impl_Input_Should_Pass_Input_To_InputManager()
{ {

34
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -971,6 +971,40 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Removing_TreeView_From_Root_Should_Preserve_TreeViewItems()
{
// Issue #3328
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var root = new TestRoot();
root.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count());
root.Child = null;
Assert.Equal(5, target.ItemContainerGenerator.Index.Containers.Count());
Assert.Equal(1, target.Presenter.Panel.Children.Count);
var rootNode = Assert.IsType<TreeViewItem>(target.Presenter.Panel.Children[0]);
Assert.Equal(3, rootNode.ItemContainerGenerator.Containers.Count());
Assert.Equal(3, rootNode.Presenter.Panel.Children.Count);
var child2Node = Assert.IsType<TreeViewItem>(rootNode.Presenter.Panel.Children[1]);
Assert.Equal(1, child2Node.ItemContainerGenerator.Containers.Count());
Assert.Equal(1, child2Node.Presenter.Panel.Children.Count);
}
private void ApplyTemplates(TreeView tree) private void ApplyTemplates(TreeView tree)
{ {
tree.ApplyTemplate(); tree.ApplyTemplate();

86
tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@ -76,6 +76,29 @@ namespace Avalonia.Styling.UnitTests
Assert.Null(target.InheritanceParent); Assert.Null(target.InheritanceParent);
} }
[Fact]
public void Adding_Element_With_Null_Parent_To_Logical_Tree_Should_Throw()
{
var target = new Border();
var visualParent = new Panel();
var logicalParent = new Panel();
var root = new TestRoot();
// Set the logical parent...
((ISetLogicalParent)target).SetParent(logicalParent);
// ...so that when it's added to `visualParent`, the parent won't be set again.
visualParent.Children.Add(target);
// Clear the logical parent. It's now a logical child of `visualParent` but doesn't have
// a logical parent itself.
((ISetLogicalParent)target).SetParent(null);
// In this case, attaching the control to a logical tree should throw.
logicalParent.Children.Add(visualParent);
Assert.Throws<InvalidOperationException>(() => root.Child = logicalParent);
}
[Fact] [Fact]
public void AttachedToLogicalParent_Should_Be_Called_When_Added_To_Tree() public void AttachedToLogicalParent_Should_Be_Called_When_Added_To_Tree()
{ {
@ -144,6 +167,50 @@ namespace Avalonia.Styling.UnitTests
Assert.True(raised); Assert.True(raised);
} }
[Fact]
public void AttachedToLogicalParent_Should_Have_Source_Set()
{
var root = new TestRoot();
var canvas = new Canvas();
var border = new Border { Child = canvas };
var raised = 0;
void Attached(object sender, LogicalTreeAttachmentEventArgs e)
{
Assert.Same(border, e.Source);
++raised;
}
border.AttachedToLogicalTree += Attached;
canvas.AttachedToLogicalTree += Attached;
root.Child = border;
Assert.Equal(2, raised);
}
[Fact]
public void AttachedToLogicalParent_Should_Have_Parent_Set()
{
var root = new TestRoot();
var canvas = new Canvas();
var border = new Border { Child = canvas };
var raised = 0;
void Attached(object sender, LogicalTreeAttachmentEventArgs e)
{
Assert.Same(root, e.Parent);
++raised;
}
border.AttachedToLogicalTree += Attached;
canvas.AttachedToLogicalTree += Attached;
root.Child = border;
Assert.Equal(2, raised);
}
[Fact] [Fact]
public void DetachedFromLogicalParent_Should_Be_Called_When_Removed_From_Tree() public void DetachedFromLogicalParent_Should_Be_Called_When_Removed_From_Tree()
{ {
@ -190,6 +257,25 @@ namespace Avalonia.Styling.UnitTests
Assert.True(raised); Assert.True(raised);
} }
[Fact]
public void Parent_Should_Be_Null_When_DetachedFromLogicalParent_Called()
{
var target = new TestControl();
var root = new TestRoot(target);
var called = 0;
target.DetachedFromLogicalTree += (s, e) =>
{
Assert.Null(target.Parent);
Assert.Null(target.InheritanceParent);
++called;
};
root.Child = null;
Assert.Equal(1, called);
}
[Fact] [Fact]
public void Adding_Tree_To_IStyleRoot_Should_Style_Controls() public void Adding_Tree_To_IStyleRoot_Should_Style_Controls()
{ {

Loading…
Cancel
Save