Browse Source

Merge branch 'master' into fixes/5027-avaloniaobject-batching

pull/5070/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
38996cea70
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  2. 67
      src/Avalonia.Controls/Control.cs
  3. 45
      src/Avalonia.Controls/Window.cs
  4. 2
      src/Avalonia.Layout/ElementManager.cs
  5. 14
      src/Avalonia.Visuals/Visual.cs
  6. 124
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

11
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -3275,7 +3275,7 @@ namespace Avalonia.Collections
addIndex); addIndex);
// next check if we need to add an item into the current group // next check if we need to add an item into the current group
// bool needsGrouping = false; bool needsGrouping = false;
if (Count == 1 && GroupDescriptions.Count > 0) if (Count == 1 && GroupDescriptions.Count > 0)
{ {
// if this is the first item being added // if this is the first item being added
@ -3302,7 +3302,7 @@ namespace Avalonia.Collections
// otherwise, we need to validate that it is within the current page. // otherwise, we need to validate that it is within the current page.
if (PageSize == 0 || (PageIndex + 1) * PageSize > leafIndex) if (PageSize == 0 || (PageIndex + 1) * PageSize > leafIndex)
{ {
//needsGrouping = true; needsGrouping = true;
int pageStartIndex = PageIndex * PageSize; int pageStartIndex = PageIndex * PageSize;
@ -3340,6 +3340,13 @@ namespace Avalonia.Collections
} }
} }
// if we need to add the item into the current group
// that will be displayed
if (needsGrouping)
{
this._group.AddToSubgroups(addedItem, false /*loading*/);
}
int addedIndex = IndexOf(addedItem); int addedIndex = IndexOf(addedItem);
// if the item is within the current page // if the item is within the current page

67
src/Avalonia.Controls/Control.cs

@ -8,6 +8,8 @@ using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
@ -23,20 +25,20 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="FocusAdorner"/> property. /// Defines the <see cref="FocusAdorner"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ITemplate<IControl>> FocusAdornerProperty = public static readonly StyledProperty<ITemplate<IControl>?> FocusAdornerProperty =
AvaloniaProperty.Register<Control, ITemplate<IControl>>(nameof(FocusAdorner)); AvaloniaProperty.Register<Control, ITemplate<IControl>?>(nameof(FocusAdorner));
/// <summary> /// <summary>
/// Defines the <see cref="Tag"/> property. /// Defines the <see cref="Tag"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<object> TagProperty = public static readonly StyledProperty<object?> TagProperty =
AvaloniaProperty.Register<Control, object>(nameof(Tag)); AvaloniaProperty.Register<Control, object?>(nameof(Tag));
/// <summary> /// <summary>
/// Defines the <see cref="ContextMenu"/> property. /// Defines the <see cref="ContextMenu"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ContextMenu> ContextMenuProperty = public static readonly StyledProperty<ContextMenu?> ContextMenuProperty =
AvaloniaProperty.Register<Control, ContextMenu>(nameof(ContextMenu)); AvaloniaProperty.Register<Control, ContextMenu?>(nameof(ContextMenu));
/// <summary> /// <summary>
/// Event raised when an element wishes to be scrolled into view. /// Event raised when an element wishes to be scrolled into view.
@ -44,16 +46,16 @@ namespace Avalonia.Controls
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent = public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble); RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private DataTemplates _dataTemplates; private DataTemplates? _dataTemplates;
private IControl _focusAdorner; private IControl? _focusAdorner;
/// <summary> /// <summary>
/// Gets or sets the control's focus adorner. /// Gets or sets the control's focus adorner.
/// </summary> /// </summary>
public ITemplate<IControl> FocusAdorner public ITemplate<IControl>? FocusAdorner
{ {
get { return GetValue(FocusAdornerProperty); } get => GetValue(FocusAdornerProperty);
set { SetValue(FocusAdornerProperty, value); } set => SetValue(FocusAdornerProperty, value);
} }
/// <summary> /// <summary>
@ -63,27 +65,27 @@ namespace Avalonia.Controls
/// Each control may define data templates which are applied to the control itself and its /// Each control may define data templates which are applied to the control itself and its
/// children. /// children.
/// </remarks> /// </remarks>
public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); public DataTemplates DataTemplates => _dataTemplates ??= new DataTemplates();
/// <summary> /// <summary>
/// Gets or sets a context menu to the control. /// Gets or sets a context menu to the control.
/// </summary> /// </summary>
public ContextMenu ContextMenu public ContextMenu? ContextMenu
{ {
get { return GetValue(ContextMenuProperty); } get => GetValue(ContextMenuProperty);
set { SetValue(ContextMenuProperty, value); } set => SetValue(ContextMenuProperty, value);
} }
/// <summary> /// <summary>
/// Gets or sets a user-defined object attached to the control. /// Gets or sets a user-defined object attached to the control.
/// </summary> /// </summary>
public object Tag public object? Tag
{ {
get { return GetValue(TagProperty); } get => GetValue(TagProperty);
set { SetValue(TagProperty, value); } set => SetValue(TagProperty, value);
} }
public new IControl Parent => (IControl)base.Parent; public new IControl? Parent => (IControl?)base.Parent;
/// <inheritdoc/> /// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
@ -106,15 +108,10 @@ namespace Avalonia.Controls
{ {
var c = i as IControl; var c = i as IControl;
if (c?.IsInitialized == false) if (c?.IsInitialized == false && c is ISupportInitialize init)
{ {
var init = c as ISupportInitialize; init.BeginInit();
init.EndInit();
if (init != null)
{
init.BeginInit();
init.EndInit();
}
} }
} }
} }
@ -131,10 +128,7 @@ namespace Avalonia.Controls
/// Gets the element that receives the focus adorner. /// Gets the element that receives the focus adorner.
/// </summary> /// </summary>
/// <returns>The control that receives the focus adorner.</returns> /// <returns>The control that receives the focus adorner.</returns>
protected virtual IControl GetTemplateFocusTarget() protected virtual IControl? GetTemplateFocusTarget() => this;
{
return this;
}
/// <inheritdoc/> /// <inheritdoc/>
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -173,15 +167,10 @@ namespace Avalonia.Controls
} }
} }
if (_focusAdorner != null) if (_focusAdorner != null && GetTemplateFocusTarget() is Visual target)
{ {
var target = (Visual)GetTemplateFocusTarget(); AdornerLayer.SetAdornedElement((Visual)_focusAdorner, target);
adornerLayer.Children.Add(_focusAdorner);
if (target != null)
{
AdornerLayer.SetAdornedElement((Visual)_focusAdorner, target);
adornerLayer.Children.Add(_focusAdorner);
}
} }
} }
} }

45
src/Avalonia.Controls/Window.cs

@ -4,10 +4,7 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Chrome;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
@ -482,10 +479,9 @@ namespace Avalonia.Controls
try try
{ {
if (!ignoreCancel && HandleClosing()) if (!ignoreCancel && ShouldCancelClose())
{ {
close = false; close = false;
return;
} }
} }
finally finally
@ -497,11 +493,25 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Handles a closing notification from <see cref="IWindowImpl.Closing"/>.
/// <returns>true if closing is cancelled. Otherwise false.</returns>
/// </summary>
protected virtual bool HandleClosing()
{
if (!ShouldCancelClose())
{
CloseInternal();
return false;
}
return true;
}
private void CloseInternal() private void CloseInternal()
{ {
foreach (var (child, _) in _children.ToList()) foreach (var (child, _) in _children.ToList())
{ {
// if we HandleClosing() before then there will be no children.
child.CloseInternal(); child.CloseInternal();
} }
@ -515,20 +525,18 @@ namespace Avalonia.Controls
PlatformImpl?.Dispose(); PlatformImpl?.Dispose();
} }
/// <summary> private bool ShouldCancelClose(CancelEventArgs args = null)
/// Handles a closing notification from <see cref="IWindowImpl.Closing"/>.
/// </summary>
protected virtual bool HandleClosing()
{ {
if (args is null)
{
args = new CancelEventArgs();
}
bool canClose = true; bool canClose = true;
foreach (var (child, _) in _children.ToList()) foreach (var (child, _) in _children.ToList())
{ {
if (!child.HandleClosing()) if (child.ShouldCancelClose(args))
{
child.CloseInternal();
}
else
{ {
canClose = false; canClose = false;
} }
@ -536,15 +544,12 @@ namespace Avalonia.Controls
if (canClose) if (canClose)
{ {
var args = new CancelEventArgs();
OnClosing(args); OnClosing(args);
return args.Cancel; return args.Cancel;
} }
else
{ return true;
return !canClose;
}
} }
protected virtual void HandleWindowStateChanged(WindowState state) protected virtual void HandleWindowStateChanged(WindowState state)

2
src/Avalonia.Layout/ElementManager.cs

@ -207,7 +207,7 @@ namespace Avalonia.Layout
} }
} }
public bool IsIndexValidInData(int currentIndex) => currentIndex >= 0 && currentIndex < _context.ItemCount; public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context.ItemCount;
public ILayoutable GetRealizedElement(int dataIndex) public ILayoutable GetRealizedElement(int dataIndex)
{ {

14
src/Avalonia.Visuals/Visual.cs

@ -150,7 +150,7 @@ namespace Avalonia
public TransformedBounds? TransformedBounds => _transformedBounds; public TransformedBounds? TransformedBounds => _transformedBounds;
/// <summary> /// <summary>
/// Gets a value indicating whether the control should be clipped to its bounds. /// Gets or sets a value indicating whether the control should be clipped to its bounds.
/// </summary> /// </summary>
public bool ClipToBounds public bool ClipToBounds
{ {
@ -191,7 +191,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets a value indicating whether this control is visible. /// Gets or sets a value indicating whether this control is visible.
/// </summary> /// </summary>
public bool IsVisible public bool IsVisible
{ {
@ -200,7 +200,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the opacity of the control. /// Gets or sets the opacity of the control.
/// </summary> /// </summary>
public double Opacity public double Opacity
{ {
@ -209,7 +209,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the opacity mask of the control. /// Gets or sets the opacity mask of the control.
/// </summary> /// </summary>
public IBrush OpacityMask public IBrush OpacityMask
{ {
@ -218,7 +218,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the render transform of the control. /// Gets or sets the render transform of the control.
/// </summary> /// </summary>
public ITransform RenderTransform public ITransform RenderTransform
{ {
@ -227,7 +227,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the transform origin of the control. /// Gets or sets the transform origin of the control.
/// </summary> /// </summary>
public RelativePoint RenderTransformOrigin public RelativePoint RenderTransformOrigin
{ {
@ -236,7 +236,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Gets the Z index of the control. /// Gets or sets the Z index of the control.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Controls with a higher <see cref="ZIndex"/> will appear in front of controls with /// Controls with a higher <see cref="ZIndex"/> will appear in front of controls with

124
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Layout;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -137,6 +136,129 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, count); Assert.Equal(1, count);
} }
} }
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Child_windows_should_be_closed_before_parent(bool programaticClose)
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var child = new Window();
int count = 0;
int windowClosing = 0;
int childClosing = 0;
int windowClosed = 0;
int childClosed = 0;
window.Closing += (sender, e) =>
{
count++;
windowClosing = count;
};
child.Closing += (sender, e) =>
{
count++;
childClosing = count;
};
window.Closed += (sender, e) =>
{
count++;
windowClosed = count;
};
child.Closed += (sender, e) =>
{
count++;
childClosed = count;
};
window.Show();
child.Show(window);
if (programaticClose)
{
window.Close();
}
else
{
var cancel = window.PlatformImpl.Closing();
Assert.Equal(false, cancel);
}
Assert.Equal(2, windowClosing);
Assert.Equal(1, childClosing);
Assert.Equal(4, windowClosed);
Assert.Equal(3, childClosed);
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Child_windows_must_not_close_before_parent_has_chance_to_Cancel_OSCloseButton(bool programaticClose)
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var child = new Window();
int count = 0;
int windowClosing = 0;
int childClosing = 0;
int windowClosed = 0;
int childClosed = 0;
window.Closing += (sender, e) =>
{
count++;
windowClosing = count;
e.Cancel = true;
};
child.Closing += (sender, e) =>
{
count++;
childClosing = count;
};
window.Closed += (sender, e) =>
{
count++;
windowClosed = count;
};
child.Closed += (sender, e) =>
{
count++;
childClosed = count;
};
window.Show();
child.Show(window);
if (programaticClose)
{
window.Close();
}
else
{
var cancel = window.PlatformImpl.Closing();
Assert.Equal(true, cancel);
}
Assert.Equal(2, windowClosing);
Assert.Equal(1, childClosing);
Assert.Equal(0, windowClosed);
Assert.Equal(0, childClosed);
}
}
[Fact] [Fact]
public void Showing_Should_Start_Renderer() public void Showing_Should_Start_Renderer()

Loading…
Cancel
Save