From fee5b4e594ab1c17e85ebe71cd2ebfbb70e9b92c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 24 Feb 2018 21:53:42 +0300 Subject: [PATCH 01/42] Support default clr-namespace when loading XAML from Uri or previewer --- samples/ControlCatalog/MainWindow.xaml | 2 +- src/Avalonia.Base/Platform/IAssetLoader.cs | 2 ++ .../DesignWindowLoader.cs | 4 ++- .../DesignerAssist.cs | 2 +- .../Avalonia.Markup.Xaml.csproj | 1 - .../AvaloniaXamlLoader.cs | 11 -------- .../AvaloniaXamlLoaderPortableXaml.cs | 27 ++++++++++++------- src/Shared/PlatformSupport/AssetLoader.cs | 20 ++++++++++++-- tests/Avalonia.UnitTests/MockAssetLoader.cs | 5 ++++ 9 files changed, 48 insertions(+), 26 deletions(-) delete mode 100644 src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index f39beced1a5..7029273a84b 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -1,6 +1,6 @@  + xmlns:local="clr-namespace:ControlCatalog"> \ No newline at end of file diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index eae8db5a146..32008ffd071 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -43,5 +43,7 @@ public interface IAssetLoader /// The resource was not found. /// Stream Open(Uri uri, Uri baseUri = null); + + Tuple OpenWithAssembly(Uri uri, Uri baseUri = null); } } diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 9ad2a216b44..d9cac47bf3e 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/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 @@ public static Window LoadDesignerWindow(string xaml, string assemblyPath) new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath)); } - var loaded = loader.Load(stream, null, baseUri); + var localAsm = assemblyPath != null ? Assembly.LoadFile(assemblyPath) : null; + var loaded = loader.Load(stream, localAsm, null, baseUri); var styles = loaded as Styles; if (styles != null) { diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 65c4c14d83f..f6f704b8380 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -24,7 +24,7 @@ public override void Initialize() 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); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 0ce2a1a9920..62c23543fdf 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -30,7 +30,6 @@ Properties\SharedAssemblyInfo.cs - diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs deleted file mode 100644 index 3142d954ffb..00000000000 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Avalonia.Markup.Xaml -{ - public class AvaloniaXamlLoader : AvaloniaXamlLoaderPortableXaml - { - public static object Parse(string xaml) - => new AvaloniaXamlLoader().Load(xaml); - - public static T Parse(string xaml) - => (T)Parse(xaml); - } -} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs index de2a79c54ea..e057aa3b537 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml /// /// Loads XAML for a avalonia application. /// - public class AvaloniaXamlLoaderPortableXaml + public class AvaloniaXamlLoader { private readonly AvaloniaXamlSchemaContext _context = GetContext(); @@ -40,7 +40,7 @@ private static AvaloniaXamlSchemaContext GetContext() /// /// Initializes a new instance of the class. /// - public AvaloniaXamlLoaderPortableXaml() + public AvaloniaXamlLoader() { } @@ -89,7 +89,7 @@ public object Load(Type type, object rootInstance = null) initialize?.BeginInit(); try { - return Load(stream, rootInstance, uri); + return Load(stream, type.Assembly, rootInstance, uri); } finally { @@ -125,11 +125,12 @@ public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); } - using (var stream = assetLocator.Open(uri, baseUri)) + var asset = assetLocator.OpenWithAssembly(uri, baseUri); + using (var stream = asset.Item2) { try { - return Load(stream, rootInstance, uri); + return Load(stream, asset.Item1, rootInstance, uri); } catch (Exception e) { @@ -147,17 +148,18 @@ public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) /// Loads XAML from a string. /// /// The string containing the XAML. + /// Default assembly for clr-namespace: /// /// The optional instance into which the XAML should be loaded. /// /// The loaded object. - public object Load(string xaml, object rootInstance = null) + public object Load(string xaml, Assembly localAssembly, object rootInstance = null) { Contract.Requires(xaml != null); using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) { - return Load(stream, rootInstance); + return Load(stream, localAssembly, rootInstance); } } @@ -165,17 +167,18 @@ public object Load(string xaml, object rootInstance = null) /// Loads XAML from a stream. /// /// The stream containing the XAML. + /// Default assembly for clr-namespace /// /// The optional instance into which the XAML should be loaded. /// /// The URI of the XAML /// The loaded object. - public object Load(Stream stream, object rootInstance = null, Uri uri = null) + public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null) { var readerSettings = new XamlXmlReaderSettings() { BaseUri = uri, - LocalAssembly = rootInstance?.GetType().GetTypeInfo().Assembly + LocalAssembly = localAssembly }; var reader = new XamlXmlReader(stream, _context, readerSettings); @@ -223,5 +226,11 @@ private static IEnumerable GetUrisFor(Type type) 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(string xaml, Assembly localAssembly) + => (T)Parse(xaml, localAssembly); } } \ No newline at end of file diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 4287fd0b9e1..eb9b04fe546 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -67,7 +67,20 @@ public bool Exists(Uri uri, Uri baseUri = null) /// /// The resource was not found. /// - public Stream Open(Uri uri, Uri baseUri = null) + public Stream Open(Uri uri, Uri baseUri = null) => OpenWithAssembly(uri, baseUri).Item2; + + /// + /// Opens the resource with the requested URI. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// An assembly (optional) and a stream containing the resource contents. + /// + /// The resource was not found. + /// + public Tuple OpenWithAssembly(Uri uri, Uri baseUri = null) { var asset = GetAsset(uri, baseUri); @@ -76,7 +89,7 @@ public Stream Open(Uri uri, Uri baseUri = null) throw new FileNotFoundException($"The resource {uri} could not be found."); } - return asset.GetStream(); + return Tuple.Create(asset.Assembly, asset.GetStream()); } private IAssetDescriptor GetAsset(Uri uri, Uri baseUri) @@ -162,6 +175,7 @@ private Dictionary ParseQueryString(Uri uri) private interface IAssetDescriptor { Stream GetStream(); + Assembly Assembly { get; } } private class AssemblyResourceDescriptor : IAssetDescriptor @@ -179,6 +193,8 @@ public Stream GetStream() { return _asm.GetManifestResourceStream(_name); } + + public Assembly Assembly => _asm; } private class AssemblyDescriptor diff --git a/tests/Avalonia.UnitTests/MockAssetLoader.cs b/tests/Avalonia.UnitTests/MockAssetLoader.cs index 5cff20c6a1c..3cf7370f1fb 100644 --- a/tests/Avalonia.UnitTests/MockAssetLoader.cs +++ b/tests/Avalonia.UnitTests/MockAssetLoader.cs @@ -27,6 +27,11 @@ public Stream Open(Uri uri, Uri baseUri = null) { return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri])); } + + public Tuple OpenWithAssembly(Uri uri, Uri baseUri = null) + { + return Tuple.Create((Assembly) null, Open(uri, baseUri)); + } public void SetDefaultAssembly(Assembly asm) { From 7b4cd4f96d1d5afad14a7eddbb79069bada4dbc7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 24 Mar 2018 15:59:44 +0100 Subject: [PATCH 02/42] Modify/add tests for non-registered properties. We should change to the behavior of other XAML frameworks where non-registered `AvaloniaProperty`s can be set on `AvaloniaObject`s via code. --- .../AvaloniaObjectTests_Binding.cs | 9 ++- .../AvaloniaObjectTests_GetValue.cs | 4 +- .../AvaloniaObjectTests_SetValue.cs | 39 +++++++++-- .../Xaml/AttachedPropertyOwner.cs | 14 ++++ .../Xaml/BasicTests.cs | 19 ++++++ .../Xaml/BindingTests.cs | 66 +++++++++++++++++++ .../Xaml/TestControl.cs | 17 +++++ 7 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index c75150ca6d5..80cd52d529a 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -92,14 +92,13 @@ public void OneTime_Binding_Ignores_Binding_Errors() } [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(() => - { - target.Bind(Class2.BarProperty, Observable.Return("foo")); - }); + target.Bind(Class2.BarProperty, Observable.Never().StartWith("foo")); + + Assert.Equal("foo", target.GetValue(Class2.BarProperty)); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs index 98f72892288..740023fd37d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs @@ -46,11 +46,11 @@ public void GetValue_Returns_Inherited_Value() } [Fact] - public void GetValue_Throws_Exception_For_Unregistered_Property() + public void GetValue_Doesnt_Throw_Exception_For_Unregistered_Property() { var target = new Class3(); - Assert.Throws(() => target.GetValue(Class1.FooProperty)); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } private class Class1 : AvaloniaObject diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 1f9c47f8ae0..a56cd717b96 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -30,6 +30,16 @@ public void SetValue_Sets_Value() 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 @@ public void SetValue_Doesnt_Raise_PropertyChanged_If_Value_Not_Changed_From_Defa } [Fact] - public void SetValue_Throws_Exception_For_Unregistered_Property() + public void SetValue_Allows_Setting_Unregistered_Property() { Class1 target = new Class1(); - Assert.Throws(() => - { - 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 @@ public Class1 Parent } } + private class AttachedOwner + { + public static readonly AttachedProperty AttachedProperty = + AvaloniaProperty.RegisterAttached("Attached"); + } + private class ImplictDouble { public ImplictDouble(double value) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs new file mode 100644 index 00000000000..aac5b01f967 --- /dev/null +++ b/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 DoubleProperty = + AvaloniaProperty.RegisterAttached("Double"); + + public static double GetDouble(Control control) => control.GetValue(DoubleProperty); + public static void SetDouble(Control control, double value) => control.SetValue(DoubleProperty, value); + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index fb432c30d46..9bee92cd7ae 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -10,6 +10,7 @@ using Avalonia.Media; using Avalonia.Styling; using Avalonia.UnitTests; +using Portable.Xaml; using System.Collections; using System.ComponentModel; using System.Linq; @@ -124,6 +125,24 @@ public void Attached_Property_In_Panel_Is_Set() Assert.Equal("Foo", ToolTip.GetTip(target)); } + [Fact] + public void NonExistent_Property_Throws() + { + var xaml = + @""; + + Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml)); + } + + [Fact] + public void Non_Attached_Property_With_Attached_Property_Syntax_Throws() + { + var xaml = + @""; + + Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml)); + } + [Fact] public void ContentControl_ContentTemplate_Is_Functional() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index a44d09dee7d..568c6482f51 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -215,5 +215,71 @@ public void Stream_Binding_To_Observable_Works() Assert.Equal("bar", textBlock.Text); } } + + [Fact] + public void Binding_To_Namespaced_Attached_Property_Works() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + 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 = @" + + +"; + 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 = @" + + +"; + 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)); + } + } } } \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs new file mode 100644 index 00000000000..d0591e5ee6c --- /dev/null +++ b/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 DoubleProperty = + AttachedPropertyOwner.DoubleProperty.AddOwner(); + + public double Double + { + get => GetValue(DoubleProperty); + set => SetValue(DoubleProperty, value); + } + } +} From f8791ecad784022ff5fc30dbbd34261f0653104d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 14 Apr 2018 16:19:05 +0200 Subject: [PATCH 03/42] Make attached property accessors static. --- src/Avalonia.Controls/Primitives/TemplatedControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 77735f3f121..1a805a3822e 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -207,7 +207,7 @@ public IControlTemplate Template /// The control. /// The property value. /// - public bool GetIsTemplateFocusTarget(Control control) + public static bool GetIsTemplateFocusTarget(Control control) { return control.GetValue(IsTemplateFocusTargetProperty); } @@ -223,7 +223,7 @@ public bool GetIsTemplateFocusTarget(Control control) /// attached property is set to true on an element in the control template, then the focus /// adorner will be shown around that control instead. /// - public void SetIsTemplateFocusTarget(Control control, bool value) + public static void SetIsTemplateFocusTarget(Control control, bool value) { control.SetValue(IsTemplateFocusTargetProperty, value); } From 44cfcfb04d49f46c3b2a5f5c075907e8872c6f8d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 14 Apr 2018 18:09:53 +0200 Subject: [PATCH 04/42] Allow any property to be registered anywhere. Makes Avalonia more like other XAML frameworks, in that any avalonia property can now be set anywhere. The XAML parser however will only allow you to set registered properties. --- src/Avalonia.Base/AttachedProperty.cs | 9 +- src/Avalonia.Base/AvaloniaObject.cs | 50 ++-- src/Avalonia.Base/AvaloniaProperty.cs | 8 +- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 283 ++++++++---------- src/Avalonia.Base/DirectProperty.cs | 3 + src/Avalonia.Base/IDirectPropertyAccessor.cs | 7 + .../AvaloniaPropertyTypeConverter.cs | 5 +- .../PortableXaml/AvaloniaXamlSchemaContext.cs | 3 +- .../PortableXaml/AvaloniaXamlType.cs | 22 +- .../Plugins/AvaloniaPropertyAccessorPlugin.cs | 55 +++- .../AvaloniaObjectTests_AddOwner.cs | 52 ++++ .../AvaloniaObjectTests_Attached.cs | 52 ++++ .../AvaloniaPropertyRegistryTests.cs | 108 +++---- 13 files changed, 386 insertions(+), 271 deletions(-) create mode 100644 tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs create mode 100644 tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 9d4d40bfeff..fdb04b6dfc8 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -9,7 +9,7 @@ namespace Avalonia /// An attached avalonia property. /// /// The type of the property's value. - public class AttachedProperty : StyledPropertyBase + public class AttachedProperty : StyledProperty { /// /// Initializes a new instance of the class. @@ -35,11 +35,10 @@ public AttachedProperty( /// /// The owner type. /// The property. - public StyledProperty AddOwner() where TOwner : IAvaloniaObject + public new AttachedProperty AddOwner() where TOwner : IAvaloniaObject { - var result = new StyledProperty(this, typeof(TOwner)); - AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result); - return result; + AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); + return this; } } } diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index a46d567d283..4ab813333dc 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -12,7 +12,6 @@ using Avalonia.Logging; using Avalonia.Threading; using Avalonia.Utilities; -using System.Reactive.Concurrency; namespace Avalonia { @@ -218,11 +217,6 @@ public object GetValue(AvaloniaProperty property) } else { - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - return GetValueInternal(property); } } @@ -377,11 +371,6 @@ public IDisposable Bind( { PriorityValue v; - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - if (!_values.TryGetValue(property, out v)) { v = CreatePriorityValue(property); @@ -804,11 +793,6 @@ private void SetStyledValue(AvaloniaProperty property, object value, BindingPrio 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 @@ private void SetStyledValue(AvaloniaProperty property, object value, BindingPrio } /// - /// Given a 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. /// /// The property. /// The registered property. - 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 @@ private void LogPropertySet(AvaloniaProperty property, object value, BindingPrio value, priority); } - - /// - /// Throws an exception indicating that the specified property is not registered on this - /// object. - /// - /// The property - private void ThrowNotRegistered(AvaloniaProperty p) - { - throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}"); - } } } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fb78e3b2a04..f7dabd3a431 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -311,7 +311,9 @@ public static AttachedProperty RegisterAttached( defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(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 @@ public static AttachedProperty RegisterAttached( defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(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; } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index ec1643427b8..c0a4ace6ed7 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/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 /// public class AvaloniaPropertyRegistry { - /// - /// The registered properties by type. - /// private readonly Dictionary> _registered = new Dictionary>(); - - /// - /// The registered properties by type cached values to increase performance. - /// - private readonly Dictionary> _registeredCache = - new Dictionary>(); - - /// - /// The registered attached properties by owner type. - /// private readonly Dictionary> _attached = new Dictionary>(); + private readonly Dictionary> _registeredCache = + new Dictionary>(); + private readonly Dictionary> _attachedCache = + new Dictionary>(); /// /// Gets the instance @@ -39,51 +29,68 @@ public class AvaloniaPropertyRegistry = new AvaloniaPropertyRegistry(); /// - /// Gets all attached s registered by an owner. + /// Gets all non-attached s registered on a type. /// - /// The owner type. + /// The type. /// A collection of definitions. - public IEnumerable GetAttached(Type ownerType) + public IEnumerable GetRegistered(Type type) { - Dictionary inner; + Contract.Requires(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(); - 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(); + _registeredCache.Add(type, result); + return result; } /// - /// Gets all s registered on a type. + /// Gets all attached s registered on a type. /// /// The type. /// A collection of definitions. - public IEnumerable GetRegistered(Type type) + public IEnumerable GetRegisteredAttached(Type type) { Contract.Requires(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 inner; + var t = type; + result = new List(); - 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; } /// @@ -99,142 +106,92 @@ public IEnumerable GetRegistered(AvaloniaObject o) } /// - /// Finds a registered on a type. + /// Finds a registered non-attached property on a type by name. /// /// The type. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(Type type, string name) { - Type currentType = type; - Dictionary cache; - AvaloniaProperty result; + Contract.Requires(type != null); + Contract.Requires(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 inner; - - if (_registered.TryGetValue(currentType, out inner)) - { - if (inner.TryGetValue(property.Id, out result)) - { - if (cache == null) - { - _registeredCache[type] = cache = new Dictionary(); - } - - cache[property.Id] = result; - - return result; - } - } - - currentType = currentType.GetTypeInfo().BaseType; - } - - return null; + return GetRegistered(type).FirstOrDefault(x => x.Name == name); } /// - /// Finds registered on an object. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) { - return FindRegistered(o.GetType(), property); + Contract.Requires(o != null); + Contract.Requires(name != null); + + return FindRegistered(o.GetType(), name); } /// - /// Finds a registered property on a type by name. + /// Finds a registered attached property on a type by name. /// /// The type. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(Type type, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name) { Contract.Requires(type != null); + Contract.Requires(ownerType != null); Contract.Requires(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); } /// - /// Finds a registered property on an object by name. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name) { - return FindRegistered(o.GetType(), name); - } + Contract.Requires(o != null); + Contract.Requires(name != null); - /// - /// Returns a type and all its base types. - /// - /// The type. - /// The type and all its base types. - private IEnumerable GetImplementedTypes(Type type) - { - while (type != null) - { - yield return type.Name; - type = type.GetTypeInfo().BaseType; - } + return FindRegisteredAttached(o.GetType(), ownerType, name); } /// @@ -245,7 +202,11 @@ private IEnumerable GetImplementedTypes(Type type) /// True if the property is registered, otherwise false. public bool IsRegistered(Type type, AvaloniaProperty property) { - return FindRegistered(type, property) != null; + Contract.Requires(type != null); + Contract.Requires(property != null); + + return Instance.GetRegistered(type).Any(x => x == property) || + Instance.GetRegisteredAttached(type).Any(x => x == property); } /// @@ -256,6 +217,9 @@ public bool IsRegistered(Type type, AvaloniaProperty property) /// True if the property is registered, otherwise false. public bool IsRegistered(object o, AvaloniaProperty property) { + Contract.Requires(o != null); + Contract.Requires(property != null); + return IsRegistered(o.GetType(), property); } @@ -274,34 +238,53 @@ public void Register(Type type, AvaloniaProperty property) Contract.Requires(type != null); Contract.Requires(property != null); - Dictionary inner; - - if (!_registered.TryGetValue(type, out inner)) + if (!_registered.TryGetValue(type, out var inner)) { inner = new Dictionary(); + 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) + /// + /// Registers an attached on a type. + /// + /// The type. + /// The property. + /// + /// You won't usually want to call this method directly, instead use the + /// + /// method. + /// + public void RegisterAttached(Type type, AvaloniaProperty property) + { + Contract.Requires(type != null); + Contract.Requires(property != null); + + if (!property.IsAttached) { - if (!_attached.TryGetValue(property.OwnerType, out inner)) - { - inner = new Dictionary(); - _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(); + inner.Add(property.Id, property); + _attached.Add(type, inner); + } + else + { + inner.Add(property.Id, property); } - _registeredCache.Clear(); + _attachedCache.Clear(); } } } \ No newline at end of file diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index 83525282850..1ce73c20ba2 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -75,6 +75,9 @@ private DirectProperty( /// public Action Setter { get; } + /// + Type IDirectPropertyAccessor.Owner => typeof(TOwner); + /// /// Registers the direct property on another type. /// diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs index 62aeef73c72..4f466526933 100644 --- a/src/Avalonia.Base/IDirectPropertyAccessor.cs +++ b/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 { /// @@ -14,6 +16,11 @@ internal interface IDirectPropertyAccessor /// bool IsReadOnly { get; } + /// + /// Gets the class that registered the property. + /// + Type Owner { get; } + /// /// Gets the value of the property on the instance. /// diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index a34ccaa4131..bc3caff3b95 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -53,10 +53,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c } } - // 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) { diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs index bdb21abd772..fda5da902a4 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs @@ -200,8 +200,7 @@ protected internal override XamlMember GetAttachableProperty(string attachablePr 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) { diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs index 7de96ea2206..59dbba70841 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs +++ b/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 diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs index 90eabc69fbb..ac64459dd7f 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/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 @@ public class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin /// 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 @@ public IPropertyAccessor Start(WeakReference reference, string propertyName) 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 @@ public IPropertyAccessor Start(WeakReference reference, string propertyName) } } + 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 _reference; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs new file mode 100644 index 00000000000..4e033be3fbe --- /dev/null +++ b/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 FooProperty = + AvaloniaProperty.Register( + "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 FooProperty = + Class1.FooProperty.AddOwner(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs new file mode 100644 index 00000000000..acaabc73df9 --- /dev/null +++ b/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(() => target.SetValue(Class2.FooProperty, "throw")); + } + + private class Class1 : AvaloniaObject + { + public static readonly AttachedProperty FooProperty = + AvaloniaProperty.RegisterAttached( + "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 FooProperty = + Class1.FooProperty.AddOwner(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs index da0b0252a35..c0306570342 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs @@ -4,12 +4,15 @@ using System.Linq; using System.Reactive.Linq; using Xunit; +using Xunit.Abstractions; namespace Avalonia.Base.UnitTests { public class AvaloniaPropertyRegistryTests { - public AvaloniaPropertyRegistryTests() + ITestOutputHelper s; + + public AvaloniaPropertyRegistryTests(ITestOutputHelper s) { // Ensure properties are registered. AvaloniaProperty p; @@ -25,7 +28,7 @@ public void GetRegistered_Returns_Registered_Properties() .Select(x => x.Name) .ToArray(); - Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names); + Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names); } [Fact] @@ -35,61 +38,41 @@ public void GetRegistered_Returns_Registered_Properties_For_Base_Types() .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 +82,34 @@ public void FindRegistered_Finds_Unqualified_Attached_Property_On_Registering_Ty { 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 +138,18 @@ private class Class2 : Class1 private class Class3 : Class1 { - public static readonly StyledProperty AttachedProperty = + public static readonly AttachedProperty AttachedProperty = AttachedOwner.AttachedProperty.AddOwner(); } - public class Class4 : AvaloniaObject - { - } - private class AttachedOwner : Class1 { public static readonly AttachedProperty AttachedProperty = AvaloniaProperty.RegisterAttached("Attached"); } + + private class AttachedOwner2 : AttachedOwner + { + } } } From f0d0429644c3488050389618e1ae0c9d6ee92418 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 14 Apr 2018 23:21:34 +0200 Subject: [PATCH 05/42] Remove unused member. --- tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs index c0306570342..8b2e500d377 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs @@ -10,8 +10,6 @@ namespace Avalonia.Base.UnitTests { public class AvaloniaPropertyRegistryTests { - ITestOutputHelper s; - public AvaloniaPropertyRegistryTests(ITestOutputHelper s) { // Ensure properties are registered. From b02ad2be485451127e197bd285c3854cf6c59390 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 14 Apr 2018 23:22:04 +0200 Subject: [PATCH 06/42] Fix failing tests. This class was registering a global attached property which affected other tests. --- tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs index acaabc73df9..ab2a2d899de 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs @@ -27,7 +27,7 @@ public void AddOwnered_Property_Retains_Validation() private class Class1 : AvaloniaObject { public static readonly AttachedProperty FooProperty = - AvaloniaProperty.RegisterAttached( + AvaloniaProperty.RegisterAttached( "Foo", "foodefault", validate: ValidateFoo); From bd33b3076b4eea33731dcf9a8eedf374aa2a1aef Mon Sep 17 00:00:00 2001 From: Stano Turza Date: Tue, 17 Apr 2018 14:35:08 +0200 Subject: [PATCH 07/42] Fix Win32 window size constraints fixes + revert testing changes --- .../Platform/IWindowBaseImpl.cs | 24 +++++++++++++-- src/Avalonia.Controls/WindowBase.cs | 9 +++++- .../Remote/PreviewerWindowImpl.cs | 8 +++++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 8 +++++ src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 8 +++++ src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 8 +++++ .../Interop/UnmanagedMethods.cs | 10 +++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 29 ++++++++++++++++++- 8 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0a01cf3df49..f043371e9db 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -55,7 +55,7 @@ public interface IWindowBaseImpl : ITopLevelImpl /// Gets the platform window handle. /// IPlatformHandle Handle { get; } - + /// /// Gets the maximum size of a window on the system. /// @@ -65,7 +65,27 @@ public interface IWindowBaseImpl : ITopLevelImpl /// Sets the client size of the toplevel. /// void Resize(Size clientSize); - + + /// + /// Minimum width of the window. + /// + double MinWidth { get; set; } + + /// + /// Maximum width of the window. + /// + double MaxWidth { get; set; } + + /// + /// Minimum height of the window. + /// + double MinHeight { get; set; } + + /// + /// Maximum height of the window. + /// + double MaxHeight { get; set; } + /// /// Gets platform specific display information /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 50068d280d5..16fc8117d5e 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -197,7 +197,14 @@ protected override Size ArrangeOverride(Size finalSize) { using (BeginAutoSizing()) { - PlatformImpl?.Resize(finalSize); + if (PlatformImpl != null) + { + PlatformImpl.MinHeight = MinHeight; + PlatformImpl.MaxHeight = MaxHeight; + PlatformImpl.MinWidth = MinWidth; + PlatformImpl.MaxWidth = MaxWidth; + PlatformImpl.Resize(finalSize); + } } return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size)); diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index fc9541abb71..703f5ec5c8a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -67,6 +67,14 @@ public void Resize(Size clientSize) RenderIfNeeded(); } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IScreenImpl Screen { get; } = new ScreenStub(); public void Activate() diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 560425286e9..6dd207178db 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -78,6 +78,14 @@ public void Resize(Size clientSize) public IScreenImpl Screen { get; } = new ScreenStub(); + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public void SetTitle(string title) { } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index a42c8a19b9e..768b1500af2 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -341,6 +341,14 @@ public Size MaxClientSize } } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IMouseDevice MouseDevice => Gtk3Platform.Mouse; public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1); diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index e5ba285f4f2..0b64aae36b2 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -161,6 +161,14 @@ public void Resize(Size clientSize) Position = pos; } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IScreenImpl Screen { get; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index aa86ab0f8d5..b679d746f3a 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -615,6 +615,16 @@ public struct BITMAPINFO 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")] diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bb3c4cf6e62..73d8228bab5 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -102,6 +102,14 @@ public Size ClientSize } } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IScreenImpl Screen { get; @@ -611,7 +619,26 @@ protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lP 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(lParam); + + if (MinWidth > 0) + mmi.ptMinTrackSize.X = (int)((MinWidth * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (MinHeight > 0) + mmi.ptMinTrackSize.Y = (int)((MinHeight * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + + if (!Double.IsInfinity(MaxWidth) && MaxWidth > 0) + mmi.ptMaxTrackSize.X = (int)((MaxWidth * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (!Double.IsInfinity(MaxHeight) && MaxHeight > 0) + mmi.ptMaxTrackSize.Y = (int)((MaxHeight * 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; From 3f87b9d02d1171a0078bd9c9a17db9cee1a58302 Mon Sep 17 00:00:00 2001 From: Amadeusz Sadowski Date: Fri, 20 Apr 2018 15:24:09 +0200 Subject: [PATCH 08/42] Support StaticResource in Bindings inside DataTemplate --- .../AvaloniaXamlLoaderPortableXaml.cs | 5 +-- .../PortableXaml/AvaloniaXamlObjectWriter.cs | 26 ++++++++++++-- .../PortableXaml/portable.xaml.github | 2 +- .../Templates/TemplateContent.cs | 8 +++-- .../Templates/TemplateLoader.cs | 3 +- .../StaticResourceExtensionTests.cs | 36 ++++++++++++++++++- 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs index de2a79c54ea..539fbeb680c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs @@ -194,11 +194,12 @@ public object Load(Stream stream, object rootInstance = null, Uri uri = null) return result; } - internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null) + internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null) { var writer = AvaloniaXamlObjectWriter.Create( reader.SchemaContext, - context); + context, + parentAmbientProvider); XamlServices.Transform(reader, writer); writer.ApplyAllDelayedProperties(); diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs index e0e2553f462..509c52503b5 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs @@ -27,6 +27,26 @@ public static AvaloniaXamlObjectWriter Create( nameScope); } + public static AvaloniaXamlObjectWriter Create( + XamlSchemaContext schemaContext, + AvaloniaXamlContext context, + IAmbientProvider parentAmbientProvider) + { + var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance }; + + var writerSettings = new XamlObjectWriterSettings() + { + ExternalNameScope = nameScope, + RegisterNamesOnExternalNamescope = true, + RootObjectInstance = context?.RootInstance + }; + + return new AvaloniaXamlObjectWriter(schemaContext, + writerSettings.WithContext(context), + nameScope, + parentAmbientProvider); + } + private readonly DelayedValuesHelper _delayedValuesHelper = new DelayedValuesHelper(); private AvaloniaNameScope _nameScope; @@ -34,9 +54,9 @@ public static AvaloniaXamlObjectWriter Create( private AvaloniaXamlObjectWriter( XamlSchemaContext schemaContext, XamlObjectWriterSettings settings, - AvaloniaNameScope nameScope - ) - : base(schemaContext, settings) + AvaloniaNameScope nameScope, + IAmbientProvider parentAmbientProvider = null) + : base(schemaContext, settings, parentAmbientProvider) { _nameScope = nameScope; } diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index c0664014455..e7077fa7df8 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit c0664014455392ac221a765e66f9837704339b6f +Subproject commit e7077fa7df836af53f220f845f59971208c3e408 diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs index 1d4dafc413b..63fb9f193c2 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs @@ -10,8 +10,10 @@ namespace Avalonia.Markup.Xaml.Templates public class TemplateContent { - public TemplateContent(IEnumerable namespaces, XamlReader reader) + public TemplateContent(IEnumerable 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 @@ public TemplateContent(IEnumerable namespaces, XamlReader 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) diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs index 1085131230c..e29485ddb04 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs @@ -14,7 +14,8 @@ public override object Load(XamlReader xamlReader, IServiceProvider serviceProvi { var tdc = (ITypeDescriptorContext)serviceProvider; var ns = tdc.GetService(); - return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader); + var ambientProvider = tdc.GetService(); + return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader, ambientProvider); } public override XamlReader Save(object value, IServiceProvider serviceProvider) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 862ce2b3c06..57293d5d16d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -323,7 +323,7 @@ public void StaticResource_Can_Be_Assigned_To_Resource_Property_In_Styles_File() Assert.Equal(0xff506070, brush.Color.ToUint32()); } - [Fact(Skip = "Not yet supported by Portable.Xaml")] + [Fact(/*Skip = "Not yet supported by Portable.Xaml"*/)] public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File() { var styleXaml = @" @@ -417,6 +417,40 @@ public void StaticResource_Can_Be_Assigned_To_Converter() } } + [Fact] + public void StaticResource_Can_Be_Assigned_To_Converter_In_DataTemplate() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + + window.DataContext = "foo"; + var presenter = window.FindControl("presenter"); + + window.Show(); + + var textBlock = (TextBlock)presenter.GetVisualChildren().Single(); + + Assert.NotNull(textBlock); + Assert.Equal("foobar", textBlock.Text); + } + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { From b89b72f0ba24283b2605293f7c4160764e3dccf9 Mon Sep 17 00:00:00 2001 From: Stano Turza Date: Tue, 17 Apr 2018 14:35:08 +0200 Subject: [PATCH 09/42] Fix Win32 window size constraints fixes + revert testing changes --- .../Platform/IWindowBaseImpl.cs | 24 +++++++++++++-- src/Avalonia.Controls/WindowBase.cs | 9 +++++- .../Remote/PreviewerWindowImpl.cs | 8 +++++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 8 +++++ src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 8 +++++ src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 8 +++++ .../Interop/UnmanagedMethods.cs | 10 +++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 29 ++++++++++++++++++- 8 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0a01cf3df49..f043371e9db 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -55,7 +55,7 @@ public interface IWindowBaseImpl : ITopLevelImpl /// Gets the platform window handle. /// IPlatformHandle Handle { get; } - + /// /// Gets the maximum size of a window on the system. /// @@ -65,7 +65,27 @@ public interface IWindowBaseImpl : ITopLevelImpl /// Sets the client size of the toplevel. /// void Resize(Size clientSize); - + + /// + /// Minimum width of the window. + /// + double MinWidth { get; set; } + + /// + /// Maximum width of the window. + /// + double MaxWidth { get; set; } + + /// + /// Minimum height of the window. + /// + double MinHeight { get; set; } + + /// + /// Maximum height of the window. + /// + double MaxHeight { get; set; } + /// /// Gets platform specific display information /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 50068d280d5..16fc8117d5e 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -197,7 +197,14 @@ protected override Size ArrangeOverride(Size finalSize) { using (BeginAutoSizing()) { - PlatformImpl?.Resize(finalSize); + if (PlatformImpl != null) + { + PlatformImpl.MinHeight = MinHeight; + PlatformImpl.MaxHeight = MaxHeight; + PlatformImpl.MinWidth = MinWidth; + PlatformImpl.MaxWidth = MaxWidth; + PlatformImpl.Resize(finalSize); + } } return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size)); diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index fc9541abb71..703f5ec5c8a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -67,6 +67,14 @@ public void Resize(Size clientSize) RenderIfNeeded(); } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IScreenImpl Screen { get; } = new ScreenStub(); public void Activate() diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 560425286e9..6dd207178db 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -78,6 +78,14 @@ public void Resize(Size clientSize) public IScreenImpl Screen { get; } = new ScreenStub(); + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public void SetTitle(string title) { } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index a42c8a19b9e..768b1500af2 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -341,6 +341,14 @@ public Size MaxClientSize } } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IMouseDevice MouseDevice => Gtk3Platform.Mouse; public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1); diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index e5ba285f4f2..0b64aae36b2 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -161,6 +161,14 @@ public void Resize(Size clientSize) Position = pos; } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IScreenImpl Screen { get; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index aa86ab0f8d5..b679d746f3a 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -615,6 +615,16 @@ public struct BITMAPINFO 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")] diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bb3c4cf6e62..73d8228bab5 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -102,6 +102,14 @@ public Size ClientSize } } + public double MinWidth { get; set; } + + public double MaxWidth { get; set; } + + public double MinHeight { get; set; } + + public double MaxHeight { get; set; } + public IScreenImpl Screen { get; @@ -611,7 +619,26 @@ protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lP 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(lParam); + + if (MinWidth > 0) + mmi.ptMinTrackSize.X = (int)((MinWidth * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (MinHeight > 0) + mmi.ptMinTrackSize.Y = (int)((MinHeight * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + + if (!Double.IsInfinity(MaxWidth) && MaxWidth > 0) + mmi.ptMaxTrackSize.X = (int)((MaxWidth * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (!Double.IsInfinity(MaxHeight) && MaxHeight > 0) + mmi.ptMaxTrackSize.Y = (int)((MaxHeight * 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; From 3f75825f46903deb399fe9f2222422c511440fc4 Mon Sep 17 00:00:00 2001 From: Stano Turza Date: Mon, 23 Apr 2018 15:31:17 +0200 Subject: [PATCH 10/42] Fix GTK --- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 28 +++++++++++++------------ src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 9 ++++++++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index d4618e7bc16..7cf0a0b2565 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -263,7 +263,7 @@ public static class D [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)] @@ -502,6 +502,8 @@ public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer public static D.cairo_set_font_size CairoSetFontSize; public static D.cairo_move_to CairoMoveTo; public static D.cairo_destroy CairoDestroy; + + public static D.gtk_window_set_geometry_hints GtkWindowSetGeometryHints; public const int G_TYPE_OBJECT = 80; } @@ -739,19 +741,19 @@ public enum GtkFileChooserAction } [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 diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 768b1500af2..7e55892570e 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -439,6 +439,15 @@ public void Resize(Size value) { if (GtkWidget.IsClosed) return; + + GdkGeometry geometry = new GdkGeometry(); + geometry.min_width = MinWidth > 0 ? (int)MinWidth : -1; + geometry.min_height = MinHeight > 0 ? (int)MinHeight : -1; + geometry.max_width = !Double.IsInfinity(MaxWidth) && MaxWidth > 0 ? (int)MaxWidth : 999999; + geometry.max_height = !Double.IsInfinity(MaxHeight) && MaxHeight > 0 ? (int)MaxHeight : 999999; + + Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE); + Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); if (OverrideRedirect) { From 586e8654f304f4099504cf02f5b377d41fea9f11 Mon Sep 17 00:00:00 2001 From: Stano Turza Date: Tue, 24 Apr 2018 10:19:01 +0200 Subject: [PATCH 11/42] replace Min Max properties with function --- .../Platform/IWindowBaseImpl.cs | 18 ++--------- src/Avalonia.Controls/WindowBase.cs | 14 ++++----- .../Remote/PreviewerWindowImpl.cs | 10 ++---- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 10 ++---- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 2 +- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 26 +++++++--------- src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs | 10 ++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 31 ++++++++++--------- 8 files changed, 46 insertions(+), 75 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index f043371e9db..4f7ac82df73 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -69,22 +69,8 @@ public interface IWindowBaseImpl : ITopLevelImpl /// /// Minimum width of the window. /// - double MinWidth { get; set; } - - /// - /// Maximum width of the window. - /// - double MaxWidth { get; set; } - - /// - /// Minimum height of the window. - /// - double MinHeight { get; set; } - - /// - /// Maximum height of the window. - /// - double MaxHeight { get; set; } + /// + void SetMinMaxSize(Size minSize, Size maxSize); /// /// Gets platform specific display information diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 16fc8117d5e..c427df1c264 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -47,6 +47,11 @@ static WindowBase() { IsVisibleProperty.OverrideDefaultValue(false); IsVisibleProperty.Changed.AddClassHandler(x => x.IsVisibleChanged); + + MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); + MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); + MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight))); + MaxHeightProperty.Changed.AddClassHandler((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) @@ -197,14 +202,7 @@ protected override Size ArrangeOverride(Size finalSize) { using (BeginAutoSizing()) { - if (PlatformImpl != null) - { - PlatformImpl.MinHeight = MinHeight; - PlatformImpl.MaxHeight = MaxHeight; - PlatformImpl.MinWidth = MinWidth; - PlatformImpl.MaxWidth = MaxWidth; - PlatformImpl.Resize(finalSize); - } + PlatformImpl?.Resize(finalSize); } return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size)); diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 703f5ec5c8a..f2676925a34 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -67,13 +67,9 @@ public void Resize(Size clientSize) RenderIfNeeded(); } - public double MinWidth { get; set; } - - public double MaxWidth { get; set; } - - public double MinHeight { get; set; } - - public double MaxHeight { get; set; } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } public IScreenImpl Screen { get; } = new ScreenStub(); diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 6dd207178db..08c2c77ad1c 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -78,13 +78,9 @@ public void Resize(Size clientSize) public IScreenImpl Screen { get; } = new ScreenStub(); - public double MinWidth { get; set; } - - public double MaxWidth { get; set; } - - public double MinHeight { get; set; } - - public double MaxHeight { get; set; } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } public void SetTitle(string title) { diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 7cf0a0b2565..0240e15a196 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -421,6 +421,7 @@ public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer 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; @@ -503,7 +504,6 @@ public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer public static D.cairo_move_to CairoMoveTo; public static D.cairo_destroy CairoDestroy; - public static D.gtk_window_set_geometry_hints GtkWindowSetGeometryHints; public const int G_TYPE_OBJECT = 80; } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 7e55892570e..0ebfea998ad 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -341,13 +341,19 @@ public Size MaxClientSize } } - public double MinWidth { get; set; } - - public double MaxWidth { get; set; } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + if (GtkWidget.IsClosed) + return; - public double MinHeight { get; set; } + 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; - public double MaxHeight { get; set; } + Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE); + } public IMouseDevice MouseDevice => Gtk3Platform.Mouse; @@ -439,15 +445,7 @@ public void Resize(Size value) { if (GtkWidget.IsClosed) return; - - GdkGeometry geometry = new GdkGeometry(); - geometry.min_width = MinWidth > 0 ? (int)MinWidth : -1; - geometry.min_height = MinHeight > 0 ? (int)MinHeight : -1; - geometry.max_width = !Double.IsInfinity(MaxWidth) && MaxWidth > 0 ? (int)MaxWidth : 999999; - geometry.max_height = !Double.IsInfinity(MaxHeight) && MaxHeight > 0 ? (int)MaxHeight : 999999; - - Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE); - + Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); if (OverrideRedirect) { diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index 0b64aae36b2..8cbc6cbdd85 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -161,13 +161,9 @@ public void Resize(Size clientSize) Position = pos; } - public double MinWidth { get; set; } - - public double MaxWidth { get; set; } - - public double MinHeight { get; set; } - - public double MaxHeight { get; set; } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } public IScreenImpl Screen { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 73d8228bab5..d9d4e4520c6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -35,6 +35,9 @@ public class WindowImpl : IWindowImpl private WindowState _showWindowState; private FramebufferManager _framebuffer; private OleDropTarget _dropTarget; + private Size _minSize; + private Size _maxSize; + #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif @@ -102,13 +105,11 @@ public Size ClientSize } } - public double MinWidth { get; set; } - - public double MaxWidth { get; set; } - - public double MinHeight { get; set; } - - public double MaxHeight { get; set; } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + _minSize = minSize; + _maxSize = maxSize; + } public IScreenImpl Screen { @@ -624,17 +625,17 @@ protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lP MINMAXINFO mmi = Marshal.PtrToStructure(lParam); - if (MinWidth > 0) - mmi.ptMinTrackSize.X = (int)((MinWidth * Scaling) + BorderThickness.Left + BorderThickness.Right); + if (_minSize.Width > 0) + mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); - if (MinHeight > 0) - mmi.ptMinTrackSize.Y = (int)((MinHeight * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + if (_minSize.Height > 0) + mmi.ptMinTrackSize.Y = (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); - if (!Double.IsInfinity(MaxWidth) && MaxWidth > 0) - mmi.ptMaxTrackSize.X = (int)((MaxWidth * Scaling) + BorderThickness.Left + BorderThickness.Right); + if (!Double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) + mmi.ptMaxTrackSize.X = (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); - if (!Double.IsInfinity(MaxHeight) && MaxHeight > 0) - mmi.ptMaxTrackSize.Y = (int)((MaxHeight * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + 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; From 69e4a5220cf91071f2cb4e371754b6af63b3f513 Mon Sep 17 00:00:00 2001 From: Stano Turza Date: Tue, 24 Apr 2018 11:03:21 +0200 Subject: [PATCH 12/42] Stubs for Android and iOS --- .../Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs | 6 +++++- src/iOS/Avalonia.iOS/EmbeddableImpl.cs | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs index 0b683239fbd..78f744cea06 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs @@ -36,7 +36,11 @@ public void Resize(Size value) _clientSize = value; UpdateParams(); } - + + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IScreenImpl Screen { get; } public Point Position diff --git a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs index 50a3cd9ec0b..3d8bafeca9b 100644 --- a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs +++ b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs @@ -14,6 +14,10 @@ public void SetTitle(string title) } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IDisposable ShowDialog() { return Disposable.Empty; From ccd62038afeb5e75afc516c0bd5c404bba9216bf Mon Sep 17 00:00:00 2001 From: Amadeusz Sadowski Date: Wed, 25 Apr 2018 21:40:54 +0200 Subject: [PATCH 13/42] Update Portable.Xaml to https://github.com/AvaloniaUI/Portable.Xaml/pull/1 --- .../Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index e7077fa7df8..faa952f3a05 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit e7077fa7df836af53f220f845f59971208c3e408 +Subproject commit faa952f3a05b4bdf2986d686f4154b1ab084508a From 8d11d7a5fa0557397fd4de749f8b68d709558538 Mon Sep 17 00:00:00 2001 From: Amadeusz Sadowski Date: Thu, 26 Apr 2018 02:42:21 +0200 Subject: [PATCH 14/42] BindingExtension Source tests and sample #1521 --- samples/BindingTest/MainWindow.xaml | 8 +++ .../MarkupExtensions/BindingExtensionTests.cs | 71 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml index 3547e331817..21e3af0d22d 100644 --- a/samples/BindingTest/MainWindow.xaml +++ b/samples/BindingTest/MainWindow.xaml @@ -1,4 +1,5 @@ @@ -6,6 +7,9 @@ + + + @@ -40,6 +44,10 @@ + + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs new file mode 100644 index 00000000000..4b0bee00f32 --- /dev/null +++ b/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 = @" + + + foobar + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("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()) + { + Setters = + { + new Setter( + Window.TemplateProperty, + new FuncControlTemplate(x => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], + })) + } + }; + } + } +} From 5dabc067edb222cdc3d3c55f295c4aab0e8572ec Mon Sep 17 00:00:00 2001 From: Amadeusz Sadowski Date: Thu, 26 Apr 2018 02:19:26 +0200 Subject: [PATCH 15/42] Added StaticResource tests for bindings within DataTemplate --- .../StaticResourceExtensionTests.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 57293d5d16d..e6116ed5631 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -323,7 +323,7 @@ public void StaticResource_Can_Be_Assigned_To_Resource_Property_In_Styles_File() 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 = @" @@ -418,7 +418,7 @@ public void StaticResource_Can_Be_Assigned_To_Converter() } [Fact] - public void StaticResource_Can_Be_Assigned_To_Converter_In_DataTemplate() + public void StaticResource_Can_Be_Assigned_To_Binding_Converter_In_DataTemplate() { using (StyledWindow()) { @@ -429,7 +429,7 @@ public void StaticResource_Can_Be_Assigned_To_Converter_In_DataTemplate() - + @@ -451,6 +451,45 @@ public void StaticResource_Can_Be_Assigned_To_Converter_In_DataTemplate() } } + [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 = @" + + + + + + + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + + window.Show(); + + var textBlock = window.GetVisualDescendants().OfType().Single(); + + Assert.NotNull(textBlock); + Assert.Equal("White-bar", textBlock.Text); + } + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { From a50a8a33148ac476363e94097ce12cbeb601364e Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Thu, 26 Apr 2018 10:35:52 +0200 Subject: [PATCH 16/42] Add CanResize to Window --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 5 +++ src/Avalonia.Controls/Window.cs | 14 ++++++++ .../Remote/PreviewerWindowImpl.cs | 4 +++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 4 +++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 3 ++ src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 2 ++ src/OSX/Avalonia.MonoMac/WindowImpl.cs | 18 ++++++++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 35 ++++++++++++++++--- 8 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 1f84574318b..3f2c9777183 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -45,6 +45,11 @@ public interface IWindowImpl : IWindowBaseImpl /// void ShowTaskbarIcon(bool value); + /// + /// Enables or disables resizing of the window + /// + void CanResize(bool value); + /// /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index c66209d3c6c..16ee3a46b3f 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -95,6 +95,9 @@ public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameSco o => o.WindowStartupLocation, (o, v) => o.WindowStartupLocation = v); + public static readonly StyledProperty CanResizeProperty = + AvaloniaProperty.Register(nameof(CanResize), true); + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; @@ -113,6 +116,8 @@ static Window() ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); + + CanResizeProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue)); } /// @@ -208,6 +213,15 @@ public WindowState WindowState } } + /// + /// Enables or disables resizing of the window + /// + public bool CanResize + { + get { return GetValue(CanResizeProperty); } + set { SetValue(CanResizeProperty, value); } + } + /// /// Gets or sets the icon of the window. /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index fc9541abb71..f949d05171e 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -93,5 +93,9 @@ public void SetIcon(IWindowIconImpl icon) public void ShowTaskbarIcon(bool value) { } + + public void CanResize(bool value) + { + } } } \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 560425286e9..01c30654860 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -95,6 +95,10 @@ public void SetIcon(IWindowIconImpl icon) public void ShowTaskbarIcon(bool value) { } + + public void CanResize(bool value) + { + } } class ClipboardStub : IClipboard diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index d4618e7bc16..f33623dd913 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -115,6 +115,8 @@ public static class D [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); @@ -395,6 +397,7 @@ public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer 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; diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index c586661a7aa..2d309e19d41 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -61,6 +61,8 @@ public void SetCoverTaskbarWhenMaximized(bool enable) } public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value); + + public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value); class EmptyDisposable : IDisposable diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index 6825fce82e0..d01cbd6ae3a 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/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 @@ public void ShowTaskbarIcon(bool value) 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 @@ public void SetSystemDecorations(bool enabled) UpdateStyle(); } + public void CanResize(bool value) + { + IsResizable = value; + UpdateStyle(); + } + public void SetTitle(string title) => Window.Title = title; class ModalDisposable : IDisposable diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bb3c4cf6e62..9014f1fb858 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -31,6 +31,7 @@ public class WindowImpl : IWindowImpl private IInputRoot _owner; private bool _trackingMouse; private bool _decorated = true; + private bool _resizable = true; private double _scaling = 1; private WindowState _showWindowState; private FramebufferManager _framebuffer; @@ -237,11 +238,17 @@ public void SetSystemDecorations(bool value) var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); - 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; @@ -799,10 +806,10 @@ private static int ToInt32(IntPtr ptr) public void ShowTaskbarIcon(bool value) { var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -20); - - style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); - 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 @@ -817,5 +824,23 @@ public void ShowTaskbarIcon(bool value) UnmanagedMethods.ShowWindow(_hwnd, windowPlacement.ShowCmd); } } + + public void CanResize(bool value) + { + if (value == _resizable) + { + return; + } + + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); + + if (value) + style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + else + style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + + UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style); + _resizable = value; + } } } From 73ca7334ee880714926fea387f2e32c1814c806a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 26 Apr 2018 18:39:28 +0200 Subject: [PATCH 17/42] Initial --- .../Data/BindingExpressionTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index f5c0c6ec151..f42e0daf2a4 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -58,7 +58,7 @@ public void Should_Set_Indexed_Value() [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 @@ public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue() [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 @@ public async Task Should_Convert_Get_Double_To_String() 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 @@ public void Should_Convert_Set_Double_To_String() 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 @@ public void Should_Handle_DataValidation() 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) From d9819ab0047f1b21ffd7e663f52d6d0442ad084d Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Fri, 27 Apr 2018 08:17:04 +0200 Subject: [PATCH 18/42] Extract WindowLong parameter into enum --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 13 ++++++++++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 16 ++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index aa86ab0f8d5..c98b6d0a705 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -558,7 +558,18 @@ public enum DIBColorTable { 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 diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9014f1fb858..ce1eaed13c4 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -78,8 +78,8 @@ public Thickness BorderThickness { 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)) @@ -236,7 +236,7 @@ public void SetSystemDecorations(bool value) return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); var systemDecorationStyles = UnmanagedMethods.WindowStyles.WS_OVERLAPPED | UnmanagedMethods.WindowStyles.WS_CAPTION @@ -258,7 +258,7 @@ public void SetSystemDecorations(bool value) Rect newRect; var oldThickness = BorderThickness; - UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style); + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); if (value) { @@ -805,7 +805,7 @@ private static int ToInt32(IntPtr ptr) public void ShowTaskbarIcon(bool value) { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -20); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); @@ -820,7 +820,7 @@ public void ShowTaskbarIcon(bool value) { //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); } } @@ -832,14 +832,14 @@ public void CanResize(bool value) return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); + 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, -16, (uint)style); + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); _resizable = value; } } From 3bc303c4e8fc3a1906616e12782ef5c420eed0f0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Apr 2018 13:12:28 +0100 Subject: [PATCH 19/42] implement menus and submenus obey screen extremities. --- src/Avalonia.Controls/ContextMenu.cs | 7 +++--- src/Avalonia.Controls/Primitives/Popup.cs | 18 +++++++++++++- src/Avalonia.Controls/Primitives/PopupRoot.cs | 24 +++++++++++++++++++ src/Avalonia.Themes.Default/MenuItem.xaml | 6 +++-- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index fdb04f4ade9..78dc994df72 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -19,7 +19,7 @@ static ContextMenu() { ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); - MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); + MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); } /// @@ -75,13 +75,14 @@ private void Show(Control control) { 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; diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index b8817d28f3d..d34e4671334 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -40,6 +40,9 @@ public class Popup : Control, IVisualTreeHost public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); + public static readonly StyledProperty ObeyScreenEdgesProperty = + AvaloniaProperty.Register(nameof(ObeyScreenEdges)); + /// /// Defines the property. /// @@ -136,6 +139,12 @@ public PlacementMode PlacementMode set { SetValue(PlacementModeProperty, value); } } + public bool ObeyScreenEdges + { + get => GetValue(ObeyScreenEdgesProperty); + set => SetValue(ObeyScreenEdgesProperty, value); + } + /// /// Gets or sets the Horizontal offset of the popup in relation to the /// @@ -234,6 +243,11 @@ public void Open() _popupRoot.Show(); + if (ObeyScreenEdges) + { + _popupRoot.SnapInsideScreenEdges(); + } + _ignoreIsOpenChanged = true; IsOpen = true; _ignoreIsOpenChanged = false; @@ -346,8 +360,10 @@ private void ChildChanged(AvaloniaPropertyChangedEventArgs e) /// The popup's position in screen coordinates. protected virtual Point GetPosition() { - return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); + + return result; } internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 507a085fed3..d0279200b62 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.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 Avalonia.Controls.Platform; using Avalonia.Controls.Presenters; using Avalonia.Interactivity; @@ -75,6 +76,29 @@ public PopupRoot(IAvaloniaDependencyResolver dependencyResolver) /// public void Dispose() => PlatformImpl?.Dispose(); + /// + /// Moves the Popups position so that it doesnt overlap screen edges. + /// This method can be called immediately after Show has been called. + /// + public void SnapInsideScreenEdges() + { + var mainWindow = Window.OpenWindows.First(); + var screen = mainWindow.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)); + } + } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 66f226d2f6c..efb31175fa2 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -45,7 +45,8 @@ + IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}" + ObeyScreenEdges="True"> @@ -92,7 +93,8 @@ + StaysOpen="True" + ObeyScreenEdges="True"> From 89767c8b78d122056d9a6827a6384be795a4e1ea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 28 Apr 2018 09:43:03 +0100 Subject: [PATCH 20/42] add comments to new properties. --- src/Avalonia.Controls/Primitives/Popup.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index d34e4671334..54838155226 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -39,7 +39,10 @@ public class Popup : Control, IVisualTreeHost /// public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); - + + /// + /// Degines the property. + /// public static readonly StyledProperty ObeyScreenEdgesProperty = AvaloniaProperty.Register(nameof(ObeyScreenEdges)); @@ -139,6 +142,10 @@ public PlacementMode PlacementMode set { SetValue(PlacementModeProperty, value); } } + /// + /// 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. + /// public bool ObeyScreenEdges { get => GetValue(ObeyScreenEdgesProperty); From 240bb40be4075aee7efa68a829bcd8852517c573 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 28 Apr 2018 09:45:08 +0100 Subject: [PATCH 21/42] whitespace fixes. --- src/Avalonia.Controls/Primitives/Popup.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 54838155226..56e09b7db64 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -39,7 +39,7 @@ public class Popup : Control, IVisualTreeHost /// public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); - + /// /// Degines the property. /// @@ -232,12 +232,12 @@ public void Open() 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; } @@ -261,7 +261,7 @@ public void Open() Opened?.Invoke(this, EventArgs.Empty); } - + /// /// Closes the popup. /// @@ -367,7 +367,7 @@ private void ChildChanged(AvaloniaPropertyChangedEventArgs e) /// The popup's position in screen coordinates. protected virtual Point GetPosition() { - var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); return result; @@ -422,8 +422,8 @@ private void PointerPressedOutside(object sender, PointerPressedEventArgs e) { if (!StaysOpen) { - if(!IsChildOrThis((IVisual)e.Source)) - { + if (!IsChildOrThis((IVisual)e.Source)) + { Close(); e.Handled = true; } @@ -435,12 +435,12 @@ private bool IsChildOrThis(IVisual child) 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) From 35b4913c0bfe712045a6ef340daebea721f8aa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 28 Apr 2018 18:13:44 +0100 Subject: [PATCH 22/42] Removed AvaloniaWin32Exception usings. --- src/Windows/Avalonia.Win32/ScreenImpl.cs | 7 ------- src/Windows/Avalonia.Win32/Win32Platform.cs | 18 +++++++----------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 113b2811dc1..e1df24151df 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/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 diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 902abaf65bb..95077f82a13 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/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 { From c1d6fe66864e797f486e7f4b1609438b270b0330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 28 Apr 2018 22:45:11 +0100 Subject: [PATCH 23/42] Fixed possible NullReferenceException in DefaultRenderLoop. --- src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs index 5dff3715b36..9cf849f59bd 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs @@ -41,12 +41,12 @@ public event EventHandler Tick { add { + _tick += value; + if (_subscriberCount++ == 0) { Start(); } - - _tick += value; } remove From 6bb25bea93a20657c441ed6b2b0e3a53a01115c7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Apr 2018 10:22:43 +0100 Subject: [PATCH 24/42] fix typo. --- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 56e09b7db64..656f3890cd0 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -41,7 +41,7 @@ public class Popup : Control, IVisualTreeHost AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); /// - /// Degines the property. + /// Defines the property. /// public static readonly StyledProperty ObeyScreenEdgesProperty = AvaloniaProperty.Register(nameof(ObeyScreenEdges)); From fd8e1ecba7e52abfe72cfa37af8d2921d72763c4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Apr 2018 20:26:10 +0100 Subject: [PATCH 25/42] walk tree to find window. --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index d0279200b62..457a7bd4b45 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; @@ -82,8 +83,9 @@ public PopupRoot(IAvaloniaDependencyResolver dependencyResolver) /// public void SnapInsideScreenEdges() { - var mainWindow = Window.OpenWindows.First(); - var screen = mainWindow.Screens.ScreenFromPoint(Position); + var window = this.GetSelfAndLogicalAncestors().OfType().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; From eb273c0fcbe3a4b6e396b280afd3f6c568a0167a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 2 May 2018 16:49:57 +0200 Subject: [PATCH 26/42] Inital --- src/Avalonia.Controls/ColumnDefinitions.cs | 2 +- src/Avalonia.Controls/GridLength.cs | 13 +++--- src/Avalonia.Controls/RowDefinitions.cs | 2 +- src/Avalonia.Visuals/CornerRadius.cs | 40 +++++++++---------- src/Avalonia.Visuals/Matrix.cs | 4 +- src/Avalonia.Visuals/Point.cs | 5 +-- src/Avalonia.Visuals/Rect.cs | 5 +-- src/Avalonia.Visuals/RelativePoint.cs | 9 ++--- src/Avalonia.Visuals/RelativeRect.cs | 12 +++--- src/Avalonia.Visuals/Size.cs | 5 +-- src/Avalonia.Visuals/Thickness.cs | 24 ++++++----- .../Converters/CornerRadiusTypeConverter.cs | 2 +- .../Converters/GridLengthTypeConverter.cs | 2 +- .../Converters/MatrixTypeConverter.cs | 2 +- .../Converters/PointTypeConverter.cs | 2 +- .../Converters/PointsListTypeConverter.cs | 2 +- .../Converters/RectTypeConverter.cs | 2 +- .../Converters/RelativePointTypeConverter.cs | 2 +- .../Converters/RelativeRectTypeConverter.cs | 2 +- .../Converters/SizeTypeConverter.cs | 2 +- .../Converters/ThicknessTypeConverter.cs | 2 +- .../GridLengthTests.cs | 18 ++++----- .../CornerRadiusTests.cs | 8 ++-- .../Media/MatrixTests.cs | 2 +- .../Media/RectTests.cs | 2 +- .../RelativePointTests.cs | 4 +- .../RelativeRectTests.cs | 6 +-- .../ThicknessTests.cs | 8 ++-- 28 files changed, 91 insertions(+), 98 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs index 32fe5a60345..972655262a9 100644 --- a/src/Avalonia.Controls/ColumnDefinitions.cs +++ b/src/Avalonia.Controls/ColumnDefinitions.cs @@ -27,7 +27,7 @@ public ColumnDefinitions() 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))); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index 789953a249a..8a079b9837b 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -180,9 +180,8 @@ public override string ToString() /// Parses a string to return a . /// /// The string. - /// The current culture. /// The . - public static GridLength Parse(string s, CultureInfo culture) + public static GridLength Parse(string s) { s = s.ToUpperInvariant(); @@ -193,12 +192,12 @@ public static GridLength Parse(string s, CultureInfo culture) 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); } } @@ -209,13 +208,13 @@ public static GridLength Parse(string s, CultureInfo culture) /// The string. /// The current culture. /// The . - public static IEnumerable ParseLengths(string s, CultureInfo culture) + public static IEnumerable 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); } } } diff --git a/src/Avalonia.Controls/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs index e677492580a..2dfad7111a8 100644 --- a/src/Avalonia.Controls/RowDefinitions.cs +++ b/src/Avalonia.Controls/RowDefinitions.cs @@ -27,7 +27,7 @@ public RowDefinitions() 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))); } } } \ No newline at end of file diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index db0ac97d492..33a553d4775 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/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 @@ public override string ToString() 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 @@ public static CornerRadius Parse(string s, CultureInfo culture) { 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); } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 70804ee04f4..4336d24de82 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -316,9 +316,9 @@ public Matrix Invert() /// The string. /// The current culture. /// The . - 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(), diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index d0c3edfeb2f..62be3bf2765 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -170,11 +170,10 @@ public static implicit operator Vector(Point p) /// Parses a string. /// /// The string. - /// The current culture. /// The . - 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(), diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 748928ada3b..73021ca29a5 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -487,11 +487,10 @@ public override string ToString() /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - 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(), diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index a2ef0e6725b..e4d7ea05cb2 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -154,11 +154,10 @@ public Point ToPixels(Size size) /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - 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 @@ public static RelativePoint Parse(string s, CultureInfo culture) } 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); } } diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index c13c3282dbb..6929a8f71e3 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -169,9 +169,9 @@ public Rect ToPixels(Size size) /// The string. /// The current culture. /// The parsed . - 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 +202,10 @@ public static RelativeRect Parse(string s, CultureInfo culture) } 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); } } diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index c5eaa33b411..b6889af6f03 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -150,11 +150,10 @@ public Size(double width, double height) /// Parses a string. /// /// The string. - /// The current culture. /// The . - 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(), diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index a5ca0a04a84..43a5fed9e76 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -165,25 +165,27 @@ public Thickness(double left, double top, double right, double bottom) /// Parses a string. /// /// The string. - /// The current culture. /// The . - 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."); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs index 5da0efae1b4..d8c1ecd4ebf 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs @@ -13,7 +13,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return CornerRadius.Parse((string)value, culture); + return CornerRadius.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs index 1f72ca325cf..05f3bed04d1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs @@ -18,7 +18,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return GridLength.Parse((string)value, culture); + return GridLength.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs index c477ff5637d..fec6e31771f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs @@ -17,7 +17,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Matrix.Parse((string)value, culture); + return Matrix.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs index 1381fe7a757..9ca7212c684 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs @@ -17,7 +17,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Point.Parse((string)value, culture); + return Point.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs index b6c6da30555..29c7dbfd39e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs @@ -23,7 +23,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c var result = new List(pointStrs.Length); foreach (var pointStr in pointStrs) { - result.Add(Point.Parse(pointStr, culture)); + result.Add(Point.Parse(pointStr)); } return result; diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs index c9c6462f896..0946f95938c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs @@ -17,7 +17,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Rect.Parse((string)value, culture); + return Rect.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs index f68b8d66e6e..b3178f1496d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs @@ -17,7 +17,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return RelativePoint.Parse((string)value, culture); + return RelativePoint.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs index 64e39e224ae..38c28338152 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs @@ -17,7 +17,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return RelativeRect.Parse((string)value, culture); + return RelativeRect.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs index 73fef9ab1fe..535e7948d59 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs @@ -17,7 +17,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Size.Parse((string)value, culture); + return Size.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs index 6ca5ec2f669..3a7652a1538 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs @@ -17,7 +17,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Thickness.Parse((string)value, culture); + return Thickness.Parse((string)value); } } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs index 0a811333d45..ab4da0ca7e0 100644 --- a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs @@ -13,7 +13,7 @@ public class GridLengthTests [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 @@ public void Parse_Should_Parse_Auto() [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 @@ public void Parse_Should_Parse_Auto_Lowercase() [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 @@ public void Parse_Should_Parse_Star() [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 @@ public void Parse_Should_Parse_Star_Value() [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 @@ public void Parse_Should_Parse_Pixel_Value() [Fact] public void Parse_Should_Throw_FormatException_For_Invalid_String() { - Assert.Throws(() => GridLength.Parse("2x", CultureInfo.InvariantCulture)); + Assert.Throws(() => 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 @@ public void ParseLengths_Accepts_Comma_Separators() [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 @@ public void ParseLengths_Accepts_Space_Separators() [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[] diff --git a/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs index bc0bbdc867c..56f99074095 100644 --- a/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs @@ -11,7 +11,7 @@ public class CornerRadiusTests [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 @@ public void Parse_Parses_Single_Uniform_Radius() [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 @@ public void Parse_Parses_Top_Bottom() [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 @@ public void Parse_Parses_TopLeft_TopRight_BottomRight_BottomLeft() [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); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs index 4c1e361952a..ff1d17164ec 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs @@ -8,7 +8,7 @@ public class MatrixTests [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); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs index 12070bfed34..cd0c7e1ace3 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs @@ -8,7 +8,7 @@ public class RectTests [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); } diff --git a/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs b/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs index f4a21fb6b49..b9eecc809fe 100644 --- a/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs @@ -11,7 +11,7 @@ public class RelativePointTests [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 @@ public void Parse_Should_Accept_Absolute_Value() [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); } diff --git a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs index 9f25dcd413f..68a0df2d9b1 100644 --- a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs @@ -14,7 +14,7 @@ public class RelativeRectTests [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 @@ public void Parse_Should_Accept_Absolute_Value() [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 @@ public void Parse_Should_Accept_Relative_Value() public void Parse_Should_Throw_Mixed_Values() { Assert.Throws(() => - RelativeRect.Parse("10%, 20%, 40, 70%", CultureInfo.InvariantCulture)); + RelativeRect.Parse("10%, 20%, 40, 70%")); } } } diff --git a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs index 03bf395d1e6..ac4c6bc781f 100644 --- a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs @@ -11,7 +11,7 @@ public class ThicknessTests [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 @@ public void Parse_Parses_Single_Uniform_Size() [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 @@ public void Parse_Parses_Horizontal_Vertical() [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 @@ public void Parse_Parses_Left_Top_Right_Bottom() [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); } From 4e4c49b1e32db6b74dae1cf93a45d3e33888db47 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 2 May 2018 16:53:12 +0200 Subject: [PATCH 27/42] fix comments --- src/Avalonia.Visuals/Matrix.cs | 1 - src/Avalonia.Visuals/RelativeRect.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 4336d24de82..b09ea2b68c0 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -314,7 +314,6 @@ public Matrix Invert() /// Parses a string. /// /// The string. - /// The current culture. /// The . public static Matrix Parse(string s) { diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index 6929a8f71e3..ad42e300574 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -167,7 +167,6 @@ public Rect ToPixels(Size size) /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . public static RelativeRect Parse(string s) { From f2e01704b2c48406dfadd9f1eea132375e2081b1 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 2 May 2018 19:35:38 +0200 Subject: [PATCH 28/42] comment removed --- src/Avalonia.Controls/GridLength.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index 8a079b9837b..608879812c6 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -206,7 +206,6 @@ public static GridLength Parse(string s) /// Parses a string to return a collection of s. /// /// The string. - /// The current culture. /// The . public static IEnumerable ParseLengths(string s) { From 35141713383d0395b005f48df8ef247cdcd210e8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 May 2018 23:00:10 +0200 Subject: [PATCH 29/42] Added PR template. --- .github/PULL_REQUEST_TEMPLATE.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..9a0da4aa9b5 --- /dev/null +++ b/.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 \ No newline at end of file From 1f4d21244a3c4cd50ed16ca5990ddb3ec4983530 Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 4 May 2018 07:35:34 +0200 Subject: [PATCH 30/42] register `DragSource` after the UI thread has set due to the fact that the UI-Thread is unknown, there is no OLEContext available and the platform specific DragSource will never be used. --- src/Windows/Avalonia.Win32/Win32Platform.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 902abaf65bb..f06f951d744 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -86,11 +86,11 @@ public static void Initialize(bool deferredRendering = true) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); - if (OleContext.Current != null) - AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - UseDeferredRendering = deferredRendering; _uiThread = UnmanagedMethods.GetCurrentThreadId(); + + if (OleContext.Current != null) + AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); } public bool HasMessages() From 1e2e5b5efce4be41f5364bc07a7800db2e0c2d83 Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 4 May 2018 07:57:40 +0200 Subject: [PATCH 31/42] fixes #1537 since the `System.Runtime.InteropServices.ComTypes.IDataObject` of .net core is missing the GUID, any OLE-Calls with this type will fail. --- src/Windows/Avalonia.Win32/DataObject.cs | 1 - .../Interop/UnmanagedMethods.cs | 27 ++++++++++++++++--- src/Windows/Avalonia.Win32/OleDataObject.cs | 5 ++-- src/Windows/Avalonia.Win32/OleDropTarget.cs | 1 - 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index 34867765e5b..13d5f662c2e 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -7,7 +7,6 @@ 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; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index aa86ab0f8d5..c5ef006b84f 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -974,7 +974,7 @@ public static extern IntPtr CreateFileMapping(IntPtr hFile, 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 +1366,13 @@ internal enum DropEffect : int 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 +1387,27 @@ internal interface IDropSource } + [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 { diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 85d1daadebb..d7b663e7bfc 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -8,15 +8,14 @@ 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; } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 500c03e3176..973564a3d1b 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -3,7 +3,6 @@ using Avalonia.Platform; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Input.IDataObject; -using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { From 5a5fc13632ae0f6110d1b0a7243cc30cbb80468b Mon Sep 17 00:00:00 2001 From: boombuler Date: Fri, 4 May 2018 08:50:30 +0200 Subject: [PATCH 32/42] force mousecursor if the mouse is over the window without the call of SetCursor the cursor will only be updated once the mouse is moved. this fixes #1536 --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 3 +++ src/Windows/Avalonia.Win32/WindowImpl.cs | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index aa86ab0f8d5..9b344f1bb40 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -849,6 +849,9 @@ public static IntPtr SetClassLong(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwN 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); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bb3c4cf6e62..c3fab0e6b97 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -383,8 +383,11 @@ public virtual IDisposable ShowDialog() 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) From fee44d59a27596efbbf829a4709efdcb427c4520 Mon Sep 17 00:00:00 2001 From: Amadeusz Sadowski Date: Fri, 4 May 2018 15:32:07 +0200 Subject: [PATCH 33/42] Cleanup AvaloniaXamlObjectWriter factory methods --- .../PortableXaml/AvaloniaXamlObjectWriter.cs | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs index 509c52503b5..240ca291a88 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs @@ -9,28 +9,10 @@ namespace Avalonia.Markup.Xaml.PortableXaml { public class AvaloniaXamlObjectWriter : XamlObjectWriter { - public static AvaloniaXamlObjectWriter Create( - XamlSchemaContext schemaContext, - AvaloniaXamlContext context) - { - var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance }; - - var writerSettings = new XamlObjectWriterSettings() - { - ExternalNameScope = nameScope, - RegisterNamesOnExternalNamescope = true, - RootObjectInstance = context?.RootInstance - }; - - return new AvaloniaXamlObjectWriter(schemaContext, - writerSettings.WithContext(context), - nameScope); - } - public static AvaloniaXamlObjectWriter Create( XamlSchemaContext schemaContext, AvaloniaXamlContext context, - IAmbientProvider parentAmbientProvider) + IAmbientProvider parentAmbientProvider = null) { var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance }; @@ -55,7 +37,7 @@ private AvaloniaXamlObjectWriter( XamlSchemaContext schemaContext, XamlObjectWriterSettings settings, AvaloniaNameScope nameScope, - IAmbientProvider parentAmbientProvider = null) + IAmbientProvider parentAmbientProvider) : base(schemaContext, settings, parentAmbientProvider) { _nameScope = nameScope; From c7f7640ae711bcda44bc536ac5f52d26ab38c8a3 Mon Sep 17 00:00:00 2001 From: Amadeusz Sadowski Date: Fri, 4 May 2018 15:51:44 +0200 Subject: [PATCH 34/42] Removed comments about not working parent ambient providers --- .../MarkupExtensions/StaticResourceExtension.cs | 7 ------- .../MarkupExtensions/StaticResourceExtensionTests.cs | 4 +--- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index 9089a136560..8e71c5f81b2 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -36,13 +36,6 @@ public override object ProvideValue(IServiceProvider serviceProvider) // 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 diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index e6116ed5631..8615efd9670 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -361,9 +361,7 @@ public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Sty 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()); } } From 1ee4fa47f925016e9325e48906cbfa88492a69dc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 May 2018 22:46:37 +0200 Subject: [PATCH 35/42] Tweak IAssetLoader API and fix tests. --- src/Avalonia.Base/Platform/IAssetLoader.cs | 16 +++++++++++++++- .../Avalonia.Markup.Xaml.csproj | 2 +- ...aderPortableXaml.cs => AvaloniaXamlLoader.cs} | 10 +++++----- src/Shared/PlatformSupport/AssetLoader.cs | 13 ++++++++----- tests/Avalonia.UnitTests/MockAssetLoader.cs | 4 ++-- 5 files changed, 31 insertions(+), 14 deletions(-) rename src/Markup/Avalonia.Markup.Xaml/{AvaloniaXamlLoaderPortableXaml.cs => AvaloniaXamlLoader.cs} (96%) diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index 32008ffd071..ba30af60bfd 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -44,6 +44,20 @@ public interface IAssetLoader /// Stream Open(Uri uri, Uri baseUri = null); - Tuple OpenWithAssembly(Uri uri, Uri baseUri = null); + /// + /// Opens the resource with the requested URI and returns the resource string and the + /// assembly containing the resource. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The resource was not found. + /// + Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 62c23543fdf..2e8771082cd 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -29,7 +29,7 @@ Properties\SharedAssemblyInfo.cs - + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs similarity index 96% rename from src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs rename to src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index e057aa3b537..5047ce5e61c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -125,12 +125,12 @@ public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); } - var asset = assetLocator.OpenWithAssembly(uri, baseUri); - using (var stream = asset.Item2) + var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); + using (var stream = asset.Item1) { try { - return Load(stream, asset.Item1, rootInstance, uri); + return Load(stream, asset.Item2, rootInstance, uri); } catch (Exception e) { @@ -153,7 +153,7 @@ public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) /// The optional instance into which the XAML should be loaded. /// /// The loaded object. - public object Load(string xaml, Assembly localAssembly, object rootInstance = null) + public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null) { Contract.Requires(xaml != null); @@ -230,7 +230,7 @@ private static IEnumerable GetUrisFor(Type type) public static object Parse(string xaml, Assembly localAssembly = null) => new AvaloniaXamlLoader().Load(xaml, localAssembly); - public static T Parse(string xaml, Assembly localAssembly) + public static T Parse(string xaml, Assembly localAssembly = null) => (T)Parse(xaml, localAssembly); } } \ No newline at end of file diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index eb9b04fe546..fa11edb57bf 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -67,20 +67,23 @@ public bool Exists(Uri uri, Uri baseUri = null) /// /// The resource was not found. /// - public Stream Open(Uri uri, Uri baseUri = null) => OpenWithAssembly(uri, baseUri).Item2; + public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; /// - /// Opens the resource with the requested URI. + /// Opens the resource with the requested URI and returns the resource string and the + /// assembly containing the resource. /// /// The URI. /// /// A base URI to use if is relative. /// - /// An assembly (optional) and a stream containing the resource contents. + /// + /// The stream containing the resource contents together with the assembly. + /// /// /// The resource was not found. /// - public Tuple OpenWithAssembly(Uri uri, Uri baseUri = null) + public Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null) { var asset = GetAsset(uri, baseUri); @@ -89,7 +92,7 @@ public Tuple OpenWithAssembly(Uri uri, Uri baseUri = null) throw new FileNotFoundException($"The resource {uri} could not be found."); } - return Tuple.Create(asset.Assembly, asset.GetStream()); + return Tuple.Create(asset.GetStream(), asset.Assembly); } private IAssetDescriptor GetAsset(Uri uri, Uri baseUri) diff --git a/tests/Avalonia.UnitTests/MockAssetLoader.cs b/tests/Avalonia.UnitTests/MockAssetLoader.cs index 3cf7370f1fb..d6b70aee16e 100644 --- a/tests/Avalonia.UnitTests/MockAssetLoader.cs +++ b/tests/Avalonia.UnitTests/MockAssetLoader.cs @@ -28,9 +28,9 @@ public Stream Open(Uri uri, Uri baseUri = null) return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri])); } - public Tuple OpenWithAssembly(Uri uri, Uri baseUri = null) + public Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null) { - return Tuple.Create((Assembly) null, Open(uri, baseUri)); + return Tuple.Create(Open(uri, baseUri), (Assembly)null); } public void SetDefaultAssembly(Assembly asm) From 9b24d244b53ece6b2b18730fd5de3c0a8b492bbb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 May 2018 23:06:37 +0200 Subject: [PATCH 36/42] Remove now-unneeded assembly references. --- samples/BindingTest/MainWindow.xaml | 4 ++-- samples/ControlCatalog/DecoratedWindow.xaml | 4 ++-- samples/ControlCatalog/MainView.xaml | 2 +- samples/RenderTest/MainWindow.xaml | 2 +- src/Avalonia.Diagnostics/Views/TreePageView.xaml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml index 3547e331817..b0a4a5b7eda 100644 --- a/samples/BindingTest/MainWindow.xaml +++ b/samples/BindingTest/MainWindow.xaml @@ -1,6 +1,6 @@ + xmlns:vm="clr-namespace:BindingTest.ViewModels" + xmlns:local="clr-namespace:BindingTest">