Browse Source

Merge pull request #3 from AvaloniaUI/master

Merge
pull/1608/head
Benedikt Schroeder 8 years ago
committed by GitHub
parent
commit
0f242aed2a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      .github/PULL_REQUEST_TEMPLATE.md
  2. 12
      samples/BindingTest/MainWindow.xaml
  3. 4
      samples/ControlCatalog/DecoratedWindow.xaml
  4. 2
      samples/ControlCatalog/MainView.xaml
  5. 2
      samples/ControlCatalog/MainWindow.xaml
  6. 2
      samples/RenderTest/MainWindow.xaml
  7. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  8. 9
      src/Avalonia.Base/AttachedProperty.cs
  9. 50
      src/Avalonia.Base/AvaloniaObject.cs
  10. 8
      src/Avalonia.Base/AvaloniaProperty.cs
  11. 283
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  12. 3
      src/Avalonia.Base/DirectProperty.cs
  13. 7
      src/Avalonia.Base/IDirectPropertyAccessor.cs
  14. 16
      src/Avalonia.Base/Platform/IAssetLoader.cs
  15. 2
      src/Avalonia.Controls/ColumnDefinitions.cs
  16. 7
      src/Avalonia.Controls/ContextMenu.cs
  17. 14
      src/Avalonia.Controls/GridLength.cs
  18. 10
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  19. 5
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  20. 39
      src/Avalonia.Controls/Primitives/Popup.cs
  21. 26
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  22. 4
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  23. 2
      src/Avalonia.Controls/RowDefinitions.cs
  24. 14
      src/Avalonia.Controls/Window.cs
  25. 5
      src/Avalonia.Controls/WindowBase.cs
  26. 4
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  27. 2
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  28. 8
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  29. 8
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  30. 2
      src/Avalonia.Diagnostics/Views/TreePageView.xaml
  31. 12
      src/Avalonia.Input/DragDropDevice.cs
  32. 27
      src/Avalonia.Input/DragEventArgs.cs
  33. 6
      src/Avalonia.Themes.Default/MenuItem.xaml
  34. 40
      src/Avalonia.Visuals/CornerRadius.cs
  35. 5
      src/Avalonia.Visuals/Matrix.cs
  36. 5
      src/Avalonia.Visuals/Point.cs
  37. 5
      src/Avalonia.Visuals/Rect.cs
  38. 9
      src/Avalonia.Visuals/RelativePoint.cs
  39. 13
      src/Avalonia.Visuals/RelativeRect.cs
  40. 4
      src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs
  41. 5
      src/Avalonia.Visuals/Size.cs
  42. 24
      src/Avalonia.Visuals/Thickness.cs
  43. 31
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  44. 15
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  45. 2
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  46. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  47. 238
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  48. 227
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs
  49. 5
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  50. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs
  51. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs
  52. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs
  53. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs
  54. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs
  55. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs
  56. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs
  57. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs
  58. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs
  59. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs
  60. 7
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  61. 14
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
  62. 3
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  63. 22
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  64. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  65. 8
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  66. 3
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs
  67. 55
      src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
  68. 4
      src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
  69. 18
      src/OSX/Avalonia.MonoMac/WindowImpl.cs
  70. 23
      src/Shared/PlatformSupport/AssetLoader.cs
  71. 1
      src/Windows/Avalonia.Win32/DataObject.cs
  72. 53
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  73. 5
      src/Windows/Avalonia.Win32/OleDataObject.cs
  74. 1
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  75. 7
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  76. 24
      src/Windows/Avalonia.Win32/Win32Platform.cs
  77. 84
      src/Windows/Avalonia.Win32/WindowImpl.cs
  78. 4
      src/iOS/Avalonia.iOS/EmbeddableImpl.cs
  79. 52
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs
  80. 52
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
  81. 9
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  82. 4
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs
  83. 39
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
  84. 106
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
  85. 18
      tests/Avalonia.Controls.UnitTests/GridLengthTests.cs
  86. 18
      tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs
  87. 71
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
  88. 79
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  89. 14
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs
  90. 34
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  91. 66
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  92. 17
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs
  93. 5
      tests/Avalonia.UnitTests/MockAssetLoader.cs
  94. 8
      tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs
  95. 2
      tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs
  96. 2
      tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs
  97. 4
      tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs
  98. 6
      tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs
  99. 8
      tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs

17
.github/PULL_REQUEST_TEMPLATE.md

@ -0,0 +1,17 @@
This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible:
- What does the pull request do?
- What is the current behavior?
- What is the updated/expected behavior with this PR?
- How was the solution implemented (if it's not obvious)?
Checklist:
- [ ] Added unit tests (if possible)?
- [ ] Added XML documentation to any related classes?
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
If the pull request fixes issue(s) list them like this:
Fixes #123
Fixes #456

12
samples/BindingTest/MainWindow.xaml

@ -1,11 +1,15 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest"
xmlns:local="clr-namespace:BindingTest;assembly=BindingTest">
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:vm="clr-namespace:BindingTest.ViewModels"
xmlns:local="clr-namespace:BindingTest">
<Window.Styles>
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Styles>
<Window.Resources>
<vm:TestItem x:Key="SharedItem" StringValue="shared" />
</Window.Resources>
<TabControl>
<TabItem Header="Basic">
@ -40,6 +44,10 @@
<TextBlock FontSize="16" Text="Binding Sources"/>
<TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True"
Text="{Binding #first.Text, Mode=TwoWay}"/>
<TextBox Watermark="Value of SharedItem.StringValue" UseFloatingWatermark="True"
Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay}"/>
<TextBox Watermark="Value of SharedItem.StringValue (duplicate)" UseFloatingWatermark="True"
Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Margin="18" Gap="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Scheduler"/>

4
samples/ControlCatalog/DecoratedWindow.xaml

@ -1,7 +1,7 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Title="Avalonia Control Gallery"
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog" HasSystemDecorations="False">
Icon="resm:ControlCatalog.Assets.test_icon.ico"
xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False">
<Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
<DockPanel Grid.Column="1" Grid.Row="1" >
<Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">

2
samples/ControlCatalog/MainView.xaml

@ -1,5 +1,5 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:pages="clr-namespace:ControlCatalog.Pages;assembly=ControlCatalog"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Classes="sidebar" Name="Sidebar">
<TabControl.Transition>

2
samples/ControlCatalog/MainWindow.xaml

@ -1,6 +1,6 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Title="Avalonia Control Gallery"
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog">
xmlns:local="clr-namespace:ControlCatalog">
<local:MainView/>
</Window>

2
samples/RenderTest/MainWindow.xaml

@ -1,6 +1,6 @@
<Window xmlns="https://github.com/avaloniaui"
Title="Avalonia Render Test"
xmlns:pages="clr-namespace:RenderTest.Pages;assembly=RenderTest">
xmlns:pages="clr-namespace:RenderTest.Pages">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Rendering">

6
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@ -36,7 +36,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_clientSize = value;
UpdateParams();
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IScreenImpl Screen { get; }
public Point Position

9
src/Avalonia.Base/AttachedProperty.cs

@ -9,7 +9,7 @@ namespace Avalonia
/// An attached avalonia property.
/// </summary>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
public class AttachedProperty<TValue> : StyledPropertyBase<TValue>
public class AttachedProperty<TValue> : StyledProperty<TValue>
{
/// <summary>
/// Initializes a new instance of the <see cref="AttachedProperty{TValue}"/> class.
@ -35,11 +35,10 @@ namespace Avalonia
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <returns>The property.</returns>
public StyledProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
{
var result = new StyledProperty<TValue>(this, typeof(TOwner));
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
return this;
}
}
}

50
src/Avalonia.Base/AvaloniaObject.cs

@ -12,7 +12,6 @@ using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.Utilities;
using System.Reactive.Concurrency;
namespace Avalonia
{
@ -218,11 +217,6 @@ namespace Avalonia
}
else
{
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
return GetValueInternal(property);
}
}
@ -377,11 +371,6 @@ namespace Avalonia
{
PriorityValue v;
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
@ -804,11 +793,6 @@ namespace Avalonia
var originalValue = value;
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value))
{
throw new ArgumentException(string.Format(
@ -836,18 +820,32 @@ namespace Avalonia
}
/// <summary>
/// Given a <see cref="AvaloniaProperty"/> returns a registered avalonia property that is
/// equal or throws if not found.
/// Given a direct property, returns a registered avalonia property that is equivalent or
/// throws if not found.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The registered property.</returns>
public AvaloniaProperty GetRegistered(AvaloniaProperty property)
private AvaloniaProperty GetRegistered(AvaloniaProperty property)
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property);
var direct = property as IDirectPropertyAccessor;
if (direct == null)
{
throw new AvaloniaInternalException(
"AvaloniaObject.GetRegistered should only be called for direct properties");
}
if (property.OwnerType.IsAssignableFrom(GetType()))
{
return property;
}
var result = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
.FirstOrDefault(x => x == property);
if (result == null)
{
ThrowNotRegistered(property);
throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
}
return result;
@ -898,15 +896,5 @@ namespace Avalonia
value,
priority);
}
/// <summary>
/// Throws an exception indicating that the specified property is not registered on this
/// object.
/// </summary>
/// <param name="p">The property</param>
private void ThrowNotRegistered(AvaloniaProperty p)
{
throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
}
}
}

8
src/Avalonia.Base/AvaloniaProperty.cs

@ -311,7 +311,9 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits);
AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(typeof(TOwner), result);
registry.RegisterAttached(typeof(THost), result);
return result;
}
@ -344,7 +346,9 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits);
AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(ownerType, result);
registry.RegisterAttached(typeof(THost), result);
return result;
}

283
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Avalonia
@ -14,23 +13,14 @@ namespace Avalonia
/// </summary>
public class AvaloniaPropertyRegistry
{
/// <summary>
/// The registered properties by type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
/// <summary>
/// The registered properties by type cached values to increase performance.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registeredCache =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
/// <summary>
/// The registered attached properties by owner type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _registeredCache =
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
/// <summary>
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@ -39,51 +29,68 @@ namespace Avalonia
= new AvaloniaPropertyRegistry();
/// <summary>
/// Gets all attached <see cref="AvaloniaProperty"/>s registered by an owner.
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// <param name="ownerType">The owner type.</param>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetAttached(Type ownerType)
public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
{
Dictionary<int, AvaloniaProperty> inner;
Contract.Requires<ArgumentNullException>(type != null);
if (_registeredCache.TryGetValue(type, out var result))
{
return result;
}
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle);
var t = type;
result = new List<AvaloniaProperty>();
if (_attached.TryGetValue(ownerType, out inner))
while (t != null)
{
return inner.Values;
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(t.TypeHandle);
if (_registered.TryGetValue(t, out var registered))
{
result.AddRange(registered.Values);
}
t = t.BaseType;
}
return Enumerable.Empty<AvaloniaProperty>();
_registeredCache.Add(type, result);
return result;
}
/// <summary>
/// Gets all <see cref="AvaloniaProperty"/>s registered on a type.
/// Gets all attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
public IEnumerable<AvaloniaProperty> GetRegisteredAttached(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
if (_attachedCache.TryGetValue(type, out var result))
{
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
return result;
}
Dictionary<int, AvaloniaProperty> inner;
var t = type;
result = new List<AvaloniaProperty>();
if (_registered.TryGetValue(type, out inner))
while (t != null)
{
if (_attached.TryGetValue(t, out var attached))
{
foreach (var p in inner)
{
yield return p.Value;
}
result.AddRange(attached.Values);
}
type = type.GetTypeInfo().BaseType;
t = t.BaseType;
}
_attachedCache.Add(type, result);
return result;
}
/// <summary>
@ -99,142 +106,92 @@ namespace Avalonia
}
/// <summary>
/// Finds a <see cref="AvaloniaProperty"/> registered on a type.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property)
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegistered(Type type, string name)
{
Type currentType = type;
Dictionary<int, AvaloniaProperty> cache;
AvaloniaProperty result;
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(name != null);
if (_registeredCache.TryGetValue(type, out cache))
if (name.Contains('.'))
{
if (cache.TryGetValue(property.Id, out result))
{
return result;
}
throw new InvalidOperationException("Attached properties not supported.");
}
while (currentType != null)
{
Dictionary<int, AvaloniaProperty> inner;
if (_registered.TryGetValue(currentType, out inner))
{
if (inner.TryGetValue(property.Id, out result))
{
if (cache == null)
{
_registeredCache[type] = cache = new Dictionary<int, AvaloniaProperty>();
}
cache[property.Id] = result;
return result;
}
}
currentType = currentType.GetTypeInfo().BaseType;
}
return null;
return GetRegistered(type).FirstOrDefault(x => x.Name == name);
}
/// <summary>
/// Finds <see cref="AvaloniaProperty"/> registered on an object.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property)
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
{
return FindRegistered(o.GetType(), property);
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(name != null);
return FindRegistered(o.GetType(), name);
}
/// <summary>
/// Finds a registered property on a type by name.
/// Finds a registered attached property on a type by name.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="name">
/// The property name. If an attached property it should be in the form
/// "OwnerType.PropertyName".
/// </param>
/// <param name="ownerType">The owner type.</param>
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public AvaloniaProperty FindRegistered(Type type, string name)
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<ArgumentNullException>(name != null);
var parts = name.Split('.');
var types = GetImplementedTypes(type).ToList();
if (parts.Length < 1 || parts.Length > 2)
if (name.Contains('.'))
{
throw new ArgumentException("Invalid property name.");
throw new InvalidOperationException("Attached properties not supported.");
}
string propertyName;
var results = GetRegistered(type);
if (parts.Length == 1)
{
propertyName = parts[0];
results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name));
}
else
{
if (!types.Contains(parts[0]))
{
results = results.Where(x => x.OwnerType.Name == parts[0]);
}
propertyName = parts[1];
}
return results.FirstOrDefault(x => x.Name == propertyName);
return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name);
}
/// <summary>
/// Finds a registered property on an object by name.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="name">
/// The property name. If an attached property it should be in the form
/// "OwnerType.PropertyName".
/// </param>
/// <param name="ownerType">The owner type.</param>
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name)
{
return FindRegistered(o.GetType(), name);
}
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(name != null);
/// <summary>
/// Returns a type and all its base types.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The type and all its base types.</returns>
private IEnumerable<string> GetImplementedTypes(Type type)
{
while (type != null)
{
yield return type.Name;
type = type.GetTypeInfo().BaseType;
}
return FindRegisteredAttached(o.GetType(), ownerType, name);
}
/// <summary>
@ -245,7 +202,11 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(Type type, AvaloniaProperty property)
{
return FindRegistered(type, property) != null;
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
return Instance.GetRegistered(type).Any(x => x == property) ||
Instance.GetRegisteredAttached(type).Any(x => x == property);
}
/// <summary>
@ -256,6 +217,9 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(object o, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return IsRegistered(o.GetType(), property);
}
@ -274,34 +238,53 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
Dictionary<int, AvaloniaProperty> inner;
if (!_registered.TryGetValue(type, out inner))
if (!_registered.TryGetValue(type, out var inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
inner.Add(property.Id, property);
_registered.Add(type, inner);
}
if (!inner.ContainsKey(property.Id))
else if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
_registeredCache.Clear();
}
if (property.IsAttached)
/// <summary>
/// Registers an attached <see cref="AvaloniaProperty"/> on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <remarks>
/// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{THost, TValue, TValue})"/>
/// method.
/// </remarks>
public void RegisterAttached(Type type, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
if (!property.IsAttached)
{
if (!_attached.TryGetValue(property.OwnerType, out inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
_attached.Add(property.OwnerType, inner);
}
throw new InvalidOperationException(
"Cannot register a non-attached property as attached.");
}
if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
if (!_attached.TryGetValue(type, out var inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
inner.Add(property.Id, property);
_attached.Add(type, inner);
}
else
{
inner.Add(property.Id, property);
}
_registeredCache.Clear();
_attachedCache.Clear();
}
}
}

3
src/Avalonia.Base/DirectProperty.cs

@ -75,6 +75,9 @@ namespace Avalonia
/// </summary>
public Action<TOwner, TValue> Setter { get; }
/// <inheritdoc/>
Type IDirectPropertyAccessor.Owner => typeof(TOwner);
/// <summary>
/// Registers the direct property on another type.
/// </summary>

7
src/Avalonia.Base/IDirectPropertyAccessor.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;
namespace Avalonia
{
/// <summary>
@ -14,6 +16,11 @@ namespace Avalonia
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Gets the class that registered the property.
/// </summary>
Type Owner { get; }
/// <summary>
/// Gets the value of the property on the instance.
/// </summary>

16
src/Avalonia.Base/Platform/IAssetLoader.cs

@ -43,5 +43,21 @@ namespace Avalonia.Platform
/// The resource was not found.
/// </exception>
Stream Open(Uri uri, Uri baseUri = null);
/// <summary>
/// Opens the resource with the requested URI and returns the resource string and the
/// assembly containing the resource.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>
/// The stream containing the resource contents together with the assembly.
/// </returns>
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null);
}
}

2
src/Avalonia.Controls/ColumnDefinitions.cs

@ -27,7 +27,7 @@ namespace Avalonia.Controls
public ColumnDefinitions(string s)
: this()
{
AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new ColumnDefinition(x)));
AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x)));
}
}
}

7
src/Avalonia.Controls/ContextMenu.cs

@ -19,7 +19,7 @@ namespace Avalonia.Controls
{
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
}
/// <summary>
@ -75,13 +75,14 @@ namespace Avalonia.Controls
{
if (control != null)
{
if(_popup == null)
if (_popup == null)
{
_popup = new Popup()
{
PlacementMode = PlacementMode.Pointer,
PlacementTarget = control,
StaysOpen = false
StaysOpen = false,
ObeyScreenEdges = true
};
_popup.Closed += PopupClosed;

14
src/Avalonia.Controls/GridLength.cs

@ -180,9 +180,8 @@ namespace Avalonia.Controls
/// Parses a string to return a <see cref="GridLength"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="GridLength"/>.</returns>
public static GridLength Parse(string s, CultureInfo culture)
public static GridLength Parse(string s)
{
s = s.ToUpperInvariant();
@ -193,12 +192,12 @@ namespace Avalonia.Controls
else if (s.EndsWith("*"))
{
var valueString = s.Substring(0, s.Length - 1).Trim();
var value = valueString.Length > 0 ? double.Parse(valueString, culture) : 1;
var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1;
return new GridLength(value, GridUnitType.Star);
}
else
{
var value = double.Parse(s, culture);
var value = double.Parse(s, CultureInfo.InvariantCulture);
return new GridLength(value, GridUnitType.Pixel);
}
}
@ -207,15 +206,14 @@ namespace Avalonia.Controls
/// Parses a string to return a collection of <see cref="GridLength"/>s.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="GridLength"/>.</returns>
public static IEnumerable<GridLength> ParseLengths(string s, CultureInfo culture)
public static IEnumerable<GridLength> ParseLengths(string s)
{
using (var tokenizer = new StringTokenizer(s, culture))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture))
{
while (tokenizer.TryReadString(out var item))
{
yield return Parse(item, culture);
yield return Parse(item);
}
}
}

10
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -55,7 +55,7 @@ namespace Avalonia.Platform
/// Gets the platform window handle.
/// </summary>
IPlatformHandle Handle { get; }
/// <summary>
/// Gets the maximum size of a window on the system.
/// </summary>
@ -65,7 +65,13 @@ namespace Avalonia.Platform
/// Sets the client size of the toplevel.
/// </summary>
void Resize(Size clientSize);
/// <summary>
/// Minimum width of the window.
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
/// <summary>
/// Gets platform specific display information
/// </summary>

5
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -45,6 +45,11 @@ namespace Avalonia.Platform
/// </summary>
void ShowTaskbarIcon(bool value);
/// <summary>
/// Enables or disables resizing of the window
/// </summary>
void CanResize(bool value);
/// <summary>
/// Gets or sets a method called before the underlying implementation is destroyed.
/// Return true to prevent the underlying implementation from closing.

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

@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
/// </summary>
@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
/// when its opened at a position where it would otherwise overlap the screen edge.
/// </summary>
public bool ObeyScreenEdges
{
get => GetValue(ObeyScreenEdgesProperty);
set => SetValue(ObeyScreenEdgesProperty, value);
}
/// <summary>
/// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
@ -216,12 +232,12 @@ namespace Avalonia.Controls.Primitives
var window = _topLevel as Window;
if (window != null)
{
window.Deactivated += WindowDeactivated;
window.Deactivated += WindowDeactivated;
}
else
{
var parentPopuproot = _topLevel as PopupRoot;
if(parentPopuproot != null && parentPopuproot.Parent!=null)
if (parentPopuproot != null && parentPopuproot.Parent != null)
{
((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
}
@ -234,13 +250,18 @@ namespace Avalonia.Controls.Primitives
_popupRoot.Show();
if (ObeyScreenEdges)
{
_popupRoot.SnapInsideScreenEdges();
}
_ignoreIsOpenChanged = true;
IsOpen = true;
_ignoreIsOpenChanged = false;
Opened?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Closes the popup.
/// </summary>
@ -346,8 +367,10 @@ namespace Avalonia.Controls.Primitives
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual Point GetPosition()
{
return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
return result;
}
internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
@ -399,8 +422,8 @@ namespace Avalonia.Controls.Primitives
{
if (!StaysOpen)
{
if(!IsChildOrThis((IVisual)e.Source))
{
if (!IsChildOrThis((IVisual)e.Source))
{
Close();
e.Handled = true;
}
@ -412,12 +435,12 @@ namespace Avalonia.Controls.Primitives
IVisual root = child.GetVisualRoot();
while (root is PopupRoot)
{
if (root == PopupRoot) return true;
if (root == PopupRoot) return true;
root = ((PopupRoot)root).Parent.GetVisualRoot();
}
return false;
}
private void WindowDeactivated(object sender, EventArgs e)
{
if (!StaysOpen)

26
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -2,10 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/>
public void Dispose() => PlatformImpl?.Dispose();
/// <summary>
/// Moves the Popups position so that it doesnt overlap screen edges.
/// This method can be called immediately after Show has been called.
/// </summary>
public void SnapInsideScreenEdges()
{
var window = this.GetSelfAndLogicalAncestors().OfType<Window>().First();
var screen = window.Screens.ScreenFromPoint(Position);
var screenX = Position.X + Bounds.Width - screen.Bounds.X;
var screenY = Position.Y + Bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
}
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{

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

@ -207,7 +207,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="control">The control.</param>
/// <returns>The property value.</returns>
/// <see cref="SetIsTemplateFocusTarget(Control, bool)"/>
public bool GetIsTemplateFocusTarget(Control control)
public static bool GetIsTemplateFocusTarget(Control control)
{
return control.GetValue(IsTemplateFocusTargetProperty);
}
@ -223,7 +223,7 @@ namespace Avalonia.Controls.Primitives
/// attached property is set to true on an element in the control template, then the focus
/// adorner will be shown around that control instead.
/// </remarks>
public void SetIsTemplateFocusTarget(Control control, bool value)
public static void SetIsTemplateFocusTarget(Control control, bool value)
{
control.SetValue(IsTemplateFocusTargetProperty, value);
}

2
src/Avalonia.Controls/RowDefinitions.cs

@ -27,7 +27,7 @@ namespace Avalonia.Controls
public RowDefinitions(string s)
: this()
{
AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new RowDefinition(x)));
AddRange(GridLength.ParseLengths(s).Select(x => new RowDefinition(x)));
}
}
}

14
src/Avalonia.Controls/Window.cs

@ -95,6 +95,9 @@ namespace Avalonia.Controls
o => o.WindowStartupLocation,
(o, v) => o.WindowStartupLocation = v);
public static readonly StyledProperty<bool> CanResizeProperty =
AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
private readonly NameScope _nameScope = new NameScope();
private object _dialogResult;
private readonly Size _maxPlatformClientSize;
@ -113,6 +116,8 @@ namespace Avalonia.Controls
ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
CanResizeProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue));
}
/// <summary>
@ -208,6 +213,15 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Enables or disables resizing of the window
/// </summary>
public bool CanResize
{
get { return GetValue(CanResizeProperty); }
set { SetValue(CanResizeProperty, value); }
}
/// <summary>
/// Gets or sets the icon of the window.
/// </summary>

5
src/Avalonia.Controls/WindowBase.cs

@ -47,6 +47,11 @@ namespace Avalonia.Controls
{
IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
IsVisibleProperty.Changed.AddClassHandler<WindowBase>(x => x.IsVisibleChanged);
MinWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
MinHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
}
public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current)

4
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
@ -30,7 +31,8 @@ namespace Avalonia.DesignerSupport
new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath));
}
var loaded = loader.Load(stream, null, baseUri);
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)
{

2
src/Avalonia.DesignerSupport/DesignerAssist.cs

@ -24,7 +24,7 @@ namespace Avalonia.DesignerSupport
var loader = new AvaloniaXamlLoader();
var baseLight = (IStyle)loader.Load(
new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"), null);
Styles.Add(baseLight);
}
}

8
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -67,6 +67,10 @@ namespace Avalonia.DesignerSupport.Remote
RenderIfNeeded();
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IScreenImpl Screen { get; } = new ScreenStub();
public void Activate()
@ -93,5 +97,9 @@ namespace Avalonia.DesignerSupport.Remote
public void ShowTaskbarIcon(bool value)
{
}
public void CanResize(bool value)
{
}
}
}

8
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -78,6 +78,10 @@ namespace Avalonia.DesignerSupport.Remote
public IScreenImpl Screen { get; } = new ScreenStub();
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public void SetTitle(string title)
{
}
@ -95,6 +99,10 @@ namespace Avalonia.DesignerSupport.Remote
public void ShowTaskbarIcon(bool value)
{
}
public void CanResize(bool value)
{
}
}
class ClipboardStub : IClipboard

2
src/Avalonia.Diagnostics/Views/TreePageView.xaml

@ -1,5 +1,5 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels;assembly=Avalonia.Diagnostics">
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels">
<Grid ColumnDefinitions="*,4,3*">
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<TreeView.DataTemplates>

12
src/Avalonia.Input/DragDropDevice.cs

@ -19,11 +19,11 @@ namespace Avalonia.Input
return null;
}
private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data)
private DragDropEffects RaiseDragEvent(Interactive target, IInputElement inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data)
{
if (target == null)
return DragDropEffects.None;
var args = new DragEventArgs(routedEvent, data)
var args = new DragEventArgs(routedEvent, data, target, inputRoot.TranslatePoint(point, target))
{
RoutedEvent = routedEvent,
DragEffects = operation
@ -35,7 +35,7 @@ namespace Avalonia.Input
private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
{
_lastTarget = GetTarget(inputRoot, point);
return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data);
return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data);
}
private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
@ -43,13 +43,13 @@ namespace Avalonia.Input
var target = GetTarget(inputRoot, point);
if (target == _lastTarget)
return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data);
return RaiseDragEvent(target, inputRoot, point, DragDrop.DragOverEvent, effects, data);
try
{
if (_lastTarget != null)
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data);
return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data);
}
finally
{
@ -75,7 +75,7 @@ namespace Avalonia.Input
{
try
{
return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data);
return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DropEvent, effects, data);
}
finally
{

27
src/Avalonia.Input/DragEventArgs.cs

@ -1,17 +1,40 @@
using Avalonia.Interactivity;
using System;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class DragEventArgs : RoutedEventArgs
{
private Interactive _target;
private Point _targetLocation;
public DragDropEffects DragEffects { get; set; }
public IDataObject Data { get; private set; }
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data)
public Point GetPosition(IVisual relativeTo)
{
var point = new Point(0, 0);
if (relativeTo == null)
{
throw new ArgumentNullException(nameof(relativeTo));
}
if (_target != null)
{
point = _target.TranslatePoint(_targetLocation, relativeTo);
}
return point;
}
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation)
: base(routedEvent)
{
this.Data = data;
this._target = target;
this._targetLocation = targetLocation;
}
}

6
src/Avalonia.Themes.Default/MenuItem.xaml

@ -45,7 +45,8 @@
<Popup Name="PART_Popup"
PlacementMode="Right"
StaysOpen="True"
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}">
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
@ -92,7 +93,8 @@
</ContentPresenter>
<Popup Name="PART_Popup"
IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
StaysOpen="True">
StaysOpen="True"
ObeyScreenEdges="True">
<Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">

40
src/Avalonia.Visuals/CornerRadius.cs

@ -4,6 +4,7 @@
using System;
using System.Globalization;
using System.Linq;
using Avalonia.Utilities;
namespace Avalonia
{
@ -53,31 +54,26 @@ namespace Avalonia
return $"{TopLeft},{TopRight},{BottomRight},{BottomLeft}";
}
public static CornerRadius Parse(string s, CultureInfo culture)
public static CornerRadius Parse(string s)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
switch (parts.Count)
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
{
case 1:
var uniform = double.Parse(parts[0], culture);
return new CornerRadius(uniform);
case 2:
var top = double.Parse(parts[0], culture);
var bottom = double.Parse(parts[1], culture);
return new CornerRadius(top, bottom);
case 4:
var topLeft = double.Parse(parts[0], culture);
var topRight = double.Parse(parts[1], culture);
var bottomRight = double.Parse(parts[2], culture);
var bottomLeft = double.Parse(parts[3], culture);
return new CornerRadius(topLeft, topRight, bottomRight, bottomLeft);
default:
if (tokenizer.TryReadDouble(out var a))
{
if (tokenizer.TryReadDouble(out var b))
{
throw new FormatException("Invalid CornerRadius.");
if (tokenizer.TryReadDouble(out var c))
{
return new CornerRadius(a, b, c, tokenizer.ReadDouble());
}
return new CornerRadius(a, b);
}
return new CornerRadius(a);
}
throw new FormatException("Invalid CornerRadius.");
}
}
@ -85,7 +81,7 @@ namespace Avalonia
{
return cr1.TopLeft.Equals(cr2.TopLeft)
&& cr1.TopRight.Equals(cr2.TopRight)
&& cr1.BottomRight.Equals(cr2.BottomRight)
&& cr1.BottomRight.Equals(cr2.BottomRight)
&& cr1.BottomLeft.Equals(cr2.BottomLeft);
}

5
src/Avalonia.Visuals/Matrix.cs

@ -314,11 +314,10 @@ namespace Avalonia
/// Parses a <see cref="Matrix"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Matrix"/>.</returns>
public static Matrix Parse(string s, CultureInfo culture)
public static Matrix Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Matrix"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix"))
{
return new Matrix(
tokenizer.ReadDouble(),

5
src/Avalonia.Visuals/Point.cs

@ -170,11 +170,10 @@ namespace Avalonia
/// Parses a <see cref="Point"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Thickness"/>.</returns>
public static Point Parse(string s, CultureInfo culture)
public static Point Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Point"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point"))
{
return new Point(
tokenizer.ReadDouble(),

5
src/Avalonia.Visuals/Rect.cs

@ -487,11 +487,10 @@ namespace Avalonia
/// Parses a <see cref="Rect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="Rect"/>.</returns>
public static Rect Parse(string s, CultureInfo culture)
public static Rect Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Rect"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect"))
{
return new Rect(
tokenizer.ReadDouble(),

9
src/Avalonia.Visuals/RelativePoint.cs

@ -154,11 +154,10 @@ namespace Avalonia
/// Parses a <see cref="RelativePoint"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="RelativePoint"/>.</returns>
public static RelativePoint Parse(string s, CultureInfo culture)
public static RelativePoint Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativePoint"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint"))
{
var x = tokenizer.ReadString();
var y = tokenizer.ReadString();
@ -180,8 +179,8 @@ namespace Avalonia
}
return new RelativePoint(
double.Parse(x, culture) * scale,
double.Parse(y, culture) * scale,
double.Parse(x, CultureInfo.InvariantCulture) * scale,
double.Parse(y, CultureInfo.InvariantCulture) * scale,
unit);
}
}

13
src/Avalonia.Visuals/RelativeRect.cs

@ -167,11 +167,10 @@ namespace Avalonia
/// Parses a <see cref="RelativeRect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The parsed <see cref="RelativeRect"/>.</returns>
public static RelativeRect Parse(string s, CultureInfo culture)
public static RelativeRect Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativeRect"))
using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect"))
{
var x = tokenizer.ReadString();
var y = tokenizer.ReadString();
@ -202,10 +201,10 @@ namespace Avalonia
}
return new RelativeRect(
double.Parse(x, culture) * scale,
double.Parse(y, culture) * scale,
double.Parse(width, culture) * scale,
double.Parse(height, culture) * scale,
double.Parse(x, CultureInfo.InvariantCulture) * scale,
double.Parse(y, CultureInfo.InvariantCulture) * scale,
double.Parse(width, CultureInfo.InvariantCulture) * scale,
double.Parse(height, CultureInfo.InvariantCulture) * scale,
unit);
}
}

4
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs

@ -41,12 +41,12 @@ namespace Avalonia.Rendering
{
add
{
_tick += value;
if (_subscriberCount++ == 0)
{
Start();
}
_tick += value;
}
remove

5
src/Avalonia.Visuals/Size.cs

@ -150,11 +150,10 @@ namespace Avalonia
/// Parses a <see cref="Size"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Size"/>.</returns>
public static Size Parse(string s, CultureInfo culture)
public static Size Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Size"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size"))
{
return new Size(
tokenizer.ReadDouble(),

24
src/Avalonia.Visuals/Thickness.cs

@ -165,25 +165,27 @@ namespace Avalonia
/// Parses a <see cref="Thickness"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="Thickness"/>.</returns>
public static Thickness Parse(string s, CultureInfo culture)
public static Thickness Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Thickness"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
{
var a = tokenizer.ReadDouble();
if (tokenizer.TryReadDouble(out var b))
if(tokenizer.TryReadDouble(out var a))
{
if (tokenizer.TryReadDouble(out var c))
if (tokenizer.TryReadDouble(out var b))
{
return new Thickness(a, b, c, tokenizer.ReadDouble());
if (tokenizer.TryReadDouble(out var c))
{
return new Thickness(a, b, c, tokenizer.ReadDouble());
}
return new Thickness(a, b);
}
return new Thickness(a, b);
return new Thickness(a);
}
return new Thickness(a);
throw new FormatException("Invalid Thickness.");
}
}

31
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -115,6 +115,8 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_title(GtkWindow gtkWindow, Utf8Buffer title);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_resizable(GtkWindow gtkWindow, bool resizable);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_decorated(GtkWindow gtkWindow, bool decorated);
@ -263,7 +265,7 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_close(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
@ -395,6 +397,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_screen_get_monitor_geometry GdkScreenGetMonitorGeometry;
public static D.gdk_screen_get_monitor_workarea GdkScreenGetMonitorWorkarea;
public static D.gtk_window_set_decorated GtkWindowSetDecorated;
public static D.gtk_window_set_resizable GtkWindowSetResizable;
public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint;
public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint;
public static D.gtk_window_set_skip_pager_hint GtkWindowSetSkipPagerHint;
@ -421,6 +424,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_window_set_override_redirect GdkWindowSetOverrideRedirect;
public static D.gtk_widget_set_size_request GtkWindowSetSizeRequest;
public static D.gtk_window_set_default_size GtkWindowSetDefaultSize;
public static D.gtk_window_set_geometry_hints GtkWindowSetGeometryHints;
public static D.gtk_window_get_position GtkWindowGetPosition;
public static D.gtk_window_move GtkWindowMove;
public static D.gtk_file_chooser_dialog_new GtkFileChooserDialogNew;
@ -502,6 +506,7 @@ namespace Avalonia.Gtk3.Interop
public static D.cairo_set_font_size CairoSetFontSize;
public static D.cairo_move_to CairoMoveTo;
public static D.cairo_destroy CairoDestroy;
public const int G_TYPE_OBJECT = 80;
}
@ -739,19 +744,19 @@ namespace Avalonia.Gtk3.Interop
}
[StructLayout(LayoutKind.Sequential)]
struct GdkGeometry
public struct GdkGeometry
{
gint min_width;
gint min_height;
gint max_width;
gint max_height;
gint base_width;
gint base_height;
gint width_inc;
gint height_inc;
gdouble min_aspect;
gdouble max_aspect;
gint win_gravity;
public gint min_width;
public gint min_height;
public gint max_width;
public gint max_height;
public gint base_width;
public gint base_height;
public gint width_inc;
public gint height_inc;
public gdouble min_aspect;
public gdouble max_aspect;
public gint win_gravity;
}
enum GdkWindowHints

15
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -341,6 +341,20 @@ namespace Avalonia.Gtk3
}
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
if (GtkWidget.IsClosed)
return;
GdkGeometry geometry = new GdkGeometry();
geometry.min_width = minSize.Width > 0 ? (int)minSize.Width : -1;
geometry.min_height = minSize.Height > 0 ? (int)minSize.Height : -1;
geometry.max_width = !Double.IsInfinity(maxSize.Width) && maxSize.Width > 0 ? (int)maxSize.Width : 999999;
geometry.max_height = !Double.IsInfinity(maxSize.Height) && maxSize.Height > 0 ? (int)maxSize.Height : 999999;
Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE);
}
public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
@ -431,6 +445,7 @@ namespace Avalonia.Gtk3
{
if (GtkWidget.IsClosed)
return;
Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height);
if (OverrideRedirect)
{

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

@ -61,6 +61,8 @@ namespace Avalonia.Gtk3
}
public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value);
public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value);
class EmptyDisposable : IDisposable

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

@ -29,7 +29,6 @@
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AvaloniaXamlLoaderPortableXaml.cs" />
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\CornerRadiusTypeConverter.cs" />
<Compile Include="Converters\MatrixTypeConverter.cs" />

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

@ -1,11 +1,237 @@
namespace Avalonia.Markup.Xaml
// 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.Xaml.Data;
using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Platform;
using Portable.Xaml;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
namespace Avalonia.Markup.Xaml
{
public class AvaloniaXamlLoader : AvaloniaXamlLoaderPortableXaml
/// <summary>
/// Loads XAML for a avalonia application.
/// </summary>
public class AvaloniaXamlLoader
{
public static object Parse(string xaml)
=> new AvaloniaXamlLoader().Load(xaml);
private readonly AvaloniaXamlSchemaContext _context = GetContext();
private static AvaloniaXamlSchemaContext GetContext()
{
var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>();
if (result == null)
{
result = AvaloniaXamlSchemaContext.Create();
AvaloniaLocator.CurrentMutable
.Bind<AvaloniaXamlSchemaContext>()
.ToConstant(result);
}
return result;
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
/// </summary>
public AvaloniaXamlLoader()
{
}
/// <summary>
/// Loads the XAML into a Avalonia component.
/// </summary>
/// <param name="obj">The object to load the XAML into.</param>
public static void Load(object obj)
{
Contract.Requires<ArgumentNullException>(obj != null);
var loader = new AvaloniaXamlLoader();
loader.Load(obj.GetType(), obj);
}
/// <summary>
/// Loads the XAML for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(Type type, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(type != null);
// HACK: Currently Visual Studio is forcing us to change the extension of xaml files
// in certain situations, so we try to load .xaml and if that's not found we try .xaml.
// Ideally we'd be able to use .xaml everywhere
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
if (assetLocator == null)
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
foreach (var uri in GetUrisFor(type))
{
if (assetLocator.Exists(uri))
{
using (var stream = assetLocator.Open(uri))
{
var initialize = rootInstance as ISupportInitialize;
initialize?.BeginInit();
try
{
return Load(stream, type.Assembly, rootInstance, uri);
}
finally
{
initialize?.EndInit();
}
}
}
}
throw new FileNotFoundException("Unable to find view for " + type.FullName);
}
/// <summary>
/// Loads XAML from a URI.
/// </summary>
/// <param name="uri">The URI of the XAML file.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(Uri uri, Uri baseUri = null, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(uri != null);
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
if (assetLocator == null)
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
using (var stream = asset.Item1)
{
try
{
return Load(stream, asset.Item2, rootInstance, uri);
}
catch (Exception e)
{
var uriString = uri.ToString();
if (!uri.IsAbsoluteUri)
{
uriString = new Uri(baseUri, uri).AbsoluteUri;
}
throw new XamlLoadException("Error loading xaml at " + uriString, e);
}
}
}
/// <summary>
/// Loads XAML from a string.
/// </summary>
/// <param name="xaml">The string containing the XAML.</param>
/// <param name="localAssembly">Default assembly for clr-namespace:</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(xaml != null);
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
{
return Load(stream, localAssembly, rootInstance);
}
}
/// <summary>
/// Loads XAML from a stream.
/// </summary>
/// <param name="stream">The stream containing the XAML.</param>
/// <param name="localAssembly">Default assembly for clr-namespace</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <param name="uri">The URI of the XAML</param>
/// <returns>The loaded object.</returns>
public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null)
{
var readerSettings = new XamlXmlReaderSettings()
{
BaseUri = uri,
LocalAssembly = localAssembly
};
var reader = new XamlXmlReader(stream, _context, readerSettings);
object result = LoadFromReader(
reader,
AvaloniaXamlContext.For(readerSettings, rootInstance));
var topLevel = result as TopLevel;
if (topLevel != null)
{
DelayedBinding.ApplyBindings(topLevel);
}
return result;
}
internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null)
{
var writer = AvaloniaXamlObjectWriter.Create(
reader.SchemaContext,
context,
parentAmbientProvider);
XamlServices.Transform(reader, writer);
writer.ApplyAllDelayedProperties();
return writer.Result;
}
internal static object LoadFromReader(XamlReader reader)
{
//return XamlServices.Load(reader);
return LoadFromReader(reader, null);
}
/// <summary>
/// Gets the URI for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The URI.</returns>
private static IEnumerable<Uri> GetUrisFor(Type type)
{
var asm = type.GetTypeInfo().Assembly.GetName().Name;
var typeName = type.FullName;
yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
}
public static object Parse(string xaml, Assembly localAssembly = null)
=> new AvaloniaXamlLoader().Load(xaml, localAssembly);
public static T Parse<T>(string xaml)
=> (T)Parse(xaml);
public static T Parse<T>(string xaml, Assembly localAssembly = null)
=> (T)Parse(xaml, localAssembly);
}
}

227
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs

@ -1,227 +0,0 @@
// 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.Xaml.Data;
using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Platform;
using Portable.Xaml;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
namespace Avalonia.Markup.Xaml
{
/// <summary>
/// Loads XAML for a avalonia application.
/// </summary>
public class AvaloniaXamlLoaderPortableXaml
{
private readonly AvaloniaXamlSchemaContext _context = GetContext();
private static AvaloniaXamlSchemaContext GetContext()
{
var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>();
if (result == null)
{
result = AvaloniaXamlSchemaContext.Create();
AvaloniaLocator.CurrentMutable
.Bind<AvaloniaXamlSchemaContext>()
.ToConstant(result);
}
return result;
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaXamlLoader"/> class.
/// </summary>
public AvaloniaXamlLoaderPortableXaml()
{
}
/// <summary>
/// Loads the XAML into a Avalonia component.
/// </summary>
/// <param name="obj">The object to load the XAML into.</param>
public static void Load(object obj)
{
Contract.Requires<ArgumentNullException>(obj != null);
var loader = new AvaloniaXamlLoader();
loader.Load(obj.GetType(), obj);
}
/// <summary>
/// Loads the XAML for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(Type type, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(type != null);
// HACK: Currently Visual Studio is forcing us to change the extension of xaml files
// in certain situations, so we try to load .xaml and if that's not found we try .xaml.
// Ideally we'd be able to use .xaml everywhere
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
if (assetLocator == null)
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
foreach (var uri in GetUrisFor(type))
{
if (assetLocator.Exists(uri))
{
using (var stream = assetLocator.Open(uri))
{
var initialize = rootInstance as ISupportInitialize;
initialize?.BeginInit();
try
{
return Load(stream, rootInstance, uri);
}
finally
{
initialize?.EndInit();
}
}
}
}
throw new FileNotFoundException("Unable to find view for " + type.FullName);
}
/// <summary>
/// Loads XAML from a URI.
/// </summary>
/// <param name="uri">The URI of the XAML file.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(Uri uri, Uri baseUri = null, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(uri != null);
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
if (assetLocator == null)
{
throw new InvalidOperationException(
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
using (var stream = assetLocator.Open(uri, baseUri))
{
try
{
return Load(stream, rootInstance, uri);
}
catch (Exception e)
{
var uriString = uri.ToString();
if (!uri.IsAbsoluteUri)
{
uriString = new Uri(baseUri, uri).AbsoluteUri;
}
throw new XamlLoadException("Error loading xaml at " + uriString, e);
}
}
}
/// <summary>
/// Loads XAML from a string.
/// </summary>
/// <param name="xaml">The string containing the XAML.</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <returns>The loaded object.</returns>
public object Load(string xaml, object rootInstance = null)
{
Contract.Requires<ArgumentNullException>(xaml != null);
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
{
return Load(stream, rootInstance);
}
}
/// <summary>
/// Loads XAML from a stream.
/// </summary>
/// <param name="stream">The stream containing the XAML.</param>
/// <param name="rootInstance">
/// The optional instance into which the XAML should be loaded.
/// </param>
/// <param name="uri">The URI of the XAML</param>
/// <returns>The loaded object.</returns>
public object Load(Stream stream, object rootInstance = null, Uri uri = null)
{
var readerSettings = new XamlXmlReaderSettings()
{
BaseUri = uri,
LocalAssembly = rootInstance?.GetType().GetTypeInfo().Assembly
};
var reader = new XamlXmlReader(stream, _context, readerSettings);
object result = LoadFromReader(
reader,
AvaloniaXamlContext.For(readerSettings, rootInstance));
var topLevel = result as TopLevel;
if (topLevel != null)
{
DelayedBinding.ApplyBindings(topLevel);
}
return result;
}
internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null)
{
var writer = AvaloniaXamlObjectWriter.Create(
reader.SchemaContext,
context);
XamlServices.Transform(reader, writer);
writer.ApplyAllDelayedProperties();
return writer.Result;
}
internal static object LoadFromReader(XamlReader reader)
{
//return XamlServices.Load(reader);
return LoadFromReader(reader, null);
}
/// <summary>
/// Gets the URI for a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The URI.</returns>
private static IEnumerable<Uri> GetUrisFor(Type type)
{
var asm = type.GetTypeInfo().Assembly.GetName().Name;
var typeName = type.FullName;
yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);
}
}
}

5
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@ -53,10 +53,7 @@ namespace Avalonia.Markup.Xaml.Converters
}
}
// First look for non-attached property on the type and then look for an attached property.
var property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, s) ??
AvaloniaPropertyRegistry.Instance.GetAttached(type)
.FirstOrDefault(x => x.Name == propertyName);
AvaloniaProperty property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, propertyName);
if (property == null)
{

2
src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs

@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return CornerRadius.Parse((string)value, culture);
return CornerRadius.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs

@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return GridLength.Parse((string)value, culture);
return GridLength.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Matrix.Parse((string)value, culture);
return Matrix.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Point.Parse((string)value, culture);
return Point.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs

@ -23,7 +23,7 @@ namespace Avalonia.Markup.Xaml.Converters
var result = new List<Point>(pointStrs.Length);
foreach (var pointStr in pointStrs)
{
result.Add(Point.Parse(pointStr, culture));
result.Add(Point.Parse(pointStr));
}
return result;

2
src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Rect.Parse((string)value, culture);
return Rect.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return RelativePoint.Parse((string)value, culture);
return RelativePoint.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return RelativeRect.Parse((string)value, culture);
return RelativeRect.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Size.Parse((string)value, culture);
return Size.Parse((string)value);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Thickness.Parse((string)value, culture);
return Thickness.Parse((string)value);
}
}
}

7
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -36,13 +36,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
// Look upwards though the ambient context for IResourceProviders which might be able
// to give us the resource.
//
// TODO: If we're in a template then only the ambient values since the root of the
// template wil be included here. We need some way to get hold of the parent ambient
// context and search that. See the test:
//
// StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File
//
foreach (var ambientValue in ambientValues)
{
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back

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

@ -11,7 +11,8 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{
public static AvaloniaXamlObjectWriter Create(
XamlSchemaContext schemaContext,
AvaloniaXamlContext context)
AvaloniaXamlContext context,
IAmbientProvider parentAmbientProvider = null)
{
var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance };
@ -23,8 +24,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
};
return new AvaloniaXamlObjectWriter(schemaContext,
writerSettings.WithContext(context),
nameScope);
writerSettings.WithContext(context),
nameScope,
parentAmbientProvider);
}
private readonly DelayedValuesHelper _delayedValuesHelper = new DelayedValuesHelper();
@ -34,9 +36,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
private AvaloniaXamlObjectWriter(
XamlSchemaContext schemaContext,
XamlObjectWriterSettings settings,
AvaloniaNameScope nameScope
)
: base(schemaContext, settings)
AvaloniaNameScope nameScope,
IAmbientProvider parentAmbientProvider)
: base(schemaContext, settings, parentAmbientProvider)
{
_nameScope = nameScope;
}

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

@ -200,8 +200,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
var type = (getter ?? setter).DeclaringType;
var prop = AvaloniaPropertyRegistry.Instance.GetAttached(type)
.FirstOrDefault(v => v.Name == attachablePropertyName);
var prop = AvaloniaPropertyRegistry.Instance.FindRegistered(type, attachablePropertyName);
if (prop != null)
{

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

@ -19,16 +19,36 @@ namespace Avalonia.Markup.Xaml.PortableXaml
public class AvaloniaXamlType : XamlType
{
static readonly AvaloniaPropertyTypeConverter propertyTypeConverter = new AvaloniaPropertyTypeConverter();
public AvaloniaXamlType(Type underlyingType, XamlSchemaContext schemaContext) :
base(underlyingType, schemaContext)
{
}
protected override XamlMember LookupAttachableMember(string name)
{
var m = base.LookupAttachableMember(name);
if (m == null)
{
// Might be an AddOwnered attached property.
var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(UnderlyingType, name);
if (avProp?.IsAttached == true)
{
return new AvaloniaPropertyXamlMember(avProp, this);
}
}
return m;
}
protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
{
var m = base.LookupMember(name, skipReadOnlyCheck);
if (m == null)
if (m == null && !name.Contains("."))
{
//so far Portable.xaml haven't found the member/property
//but what if we have AvaloniaProperty

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit c0664014455392ac221a765e66f9837704339b6f
Subproject commit cdf46d7892def8a6ba29f12a9339147377f7cf5c

8
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -10,8 +10,10 @@ namespace Avalonia.Markup.Xaml.Templates
public class TemplateContent
{
public TemplateContent(IEnumerable<NamespaceDeclaration> namespaces, XamlReader reader)
public TemplateContent(IEnumerable<NamespaceDeclaration> namespaces, XamlReader reader,
IAmbientProvider ambientProvider)
{
ParentAmbientProvider = ambientProvider;
List = new XamlNodeList(reader.SchemaContext);
//we need to rpeserve all namespace and prefixes to writer
@ -26,9 +28,11 @@ namespace Avalonia.Markup.Xaml.Templates
public XamlNodeList List { get; }
private IAmbientProvider ParentAmbientProvider { get; }
public IControl Load()
{
return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader());
return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader(), parentAmbientProvider: ParentAmbientProvider);
}
public static IControl Load(object templateContent)

3
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs

@ -14,7 +14,8 @@ namespace Avalonia.Markup.Xaml.Templates
{
var tdc = (ITypeDescriptorContext)serviceProvider;
var ns = tdc.GetService<IXamlNamespaceResolver>();
return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader);
var ambientProvider = tdc.GetService<IAmbientProvider>();
return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader, ambientProvider);
}
public override XamlReader Save(object value, IServiceProvider serviceProvider)

55
src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Data;
@ -15,9 +16,9 @@ namespace Avalonia.Markup.Data.Plugins
/// <inheritdoc/>
public bool Match(object obj, string propertyName)
{
if (obj is AvaloniaObject a)
if (obj is AvaloniaObject o)
{
return AvaloniaPropertyRegistry.Instance.FindRegistered(a, propertyName) != null;
return LookupProperty(o, propertyName) != null;
}
return false;
@ -39,7 +40,7 @@ namespace Avalonia.Markup.Data.Plugins
var instance = reference.Target;
var o = (AvaloniaObject)instance;
var p = AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName);
var p = LookupProperty(o, propertyName);
if (p != null)
{
@ -57,6 +58,54 @@ namespace Avalonia.Markup.Data.Plugins
}
}
private static AvaloniaProperty LookupProperty(AvaloniaObject o, string propertyName)
{
if (!propertyName.Contains("."))
{
return AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName);
}
else
{
var split = propertyName.Split('.');
if (split.Length == 2)
{
// HACK: We need a way to resolve types here using something like IXamlTypeResolver.
// We don't currently have that so we have to make our best guess.
var type = split[0];
var name = split[1];
var registry = AvaloniaPropertyRegistry.Instance;
var registered = registry.GetRegisteredAttached(o.GetType())
.Concat(registry.GetRegistered(o.GetType()));
foreach (var p in registered)
{
if (p.Name == name && IsOfType(p.OwnerType, type))
{
return p;
}
}
}
}
return null;
}
private static bool IsOfType(Type type, string typeName)
{
while (type != null)
{
if (type.Name == typeName)
{
return true;
}
type = type.BaseType;
}
return false;
}
private class Accessor : PropertyAccessorBase
{
private readonly WeakReference<AvaloniaObject> _reference;

4
src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs

@ -161,6 +161,10 @@ namespace Avalonia.MonoMac
Position = pos;
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IScreenImpl Screen
{
get;

18
src/OSX/Avalonia.MonoMac/WindowImpl.cs

@ -9,6 +9,7 @@ namespace Avalonia.MonoMac
class WindowImpl : WindowBaseImpl, IWindowImpl
{
public bool IsDecorated = true;
public bool IsResizable = true;
public CGRect? UndecoratedLastUnmaximizedFrame;
public WindowImpl()
@ -76,10 +77,15 @@ namespace Avalonia.MonoMac
protected override NSWindowStyle GetStyle()
{
var windowStyle = NSWindowStyle.Borderless;
if (IsDecorated)
return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable |
NSWindowStyle.Titled;
return NSWindowStyle.Borderless;
windowStyle |= NSWindowStyle.Closable | NSWindowStyle.Miniaturizable | NSWindowStyle.Titled;
if (IsResizable)
windowStyle |= NSWindowStyle.Resizable;
return windowStyle;
}
public void SetSystemDecorations(bool enabled)
@ -88,6 +94,12 @@ namespace Avalonia.MonoMac
UpdateStyle();
}
public void CanResize(bool value)
{
IsResizable = value;
UpdateStyle();
}
public void SetTitle(string title) => Window.Title = title;
class ModalDisposable : IDisposable

23
src/Shared/PlatformSupport/AssetLoader.cs

@ -67,7 +67,23 @@ namespace Avalonia.Shared.PlatformSupport
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public Stream Open(Uri uri, Uri baseUri = null)
public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
/// <summary>
/// Opens the resource with the requested URI and returns the resource string and the
/// assembly containing the resource.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>
/// The stream containing the resource contents together with the assembly.
/// </returns>
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
public Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null)
{
var asset = GetAsset(uri, baseUri);
@ -76,7 +92,7 @@ namespace Avalonia.Shared.PlatformSupport
throw new FileNotFoundException($"The resource {uri} could not be found.");
}
return asset.GetStream();
return Tuple.Create(asset.GetStream(), asset.Assembly);
}
private IAssetDescriptor GetAsset(Uri uri, Uri baseUri)
@ -162,6 +178,7 @@ namespace Avalonia.Shared.PlatformSupport
private interface IAssetDescriptor
{
Stream GetStream();
Assembly Assembly { get; }
}
private class AssemblyResourceDescriptor : IAssetDescriptor
@ -179,6 +196,8 @@ namespace Avalonia.Shared.PlatformSupport
{
return _asm.GetManifestResourceStream(_name);
}
public Assembly Assembly => _asm;
}
private class AssemblyDescriptor

1
src/Windows/Avalonia.Win32/DataObject.cs

@ -7,7 +7,6 @@ using System.Text;
using Avalonia.Input;
using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject;
using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

53
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -558,7 +558,18 @@ namespace Avalonia.Win32.Interop
{
DIB_RGB_COLORS = 0, /* color table in RGBs */
DIB_PAL_COLORS /* color table in palette indices */
};
}
public enum WindowLongParam
{
GWL_WNDPROC = -4,
GWL_HINSTANCE = -6,
GWL_HWNDPARENT = -8,
GWL_ID = -12,
GWL_STYLE = -16,
GWL_EXSTYLE = -20,
GWL_USERDATA = -21
}
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
@ -615,6 +626,16 @@ namespace Avalonia.Win32.Interop
public uint[] cols;
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
}
public const int SizeOf_BITMAPINFOHEADER = 40;
[DllImport("user32.dll")]
@ -849,6 +870,9 @@ namespace Avalonia.Win32.Interop
return SetClassLong64(hWnd, nIndex, dwNewLong);
}
[DllImport("user32.dll", EntryPoint = "SetCursor")]
internal static extern IntPtr SetCursor(IntPtr hCursor);
[DllImport("ole32.dll", PreserveSig = true)]
internal static extern int CoCreateInstance(ref Guid clsid,
IntPtr ignore1, int ignore2, ref Guid iid, [MarshalAs(UnmanagedType.IUnknown), Out] out object pUnkOuter);
@ -974,7 +998,7 @@ namespace Avalonia.Win32.Interop
public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
public static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
@ -1366,13 +1390,13 @@ namespace Avalonia.Win32.Interop
internal interface IDropTarget
{
[PreserveSig]
UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
[PreserveSig]
UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
[PreserveSig]
UnmanagedMethods.HRESULT DragLeave();
[PreserveSig]
UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
}
[ComImport]
@ -1387,6 +1411,27 @@ namespace Avalonia.Win32.Interop
}
[ComImport]
[Guid("0000010E-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleDataObject
{
void GetData([In] ref FORMATETC format, out STGMEDIUM medium);
void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium);
[PreserveSig]
int QueryGetData([In] ref FORMATETC format);
[PreserveSig]
int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut);
void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release);
IEnumFORMATETC EnumFormatEtc(DATADIR direction);
[PreserveSig]
int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection);
void DUnadvise(int connection);
[PreserveSig]
int EnumDAdvise(out IEnumSTATDATA enumAdvise);
}
[StructLayoutAttribute(LayoutKind.Sequential)]
internal struct _DROPFILES
{

5
src/Windows/Avalonia.Win32/OleDataObject.cs

@ -8,15 +8,14 @@ using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Avalonia.Input;
using Avalonia.Win32.Interop;
using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
namespace Avalonia.Win32
{
class OleDataObject : Avalonia.Input.IDataObject
{
private IDataObject _wrapped;
private IOleDataObject _wrapped;
public OleDataObject(IDataObject wrapped)
public OleDataObject(IOleDataObject wrapped)
{
_wrapped = wrapped;
}

1
src/Windows/Avalonia.Win32/OleDropTarget.cs

@ -3,7 +3,6 @@ using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject;
using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
namespace Avalonia.Win32
{

7
src/Windows/Avalonia.Win32/ScreenImpl.cs

@ -2,16 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Utilities;
using static Avalonia.Win32.Interop.UnmanagedMethods;
#if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#endif
namespace Avalonia.Win32
{
public class ScreenImpl : IScreenImpl

24
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -1,27 +1,23 @@
// 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.Input.Platform;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.Threading;
using System.IO;
#if NETSTANDARD
using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
#else
using System.ComponentModel;
#endif
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
namespace Avalonia
{
@ -86,11 +82,11 @@ namespace Avalonia.Win32
.Bind<IWindowingPlatform>().ToConstant(s_instance)
.Bind<IPlatformIconLoader>().ToConstant(s_instance);
if (OleContext.Current != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
UseDeferredRendering = deferredRendering;
_uiThread = UnmanagedMethods.GetCurrentThreadId();
if (OleContext.Current != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
}
public bool HasMessages()

84
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -31,10 +31,14 @@ namespace Avalonia.Win32
private IInputRoot _owner;
private bool _trackingMouse;
private bool _decorated = true;
private bool _resizable = true;
private double _scaling = 1;
private WindowState _showWindowState;
private FramebufferManager _framebuffer;
private OleDropTarget _dropTarget;
private Size _minSize;
private Size _maxSize;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
#endif
@ -77,8 +81,8 @@ namespace Avalonia.Win32
{
get
{
var style = UnmanagedMethods.GetWindowLong(_hwnd, -16);
var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, -20);
var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
var padding = new UnmanagedMethods.RECT();
if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle))
@ -102,6 +106,12 @@ namespace Avalonia.Win32
}
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
_minSize = minSize;
_maxSize = maxSize;
}
public IScreenImpl Screen
{
get;
@ -235,13 +245,19 @@ namespace Avalonia.Win32
return;
}
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16);
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW;
var systemDecorationStyles = UnmanagedMethods.WindowStyles.WS_OVERLAPPED
| UnmanagedMethods.WindowStyles.WS_CAPTION
| UnmanagedMethods.WindowStyles.WS_SYSMENU
| UnmanagedMethods.WindowStyles.WS_MINIMIZEBOX
| UnmanagedMethods.WindowStyles.WS_MAXIMIZEBOX;
style |= systemDecorationStyles;
if (!value)
{
style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW;
style ^= systemDecorationStyles;
}
UnmanagedMethods.RECT windowRect;
@ -251,7 +267,7 @@ namespace Avalonia.Win32
Rect newRect;
var oldThickness = BorderThickness;
UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style);
UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style);
if (value)
{
@ -383,8 +399,11 @@ namespace Avalonia.Win32
public void SetCursor(IPlatformHandle cursor)
{
UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR,
cursor?.Handle ?? DefaultCursor);
var hCursor = cursor?.Handle ?? DefaultCursor;
UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, hCursor);
if (_owner.IsPointerOver)
UnmanagedMethods.SetCursor(hCursor);
}
protected virtual IntPtr CreateWindowOverride(ushort atom)
@ -611,7 +630,26 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_MOVE:
PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)));
return IntPtr.Zero;
case UnmanagedMethods.WindowsMessage.WM_GETMINMAXINFO:
MINMAXINFO mmi = Marshal.PtrToStructure<UnmanagedMethods.MINMAXINFO>(lParam);
if (_minSize.Width > 0)
mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
if (_minSize.Height > 0)
mmi.ptMinTrackSize.Y = (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
if (!Double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0)
mmi.ptMaxTrackSize.X = (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
if (!Double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0)
mmi.ptMaxTrackSize.Y = (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
Marshal.StructureToPtr(mmi, lParam, true);
return IntPtr.Zero;
case UnmanagedMethods.WindowsMessage.WM_DISPLAYCHANGE:
(Screen as ScreenImpl)?.InvalidateScreensCache();
return IntPtr.Zero;
@ -798,11 +836,11 @@ namespace Avalonia.Win32
public void ShowTaskbarIcon(bool value)
{
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -20);
style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE);
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW;
style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE);
style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW;
if (value)
style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW;
else
@ -813,9 +851,27 @@ namespace Avalonia.Win32
{
//Toggle to make the styles stick
UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide);
UnmanagedMethods.SetWindowLong(_hwnd, -20, (uint)style);
UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE, (uint)style);
UnmanagedMethods.ShowWindow(_hwnd, windowPlacement.ShowCmd);
}
}
public void CanResize(bool value)
{
if (value == _resizable)
{
return;
}
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
if (value)
style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME;
else
style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME);
UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style);
_resizable = value;
}
}
}

4
src/iOS/Avalonia.iOS/EmbeddableImpl.cs

@ -14,6 +14,10 @@ namespace Avalonia.iOS
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IDisposable ShowDialog()
{
return Disposable.Empty;

52
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs

@ -0,0 +1,52 @@
// 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 Xunit;
namespace Avalonia.Base.UnitTests
{
public class AvaloniaObjectTests_AddOwner
{
[Fact]
public void AddOwnered_Property_Retains_Default_Value()
{
var target = new Class2();
Assert.Equal("foodefault", target.GetValue(Class2.FooProperty));
}
[Fact]
public void AddOwnered_Property_Does_Not_Retain_Validation()
{
var target = new Class2();
target.SetValue(Class2.FooProperty, "throw");
}
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =
AvaloniaProperty.Register<Class1, string>(
"Foo",
"foodefault",
validate: ValidateFoo);
private static string ValidateFoo(AvaloniaObject arg1, string arg2)
{
if (arg2 == "throw")
{
throw new IndexOutOfRangeException();
}
return arg2;
}
}
private class Class2 : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =
Class1.FooProperty.AddOwner<Class2>();
}
}
}

52
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs

@ -0,0 +1,52 @@
// 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 Xunit;
namespace Avalonia.Base.UnitTests
{
public class AvaloniaObjectTests_Attached
{
[Fact]
public void AddOwnered_Property_Retains_Default_Value()
{
var target = new Class2();
Assert.Equal("foodefault", target.GetValue(Class2.FooProperty));
}
[Fact]
public void AddOwnered_Property_Retains_Validation()
{
var target = new Class2();
Assert.Throws<IndexOutOfRangeException>(() => target.SetValue(Class2.FooProperty, "throw"));
}
private class Class1 : AvaloniaObject
{
public static readonly AttachedProperty<string> FooProperty =
AvaloniaProperty.RegisterAttached<Class1, Class2, string>(
"Foo",
"foodefault",
validate: ValidateFoo);
private static string ValidateFoo(AvaloniaObject arg1, string arg2)
{
if (arg2 == "throw")
{
throw new IndexOutOfRangeException();
}
return arg2;
}
}
private class Class2 : AvaloniaObject
{
public static readonly AttachedProperty<string> FooProperty =
Class1.FooProperty.AddOwner<Class2>();
}
}
}

9
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -92,14 +92,13 @@ namespace Avalonia.Base.UnitTests
}
[Fact]
public void Bind_Throws_Exception_For_Unregistered_Property()
public void Bind_Does_Not_Throw_Exception_For_Unregistered_Property()
{
Class1 target = new Class1();
Assert.Throws<ArgumentException>(() =>
{
target.Bind(Class2.BarProperty, Observable.Return("foo"));
});
target.Bind(Class2.BarProperty, Observable.Never<string>().StartWith("foo"));
Assert.Equal("foo", target.GetValue(Class2.BarProperty));
}
[Fact]

4
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs

@ -46,11 +46,11 @@ namespace Avalonia.Base.UnitTests
}
[Fact]
public void GetValue_Throws_Exception_For_Unregistered_Property()
public void GetValue_Doesnt_Throw_Exception_For_Unregistered_Property()
{
var target = new Class3();
Assert.Throws<ArgumentException>(() => target.GetValue(Class1.FooProperty));
Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
}
private class Class1 : AvaloniaObject

39
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs

@ -30,6 +30,16 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("newvalue", target.GetValue(Class1.FooProperty));
}
[Fact]
public void SetValue_Sets_Attached_Value()
{
Class2 target = new Class2();
target.SetValue(AttachedOwner.AttachedProperty, "newvalue");
Assert.Equal("newvalue", target.GetValue(AttachedOwner.AttachedProperty));
}
[Fact]
public void SetValue_Raises_PropertyChanged()
{
@ -84,14 +94,27 @@ namespace Avalonia.Base.UnitTests
}
[Fact]
public void SetValue_Throws_Exception_For_Unregistered_Property()
public void SetValue_Allows_Setting_Unregistered_Property()
{
Class1 target = new Class1();
Assert.Throws<ArgumentException>(() =>
{
target.SetValue(Class2.BarProperty, "invalid");
});
Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, Class2.BarProperty));
target.SetValue(Class2.BarProperty, "bar");
Assert.Equal("bar", target.GetValue(Class2.BarProperty));
}
[Fact]
public void SetValue_Allows_Setting_Unregistered_Attached_Property()
{
Class1 target = new Class1();
Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, AttachedOwner.AttachedProperty));
target.SetValue(AttachedOwner.AttachedProperty, "bar");
Assert.Equal("bar", target.GetValue(AttachedOwner.AttachedProperty));
}
[Fact]
@ -189,6 +212,12 @@ namespace Avalonia.Base.UnitTests
}
}
private class AttachedOwner
{
public static readonly AttachedProperty<string> AttachedProperty =
AvaloniaProperty.RegisterAttached<AttachedOwner, Class2, string>("Attached");
}
private class ImplictDouble
{
public ImplictDouble(double value)

106
tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs

@ -4,12 +4,13 @@
using System.Linq;
using System.Reactive.Linq;
using Xunit;
using Xunit.Abstractions;
namespace Avalonia.Base.UnitTests
{
public class AvaloniaPropertyRegistryTests
{
public AvaloniaPropertyRegistryTests()
public AvaloniaPropertyRegistryTests(ITestOutputHelper s)
{
// Ensure properties are registered.
AvaloniaProperty p;
@ -25,7 +26,7 @@ namespace Avalonia.Base.UnitTests
.Select(x => x.Name)
.ToArray();
Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names);
Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names);
}
[Fact]
@ -35,61 +36,41 @@ namespace Avalonia.Base.UnitTests
.Select(x => x.Name)
.ToArray();
Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names);
Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux" }, names);
}
[Fact]
public void GetAttached_Returns_Registered_Properties_For_Base_Types()
public void GetRegisteredAttached_Returns_Registered_Properties()
{
string[] names = AvaloniaPropertyRegistry.Instance.GetAttached(typeof(AttachedOwner)).Select(x => x.Name).ToArray();
string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class1))
.Select(x => x.Name)
.ToArray();
Assert.Equal(new[] { "Attached" }, names);
}
[Fact]
public void FindRegistered_Finds_Untyped_Property()
public void GetRegisteredAttached_Returns_Registered_Properties_For_Base_Types()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo");
string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class2))
.Select(x => x.Name)
.ToArray();
Assert.Equal(Class1.FooProperty, result);
Assert.Equal(new[] { "Attached" }, names);
}
[Fact]
public void FindRegistered_Finds_Typed_Property()
public void FindRegistered_Finds_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Class1.Foo");
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo");
Assert.Equal(Class1.FooProperty, result);
}
[Fact]
public void FindRegistered_Finds_Typed_Inherited_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class1.Foo");
Assert.Equal(Class2.FooProperty, result);
}
[Fact]
public void FindRegistered_Finds_Inherited_Property_With_Derived_Type_Name()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class2.Foo");
Assert.Equal(Class2.FooProperty, result);
}
[Fact]
public void FindRegistered_Finds_Attached_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "AttachedOwner.Attached");
Assert.Equal(AttachedOwner.AttachedProperty, result);
}
[Fact]
public void FindRegistered_Doesnt_Finds_Unqualified_Attached_Property()
public void FindRegistered_Doesnt_Find_Nonregistered_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached");
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar");
Assert.Null(result);
}
@ -99,55 +80,34 @@ namespace Avalonia.Base.UnitTests
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(AttachedOwner), "Attached");
Assert.True(AttachedOwner.AttachedProperty == result);
Assert.Same(AttachedOwner.AttachedProperty, result);
}
[Fact]
public void FindRegistered_Finds_AddOwnered_Untyped_Attached_Property()
public void FindRegistered_Finds_AddOwnered_Attached_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Attached");
Assert.True(AttachedOwner.AttachedProperty == result);
Assert.Same(AttachedOwner.AttachedProperty, result);
}
[Fact]
public void FindRegistered_Finds_AddOwnered_Typed_Attached_Property()
public void FindRegistered_Doesnt_Find_Non_AddOwnered_Attached_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class3.Attached");
Assert.True(AttachedOwner.AttachedProperty == result);
}
[Fact]
public void FindRegistered_Finds_AddOwnered_AttachedTyped_Attached_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "AttachedOwner.Attached");
Assert.True(AttachedOwner.AttachedProperty == result);
}
[Fact]
public void FindRegistered_Finds_AddOwnered_BaseTyped_Attached_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class1.Attached");
Assert.True(AttachedOwner.AttachedProperty == result);
}
[Fact]
public void FindRegistered_Doesnt_Find_Nonregistered_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar");
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached");
Assert.Null(result);
}
[Fact]
public void FindRegistered_Doesnt_Find_Nonregistered_Attached_Property()
public void FindRegisteredAttached_Finds_Property()
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class4), "AttachedOwner.Attached");
var result = AvaloniaPropertyRegistry.Instance.FindRegisteredAttached(
typeof(Class1),
typeof(AttachedOwner),
"Attached");
Assert.Null(result);
Assert.Equal(AttachedOwner.AttachedProperty, result);
}
private class Class1 : AvaloniaObject
@ -176,18 +136,18 @@ namespace Avalonia.Base.UnitTests
private class Class3 : Class1
{
public static readonly StyledProperty<string> AttachedProperty =
public static readonly AttachedProperty<string> AttachedProperty =
AttachedOwner.AttachedProperty.AddOwner<Class3>();
}
public class Class4 : AvaloniaObject
{
}
private class AttachedOwner : Class1
{
public static readonly AttachedProperty<string> AttachedProperty =
AvaloniaProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
}
private class AttachedOwner2 : AttachedOwner
{
}
}
}

18
tests/Avalonia.Controls.UnitTests/GridLengthTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Parse_Should_Parse_Auto()
{
var result = GridLength.Parse("Auto", CultureInfo.InvariantCulture);
var result = GridLength.Parse("Auto");
Assert.Equal(GridLength.Auto, result);
}
@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Parse_Should_Parse_Auto_Lowercase()
{
var result = GridLength.Parse("auto", CultureInfo.InvariantCulture);
var result = GridLength.Parse("auto");
Assert.Equal(GridLength.Auto, result);
}
@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Parse_Should_Parse_Star()
{
var result = GridLength.Parse("*", CultureInfo.InvariantCulture);
var result = GridLength.Parse("*");
Assert.Equal(new GridLength(1, GridUnitType.Star), result);
}
@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Parse_Should_Parse_Star_Value()
{
var result = GridLength.Parse("2*", CultureInfo.InvariantCulture);
var result = GridLength.Parse("2*");
Assert.Equal(new GridLength(2, GridUnitType.Star), result);
}
@ -45,7 +45,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Parse_Should_Parse_Pixel_Value()
{
var result = GridLength.Parse("2", CultureInfo.InvariantCulture);
var result = GridLength.Parse("2");
Assert.Equal(new GridLength(2, GridUnitType.Pixel), result);
}
@ -53,13 +53,13 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Parse_Should_Throw_FormatException_For_Invalid_String()
{
Assert.Throws<FormatException>(() => GridLength.Parse("2x", CultureInfo.InvariantCulture));
Assert.Throws<FormatException>(() => GridLength.Parse("2x"));
}
[Fact]
public void ParseLengths_Accepts_Comma_Separators()
{
var result = GridLength.ParseLengths("*,Auto,2*,4", CultureInfo.InvariantCulture).ToList();
var result = GridLength.ParseLengths("*,Auto,2*,4").ToList();
Assert.Equal(
new[]
@ -75,7 +75,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void ParseLengths_Accepts_Space_Separators()
{
var result = GridLength.ParseLengths("* Auto 2* 4", CultureInfo.InvariantCulture).ToList();
var result = GridLength.ParseLengths("* Auto 2* 4").ToList();
Assert.Equal(
new[]
@ -91,7 +91,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void ParseLengths_Accepts_Comma_Separators_With_Spaces()
{
var result = GridLength.ParseLengths("*, Auto, 2* ,4", CultureInfo.InvariantCulture).ToList();
var result = GridLength.ParseLengths("*, Auto, 2* ,4").ToList();
Assert.Equal(
new[]

18
tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs

@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data
[Fact]
public async Task Should_Convert_Get_String_To_Double()
{
var data = new Class1 { StringValue = "5.6" };
var data = new Class1 { StringValue = $"{5.6}" };
var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
var result = await target.Take(1);
@ -94,12 +94,12 @@ namespace Avalonia.Markup.UnitTests.Data
[Fact]
public void Should_Convert_Set_String_To_Double()
{
var data = new Class1 { StringValue = (5.6).ToString() };
var data = new Class1 { StringValue = $"{5.6}" };
var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double));
target.OnNext(6.7);
Assert.Equal((6.7).ToString(), data.StringValue);
Assert.Equal($"{6.7}", data.StringValue);
GC.KeepAlive(data);
}
@ -111,7 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data
var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string));
var result = await target.Take(1);
Assert.Equal((5.6).ToString(), result);
Assert.Equal($"{5.6}", result);
GC.KeepAlive(data);
}
@ -122,7 +122,7 @@ namespace Avalonia.Markup.UnitTests.Data
var data = new Class1 { DoubleValue = 5.6 };
var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string));
target.OnNext("6.7");
target.OnNext($"{6.7}");
Assert.Equal(6.7, data.DoubleValue);
@ -318,15 +318,15 @@ namespace Avalonia.Markup.UnitTests.Data
target.Subscribe(x => result.Add(x));
target.OnNext(1.2);
target.OnNext("3.4");
target.OnNext($"{3.4}");
target.OnNext("bar");
Assert.Equal(
new[]
{
new BindingNotification("5.6"),
new BindingNotification("1.2"),
new BindingNotification("3.4"),
new BindingNotification($"{5.6}"),
new BindingNotification($"{1.2}"),
new BindingNotification($"{3.4}"),
new BindingNotification(
new InvalidCastException("'bar' is not a valid number."),
BindingErrorType.Error)

71
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
public class BindingExtensionTests
{
[Fact]
public void BindingExtension_Binds_To_Source()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<x:String x:Key='text'>foobar</x:String>
</Window.Resources>
<TextBlock Name='textBlock' Text='{Binding Source={StaticResource text}}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
window.Show();
Assert.Equal("foobar", textBlock.Text);
}
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(
assetLoader: new MockAssetLoader(assets),
theme: () => new Styles
{
WindowStyle(),
});
return UnitTestApplication.Start(services);
}
private Style WindowStyle()
{
return new Style(x => x.OfType<Window>())
{
Setters =
{
new Setter(
Window.TemplateProperty,
new FuncControlTemplate<Window>(x =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
}))
}
};
}
}
}

79
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -323,7 +323,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact(Skip = "Not yet supported by Portable.Xaml")]
[Fact]
public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File()
{
var styleXaml = @"
@ -361,9 +361,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var border = (Border)button.GetVisualChildren().Single();
var brush = (SolidColorBrush)border.Background;
// To make this work we somehow need to be able to get hold of the parent ambient
// context from Portable.Xaml. See TODO in StaticResourceExtension.
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
@ -417,6 +415,79 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_Binding_Converter_In_DataTemplate()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Window.Resources>
<local:TestValueConverter x:Key='converter' Append='bar'/>
<DataTemplate x:Key='PurpleData'>
<TextBlock Name='textBlock' Text='{Binding Converter={StaticResource converter}}'/>
</DataTemplate>
</Window.Resources>
<ContentPresenter Name='presenter' Content='foo' ContentTemplate='{StaticResource PurpleData}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
window.DataContext = "foo";
var presenter = window.FindControl<ContentPresenter>("presenter");
window.Show();
var textBlock = (TextBlock)presenter.GetVisualChildren().Single();
Assert.NotNull(textBlock);
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void StaticResource_Is_Correctly_Chosen_From_Within_DataTemplate()
{
// this tests if IAmbientProviders in DataTemplate contexts are in correct order
// if they wouldn't be, Purple brush would be bound to
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Window.Resources>
<local:TestValueConverter x:Key='converter' Append='-bar'/>
<SolidColorBrush x:Key='brush' Color='Purple'/>
<DataTemplate x:Key='WhiteData'>
<Border>
<Border.Resources>
<SolidColorBrush x:Key='brush' Color='White'/>
</Border.Resources>
<TextBlock Name='textBlock' Text='{Binding Color, Source={StaticResource brush}, Converter={StaticResource converter}}' Foreground='{StaticResource brush}' />
</Border>
</DataTemplate>
</Window.Resources>
<ContentPresenter Content='foo' ContentTemplate='{StaticResource WhiteData}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
window.Show();
var textBlock = window.GetVisualDescendants().OfType<TextBlock>().Single();
Assert.NotNull(textBlock);
Assert.Equal("White-bar", textBlock.Text);
}
}
[Fact]
public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed()
{

14
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs

@ -0,0 +1,14 @@
using System;
using Avalonia.Controls;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class AttachedPropertyOwner
{
public static readonly AttachedProperty<double> DoubleProperty =
AvaloniaProperty.RegisterAttached<AttachedPropertyOwner, Control, double>("Double");
public static double GetDouble(Control control) => control.GetValue(DoubleProperty);
public static void SetDouble(Control control, double value) => control.SetValue(DoubleProperty, value);
}
}

34
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -11,6 +11,7 @@ using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Portable.Xaml;
using System.Collections;
using System.ComponentModel;
using System.Linq;
@ -80,6 +81,21 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(21.0, TextBlock.GetFontSize(target));
}
[Fact]
public void Attached_Property_Is_Set_On_Control_Outside_Avalonia_Namspace()
{
// Test for issue #1548
var xaml =
@"<UserControl xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<local:TestControl Grid.Column='2' />
</UserControl>";
var target = AvaloniaXamlLoader.Parse<UserControl>(xaml);
Assert.Equal(2, Grid.GetColumn((TestControl)target.Content));
}
[Fact]
public void Attached_Property_With_Namespace_Is_Set()
{
@ -125,6 +141,24 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal("Foo", ToolTip.GetTip(target));
}
[Fact]
public void NonExistent_Property_Throws()
{
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' DoesntExist='foo'/>";
Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
}
[Fact]
public void Non_Attached_Property_With_Attached_Property_Syntax_Throws()
{
var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.Text='foo'/>";
Assert.Throws<XamlObjectWriterException>(() => AvaloniaXamlLoader.Parse<ContentControl>(xaml));
}
[Fact]
public void ContentControl_ContentTemplate_Is_Functional()
{

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

@ -215,5 +215,71 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal("bar", textBlock.Text);
}
}
[Fact]
public void Binding_To_Namespaced_Attached_Property_Works()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBlock local:AttachedPropertyOwner.Double='{Binding}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = (TextBlock)window.Content;
window.DataContext = 5.6;
window.ApplyTemplate();
Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock));
}
}
[Fact]
public void Binding_To_AddOwnered_Attached_Property_Works()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<local:TestControl Double='{Binding}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var testControl = (TestControl)window.Content;
window.DataContext = 5.6;
window.ApplyTemplate();
Assert.Equal(5.6, testControl.Double);
}
}
[Fact]
public void Binding_To_Attached_Property_Using_AddOwnered_Type_Works()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBlock local:TestControl.Double='{Binding}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = (TextBlock)window.Content;
window.DataContext = 5.6;
window.ApplyTemplate();
Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock));
}
}
}
}

17
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Controls;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class TestControl : Control
{
public static readonly StyledProperty<double> DoubleProperty =
AttachedPropertyOwner.DoubleProperty.AddOwner<TestControl>();
public double Double
{
get => GetValue(DoubleProperty);
set => SetValue(DoubleProperty, value);
}
}
}

5
tests/Avalonia.UnitTests/MockAssetLoader.cs

@ -27,6 +27,11 @@ namespace Avalonia.UnitTests
{
return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri]));
}
public Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null)
{
return Tuple.Create(Open(uri, baseUri), (Assembly)null);
}
public void SetDefaultAssembly(Assembly asm)
{

8
tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs

@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Parses_Single_Uniform_Radius()
{
var result = CornerRadius.Parse("3.4", CultureInfo.InvariantCulture);
var result = CornerRadius.Parse("3.4");
Assert.Equal(new CornerRadius(3.4), result);
}
@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Parses_Top_Bottom()
{
var result = CornerRadius.Parse("1.1,2.2", CultureInfo.InvariantCulture);
var result = CornerRadius.Parse("1.1,2.2");
Assert.Equal(new CornerRadius(1.1, 2.2), result);
}
@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Parses_TopLeft_TopRight_BottomRight_BottomLeft()
{
var result = CornerRadius.Parse("1.1,2.2,3.3,4.4", CultureInfo.InvariantCulture);
var result = CornerRadius.Parse("1.1,2.2,3.3,4.4");
Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result);
}
@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Accepts_Spaces()
{
var result = CornerRadius.Parse("1.1 2.2 3.3 4.4", CultureInfo.InvariantCulture);
var result = CornerRadius.Parse("1.1 2.2 3.3 4.4");
Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result);
}

2
tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs

@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media
[Fact]
public void Parse_Parses()
{
var matrix = Matrix.Parse("1,2,3,-4,5 6", CultureInfo.CurrentCulture);
var matrix = Matrix.Parse("1,2,3,-4,5 6");
var expected = new Matrix(1, 2, 3, -4, 5, 6);
Assert.Equal(expected, matrix);
}

2
tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs

@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media
[Fact]
public void Parse_Parses()
{
var rect = Rect.Parse("1,2 3,-4", CultureInfo.CurrentCulture);
var rect = Rect.Parse("1,2 3,-4");
var expected = new Rect(1, 2, 3, -4);
Assert.Equal(expected, rect);
}

4
tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs

@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Should_Accept_Absolute_Value()
{
var result = RelativePoint.Parse("4,5", CultureInfo.InvariantCulture);
var result = RelativePoint.Parse("4,5");
Assert.Equal(new RelativePoint(4, 5, RelativeUnit.Absolute), result);
}
@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Should_Accept_Relative_Value()
{
var result = RelativePoint.Parse("25%, 50%", CultureInfo.InvariantCulture);
var result = RelativePoint.Parse("25%, 50%");
Assert.Equal(new RelativePoint(0.25, 0.5, RelativeUnit.Relative), result);
}

6
tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs

@ -14,7 +14,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Should_Accept_Absolute_Value()
{
var result = RelativeRect.Parse("4,5,50,60", CultureInfo.InvariantCulture);
var result = RelativeRect.Parse("4,5,50,60");
Assert.Equal(new RelativeRect(4, 5, 50, 60, RelativeUnit.Absolute), result, Compare);
}
@ -22,7 +22,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Should_Accept_Relative_Value()
{
var result = RelativeRect.Parse("10%, 20%, 40%, 70%", CultureInfo.InvariantCulture);
var result = RelativeRect.Parse("10%, 20%, 40%, 70%");
Assert.Equal(new RelativeRect(0.1, 0.2, 0.4, 0.7, RelativeUnit.Relative), result, Compare);
}
@ -31,7 +31,7 @@ namespace Avalonia.Visuals.UnitTests
public void Parse_Should_Throw_Mixed_Values()
{
Assert.Throws<FormatException>(() =>
RelativeRect.Parse("10%, 20%, 40, 70%", CultureInfo.InvariantCulture));
RelativeRect.Parse("10%, 20%, 40, 70%"));
}
}
}

8
tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs

@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Parses_Single_Uniform_Size()
{
var result = Thickness.Parse("1.2", CultureInfo.InvariantCulture);
var result = Thickness.Parse("1.2");
Assert.Equal(new Thickness(1.2), result);
}
@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Parses_Horizontal_Vertical()
{
var result = Thickness.Parse("1.2,3.4", CultureInfo.InvariantCulture);
var result = Thickness.Parse("1.2,3.4");
Assert.Equal(new Thickness(1.2, 3.4), result);
}
@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Parses_Left_Top_Right_Bottom()
{
var result = Thickness.Parse("1.2, 3.4, 5, 6", CultureInfo.InvariantCulture);
var result = Thickness.Parse("1.2, 3.4, 5, 6");
Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result);
}
@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Parse_Accepts_Spaces()
{
var result = Thickness.Parse("1.2 3.4 5 6", CultureInfo.InvariantCulture);
var result = Thickness.Parse("1.2 3.4 5 6");
Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result);
}

Loading…
Cancel
Save