Browse Source

Merge branch 'master' into datagrid-integration

pull/2109/head
Jumar Macato 8 years ago
committed by GitHub
parent
commit
01d66d953b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs
  2. 22
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  3. 2
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  4. 94
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  5. 8
      samples/ControlCatalog/SideBar.xaml
  6. 73
      samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs
  7. 13
      samples/ControlCatalog/ViewModels/MenuItemViewModel.cs
  8. 89
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  9. 8
      samples/RenderDemo/SideBar.xaml
  10. 4
      src/Avalonia.Animation/Cue.cs
  11. 109
      src/Avalonia.Base/AvaloniaObject.cs
  12. 55
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  13. 2
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  14. 7
      src/Avalonia.Base/IAvaloniaObject.cs
  15. 8
      src/Avalonia.Base/IPriorityValueOwner.cs
  16. 53
      src/Avalonia.Base/Logging/LoggerExtensions.cs
  17. 2
      src/Avalonia.Base/PriorityValue.cs
  18. 4
      src/Avalonia.Base/ValueStore.cs
  19. 14
      src/Avalonia.Controls/ContentControl.cs
  20. 96
      src/Avalonia.Controls/ContextMenu.cs
  21. 6
      src/Avalonia.Controls/Design.cs
  22. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  23. 2
      src/Avalonia.Controls/ItemsControl.cs
  24. 151
      src/Avalonia.Controls/Menu.cs
  25. 192
      src/Avalonia.Controls/MenuBase.cs
  26. 29
      src/Avalonia.Controls/Mixins/ContentControlMixin.cs
  27. 4
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  28. 80
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  29. 42
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  30. 15
      src/Avalonia.Controls/Presenters/IContentPresenter.cs
  31. 37
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  32. 21
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  33. 21
      src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
  34. 8
      src/Avalonia.Controls/Primitives/Popup.cs
  35. 25
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  36. 23
      src/Avalonia.Controls/Shapes/Shape.cs
  37. 12
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  38. 12
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  39. 2
      src/Avalonia.Input/InputElement.cs
  40. 15
      src/Avalonia.Layout/LayoutManager.cs
  41. 79
      src/Avalonia.Layout/LayoutQueue.cs
  42. 4
      src/Avalonia.Layout/Properties/AssemblyInfo.cs
  43. 2
      src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj
  44. 8
      src/Avalonia.Remote.Protocol/DesignMessages.cs
  45. BIN
      src/Avalonia.Remote.Protocol/Key.snk
  46. 2
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  47. 14
      src/Avalonia.Styling/Styling/Selectors.cs
  48. 6
      src/Avalonia.Styling/Styling/Style.cs
  49. 79
      src/Avalonia.Styling/Styling/Styles.cs
  50. 22
      src/Avalonia.Themes.Default/ContextMenu.xaml
  51. 2
      src/Avalonia.Visuals/Media/DrawingContext.cs
  52. 7
      src/Avalonia.Visuals/Media/GeometryDrawing.cs
  53. 11
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  54. 37
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  55. 31
      src/Avalonia.Visuals/Visual.cs
  56. 16
      src/Avalonia.X11/X11Window.cs
  57. 2
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  58. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  59. 4
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  60. 40
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  61. 99
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs
  62. 14
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs
  63. 45
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  64. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  65. 4
      src/Markup/Avalonia.Markup/Data/RelativeSource.cs
  66. 17
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  67. 16
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs
  68. 2
      tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
  69. 68
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  70. 71
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  71. 28
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  72. 32
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  73. 16
      tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs
  74. 11
      tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs
  75. 119
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  76. 196
      tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs
  77. 16
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs
  78. 66
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs
  79. 1
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
  80. 1
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
  81. 1
      tests/Avalonia.Styling.UnitTests/TestControlBase.cs
  82. 1
      tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs
  83. 5
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs
  84. 13
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  85. 46
      tests/Avalonia.Visuals.UnitTests/VisualTests.cs

2
samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs

@ -21,7 +21,7 @@ namespace BindingDemo.ViewModels
}
else
{
throw new ArgumentOutOfRangeException("Value must be less than 10.");
throw new ArgumentOutOfRangeException(nameof(value), "Value must be less than 10.");
}
}
}

22
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -10,7 +10,8 @@
HorizontalAlignment="Center"
Spacing="16">
<Border Background="{DynamicResource ThemeAccentBrush}"
Padding="48,48,48,48">
Margin="16"
Padding="48,48,48,48">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Standard _Menu Item"/>
@ -31,7 +32,24 @@
</MenuItem>
</ContextMenu>
</Border.ContextMenu>
<TextBlock Text="Right Click Here"/>
<TextBlock Text="Defined in XAML"/>
</Border>
<Border Background="{DynamicResource ThemeAccentBrush}"
Margin="16"
Padding="48,48,48,48">
<Border.ContextMenu>
<ContextMenu Items="{Binding MenuItems}">
<ContextMenu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</ContextMenu.Styles>
</ContextMenu>
</Border.ContextMenu>
<TextBlock Text="Dynamically Generated"/>
</Border>
</StackPanel>
</StackPanel>

2
samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs

@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@ -8,6 +9,7 @@ namespace ControlCatalog.Pages
public ContextMenuPage()
{
this.InitializeComponent();
DataContext = new ContextMenuPageViewModel();
}
private void InitializeComponent()

94
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
using ReactiveUI;
namespace ControlCatalog.Pages
@ -13,51 +14,7 @@ namespace ControlCatalog.Pages
public MenuPage()
{
this.InitializeComponent();
var vm = new MenuPageViewModel();
vm.MenuItems = new[]
{
new MenuItemViewModel
{
Header = "_File",
Items = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = vm.OpenCommand },
new MenuItemViewModel { Header = "Save", Command = vm.SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = vm.OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = vm.OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
}
},
}
},
new MenuItemViewModel
{
Header = "_Edit",
Items = new[]
{
new MenuItemViewModel { Header = "_Copy" },
new MenuItemViewModel { Header = "_Paste" },
}
}
};
DataContext = vm;
DataContext = new MenuPageViewModel();
}
private void InitializeComponent()
@ -65,51 +22,4 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
}
public class MenuPageViewModel
{
public MenuPageViewModel()
{
OpenCommand = ReactiveCommand.CreateFromTask(Open);
SaveCommand = ReactiveCommand.Create(Save);
OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
}
public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
public ReactiveCommand<Unit, Unit> OpenCommand { get; }
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<string, Unit> OpenRecentCommand { get; }
public async Task Open()
{
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync(App.Current.MainWindow);
if (result != null)
{
foreach (var path in result)
{
System.Diagnostics.Debug.WriteLine($"Opened: {path}");
}
}
}
public void Save()
{
System.Diagnostics.Debug.WriteLine("Save");
}
public void OpenRecent(string path)
{
System.Diagnostics.Debug.WriteLine($"Open recent: {path}");
}
}
public class MenuItemViewModel
{
public string Header { get; set; }
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
public IList<MenuItemViewModel> Items { get; set; }
}
}

8
samples/ControlCatalog/SideBar.xaml

@ -1,6 +1,14 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.SideBar">
<Design.PreviewWith>
<Border Padding="20">
<TabControl Classes="sidebar">
<TabItem Header="Item1"/>
<TabItem Header="Item2"/>
</TabControl>
</Border>
</Design.PreviewWith>
<Style Selector="TabControl.sidebar">
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Padding" Value="8 0 0 0"/>

73
samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs

@ -0,0 +1,73 @@
using System.Collections.Generic;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Controls;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
public class ContextMenuPageViewModel
{
public ContextMenuPageViewModel()
{
OpenCommand = ReactiveCommand.CreateFromTask(Open);
SaveCommand = ReactiveCommand.Create(Save);
OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
MenuItems = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = OpenCommand },
new MenuItemViewModel { Header = "Save", Command = SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
}
},
};
}
public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
public ReactiveCommand<Unit, Unit> OpenCommand { get; }
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<string, Unit> OpenRecentCommand { get; }
public async Task Open()
{
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync(App.Current.MainWindow);
if (result != null)
{
foreach (var path in result)
{
System.Diagnostics.Debug.WriteLine($"Opened: {path}");
}
}
}
public void Save()
{
System.Diagnostics.Debug.WriteLine("Save");
}
public void OpenRecent(string path)
{
System.Diagnostics.Debug.WriteLine($"Open recent: {path}");
}
}
}

13
samples/ControlCatalog/ViewModels/MenuItemViewModel.cs

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Windows.Input;
namespace ControlCatalog.ViewModels
{
public class MenuItemViewModel
{
public string Header { get; set; }
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
public IList<MenuItemViewModel> Items { get; set; }
}
}

89
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Controls;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
public class MenuPageViewModel
{
public MenuPageViewModel()
{
OpenCommand = ReactiveCommand.CreateFromTask(Open);
SaveCommand = ReactiveCommand.Create(Save);
OpenRecentCommand = ReactiveCommand.Create<string>(OpenRecent);
MenuItems = new[]
{
new MenuItemViewModel
{
Header = "_File",
Items = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = OpenCommand },
new MenuItemViewModel { Header = "Save", Command = SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
}
},
}
},
new MenuItemViewModel
{
Header = "_Edit",
Items = new[]
{
new MenuItemViewModel { Header = "_Copy" },
new MenuItemViewModel { Header = "_Paste" },
}
}
};
}
public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
public ReactiveCommand<Unit, Unit> OpenCommand { get; }
public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<string, Unit> OpenRecentCommand { get; }
public async Task Open()
{
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync(App.Current.MainWindow);
if (result != null)
{
foreach (var path in result)
{
System.Diagnostics.Debug.WriteLine($"Opened: {path}");
}
}
}
public void Save()
{
System.Diagnostics.Debug.WriteLine("Save");
}
public void OpenRecent(string path)
{
System.Diagnostics.Debug.WriteLine($"Open recent: {path}");
}
}
}

8
samples/RenderDemo/SideBar.xaml

@ -1,5 +1,5 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TabControl.sidebar">
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Padding" Value="8 0 0 0"/>
@ -17,7 +17,7 @@
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
Background="{TemplateBinding Background}">
<ItemsPresenter
Name="PART_ItemsPresenter"
Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
@ -25,7 +25,7 @@
</ItemsPresenter>
</ScrollViewer>
<ContentPresenter
Name="PART_Content"
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
@ -63,4 +63,4 @@
<Style Selector="TabControl.sidebar > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
</Styles>
</Styles>

4
src/Avalonia.Animation/Cue.cs

@ -30,7 +30,7 @@ namespace Avalonia.Animation
/// <summary>
/// Parses a string to a <see cref="Cue"/> object.
/// </summary>
public static object Parse(string value, CultureInfo culture)
public static Cue Parse(string value, CultureInfo culture)
{
string v = value;
@ -70,7 +70,7 @@ namespace Avalonia.Animation
}
}
public class CueTypeConverter : TypeConverter
public class CueTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{

109
src/Avalonia.Base/AvaloniaObject.cs

@ -22,27 +22,11 @@ namespace Avalonia
/// </remarks>
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{
/// <summary>
/// The parent object that inherited values are inherited from.
/// </summary>
private IAvaloniaObject _inheritanceParent;
/// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected.
/// </summary>
private List<DirectBindingSubscription> _directBindings;
/// <summary>
/// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
/// </summary>
private PropertyChangedEventHandler _inpcChanged;
/// <summary>
/// Event handler for <see cref="PropertyChanged"/> implementation.
/// </summary>
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private EventHandler<AvaloniaPropertyChangedEventArgs> _inheritablePropertyChanged;
private ValueStore _values;
private ValueStore Values => _values ?? (_values = new ValueStore(this));
@ -52,32 +36,7 @@ namespace Avalonia
public AvaloniaObject()
{
VerifyAccess();
void Notify(AvaloniaProperty property)
{
object value = property.IsDirect ?
((IDirectPropertyAccessor)property).GetValue(this) :
((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
var e = new AvaloniaPropertyChangedEventArgs(
this,
property,
AvaloniaProperty.UnsetValue,
value,
BindingPriority.Unset);
property.NotifyInitialized(e);
}
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
{
Notify(property);
}
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()))
{
Notify(property);
}
AvaloniaPropertyRegistry.Instance.NotifyInitialized(this);
}
/// <summary>
@ -98,6 +57,15 @@ namespace Avalonia
remove { _inpcChanged -= value; }
}
/// <summary>
/// Raised when an inheritable <see cref="AvaloniaProperty"/> value changes on this object.
/// </summary>
event EventHandler<AvaloniaPropertyChangedEventArgs> IAvaloniaObject.InheritablePropertyChanged
{
add { _inheritablePropertyChanged += value; }
remove { _inheritablePropertyChanged -= value; }
}
/// <summary>
/// Gets or sets the parent object that inherited <see cref="AvaloniaProperty"/> values
/// are inherited from.
@ -118,8 +86,9 @@ namespace Avalonia
{
if (_inheritanceParent != null)
{
_inheritanceParent.PropertyChanged -= ParentPropertyChanged;
_inheritanceParent.InheritablePropertyChanged -= ParentPropertyChanged;
}
var properties = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
.Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()));
var inherited = (from property in properties
@ -144,7 +113,7 @@ namespace Avalonia
if (_inheritanceParent != null)
{
_inheritanceParent.PropertyChanged += ParentPropertyChanged;
_inheritanceParent.InheritablePropertyChanged += ParentPropertyChanged;
}
}
}
@ -421,6 +390,7 @@ namespace Avalonia
internal void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
{
LogIfError(property, notification);
UpdateDataValidation(property, notification);
}
@ -452,6 +422,23 @@ namespace Avalonia
});
}
/// <summary>
/// Logs a binding error for a property.
/// </summary>
/// <param name="property">The property that the error occurred on.</param>
/// <param name="e">The binding error.</param>
protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
{
Logger.Log(
LogEventLevel.Warning,
LogArea.Binding,
this,
"Error in binding to {Target}.{Property}: {Message}",
this,
property,
e.Message);
}
/// <summary>
/// Called to update the validation state for properties for which data validation is
/// enabled.
@ -509,6 +496,11 @@ namespace Avalonia
PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name);
_inpcChanged(this, e2);
}
if (property.Inherits)
{
_inheritablePropertyChanged?.Invoke(this, e);
}
}
finally
{
@ -628,7 +620,7 @@ namespace Avalonia
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The default value.</returns>
internal object GetDefaultValue(AvaloniaProperty property)
private object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
return aobj.GetValue(property);
@ -648,7 +640,7 @@ namespace Avalonia
if (notification != null)
{
notification.LogIfError(this, property);
LogIfError(property, notification);
value = notification.Value;
}
@ -780,6 +772,29 @@ namespace Avalonia
return description?.Description ?? o.ToString();
}
/// <summary>
/// Logs a mesage if the notification represents a binding error.
/// </summary>
/// <param name="property">The property being bound.</param>
/// <param name="notification">The binding notification.</param>
private void LogIfError(AvaloniaProperty property, BindingNotification notification)
{
if (notification.ErrorType == BindingErrorType.Error)
{
if (notification.Error is AggregateException aggregate)
{
foreach (var inner in aggregate.InnerExceptions)
{
LogBindingError(property, inner);
}
}
else
{
LogBindingError(property, notification.Error);
}
}
}
/// <summary>
/// Logs a property set message.
/// </summary>

55
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Data;
namespace Avalonia
{
@ -23,6 +24,8 @@ namespace Avalonia
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>> _initializedCache =
new Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>>();
/// <summary>
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@ -226,6 +229,7 @@ namespace Avalonia
}
_registeredCache.Clear();
_initializedCache.Clear();
}
/// <summary>
@ -261,6 +265,57 @@ namespace Avalonia
}
_attachedCache.Clear();
_initializedCache.Clear();
}
internal void NotifyInitialized(AvaloniaObject o)
{
Contract.Requires<ArgumentNullException>(o != null);
var type = o.GetType();
void Notify(AvaloniaProperty property, object value)
{
var e = new AvaloniaPropertyChangedEventArgs(
o,
property,
AvaloniaProperty.UnsetValue,
value,
BindingPriority.Unset);
property.NotifyInitialized(e);
}
if (!_initializedCache.TryGetValue(type, out var items))
{
var build = new Dictionary<AvaloniaProperty, object>();
foreach (var property in GetRegistered(type))
{
var value = !property.IsDirect ?
((IStyledPropertyAccessor)property).GetDefaultValue(type) :
null;
build.Add(property, value);
}
foreach (var property in GetRegisteredAttached(type))
{
if (!build.ContainsKey(property))
{
var value = ((IStyledPropertyAccessor)property).GetDefaultValue(type);
build.Add(property, value);
}
}
items = build.ToList();
_initializedCache.Add(type, items);
}
foreach (var i in items)
{
var value = i.Key.IsDirect ? o.GetValue(i.Key) : i.Value;
Notify(i.Key, value);
}
}
}
}

2
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -21,7 +21,7 @@ namespace Avalonia.Data.Core.Plugins
{
if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8)
{
var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(method));
var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(methodName));
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}

7
src/Avalonia.Base/IAvaloniaObject.cs

@ -16,6 +16,11 @@ namespace Avalonia
/// </summary>
event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
/// <summary>
/// Raised when an inheritable <see cref="AvaloniaProperty"/> value changes on this object.
/// </summary>
event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary>
@ -97,4 +102,4 @@ namespace Avalonia
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue);
}
}
}

8
src/Avalonia.Base/IPriorityValueOwner.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Data;
using Avalonia.Utilities;
@ -28,6 +29,13 @@ namespace Avalonia
/// <param name="notification">The notification.</param>
void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
/// <summary>
/// Logs a binding error.
/// </summary>
/// <param name="property">The property the error occurred on.</param>
/// <param name="e">The binding error.</param>
void LogError(AvaloniaProperty property, Exception e);
/// <summary>
/// Ensures that the current thread is the UI thread.
/// </summary>

53
src/Avalonia.Base/Logging/LoggerExtensions.cs

@ -1,53 +0,0 @@
using System;
using Avalonia.Data;
namespace Avalonia.Logging
{
internal static class LoggerExtensions
{
public static void LogIfError(
this BindingNotification notification,
object source,
AvaloniaProperty property)
{
if (notification.ErrorType == BindingErrorType.Error)
{
if (notification.Error is AggregateException aggregate)
{
foreach (var inner in aggregate.InnerExceptions)
{
LogError(source, property, inner);
}
}
else
{
LogError(source, property, notification.Error);
}
}
}
private static void LogError(object source, AvaloniaProperty property, Exception e)
{
var level = LogEventLevel.Warning;
if (e is BindingChainException b &&
!string.IsNullOrEmpty(b.Expression) &&
string.IsNullOrEmpty(b.ExpressionErrorPoint))
{
// The error occurred at the root of the binding chain: it's possible that the
// DataContext isn't set up yet, so log at Information level instead of Warning
// to prevent spewing hundreds of errors.
level = LogEventLevel.Information;
}
Logger.Log(
level,
LogArea.Binding,
source,
"Error in binding to {Target}.{Property}: {Message}",
source,
property,
e.Message);
}
}
}

2
src/Avalonia.Base/PriorityValue.cs

@ -197,7 +197,7 @@ namespace Avalonia
/// <param name="error">The binding error.</param>
public void LevelError(PriorityLevel level, BindingNotification error)
{
error.LogIfError(Owner, Property);
Owner.LogError(Property, error.Error);
}
/// <summary>

4
src/Avalonia.Base/ValueStore.cs

@ -118,6 +118,10 @@ namespace Avalonia
return dict;
}
public void LogError(AvaloniaProperty property, Exception e)
{
_owner.LogBindingError(property, e);
}
public object GetValue(AvaloniaProperty property)
{

14
src/Avalonia.Controls/ContentControl.cs

@ -97,7 +97,19 @@ namespace Avalonia.Controls
/// <inheritdoc/>
void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
Presenter = presenter;
RegisterContentPresenter(presenter);
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
/// <param name="presenter">The presenter.</param>
protected virtual void RegisterContentPresenter(IContentPresenter presenter)
{
if (presenter.Name == "PART_ContentPresenter")
{
Presenter = presenter;
}
}
}
}

96
src/Avalonia.Controls/ContextMenu.cs

@ -1,34 +1,31 @@
using System;
using System.Reactive.Linq;
using System.Linq;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using System.Collections.Generic;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls
{
public class ContextMenu : SelectingItemsControl, IMenu
/// <summary>
/// A control context menu.
/// </summary>
public class ContextMenu : MenuBase
{
private readonly IMenuInteractionHandler _interaction;
private bool _isOpen;
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Vertical });
private Popup _popup;
/// <summary>
/// Defines the <see cref="IsOpen"/> property.
/// </summary>
public static readonly DirectProperty<ContextMenu, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<ContextMenu, bool>(nameof(IsOpen), o => o.IsOpen);
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
/// </summary>
public ContextMenu()
: this(new DefaultMenuInteractionHandler(true))
{
_interaction = AvaloniaLocator.Current.GetService<IMenuInteractionHandler>() ??
new DefaultMenuInteractionHandler();
}
/// <summary>
@ -36,10 +33,8 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="interactionHandler">The menu interaction handler.</param>
public ContextMenu(IMenuInteractionHandler interactionHandler)
: base(interactionHandler)
{
Contract.Requires<ArgumentNullException>(interactionHandler != null);
_interaction = interactionHandler;
}
/// <summary>
@ -47,44 +42,10 @@ namespace Avalonia.Controls
/// </summary>
static ContextMenu()
{
ItemsPanelProperty.OverrideDefaultValue(typeof(ContextMenu), DefaultPanel);
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
}
/// <summary>
/// Gets a value indicating whether the popup is open
/// </summary>
public bool IsOpen => _isOpen;
/// <inheritdoc/>
IMenuInteractionHandler IMenu.InteractionHandler => _interaction;
/// <inheritdoc/>
IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
(IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
}
}
/// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems
{
get
{
return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
/// <summary>
/// Occurs when the value of the
/// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
@ -121,7 +82,7 @@ namespace Avalonia.Controls
/// <summary>
/// Opens the menu.
/// </summary>
public void Open() => Open(null);
public override void Open() => Open(null);
/// <summary>
/// Opens a context menu on the specified control.
@ -139,21 +100,20 @@ namespace Avalonia.Controls
ObeyScreenEdges = true
};
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
_interaction.Attach(this);
}
((ISetLogicalParent)_popup).SetParent(control);
_popup.Child = this;
_popup.IsOpen = true;
SetAndRaise(IsOpenProperty, ref _isOpen, true);
IsOpen = true;
}
/// <summary>
/// Closes the menu.
/// </summary>
public void Close()
public override void Close()
{
if (_popup != null && _popup.IsVisible)
{
@ -161,8 +121,17 @@ namespace Avalonia.Controls
}
SelectedIndex = -1;
IsOpen = false;
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
SetAndRaise(IsOpenProperty, ref _isOpen, false);
private void PopupOpened(object sender, EventArgs e)
{
Focus();
}
private void PopupClosed(object sender, EventArgs e)
@ -176,7 +145,7 @@ namespace Avalonia.Controls
i.IsSubMenuOpen = false;
}
contextMenu._isOpen = false;
contextMenu.IsOpen = false;
contextMenu.SelectedIndex = -1;
}
}
@ -186,7 +155,7 @@ namespace Avalonia.Controls
var control = (Control)sender;
var contextMenu = control.ContextMenu;
if (control.ContextMenu._isOpen)
if (control.ContextMenu.IsOpen)
{
if (contextMenu.CancelClosing())
return;
@ -218,10 +187,5 @@ namespace Avalonia.Controls
ContextMenuOpening?.Invoke(this, eventArgs);
return eventArgs.Cancel;
}
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap)
{
throw new NotImplementedException();
}
}
}

6
src/Avalonia.Controls/Design.cs

@ -48,14 +48,14 @@ namespace Avalonia.Controls
}
public static readonly AttachedProperty<Control> PreviewWithProperty = AvaloniaProperty
.RegisterAttached<Style, Control>("PreviewWith", typeof (Design));
.RegisterAttached<AvaloniaObject, Control>("PreviewWith", typeof (Design));
public static void SetPreviewWith(Style target, Control control)
public static void SetPreviewWith(AvaloniaObject target, Control control)
{
target.SetValue(PreviewWithProperty, control);
}
public static Control GetPreviewWith(Style target)
public static Control GetPreviewWith(AvaloniaObject target)
{
return target.GetValue(PreviewWithProperty);
}

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -11,11 +11,13 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
private double _scaling = 1;
private Size _clientSize;
public IInputRoot InputRoot { get; private set; }
public bool IsDisposed { get; private set; }
public virtual void Dispose()
{
//No-op
IsDisposed = true;
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);

2
src/Avalonia.Controls/ItemsControl.cs

@ -64,6 +64,7 @@ namespace Avalonia.Controls
static ItemsControl()
{
ItemsProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemsChanged);
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged);
}
/// <summary>
@ -73,7 +74,6 @@ namespace Avalonia.Controls
{
PseudoClasses.Add(":empty");
SubscribeToItems(_items);
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged);
}
/// <summary>

151
src/Avalonia.Controls/Menu.cs

@ -1,56 +1,26 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
/// <summary>
/// A top-level menu control.
/// </summary>
public class Menu : SelectingItemsControl, IFocusScope, IMainMenu, IMenu
public class Menu : MenuBase, IMainMenu
{
/// <summary>
/// Defines the <see cref="IsOpen"/> property.
/// </summary>
public static readonly DirectProperty<Menu, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<Menu, bool>(
nameof(IsOpen),
o => o.IsOpen);
/// <summary>
/// Defines the <see cref="MenuOpened"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuOpenedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuOpened), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="MenuClosed"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuClosedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuClosed), RoutingStrategies.Bubble);
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Horizontal });
private readonly IMenuInteractionHandler _interaction;
private bool _isOpen;
/// <summary>
/// Initializes a new instance of the <see cref="Menu"/> class.
/// </summary>
public Menu()
{
_interaction = AvaloniaLocator.Current.GetService<IMenuInteractionHandler>() ??
new DefaultMenuInteractionHandler();
}
/// <summary>
@ -58,82 +28,17 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="interactionHandler">The menu interaction handler.</param>
public Menu(IMenuInteractionHandler interactionHandler)
: base(interactionHandler)
{
Contract.Requires<ArgumentNullException>(interactionHandler != null);
_interaction = interactionHandler;
}
/// <summary>
/// Initializes static members of the <see cref="Menu"/> class.
/// </summary>
static Menu()
{
ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel);
MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened);
}
/// <summary>
/// Gets a value indicating whether the menu is open.
/// </summary>
public bool IsOpen
{
get { return _isOpen; }
private set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
}
/// <inheritdoc/>
IMenuInteractionHandler IMenu.InteractionHandler => _interaction;
/// <inheritdoc/>
IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
(IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
}
}
/// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems
{
get
{
return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
/// <summary>
/// Occurs when a <see cref="Menu"/> is opened.
/// </summary>
public event EventHandler<RoutedEventArgs> MenuOpened
{
add { AddHandler(MenuOpenedEvent, value); }
remove { RemoveHandler(MenuOpenedEvent, value); }
}
/// <summary>
/// Occurs when a <see cref="Menu"/> is closed.
/// </summary>
public event EventHandler<RoutedEventArgs> MenuClosed
{
add { AddHandler(MenuClosedEvent, value); }
remove { RemoveHandler(MenuClosedEvent, value); }
}
/// <summary>
/// Closes the menu.
/// </summary>
public void Close()
public override void Close()
{
if (IsOpen)
{
@ -153,10 +58,8 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Opens the menu in response to the Alt/F10 key.
/// </summary>
public void Open()
/// <inheritdoc/>
public override void Open()
{
if (!IsOpen)
{
@ -170,15 +73,6 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<MenuItem>(this, MenuItem.HeaderProperty, null);
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
@ -190,41 +84,6 @@ namespace Avalonia.Controls
{
inputRoot.AccessKeyHandler.MainMenu = this;
}
_interaction.Attach(this);
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_interaction.Detach(this);
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
// Don't handle here: let the interaction handler handle it.
}
/// <summary>
/// Called when a submenu opens somewhere in the menu.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnSubmenuOpened(RoutedEventArgs e)
{
if (e.Source is MenuItem menuItem && menuItem.Parent == this)
{
foreach (var child in this.GetLogicalChildren().OfType<MenuItem>())
{
if (child != menuItem && child.IsSubMenuOpen)
{
child.IsSubMenuOpen = false;
}
}
}
IsOpen = true;
}
}
}

192
src/Avalonia.Controls/MenuBase.cs

@ -0,0 +1,192 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
/// <summary>
/// Base class for menu controls.
/// </summary>
public abstract class MenuBase : SelectingItemsControl, IFocusScope, IMenu
{
/// <summary>
/// Defines the <see cref="IsOpen"/> property.
/// </summary>
public static readonly DirectProperty<Menu, bool> IsOpenProperty =
AvaloniaProperty.RegisterDirect<Menu, bool>(
nameof(IsOpen),
o => o.IsOpen);
/// <summary>
/// Defines the <see cref="MenuOpened"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuOpenedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuOpened), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="MenuClosed"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuClosedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuClosed), RoutingStrategies.Bubble);
private bool _isOpen;
/// <summary>
/// Initializes a new instance of the <see cref="MenuBase"/> class.
/// </summary>
public MenuBase()
{
InteractionHandler = new DefaultMenuInteractionHandler(false);
}
/// <summary>
/// Initializes a new instance of the <see cref="MenuBase"/> class.
/// </summary>
/// <param name="interactionHandler">The menu interaction handler.</param>
public MenuBase(IMenuInteractionHandler interactionHandler)
{
Contract.Requires<ArgumentNullException>(interactionHandler != null);
InteractionHandler = interactionHandler;
}
/// <summary>
/// Initializes static members of the <see cref="MenuBase"/> class.
/// </summary>
static MenuBase()
{
MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>(x => x.OnSubmenuOpened);
}
/// <summary>
/// Gets a value indicating whether the menu is open.
/// </summary>
public bool IsOpen
{
get { return _isOpen; }
protected set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
}
/// <inheritdoc/>
IMenuInteractionHandler IMenu.InteractionHandler => InteractionHandler;
/// <inheritdoc/>
IMenuItem IMenuElement.SelectedItem
{
get
{
var index = SelectedIndex;
return (index != -1) ?
(IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
null;
}
set
{
SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
}
}
/// <inheritdoc/>
IEnumerable<IMenuItem> IMenuElement.SubItems
{
get
{
return ItemContainerGenerator.Containers
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
/// <summary>
/// Gets the interaction handler for the menu.
/// </summary>
protected IMenuInteractionHandler InteractionHandler { get; }
/// <summary>
/// Occurs when a <see cref="Menu"/> is opened.
/// </summary>
public event EventHandler<RoutedEventArgs> MenuOpened
{
add { AddHandler(MenuOpenedEvent, value); }
remove { RemoveHandler(MenuOpenedEvent, value); }
}
/// <summary>
/// Occurs when a <see cref="Menu"/> is closed.
/// </summary>
public event EventHandler<RoutedEventArgs> MenuClosed
{
add { AddHandler(MenuClosedEvent, value); }
remove { RemoveHandler(MenuClosedEvent, value); }
}
/// <summary>
/// Closes the menu.
/// </summary>
public abstract void Close();
/// <summary>
/// Opens the menu.
/// </summary>
public abstract void Open();
/// <inheritdoc/>
bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<MenuItem>(this, MenuItem.HeaderProperty, null);
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
// Don't handle here: let the interaction handler handle it.
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
InteractionHandler.Attach(this);
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
InteractionHandler.Detach(this);
}
/// <summary>
/// Called when a submenu opens somewhere in the menu.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnSubmenuOpened(RoutedEventArgs e)
{
if (e.Source is MenuItem menuItem && menuItem.Parent == this)
{
foreach (var child in this.GetLogicalChildren().OfType<MenuItem>())
{
if (child != menuItem && child.IsSubMenuOpen)
{
child.IsSubMenuOpen = false;
}
}
}
IsOpen = true;
}
}
}

29
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@ -19,8 +19,8 @@ namespace Avalonia.Controls.Mixins
/// <para>
/// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
/// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
/// updates keeps the control's logical children in sync with the content being displayed by
/// the control.
/// keeps the control's logical children in sync with the content being displayed by the
/// control.
/// </para>
public class ContentControlMixin
{
@ -49,25 +49,42 @@ namespace Avalonia.Controls.Mixins
Contract.Requires<ArgumentNullException>(content != null);
Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null);
void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e)
{
if (s is IControl sender && sender?.TemplatedParent is TControl parent)
{
UpdateLogicalChild(
sender,
logicalChildrenSelector(parent),
e.OldValue,
null);
}
}
void TemplateApplied(object s, RoutedEventArgs ev)
{
if (s is TControl sender)
{
var e = (TemplateAppliedEventArgs)ev;
var presenter = (IControl)e.NameScope.Find(presenterName);
var presenter = e.NameScope.Find(presenterName) as IContentPresenter;
if (presenter != null)
{
presenter.ApplyTemplate();
var logicalChildren = logicalChildrenSelector(sender);
var subscription = presenter
var subscription = new CompositeDisposable();
presenter.ChildChanging += ChildChanging;
subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging));
subscription.Add(presenter
.GetPropertyChangedObservable(ContentPresenter.ChildProperty)
.Subscribe(c => UpdateLogicalChild(
sender,
logicalChildren,
c.OldValue,
c.NewValue));
null,
c.NewValue)));
UpdateLogicalChild(
sender,

4
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -965,11 +965,11 @@ namespace Avalonia.Controls
{
if (value < Minimum)
{
throw new ArgumentOutOfRangeException(nameof(Minimum), string.Format("Value must be greater than Minimum value of {0}", Minimum));
throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be greater than Minimum value of {0}", Minimum));
}
else if (value > Maximum)
{
throw new ArgumentOutOfRangeException(nameof(Maximum), string.Format("Value must be less than Maximum value of {0}", Maximum));
throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be less than Maximum value of {0}", Maximum));
}
}

80
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -13,18 +13,21 @@ namespace Avalonia.Controls.Platform
/// </summary>
public class DefaultMenuInteractionHandler : IMenuInteractionHandler
{
private readonly bool _isContextMenu;
private IDisposable _inputManagerSubscription;
private IRenderRoot _root;
public DefaultMenuInteractionHandler()
: this(Input.InputManager.Instance, DefaultDelayRun)
public DefaultMenuInteractionHandler(bool isContextMenu)
: this(isContextMenu, Input.InputManager.Instance, DefaultDelayRun)
{
}
public DefaultMenuInteractionHandler(
bool isContextMenu,
IInputManager inputManager,
Action<Action, TimeSpan> delayRun)
{
_isContextMenu = isContextMenu;
InputManager = inputManager;
DelayRun = delayRun;
}
@ -59,7 +62,7 @@ namespace Avalonia.Controls.Platform
window.Deactivated += WindowDeactivated;
}
_inputManagerSubscription = InputManager.Process.Subscribe(RawInput);
_inputManagerSubscription = InputManager?.Process.Subscribe(RawInput);
}
public virtual void Detach(IMenu menu)
@ -125,23 +128,16 @@ namespace Avalonia.Controls.Platform
protected internal virtual void KeyDown(object sender, KeyEventArgs e)
{
var item = GetMenuItem(e.Source as IControl);
if (item != null)
{
KeyDown(item, e);
}
KeyDown(GetMenuItem(e.Source as IControl), e);
}
protected internal virtual void KeyDown(IMenuItem item, KeyEventArgs e)
{
Contract.Requires<ArgumentNullException>(item != null);
switch (e.Key)
{
case Key.Up:
case Key.Down:
if (item.IsTopLevel)
if (item?.IsTopLevel == true)
{
if (item.HasSubMenu && !item.IsSubMenuOpen)
{
@ -156,7 +152,7 @@ namespace Avalonia.Controls.Platform
break;
case Key.Left:
if (item.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen)
if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen)
{
parent.Close();
parent.Focus();
@ -169,7 +165,7 @@ namespace Avalonia.Controls.Platform
break;
case Key.Right:
if (!item.IsTopLevel && item.HasSubMenu)
if (item != null && !item.IsTopLevel && item.HasSubMenu)
{
Open(item, true);
e.Handled = true;
@ -181,47 +177,65 @@ namespace Avalonia.Controls.Platform
break;
case Key.Enter:
if (!item.HasSubMenu)
if (item != null)
{
Click(item);
}
else
{
Open(item, true);
}
if (!item.HasSubMenu)
{
Click(item);
}
else
{
Open(item, true);
}
e.Handled = true;
e.Handled = true;
}
break;
case Key.Escape:
if (item.Parent != null)
if (item?.Parent != null)
{
item.Parent.Close();
item.Parent.Focus();
e.Handled = true;
}
else
{
Menu.Close();
}
e.Handled = true;
break;
default:
var direction = e.Key.ToNavigationDirection();
if (direction.HasValue && item.Parent?.MoveSelection(direction.Value, true) == true)
if (direction.HasValue)
{
// If the the parent is an IMenu which successfully moved its selection,
// and the current menu is open then close the current menu and open the
// new menu.
if (item.IsSubMenuOpen && item.Parent is IMenu)
if (item == null && _isContextMenu)
{
item.Close();
Open(item.Parent.SelectedItem, true);
if (Menu.MoveSelection(direction.Value, true) == true)
{
e.Handled = true;
}
}
else if (item.Parent?.MoveSelection(direction.Value, true) == true)
{
// If the the parent is an IMenu which successfully moved its selection,
// and the current menu is open then close the current menu and open the
// new menu.
if (item.IsSubMenuOpen && item.Parent is IMenu)
{
item.Close();
Open(item.Parent.SelectedItem, true);
}
e.Handled = true;
}
e.Handled = true;
}
break;
}
if (!e.Handled && item.Parent is IMenuItem parentItem)
if (!e.Handled && item?.Parent is IMenuItem parentItem)
{
KeyDown(parentItem, e);
}

42
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -5,6 +5,7 @@ using System;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -82,6 +83,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
EventHandler<AvaloniaPropertyChangedEventArgs> _childChanging;
private IDataTemplate _dataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
@ -188,6 +190,13 @@ namespace Avalonia.Controls.Presenters
set { SetValue(PaddingProperty, value); }
}
/// <inheritdoc/>
event EventHandler<AvaloniaPropertyChangedEventArgs> IContentPresenter.ChildChanging
{
add => _childChanging += value;
remove => _childChanging -= value;
}
/// <inheritdoc/>
public sealed override void ApplyTemplate()
{
@ -215,9 +224,30 @@ namespace Avalonia.Controls.Presenters
var newChild = CreateChild();
// Remove the old child if we're not recycling it.
if (oldChild != null && newChild != oldChild)
if (newChild != oldChild)
{
VisualChildren.Remove(oldChild);
if (oldChild != null)
{
VisualChildren.Remove(oldChild);
}
if (oldChild?.Parent == this)
{
// If we're the child's parent then the presenter isn't in a ContentControl's
// template.
LogicalChildren.Remove(oldChild);
}
else
{
// If we're in a ContentControl's template then invoke ChildChanging to let
// ContentControlMixin handle removing the logical child.
_childChanging?.Invoke(this, new AvaloniaPropertyChangedEventArgs(
this,
ChildProperty,
oldChild,
newChild,
BindingPriority.LocalValue));
}
}
// Set the DataContext if the data isn't a control.
@ -241,11 +271,9 @@ namespace Avalonia.Controls.Presenters
Child = newChild;
if (oldChild?.Parent == this)
{
LogicalChildren.Remove(oldChild);
}
// If we're in a ContentControl's template then the child's parent will have been
// set by ContentControlMixin in response to Child changing. If not, then we're
// standalone and should make the control our own logical child.
if (newChild.Parent == null && TemplatedParent == null)
{
LogicalChildren.Add(newChild);

15
src/Avalonia.Controls/Presenters/IContentPresenter.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls.Presenters
@ -20,5 +22,16 @@ namespace Avalonia.Controls.Presenters
/// Gets or sets the content to be displayed by the presenter.
/// </summary>
object Content { get; set; }
/// <summary>
/// Raised when <see cref="Child"/> property is about to change.
/// </summary>
/// <remarks>
/// This event should be raised after the child has been removed from the visual tree,
/// but before the <see cref="Child"/> property has changed. It is intended for consumption
/// by <see cref="ContentControlMixin"/> in order to update the host control's logical
/// children.
/// </remarks>
event EventHandler<AvaloniaPropertyChangedEventArgs> ChildChanging;
}
}
}

37
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
namespace Avalonia.Controls.Primitives
@ -20,7 +22,18 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="HeaderTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(HeaderTemplate));
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(HeaderTemplate));
/// <summary>
/// Initializes static members of the <see cref="ContentControl"/> class.
/// </summary>
static HeaderedContentControl()
{
ContentControlMixin.Attach<HeaderedContentControl>(
HeaderProperty,
x => x.LogicalChildren,
"PART_HeaderPresenter");
}
/// <summary>
/// Gets or sets the header content.
@ -29,7 +42,16 @@ namespace Avalonia.Controls.Primitives
{
get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
}
/// <summary>
/// Gets the header presenter from the control's template.
/// </summary>
public IContentPresenter HeaderPresenter
{
get;
private set;
}
/// <summary>
/// Gets or sets the data template used to display the header content of the control.
@ -39,5 +61,16 @@ namespace Avalonia.Controls.Primitives
get { return GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
}
/// <inheritdoc/>
protected override void RegisterContentPresenter(IContentPresenter presenter)
{
base.RegisterContentPresenter(presenter);
if (presenter.Name == "PART_HeaderPresenter")
{
HeaderPresenter = presenter;
}
}
}
}

21
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@ -9,7 +9,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Represents an <see cref="ItemsControl"/> with a related header.
/// </summary>
public class HeaderedItemsControl : ItemsControl
public class HeaderedItemsControl : ItemsControl, IContentPresenterHost
{
/// <summary>
/// Defines the <see cref="Header"/> property.
@ -40,17 +40,28 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets the header presenter from the control's template.
/// </summary>
public ContentPresenter HeaderPresenter
public IContentPresenter HeaderPresenter
{
get;
private set;
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
HeaderPresenter = e.NameScope.Find<ContentPresenter>("PART_HeaderPresenter");
base.OnTemplateApplied(e);
RegisterContentPresenter(presenter);
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
/// <param name="presenter">The presenter.</param>
protected virtual void RegisterContentPresenter(IContentPresenter presenter)
{
if (presenter.Name == "PART_HeaderPresenter")
{
HeaderPresenter = presenter;
}
}
}
}

21
src/Avalonia.Controls/Primitives/HeaderedSelectingControl.cs → src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs

@ -9,7 +9,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Represents a <see cref="SelectingItemsControl"/> with a related header.
/// </summary>
public class HeaderedSelectingItemsControl : SelectingItemsControl
public class HeaderedSelectingItemsControl : SelectingItemsControl, IContentPresenterHost
{
/// <summary>
/// Defines the <see cref="Header"/> property.
@ -40,17 +40,28 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets the header presenter from the control's template.
/// </summary>
public ContentPresenter HeaderPresenter
public IContentPresenter HeaderPresenter
{
get;
private set;
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
base.OnTemplateApplied(e);
HeaderPresenter = e.NameScope.Find<ContentPresenter>("PART_HeaderPresenter");
RegisterContentPresenter(presenter);
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
/// <param name="presenter">The presenter.</param>
protected virtual void RegisterContentPresenter(IContentPresenter presenter)
{
if (presenter.Name == "PART_HeaderPresenter")
{
HeaderPresenter = presenter;
}
}
}
}

8
src/Avalonia.Controls/Primitives/Popup.cs

@ -252,9 +252,9 @@ namespace Avalonia.Controls.Primitives
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot != null && parentPopuproot.Parent != null)
if (parentPopuproot?.Parent is Popup popup)
{
((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
popup.Closed += ParentClosed;
}
}
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
@ -293,9 +293,9 @@ namespace Avalonia.Controls.Primitives
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot != null && parentPopuproot.Parent != null)
if (parentPopuproot?.Parent is Popup popup)
{
((Popup)parentPopuproot.Parent).Closed -= ParentClosed;
popup.Closed -= ParentClosed;
}
}
_nonClientListener?.Dispose();

25
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -61,6 +61,11 @@ namespace Avalonia.Controls.Remote.Server
{
var result = InputModifiers.None;
if (modifiers == null)
{
return result;
}
foreach(var modifier in modifiers)
{
switch (modifier)
@ -265,11 +270,15 @@ namespace Avalonia.Controls.Remote.Server
var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
var data = new byte[width * height * bpp];
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt,
null);
Paint?.Invoke(new Rect(0, 0, width, height));
if (width > 0 && height > 0)
{
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt,
null);
Paint?.Invoke(new Rect(0, 0, width, height));
}
}
finally
{
@ -301,8 +310,7 @@ namespace Avalonia.Controls.Remote.Server
return;
}
if (ClientSize.Width < 1 || ClientSize.Height < 1)
return;
var format = ProtocolPixelFormat.Rgba8888;
foreach(var fmt in _supportedFormats)
if (fmt <= ProtocolPixelFormat.MaxValue)
@ -323,8 +331,11 @@ namespace Avalonia.Controls.Remote.Server
public override void Invalidate(Rect rect)
{
_invalidated = true;
Dispatcher.UIThread.Post(RenderIfNeeded);
if (!IsDisposed)
{
_invalidated = true;
Dispatcher.UIThread.Post(RenderIfNeeded);
}
}
public override IMouseDevice MouseDevice { get; } = new MouseDevice();

23
src/Avalonia.Controls/Shapes/Shape.cs

@ -195,7 +195,7 @@ namespace Avalonia.Controls.Shapes
if (deferCalculateTransform)
{
_calculateTransformOnArrange = true;
return DefiningGeometry.Bounds.Size;
return DefiningGeometry?.Bounds.Size ?? Size.Empty;
}
else
{
@ -217,17 +217,22 @@ namespace Avalonia.Controls.Shapes
private Size CalculateShapeSizeAndSetTransform(Size availableSize)
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
if (DefiningGeometry != null)
{
_transform = transform;
_renderedGeometry = null;
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
{
_transform = transform;
_renderedGeometry = null;
}
return size;
}
return size;
return Size.Empty;
}
internal static (Size, Matrix) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch)

12
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -35,13 +35,13 @@ namespace Avalonia.DesignerSupport
var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null;
var loaded = loader.Load(stream, localAsm, null, baseUri);
var styles = loaded as Styles;
if (styles != null)
var style = loaded as IStyle;
if (style != null)
{
var substitute = styles.OfType<Style>().Select(Design.GetPreviewWith).FirstOrDefault(s => s != null);
var substitute = Design.GetPreviewWith((AvaloniaObject)style);
if (substitute != null)
{
substitute.Styles.AddRange(styles);
substitute.Styles.Add(style);
control = substitute;
}
else
@ -51,8 +51,8 @@ namespace Avalonia.DesignerSupport
{
new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"},
new TextBlock {Text = "<Design.PreviewWith>"},
new TextBlock {Text = " <Border Padding=20><!-- YOUR CONTROL FOR PREVIEW HERE--></Border>"},
new TextBlock {Text = "<Design.PreviewWith>"},
new TextBlock {Text = " <Border Padding=20><!-- YOUR CONTROL FOR PREVIEW HERE --></Border>"},
new TextBlock {Text = "</Design.PreviewWith>"},
new TextBlock {Text = "before setters in your first Style"}
}
};

12
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -8,6 +8,7 @@ using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Designer;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
using Portable.Xaml;
namespace Avalonia.DesignerSupport.Remote
{
@ -204,9 +205,18 @@ namespace Avalonia.DesignerSupport.Remote
}
catch (Exception e)
{
var xamlException = e as XamlException;
s_transport.Send(new UpdateXamlResultMessage
{
Error = e.ToString()
Error = e.ToString(),
Exception = new ExceptionDetails
{
ExceptionType = e.GetType().FullName,
Message = e.Message.ToString(),
LineNumber = xamlException?.LineNumber,
LinePosition = xamlException?.LinePosition,
}
});
}
}

2
src/Avalonia.Input/InputElement.cs

@ -375,7 +375,7 @@ namespace Avalonia.Input
/// </summary>
public void Focus()
{
FocusManager.Instance.Focus(this);
FocusManager.Instance?.Focus(this);
}
/// <inheritdoc/>

15
src/Avalonia.Layout/LayoutManager.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Logging;
using Avalonia.Threading;
@ -13,8 +12,8 @@ namespace Avalonia.Layout
/// </summary>
public class LayoutManager : ILayoutManager
{
private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
private bool _queued;
private bool _running;
@ -80,6 +79,9 @@ namespace Avalonia.Layout
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
_toMeasure.BeginLoop(MaxPasses);
_toArrange.BeginLoop(MaxPasses);
try
{
for (var pass = 0; pass < MaxPasses; ++pass)
@ -98,6 +100,9 @@ namespace Avalonia.Layout
_running = false;
}
_toMeasure.EndLoop();
_toArrange.EndLoop();
stopwatch.Stop();
Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed);
}
@ -112,7 +117,7 @@ namespace Avalonia.Layout
Arrange(root);
// Running the initial layout pass may have caused some control to be invalidated
// so run a full layout pass now (this usually due to scrollbars; its not known
// so run a full layout pass now (this usually due to scrollbars; its not known
// whether they will need to be shown until the layout pass has run and if the
// first guess was incorrect the layout will need to be updated).
ExecuteLayoutPass();
@ -133,7 +138,7 @@ namespace Avalonia.Layout
private void ExecuteArrangePass()
{
while (_toArrange.Count > 0 && _toMeasure.Count == 0)
while (_toArrange.Count > 0)
{
var control = _toArrange.Dequeue();

79
src/Avalonia.Layout/LayoutQueue.cs

@ -0,0 +1,79 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Layout
{
internal class LayoutQueue<T> : IReadOnlyCollection<T>
{
private struct Info
{
public bool Active;
public int Count;
}
public LayoutQueue(Func<T, bool> shouldEnqueue)
{
_shouldEnqueue = shouldEnqueue;
}
private Func<T, bool> _shouldEnqueue;
private Queue<T> _inner = new Queue<T>();
private Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
private int _maxEnqueueCountPerLoop = 1;
public int Count => _inner.Count;
public IEnumerator<T> GetEnumerator() => (_inner as IEnumerable<T>).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
public T Dequeue()
{
var result = _inner.Dequeue();
if (_loopQueueInfo.TryGetValue(result, out var info))
{
info.Active = false;
_loopQueueInfo[result] = info;
}
return result;
}
public void Enqueue(T item)
{
_loopQueueInfo.TryGetValue(item, out var info);
if (!info.Active && info.Count < _maxEnqueueCountPerLoop)
{
_inner.Enqueue(item);
_loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
}
}
public void BeginLoop(int maxEnqueueCountPerLoop)
{
_maxEnqueueCountPerLoop = maxEnqueueCountPerLoop;
}
public void EndLoop()
{
var notfinalized = _loopQueueInfo.Where(v => v.Value.Count == _maxEnqueueCountPerLoop).ToArray();
_loopQueueInfo.Clear();
//prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
//one more time as a final attempt
foreach (var item in notfinalized)
{
if (_shouldEnqueue(item.Key))
{
_loopQueueInfo[item.Key] = new Info() { Active = true, Count = item.Value.Count + 1 };
_inner.Enqueue(item.Key);
}
}
}
}
}

4
src/Avalonia.Layout/Properties/AssemblyInfo.cs

@ -1,6 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")]

2
src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj

@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)</DefineConstants>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Input\Key.cs" />

8
src/Avalonia.Remote.Protocol/DesignMessages.cs

@ -15,6 +15,7 @@ namespace Avalonia.Remote.Protocol.Designer
{
public string Error { get; set; }
public string Handle { get; set; }
public ExceptionDetails Exception { get; set; }
}
[AvaloniaRemoteMessageGuid("854887CF-2694-4EB6-B499-7461B6FB96C7")]
@ -23,4 +24,11 @@ namespace Avalonia.Remote.Protocol.Designer
public string SessionId { get; set; }
}
public class ExceptionDetails
{
public string ExceptionType { get; set; }
public string Message { get; set; }
public int? LineNumber { get; set; }
public int? LinePosition { get; set; }
}
}

BIN
src/Avalonia.Remote.Protocol/Key.snk

Binary file not shown.

2
src/Avalonia.Remote.Protocol/MetsysBson.cs

@ -749,7 +749,7 @@ namespace Metsys.Bson
if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert)
{
throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), "lambdaExpression");
throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), nameof(lambdaExpression));
}
return memberExpression.Member.Name;
default:

14
src/Avalonia.Styling/Styling/Selectors.cs

@ -28,6 +28,9 @@ namespace Avalonia.Styling
/// <returns>The selector.</returns>
public static Selector Class(this Selector previous, string name)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
var tac = previous as TypeNameAndClassSelector;
if (tac != null)
@ -59,6 +62,8 @@ namespace Avalonia.Styling
/// <returns>The selector.</returns>
public static Selector Is(this Selector previous, Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
return TypeNameAndClassSelector.Is(previous, type);
}
@ -81,6 +86,9 @@ namespace Avalonia.Styling
/// <returns>The selector.</returns>
public static Selector Name(this Selector previous, string name)
{
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
var tac = previous as TypeNameAndClassSelector;
if (tac != null)
@ -113,6 +121,8 @@ namespace Avalonia.Styling
/// <returns>The selector.</returns>
public static Selector OfType(this Selector previous, Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
return TypeNameAndClassSelector.OfType(previous, type);
}
@ -137,6 +147,8 @@ namespace Avalonia.Styling
/// <returns>The selector.</returns>
public static Selector PropertyEquals<T>(this Selector previous, AvaloniaProperty<T> property, object value)
{
Contract.Requires<ArgumentNullException>(property != null);
return new PropertyEqualsSelector(previous, property, value);
}
@ -149,6 +161,8 @@ namespace Avalonia.Styling
/// <returns>The selector.</returns>
public static Selector PropertyEquals(this Selector previous, AvaloniaProperty property, object value)
{
Contract.Requires<ArgumentNullException>(property != null);
return new PropertyEqualsSelector(previous, property, value);
}

6
src/Avalonia.Styling/Styling/Style.cs

@ -143,6 +143,7 @@ namespace Avalonia.Styling
}
controlSubscriptions.Add(subs);
controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
Subscriptions.Add(subs);
}
@ -159,8 +160,9 @@ namespace Avalonia.Styling
var sub = setter.Apply(this, control, null);
subs.Add(sub);
}
controlSubscriptions.Add(subs);
controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
Subscriptions.Add(subs);
return true;
}
@ -223,7 +225,7 @@ namespace Avalonia.Styling
{
if (!_applied.TryGetValue(control, out var subscriptions))
{
subscriptions = new CompositeDisposable(2);
subscriptions = new CompositeDisposable(3);
subscriptions.Add(control.StyleDetach.Subscribe(ControlDetach));
_applied.Add(control, subscriptions);
}

79
src/Avalonia.Styling/Styling/Styles.cs

@ -2,7 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
@ -12,16 +14,17 @@ namespace Avalonia.Styling
/// <summary>
/// A style that consists of a number of child styles.
/// </summary>
public class Styles : AvaloniaList<IStyle>, IStyle, ISetStyleParent
public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetStyleParent
{
private IResourceNode _parent;
private IResourceDictionary _resources;
private AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
private Dictionary<Type, List<IStyle>> _cache;
public Styles()
{
ResetBehavior = ResetBehavior.Remove;
this.ForEachItem(
_styles.ResetBehavior = ResetBehavior.Remove;
_styles.ForEachItem(
x =>
{
if (x.ResourceParent == null && x is ISetStyleParent setParent)
@ -57,9 +60,18 @@ namespace Avalonia.Styling
() => { });
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _styles.CollectionChanged += value;
remove => _styles.CollectionChanged -= value;
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <inheritdoc/>
public int Count => _styles.Count;
/// <inheritdoc/>
public bool HasResources => _resources?.Count > 0 || this.Any(x => x.HasResources);
@ -94,6 +106,19 @@ namespace Avalonia.Styling
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
bool ICollection<IStyle>.IsReadOnly => false;
/// <inheritdoc/>
IStyle IReadOnlyList<IStyle>.this[int index] => _styles[index];
/// <inheritdoc/>
public IStyle this[int index]
{
get => _styles[index];
set => _styles[index] = value;
}
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// </summary>
@ -172,6 +197,54 @@ namespace Avalonia.Styling
return false;
}
/// <inheritdoc/>
public void AddRange(IEnumerable<IStyle> items) => _styles.AddRange(items);
/// <inheritdoc/>
public void InsertRange(int index, IEnumerable<IStyle> items) => _styles.InsertRange(index, items);
/// <inheritdoc/>
public void Move(int oldIndex, int newIndex) => _styles.Move(oldIndex, newIndex);
/// <inheritdoc/>
public void MoveRange(int oldIndex, int count, int newIndex) => _styles.MoveRange(oldIndex, count, newIndex);
/// <inheritdoc/>
public void RemoveAll(IEnumerable<IStyle> items) => _styles.RemoveAll(items);
/// <inheritdoc/>
public void RemoveRange(int index, int count) => _styles.RemoveRange(index, count);
/// <inheritdoc/>
public int IndexOf(IStyle item) => _styles.IndexOf(item);
/// <inheritdoc/>
public void Insert(int index, IStyle item) => _styles.Insert(index, item);
/// <inheritdoc/>
public void RemoveAt(int index) => _styles.RemoveAt(index);
/// <inheritdoc/>
public void Add(IStyle item) => _styles.Add(item);
/// <inheritdoc/>
public void Clear() => _styles.Clear();
/// <inheritdoc/>
public bool Contains(IStyle item) => _styles.Contains(item);
/// <inheritdoc/>
public void CopyTo(IStyle[] array, int arrayIndex) => _styles.CopyTo(array, arrayIndex);
/// <inheritdoc/>
public bool Remove(IStyle item) => _styles.Remove(item);
/// <inheritdoc/>
public IEnumerator<IStyle> GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
{

22
src/Avalonia.Themes.Default/ContextMenu.xaml

@ -10,12 +10,22 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
KeyboardNavigation.TabNavigation="Continue"/>
<ScrollViewer>
<Panel>
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
KeyboardNavigation.TabNavigation="Continue"/>
<Rectangle Name="iconSeparator"
Fill="{DynamicResource ThemeControlMidBrush}"
HorizontalAlignment="Left"
IsHitTestVisible="False"
Margin="29,2,0,2"
Width="1"/>
</Panel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Style>

2
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -109,6 +109,8 @@ namespace Avalonia.Media
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry)
{
Contract.Requires<ArgumentNullException>(geometry != null);
if (brush != null || PenIsVisible(pen))
{
PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl);

7
src/Avalonia.Visuals/Media/GeometryDrawing.cs

@ -31,7 +31,10 @@
public override void Draw(DrawingContext context)
{
context.DrawGeometry(Brush, Pen, Geometry);
if (Geometry != null)
{
context.DrawGeometry(Brush, Pen, Geometry);
}
}
public override Rect GetBounds()
@ -41,4 +44,4 @@
return Geometry?.GetRenderBounds(pen) ?? new Rect();
}
}
}
}

11
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@ -89,9 +89,14 @@ namespace Avalonia.Rendering.SceneGraph
/// <inheritdoc/>
public override bool HitTest(Point p)
{
p *= Transform.Invert();
return (Brush != null && Geometry.FillContains(p)) ||
(Pen != null && Geometry.StrokeContains(Pen, p));
if (Transform.HasInverse)
{
p *= Transform.Invert();
return (Brush != null && Geometry.FillContains(p)) ||
(Pen != null && Geometry.StrokeContains(Pen, p));
}
return false;
}
}
}

37
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -322,36 +322,51 @@ namespace Avalonia.Rendering.SceneGraph
}
}
/// <summary>
/// Ensures that this node draw operations have been created and are mutable (in case we are using cloned operations).
/// </summary>
private void EnsureDrawOperationsCreated()
{
if (_drawOperations == null)
{
_drawOperations = new List<IRef<IDrawOperation>>();
_drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
_drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
_drawOperationsCloned = false;
}
else if (_drawOperationsCloned)
{
_drawOperations = new List<IRef<IDrawOperation>>(_drawOperations.Select(op => op.Clone()));
_drawOperationsRefCounter.Dispose();
_drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
_drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
_drawOperationsCloned = false;
}
}
public bool Disposed { get; }
public void Dispose()
/// <summary>
/// Creates disposable that will dispose all items in passed draw operations after being disposed.
/// It is crucial that we don't capture current <see cref="VisualNode"/> instance
/// as draw operations can be cloned and may persist across subsequent scenes.
/// </summary>
/// <param name="drawOperations">Draw operations that need to be disposed.</param>
/// <returns>Disposable for given draw operations.</returns>
private static IDisposable CreateDisposeDrawOperations(List<IRef<IDrawOperation>> drawOperations)
{
_drawOperationsRefCounter?.Dispose();
return Disposable.Create(() =>
{
foreach (var operation in drawOperations)
{
operation.Dispose();
}
});
}
private void DisposeDrawOperations()
public bool Disposed { get; private set; }
public void Dispose()
{
foreach (var operation in DrawOperations)
{
operation.Dispose();
}
_drawOperationsRefCounter?.Dispose();
Disposed = true;
}
}
}

31
src/Avalonia.Visuals/Visual.cs

@ -8,6 +8,7 @@ using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.VisualTree;
@ -448,6 +449,34 @@ namespace Avalonia
RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
}
protected override sealed void LogBindingError(AvaloniaProperty property, Exception e)
{
// Don't log a binding error unless the control is attached to a logical or visual tree.
// In theory this should only need to check for logical tree attachment, but in practise
// due to ContentControlMixin only taking effect when the template has finished being
// applied, some controls are attached to the visual tree before the logical tree.
if (((ILogical)this).IsAttachedToLogicalTree || ((IVisual)this).IsAttachedToVisualTree)
{
if (e is BindingChainException b &&
string.IsNullOrEmpty(b.ExpressionErrorPoint) &&
DataContext == null)
{
// The error occurred at the root of the binding chain and DataContext is null;
// don't log this - the DataContext probably hasn't been set up yet.
return;
}
Logger.Log(
LogEventLevel.Warning,
LogArea.Binding,
this,
"Error in binding to {Target}.{Property}: {Message}",
this,
property,
e.Message);
}
}
/// <summary>
/// Gets the visual offset from the specified ancestor.
/// </summary>
@ -522,7 +551,7 @@ namespace Avalonia
{
if (c == null)
{
throw new ArgumentNullException("Cannot add null to VisualChildren.");
throw new ArgumentNullException(nameof(c), "Cannot add null to VisualChildren.");
}
if (c.VisualParent != null)

16
src/Avalonia.X11/X11Window.cs

@ -557,8 +557,14 @@ namespace Avalonia.X11
private bool _systemDecorations = true;
private bool _canResize = true;
private (Size minSize, Size maxSize) _scaledMinMaxSize;
private (PixelSize minSize, PixelSize maxSize) _minMaxSize;
private const int MaxWindowDimension = 100000;
private (Size minSize, Size maxSize) _scaledMinMaxSize =
(new Size(1, 1), new Size(double.PositiveInfinity, double.PositiveInfinity));
private (PixelSize minSize, PixelSize maxSize) _minMaxSize = (new PixelSize(1, 1),
new PixelSize(MaxWindowDimension, MaxWindowDimension));
private double _scaling = 1;
void ScheduleInput(RawInputEventArgs args, ref XEvent xev)
@ -874,10 +880,10 @@ namespace Avalonia.X11
(int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling),
(int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling));
const int maxDim = 100000;
const int maxDim = MaxWindowDimension;
var max = new PixelSize(
(int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, minSize.Width * Scaling)),
(int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, minSize.Height * Scaling)));
(int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)),
(int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling)));
_minMaxSize = (min, max);
UpdateSizeHints(null);

2
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@ -84,7 +84,7 @@ namespace Avalonia.Gtk3
public RenderOp(GtkWidget widget, ManagedCairoSurface surface, double factor, int width, int height)
{
_widget = widget;
_surface = surface ?? throw new ArgumentNullException();
_surface = surface ?? throw new ArgumentNullException(nameof(surface));
_factor = factor;
_width = width;
_height = height;

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -9,6 +9,7 @@
<ItemGroup>
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
<Compile Include="Converters\AvaloniaEventConverter.cs" />
<Compile Include="Converters\FontFamilyTypeConverter.cs" />
<Compile Include="Converters\MemberSelectorTypeConverter.cs" />
<Compile Include="Converters\NullableTypeConverter.cs" />

4
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@ -11,6 +11,7 @@ using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml
{
using System.Reflection;
using Avalonia.Media;
/// <summary>
@ -41,7 +42,8 @@ namespace Avalonia.Markup.Xaml
{ typeof(WindowIcon), typeof(IconTypeConverter) },
{ typeof(CultureInfo), typeof(CultureInfoConverter) },
{ typeof(Uri), typeof(AvaloniaUriTypeConverter) },
{ typeof(FontFamily), typeof(FontFamilyTypeConverter) }
{ typeof(FontFamily), typeof(FontFamilyTypeConverter) },
{ typeof(EventInfo), typeof(AvaloniaEventConverter) },
};
internal static Type GetBuiltinTypeConverter(Type type)

40
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -1,21 +1,20 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Platform;
using Portable.Xaml;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml.Linq;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Platform;
using Portable.Xaml;
namespace Avalonia.Markup.Xaml
{
@ -24,29 +23,7 @@ namespace Avalonia.Markup.Xaml
/// </summary>
public class AvaloniaXamlLoader
{
private readonly AvaloniaXamlSchemaContext _context = GetContext();
public bool IsDesignMode
{
get => _context.IsDesignMode;
set => _context.IsDesignMode = value;
}
private static AvaloniaXamlSchemaContext GetContext()
{
var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>();
if (result == null)
{
result = AvaloniaXamlSchemaContext.Create();
AvaloniaLocator.CurrentMutable
.Bind<AvaloniaXamlSchemaContext>()
.ToConstant(result);
}
return result;
}
public bool IsDesignMode { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
@ -188,7 +165,8 @@ namespace Avalonia.Markup.Xaml
LocalAssembly = localAssembly
};
var reader = new XamlXmlReader(stream, _context, readerSettings);
var context = IsDesignMode ? AvaloniaXamlSchemaContext.DesignInstance : AvaloniaXamlSchemaContext.Instance;
var reader = new XamlXmlReader(stream, context, readerSettings);
object result = LoadFromReader(
reader,

99
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs

@ -0,0 +1,99 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.PortableXaml;
using Portable.Xaml;
namespace Avalonia.Markup.Xaml.Converters
{
internal class AvaloniaEventConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var text = value as string;
if (text != null)
{
var rootObjectProvider = context.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
var destinationTypeProvider = context.GetService(typeof(IDestinationTypeProvider)) as IDestinationTypeProvider;
if (rootObjectProvider != null && destinationTypeProvider != null)
{
var target = rootObjectProvider.RootObject;
var eventType = destinationTypeProvider.GetDestinationType();
var eventParameters = eventType.GetRuntimeMethods().First(r => r.Name == "Invoke").GetParameters();
// go in reverse to match System.Xaml behaviour
var methods = target.GetType().GetRuntimeMethods().Reverse();
// find based on exact match parameter types first
foreach (var method in methods)
{
if (method.Name != text)
continue;
var parameters = method.GetParameters();
if (eventParameters.Length != parameters.Length)
continue;
if (parameters.Length == 0)
return method.CreateDelegate(eventType, target);
for (int i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
var eventParam = eventParameters[i];
if (param.ParameterType != eventParam.ParameterType)
break;
if (i == parameters.Length - 1)
return method.CreateDelegate(eventType, target);
}
}
// EnhancedXaml: Find method with compatible base class parameters
foreach (var method in methods)
{
if (method.Name != text)
continue;
var parameters = method.GetParameters();
if (parameters.Length == 0 || eventParameters.Length != parameters.Length)
continue;
for (int i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
var eventParam = eventParameters[i];
if (!param.ParameterType.GetTypeInfo().IsAssignableFrom(eventParam.ParameterType.GetTypeInfo()))
break;
if (i == parameters.Length - 1)
return method.CreateDelegate(eventType, target);
}
}
var contextProvider = (IXamlSchemaContextProvider)context.GetService(typeof(IXamlSchemaContextProvider));
var avaloniaContext = (AvaloniaXamlSchemaContext)contextProvider.SchemaContext;
if (avaloniaContext.IsDesignMode)
{
// We want to ignore missing events in the designer, so if event handler
// wasn't found create an empty delegate.
var lambdaExpression = Expression.Lambda(
eventType,
Expression.Empty(),
eventParameters.Select(x => Expression.Parameter(x.ParameterType)));
return lambdaExpression.Compile();
}
else
{
throw new XamlObjectWriterException($"Referenced value method {text} in type {target.GetType()} indicated by event {eventType.FullName} was not found");
}
}
}
return base.ConvertFrom(context, culture, value);
}
}
}

14
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs

@ -49,6 +49,18 @@ namespace Avalonia.Markup.Xaml.PortableXaml
//Portable.Xaml is not searching for Type Converter
result = new TypeConverterAttribute(typeof(SetterValueTypeConverter));
}
else if (attributeType == typeof(TypeConverterAttribute) && _info is EventInfo)
{
// If a type converter for `EventInfo` is registered, then use that to convert
// event handler values. This is used by the designer to override the lookup
// for event handlers with a null handler.
var eventConverter = AvaloniaTypeConverters.GetTypeConverter(typeof(EventInfo));
if (eventConverter != null)
{
result = new TypeConverterAttribute(eventConverter);
}
}
if (result == null)
{
@ -68,4 +80,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml
private readonly MemberInfo _info;
}
}
}

45
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@ -1,21 +1,48 @@
using Avalonia.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Context;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Avalonia.Markup.Xaml.PortableXaml
{
internal class AvaloniaXamlSchemaContext : XamlSchemaContext
{
public bool IsDesignMode { get; set; }
private static AvaloniaXamlSchemaContext s_instance;
private static AvaloniaXamlSchemaContext s_designInstance;
public static AvaloniaXamlSchemaContext Instance
{
get
{
if (s_instance == null)
{
s_instance = Create();
}
return s_instance;
}
}
public static AvaloniaXamlSchemaContext DesignInstance
{
get
{
if (s_designInstance == null)
{
s_designInstance = Create();
s_designInstance.IsDesignMode = true;
}
return s_designInstance;
}
}
public bool IsDesignMode { get; private set; }
public static AvaloniaXamlSchemaContext Create(IRuntimeTypeProvider typeProvider = null)
{
return new AvaloniaXamlSchemaContext(typeProvider ?? new AvaloniaRuntimeTypeProvider());

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@ -385,4 +385,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{
}
}
}
}

4
src/Markup/Avalonia.Markup/Data/RelativeSource.cs

@ -85,9 +85,9 @@ namespace Avalonia.Data
get { return _ancestorLevel; }
set
{
if (_ancestorLevel <= 0)
if (value <= 0)
{
throw new ArgumentOutOfRangeException("AncestorLevel may not be set to less than 1.");
throw new ArgumentOutOfRangeException(nameof(value), "AncestorLevel may not be set to less than 1.");
}
_ancestorLevel = value;

17
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@ -50,10 +50,10 @@ namespace Avalonia.Markup.Parsers
{
case SelectorGrammar.OfTypeSyntax ofType:
result = result.OfType(_typeResolver(ofType.Xmlns, ofType.TypeName));
result = result.OfType(Resolve(ofType.Xmlns, ofType.TypeName));
break;
case SelectorGrammar.IsSyntax @is:
result = result.Is(_typeResolver(@is.Xmlns, @is.TypeName));
result = result.Is(Resolve(@is.Xmlns, @is.TypeName));
break;
case SelectorGrammar.ClassSyntax @class:
result = result.Class(@class.Class);
@ -113,5 +113,18 @@ namespace Avalonia.Markup.Parsers
return result;
}
private Type Resolve(string xmlns, string typeName)
{
var result = _typeResolver(xmlns, typeName);
if (result == null)
{
var type = string.IsNullOrWhiteSpace(xmlns) ? typeName : xmlns + ':' + typeName;
throw new InvalidOperationException($"Could not resolve type '{type}'");
}
return result;
}
}
}

16
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using Xunit;
namespace Avalonia.Base.UnitTests
@ -115,6 +116,21 @@ namespace Avalonia.Base.UnitTests
Assert.True(raised);
}
[Fact]
public void PropertyChanged_Is_Raised_In_Parent_Before_Child()
{
var parent = new Class1();
var child = new Class2 { Parent = parent };
var result = new List<object>();
parent.PropertyChanged += (s, e) => result.Add(parent);
child.PropertyChanged += (s, e) => result.Add(child);
parent.SetValue(Class1.BazProperty, "changed");
Assert.Equal(new[] { parent, child }, result);
}
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =

2
tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs

@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests
target.Header = "Foo";
target.ApplyTemplate();
target.HeaderPresenter.UpdateChild();
((ContentPresenter)target.HeaderPresenter).UpdateChild();
var child = target.HeaderPresenter.Child;

68
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -267,6 +267,74 @@ namespace Avalonia.Controls.UnitTests
Assert.True(true);
}
[Fact]
public void LayoutManager_Should_Measure_Arrange_All()
{
var virtualizationMode = ItemVirtualizationMode.Simple;
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = new AvaloniaList<string>(Enumerable.Range(1, 7).Select(v => v.ToString()));
var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight };
wnd.IsVisible = true;
var target = new ListBox();
wnd.Content = target;
var lm = wnd.LayoutManager;
target.Height = 110;
target.Width = 50;
target.DataContext = items;
target.VirtualizationMode = virtualizationMode;
target.ItemTemplate = new FuncDataTemplate<object>(c =>
{
var tb = new TextBlock() { Height = 10, Width = 30 };
tb.Bind(TextBlock.TextProperty, new Data.Binding());
return tb;
}, true);
lm.ExecuteInitialLayoutPass(wnd);
target.Items = items;
lm.ExecuteLayoutPass();
items.Insert(3, "3+");
lm.ExecuteLayoutPass();
items.Insert(4, "4+");
lm.ExecuteLayoutPass();
//RESET
items.Clear();
foreach (var i in Enumerable.Range(1, 7))
{
items.Add(i.ToString());
}
//working bit better with this line no outof memory or remaining to arrange/measure ???
//lm.ExecuteLayoutPass();
items.Insert(2, "2+");
lm.ExecuteLayoutPass();
//after few more layout cycles layoutmanager shouldn't hold any more visual for measure/arrange
lm.ExecuteLayoutPass();
lm.ExecuteLayoutPass();
var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
var toMeasure = lm.GetType().GetField("_toMeasure", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
var toArrange = lm.GetType().GetField("_toArrange", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
Assert.Equal(0, toMeasure.Count());
Assert.Equal(0, toArrange.Count());
}
}
private FuncControlTemplate ListBoxTemplate()
{
return new FuncControlTemplate<ListBox>(parent =>

71
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Up_Opens_MenuItem_With_SubMenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true);
var e = new KeyEventArgs { Key = Key.Up, Source = item };
@ -27,7 +27,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Down_Opens_MenuItem_With_SubMenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true);
var e = new KeyEventArgs { Key = Key.Down, Source = item };
@ -41,7 +41,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Right_Selects_Next_MenuItem()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>(x => x.MoveSelection(NavigationDirection.Right, true) == true);
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu);
var e = new KeyEventArgs { Key = Key.Right, Source = item };
@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Left_Selects_Previous_MenuItem()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>(x => x.MoveSelection(NavigationDirection.Left, true) == true);
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu);
var e = new KeyEventArgs { Key = Key.Left, Source = item };
@ -69,7 +69,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Enter_On_Item_With_No_SubMenu_Causes_Click()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu);
var e = new KeyEventArgs { Key = Key.Enter, Source = item };
@ -84,7 +84,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Enter_On_Item_With_SubMenu_Opens_SubMenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var e = new KeyEventArgs { Key = Key.Enter, Source = item };
@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Escape_Closes_Parent_Menu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu);
var e = new KeyEventArgs { Key = Key.Escape, Source = item };
@ -113,7 +113,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerEnter_Opens_Item_When_Old_Item_Is_Open()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var item = Mock.Of<IMenuItem>(x =>
x.IsSubMenuOpen == true &&
@ -141,7 +141,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerLeave_Deselects_Item_When_Menu_Not_Open()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
@ -156,7 +156,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerLeave_Doesnt_Deselect_Item_When_Menu_Open()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
@ -175,7 +175,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Up_Selects_Previous_MenuItem()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new KeyEventArgs { Key = Key.Up, Source = item };
@ -189,7 +189,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Down_Selects_Next_MenuItem()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new KeyEventArgs { Key = Key.Down, Source = item };
@ -203,7 +203,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Left_Closes_Parent_SubMenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var parentItem = Mock.Of<IMenuItem>(x => x.HasSubMenu == true && x.IsSubMenuOpen == true);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new KeyEventArgs { Key = Key.Left, Source = item };
@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Right_With_SubMenu_Items_Opens_SubMenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var e = new KeyEventArgs { Key = Key.Right, Source = item };
@ -233,7 +233,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Right_On_TopLevel_Child_Navigates_TopLevel_Selection()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x =>
x.IsSubMenuOpen == true &&
@ -263,7 +263,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Enter_On_Item_With_No_SubMenu_Causes_Click()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
@ -279,7 +279,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Enter_On_Item_With_SubMenu_Opens_SubMenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var e = new KeyEventArgs { Key = Key.Enter, Source = item };
@ -294,7 +294,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void Escape_Closes_Parent_MenuItem()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new KeyEventArgs { Key = Key.Escape, Source = item };
@ -309,7 +309,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerEnter_Selects_Item()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
@ -325,7 +325,7 @@ namespace Avalonia.Controls.UnitTests.Platform
public void PointerEnter_Opens_Submenu_After_Delay()
{
var timer = new TestTimer();
var target = new DefaultMenuInteractionHandler(null, timer.RunOnce);
var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
@ -344,7 +344,7 @@ namespace Avalonia.Controls.UnitTests.Platform
public void PointerEnter_Closes_Sibling_Submenu_After_Delay()
{
var timer = new TestTimer();
var target = new DefaultMenuInteractionHandler(null, timer.RunOnce);
var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
@ -365,7 +365,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerLeave_Deselects_Item()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
@ -381,7 +381,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerLeave_Doesnt_Deselect_Sibling()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
@ -398,7 +398,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerLeave_Doesnt_Deselect_Item_If_Pointer_Over_Submenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true);
@ -413,7 +413,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerReleased_On_Item_With_No_SubMenu_Causes_Click()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
@ -430,7 +430,7 @@ namespace Avalonia.Controls.UnitTests.Platform
public void Selection_Is_Correct_When_Pointer_Temporarily_Exits_Item_To_Select_SubItem()
{
var timer = new TestTimer();
var target = new DefaultMenuInteractionHandler(null, timer.RunOnce);
var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
@ -467,7 +467,7 @@ namespace Avalonia.Controls.UnitTests.Platform
[Fact]
public void PointerPressed_On_Item_With_SubMenu_Causes_Opens_Submenu()
{
var target = new DefaultMenuInteractionHandler();
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
@ -481,6 +481,23 @@ namespace Avalonia.Controls.UnitTests.Platform
}
}
public class ContextMenu
{
[Fact]
public void Down_Selects_Selects_First_MenuItem_When_No_Selection()
{
var target = new DefaultMenuInteractionHandler(true);
var contextMenu = Mock.Of<IMenu>(x => x.MoveSelection(NavigationDirection.Down, true) == true);
var e = new KeyEventArgs { Key = Key.Down, Source = contextMenu };
target.Attach(contextMenu);
target.KeyDown(contextMenu, e);
Mock.Get(contextMenu).Verify(x => x.MoveSelection(NavigationDirection.Down, true));
Assert.True(e.Handled);
}
}
private class TestTimer
{
private Action _action;

28
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -4,6 +4,7 @@
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
@ -266,6 +267,31 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.IsType<Canvas>(target.Child);
}
[Fact]
public void Should_Not_Bind_Old_Child_To_New_DataContext()
{
// Test for issue #1099.
var textBlock = new TextBlock
{
[!TextBlock.TextProperty] = new Binding(),
};
var (target, host) = CreateTarget();
host.DataTemplates.Add(new FuncDataTemplate<string>(x => textBlock));
host.DataTemplates.Add(new FuncDataTemplate<int>(x => new Canvas()));
target.Content = "foo";
Assert.Same(textBlock, target.Child);
textBlock.PropertyChanged += (s, e) =>
{
Assert.NotEqual(e.NewValue, "42");
};
target.Content = 42;
}
(ContentPresenter presenter, ContentControl templatedParent) CreateTarget()
{
var templatedParent = new ContentControl
@ -288,4 +314,4 @@ namespace Avalonia.Controls.UnitTests.Presenters
public IControl Child { get; set; }
}
}
}
}

32
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -14,6 +14,7 @@ using System.Linq;
using Xunit;
using Avalonia.Rendering;
using Avalonia.Media;
using Avalonia.Data;
namespace Avalonia.Controls.UnitTests.Presenters
{
@ -204,7 +205,6 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.NotEqual(foo, logicalChildren.First());
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
@ -221,5 +221,35 @@ namespace Avalonia.Controls.UnitTests.Presenters
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
[Fact]
public void Should_Not_Bind_Old_Child_To_New_DataContext()
{
// Test for issue #1099.
var textBlock = new TextBlock
{
[!TextBlock.TextProperty] = new Binding(),
};
var target = new ContentPresenter()
{
DataTemplates =
{
new FuncDataTemplate<string>(x => textBlock),
new FuncDataTemplate<int>(x => new Canvas()),
},
};
var root = new TestRoot(target);
target.Content = "foo";
Assert.Same(textBlock, target.Child);
textBlock.PropertyChanged += (s, e) =>
{
Assert.NotEqual(e.NewValue, "42");
};
target.Content = 42;
}
}
}

16
tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs

@ -0,0 +1,16 @@
using Avalonia.Controls.Shapes;
using Xunit;
namespace Avalonia.Controls.UnitTests.Shapes
{
public class PathTests
{
[Fact]
public void Path_With_Null_Data_Does_Not_Throw_On_Measure()
{
var target = new Path();
target.Measure(Size.Infinity);
}
}
}

11
tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs

@ -9,6 +9,7 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Designer;
using Avalonia.Remote.Protocol.Viewport;
using Xunit;
@ -109,6 +110,16 @@ namespace Avalonia.DesignerSupport.Tests
return Guid.NewGuid().ToString();
if (t == typeof(Guid))
return Guid.NewGuid();
if (t == typeof(Exception))
return new Exception("Here");
if (t == typeof(ExceptionDetails))
return new ExceptionDetails
{
ExceptionType = "Exception",
LineNumber = 5,
LinePosition = 6,
Message = "Here",
};
throw new Exception($"Doesn't know how to fabricate a random value for {t}, path {pathInfo}");
}

119
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@ -1,11 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.UnitTests;
using System;
using Xunit;
using System.Collections.Generic;
namespace Avalonia.Layout.UnitTests
{
@ -74,7 +73,6 @@ namespace Avalonia.Layout.UnitTests
}
};
var order = new List<ILayoutable>();
Size MeasureOverride(ILayoutable control, Size size)
{
@ -110,7 +108,6 @@ namespace Avalonia.Layout.UnitTests
}
};
var order = new List<ILayoutable>();
Size MeasureOverride(ILayoutable control, Size size)
{
@ -196,9 +193,9 @@ namespace Avalonia.Layout.UnitTests
Width = 100,
Height = 100,
};
var arrangeSize = default(Size);
root.DoArrangeOverride = (_, s) =>
{
arrangeSize = s;
@ -207,7 +204,7 @@ namespace Avalonia.Layout.UnitTests
root.LayoutManager.ExecuteInitialLayoutPass(root);
Assert.Equal(new Size(100, 100), arrangeSize);
root.Width = 120;
root.LayoutManager.ExecuteLayoutPass();
@ -238,7 +235,111 @@ namespace Avalonia.Layout.UnitTests
border.Height = 100;
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(new Size(100, 100), panel.DesiredSize);
Assert.Equal(new Size(100, 100), panel.DesiredSize);
}
[Fact]
public void LayoutManager_Should_Prevent_Infinite_Loop_On_Measure()
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
control.Measured = false;
int cnt = 0;
int maxcnt = 100;
control.DoMeasureOverride = (l, s) =>
{
//emulate a problem in the logic of a control that triggers
//invalidate measure during measure
//it can lead to an infinite loop in layoutmanager
if (++cnt < maxcnt)
{
control.InvalidateMeasure();
}
return new Size(100, 100);
};
control.InvalidateMeasure();
root.LayoutManager.ExecuteLayoutPass();
Assert.True(cnt < 100);
}
[Fact]
public void LayoutManager_Should_Prevent_Infinite_Loop_On_Arrange()
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
control.Arranged = false;
int cnt = 0;
int maxcnt = 100;
control.DoArrangeOverride = (l, s) =>
{
//emulate a problem in the logic of a control that triggers
//invalidate measure during arrange
//it can lead to infinity loop in layoutmanager
if (++cnt < maxcnt)
{
control.InvalidateArrange();
}
return new Size(100, 100);
};
control.InvalidateArrange();
root.LayoutManager.ExecuteLayoutPass();
Assert.True(cnt < 100);
}
[Fact]
public void LayoutManager_Should_Properly_Arrange_Visuals_Even_When_There_Are_Issues_With_Previous_Arranged()
{
var nonArrageableTargets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
var targets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
StackPanel panel;
var root = new LayoutTestRoot
{
Child = panel = new StackPanel()
};
panel.Children.AddRange(nonArrageableTargets);
panel.Children.AddRange(targets);
root.LayoutManager.ExecuteInitialLayoutPass(root);
foreach (var c in panel.Children.OfType<LayoutTestControl>())
{
c.Measured = c.Arranged = false;
c.InvalidateMeasure();
}
foreach (var c in nonArrageableTargets)
{
c.DoArrangeOverride = (l, s) =>
{
//emulate a problem in the logic of a control that triggers
//invalidate measure during arrange
c.InvalidateMeasure();
return new Size(100, 100);
};
}
root.LayoutManager.ExecuteLayoutPass();
//altough nonArrageableTargets has rubbish logic and can't be measured/arranged properly
//layoutmanager should process properly other visuals
Assert.All(targets, c => Assert.True(c.Arranged));
}
}
}

196
tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs

@ -0,0 +1,196 @@
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Avalonia.Layout.UnitTests
{
public class LayoutQueueTests
{
[Fact]
public void Should_Enqueue()
{
var target = new LayoutQueue<string>(_ => true);
var refQueue = new Queue<string>();
var items = new[] { "1", "2", "3" };
foreach (var item in items)
{
target.Enqueue(item);
refQueue.Enqueue(item);
}
Assert.Equal(refQueue, target);
}
[Fact]
public void Should_Dequeue()
{
var target = new LayoutQueue<string>(_ => true);
var refQueue = new Queue<string>();
var items = new[] { "1", "2", "3" };
foreach (var item in items)
{
target.Enqueue(item);
refQueue.Enqueue(item);
}
while (refQueue.Count > 0)
{
Assert.Equal(refQueue.Dequeue(), target.Dequeue());
}
}
[Fact]
public void Should_Enqueue_UniqueElements()
{
var target = new LayoutQueue<string>(_ => true);
var items = new[] { "1", "2", "3", "1" };
foreach (var item in items)
{
target.Enqueue(item);
}
Assert.Equal(3, target.Count);
Assert.Equal(items.Take(3), target);
}
[Fact]
public void Shouldnt_Enqueue_More_Than_Limit_In_Loop()
{
var target = new LayoutQueue<string>(_ => true);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
}
[Fact]
public void Shouldnt_Count_Unique_Enqueue_For_Limit_In_Loop()
{
var target = new LayoutQueue<string>(_ => true);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
}
[Fact]
public void Should_Enqueue_When_Condition_True_After_Loop_When_Limit_Met()
{
var target = new LayoutQueue<string>(_ => true);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added to queue
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
target.EndLoop();
//after loop should be added once
Assert.Equal(1, target.Count);
Assert.Equal("Foo", target.First());
}
[Fact]
public void Shouldnt_Enqueue_When_Condition_False_After_Loop_When_Limit_Met()
{
var target = new LayoutQueue<string>(_ => false);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
target.EndLoop();
Assert.Equal(0, target.Count);
}
}
}

16
tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs

@ -1,8 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Parsers;
using Avalonia.Styling;
using Xunit;
namespace Avalonia.Markup.UnitTests.Parsers
@ -15,5 +13,19 @@ namespace Avalonia.Markup.UnitTests.Parsers
var target = new SelectorParser((ns, type) => typeof(TextBlock));
var result = target.Parse("TextBlock[IsPointerOver=True]");
}
[Fact]
public void Throws_If_OfType_Type_Not_Found()
{
var target = new SelectorParser((ns, type) => null);
Assert.Throws<InvalidOperationException>(() => target.Parse("NotFound"));
}
[Fact]
public void Throws_If_Is_Type_Not_Found()
{
var target = new SelectorParser((ns, type) => null);
Assert.Throws<InvalidOperationException>(() => target.Parse(":is(NotFound)"));
}
}
}

66
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs

@ -0,0 +1,66 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Portable.Xaml;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class EventTests
{
[Fact]
public void Event_Is_Attached()
{
var xaml = @"<Button xmlns='https://github.com/avaloniaui' Click='OnClick'/>";
var loader = new AvaloniaXamlLoader();
var target = new MyButton();
loader.Load(xaml, rootInstance: target);
RaiseClick(target);
Assert.True(target.Clicked);
}
[Fact]
public void Exception_Is_Thrown_If_Event_Not_Found()
{
var xaml = @"<Button xmlns='https://github.com/avaloniaui' Click='NotFound'/>";
var loader = new AvaloniaXamlLoader();
var target = new MyButton();
Assert.Throws<XamlObjectWriterException>(() => loader.Load(xaml, rootInstance: target));
}
[Fact]
public void Exception_Is_Not_Thrown_If_Event_Not_Found_In_Design_Mode()
{
var xaml = @"<Button xmlns='https://github.com/avaloniaui' Click='NotFound'/>";
var loader = new AvaloniaXamlLoader { IsDesignMode = true };
var target = new MyButton();
loader.Load(xaml, rootInstance: target);
}
private void RaiseClick(MyButton target)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = Button.KeyDownEvent,
Key = Key.Enter,
});
}
class MyButton : Button
{
public bool Clicked { get; private set; }
public void OnClick(object sender, EventArgs e)
{
Clicked = true;
}
}
}
}

1
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@ -89,6 +89,7 @@ namespace Avalonia.Styling.UnitTests
}
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;

1
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@ -119,6 +119,7 @@ namespace Avalonia.Styling.UnitTests
}
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;

1
tests/Avalonia.Styling.UnitTests/TestControlBase.cs

@ -19,6 +19,7 @@ namespace Avalonia.Styling.UnitTests
#pragma warning disable CS0067 // Event not used
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
#pragma warning restore CS0067
public string Name { get; set; }

1
tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs

@ -12,6 +12,7 @@ namespace Avalonia.Styling.UnitTests
public abstract class TestTemplatedControl : ITemplatedControl, IStyleable
{
public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
public abstract Classes Classes
{

5
tests/Avalonia.UnitTests/TestTemplatedRoot.cs

@ -18,7 +18,10 @@ namespace Avalonia.UnitTests
public TestTemplatedRoot()
{
Template = new FuncControlTemplate<TestTemplatedRoot>(x => new ContentPresenter());
Template = new FuncControlTemplate<TestTemplatedRoot>(x => new ContentPresenter
{
Name = "PART_ContentPresenter",
});
}
public event EventHandler<NameScopeEventArgs> Registered

13
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@ -61,6 +61,19 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.Equal(1, bitmap.RefCount);
}
[Fact]
public void HitTest_On_Geometry_Node_With_Zero_Transform_Does_Not_Throw()
{
var geometry = Mock.Of<IGeometryImpl>();
var geometryNode = new GeometryNode(
new Matrix(),
Brushes.Black,
null,
geometry);
geometryNode.HitTest(new Point());
}
private class TestDrawOperation : DrawOperation
{
public TestDrawOperation(Rect bounds, Matrix transform, Pen pen)

46
tests/Avalonia.Visuals.UnitTests/VisualTests.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;
@ -236,5 +237,50 @@ namespace Avalonia.Visuals.UnitTests
//child is centered (400 - 100*2 scale)/2
Assert.Equal(new Point(100, 100), point);
}
[Fact]
public void Should_Not_Log_Binding_Error_When_Not_Attached_To_Logical_Tree()
{
var target = new Decorator { DataContext = "foo" };
var called = false;
LogCallback checkLogMessage = (level, area, src, mt, pv) =>
{
if (level >= Logging.LogEventLevel.Warning)
{
called = true;
}
};
using (TestLogSink.Start(checkLogMessage))
{
target.Bind(Decorator.TagProperty, new Binding("Foo"));
}
Assert.False(called);
}
[Fact]
public void Should_Log_Binding_Error_When_Attached_To_Logical_Tree()
{
var target = new Decorator();
var root = new TestRoot { Child = target, DataContext = "foo" };
var called = false;
LogCallback checkLogMessage = (level, area, src, mt, pv) =>
{
if (level >= Logging.LogEventLevel.Warning)
{
called = true;
}
};
using (TestLogSink.Start(checkLogMessage))
{
target.Bind(Decorator.TagProperty, new Binding("Foo"));
}
Assert.True(called);
}
}
}

Loading…
Cancel
Save