From 1b0ff07133851b32f853d6815a7cc17612475f0e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 13 Nov 2020 13:21:23 +0100 Subject: [PATCH 01/35] Fixes Issue 4980: Message loop exit when GetMessage < 0 --- src/Avalonia.Base/Logging/LogArea.cs | 5 +++++ .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 4 ++-- src/Windows/Avalonia.Win32/Win32Platform.cs | 11 ++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs index 3c19b47a05..ce330247f3 100644 --- a/src/Avalonia.Base/Logging/LogArea.cs +++ b/src/Avalonia.Base/Logging/LogArea.cs @@ -34,5 +34,10 @@ namespace Avalonia.Logging /// The log event comes from the control system. /// public const string Control = "Control"; + + /// + /// The log evevnt come from Win32Platform + /// + public const string Win32Platform = nameof(Win32Platform); } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f88c57cf59..85938829f6 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -931,8 +931,8 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", EntryPoint = "MapVirtualKeyW")] public static extern uint MapVirtualKey(uint uCode, uint uMapType); - [DllImport("user32.dll", EntryPoint = "GetMessageW")] - public static extern sbyte GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); + [DllImport("user32.dll", EntryPoint = "GetMessageW",SetLastError = true)] + public static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); [DllImport("user32.dll")] public static extern int GetMessageTime(); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 5b16cae26e..209cb63603 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -137,13 +137,18 @@ namespace Avalonia.Win32 public void RunLoop(CancellationToken cancellationToken) { - while (!cancellationToken.IsCancellationRequested) + var result = 0; + while (!cancellationToken.IsCancellationRequested + && (result = UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0)) > 0) { - UnmanagedMethods.MSG msg; - UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0); UnmanagedMethods.TranslateMessage(ref msg); UnmanagedMethods.DispatchMessage(ref msg); } + if (result < 0) + { + Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.Win32Platform) + ?.Log(this, "Unmanaged error in message loop. Error Code: {0}", Marshal.GetLastWin32Error()); + } } public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action callback) From b31b6d68c9791085766550cdc29bcde12b1608a0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 13 Nov 2020 16:17:03 +0100 Subject: [PATCH 02/35] Fixes ProcessMessage --- src/Windows/Avalonia.Win32/Win32Platform.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 209cb63603..1ed4d8dc9c 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -129,10 +129,18 @@ namespace Avalonia.Win32 public void ProcessMessage() { - UnmanagedMethods.MSG msg; - UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0); - UnmanagedMethods.TranslateMessage(ref msg); - UnmanagedMethods.DispatchMessage(ref msg); + + if (UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0) > 0) + { + UnmanagedMethods.TranslateMessage(ref msg); + UnmanagedMethods.DispatchMessage(ref msg); + } + else + { + Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.Win32Platform) + ?.Log(this, "Unmanaged error in {0}. Error Code: {1}", nameof(ProcessMessage), Marshal.GetLastWin32Error()); + + } } public void RunLoop(CancellationToken cancellationToken) @@ -147,7 +155,7 @@ namespace Avalonia.Win32 if (result < 0) { Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.Win32Platform) - ?.Log(this, "Unmanaged error in message loop. Error Code: {0}", Marshal.GetLastWin32Error()); + ?.Log(this, "Unmanaged error in {0}. Error Code: {1}" ,nameof(RunLoop), Marshal.GetLastWin32Error()); } } From cfb81aedcaae7d501017a4224f18ffc8348ae4de Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 14 Nov 2020 00:44:44 +0100 Subject: [PATCH 03/35] fixes typo. --- src/Avalonia.Base/Logging/LogArea.cs | 2 +- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs index ce330247f3..2ad220dddd 100644 --- a/src/Avalonia.Base/Logging/LogArea.cs +++ b/src/Avalonia.Base/Logging/LogArea.cs @@ -36,7 +36,7 @@ namespace Avalonia.Logging public const string Control = "Control"; /// - /// The log evevnt come from Win32Platform + /// The log event comes from Win32Platform. /// public const string Win32Platform = nameof(Win32Platform); } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 85938829f6..b164bcc611 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -931,7 +931,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", EntryPoint = "MapVirtualKeyW")] public static extern uint MapVirtualKey(uint uCode, uint uMapType); - [DllImport("user32.dll", EntryPoint = "GetMessageW",SetLastError = true)] + [DllImport("user32.dll", EntryPoint = "GetMessageW", SetLastError = true)] public static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); [DllImport("user32.dll")] diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 1ed4d8dc9c..345c825a5d 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -155,7 +155,7 @@ namespace Avalonia.Win32 if (result < 0) { Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.Win32Platform) - ?.Log(this, "Unmanaged error in {0}. Error Code: {1}" ,nameof(RunLoop), Marshal.GetLastWin32Error()); + ?.Log(this, "Unmanaged error in {0}. Error Code: {1}", nameof(RunLoop), Marshal.GetLastWin32Error()); } } From 7ff860c8ef54c5ce38b2e6c672297e651c1142c2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 14 Nov 2020 14:40:21 +0100 Subject: [PATCH 04/35] fix ProcessMessage issue --- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 345c825a5d..e854ceae0b 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -130,7 +130,7 @@ namespace Avalonia.Win32 public void ProcessMessage() { - if (UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0) > 0) + if (UnmanagedMethods.GetMessage(out var msg, IntPtr.Zero, 0, 0) > -1) { UnmanagedMethods.TranslateMessage(ref msg); UnmanagedMethods.DispatchMessage(ref msg); From 80a87d7aa31a8b177690207d3d043e90d68f093e Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 15 Nov 2020 17:32:43 +0200 Subject: [PATCH 05/35] add failing tests for CompiledBidning with Source set to StaticResource and x:Static --- .../CompiledBindingExtensionTests.cs | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 50dfa2c7b0..c19640c244 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -10,6 +10,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Markup.Data; +using Avalonia.Media; using Avalonia.UnitTests; using XamlX; using Xunit; @@ -536,7 +537,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } [Fact] - public void ResolvesSourceBindingLongForm() + public void Binds_To_Source() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -559,6 +560,49 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Binds_To_Source_StaticResource() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + Assert.Equal("foobar", textBlock.Text); + } + } + + [Fact] + public void Binds_To_Source_xStatic() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var contentControl = window.FindControl("contentControl"); + + Assert.Equal(Brushes.Red.Color, contentControl.Content); + } + } + [Fact] public void CompilesBindingWhenRequested() { From 06d81f6e23cde111166393f441558dc1354247df Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 15 Nov 2020 17:40:34 +0200 Subject: [PATCH 06/35] make CompiledBinding work with x:Static and StaticResource --- .../AvaloniaXamlIlBindingPathTransformer.cs | 33 ++++++++++++++++--- ...ransformSyntheticCompiledBindingMembers.cs | 4 +-- .../CompiledBindingExtension.cs | 10 ++++++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs index fc32084687..0d54e97bd9 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs @@ -15,14 +15,37 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { if (node is XamlAstConstructableObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)) { - IXamlType startType; - var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault(); - if (parentDataContextNode is null) + IXamlType startType = null; + var sourceProperty = binding.Children.OfType().FirstOrDefault(c => c.Property.Name == "Source"); + if ((sourceProperty?.Values.Count ?? 0) == 1) { - throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding); + var sourceValue = sourceProperty.Values[0]; + switch (sourceValue) + { + case XamlAstTextNode textNode: + startType = textNode.Type?.GetClrType(); + break; + + case XamlMarkupExtensionNode extension: + startType = extension.Type?.GetClrType(); + break; + + case XamlStaticExtensionNode staticExtension: + startType = staticExtension.Type?.GetClrType(); + break; + } } - startType = parentDataContextNode.DataContextType; + if (startType == null) + { + var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault(); + if (parentDataContextNode is null) + { + throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding); + } + + startType = parentDataContextNode.DataContextType; + } XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startType); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs index 154c6a235c..6cb6e4e2d1 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs @@ -27,8 +27,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } else if (prop.Name == "Source") { - return new AvaloniaSyntheticCompiledBindingProperty(node, - SyntheticCompiledBindingPropertyName.Source); + //return new AvaloniaSyntheticCompiledBindingProperty(node, + // SyntheticCompiledBindingPropertyName.Source); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index da39920eb3..17d2ea7ae9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -32,6 +32,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Mode = Mode, Priority = Priority, StringFormat = StringFormat, + Source = Source, DefaultAnchor = new WeakReference(GetDefaultAnchor(provider)) }; } @@ -52,6 +53,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation) { + if (Source != null) + { + return CreateSourceObserver( + Source, + Path.BuildExpression(enableDataValidation)); + } + if (Path.RawSource != null) { return CreateSourceObserver( @@ -77,5 +85,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions [ConstructorArgument("path")] public CompiledBindingPath Path { get; set; } + + public object Source { get; set; } } } From f9de2a9c8236573df5ffe8dd0acc4417f3e43665 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 15 Nov 2020 17:42:05 +0200 Subject: [PATCH 07/35] add more tests for CompiledBinding Source with StaticResource --- .../CompiledBindingExtensionTests.cs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index c19640c244..de9ecc8bb0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -583,6 +583,81 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Binds_To_Source_StaticResource1() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + test + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + Assert.Equal("foobar", textBlock.Text); + } + } + + [Fact] + public void Binds_To_Source_StaticResource_In_ResourceDictionary() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + Assert.Equal("foobar", textBlock.Text); + } + } + + [Fact] + public void Binds_To_Source_StaticResource_In_ResourceDictionary1() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + test + + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + Assert.Equal("foobar", textBlock.Text); + } + } + [Fact] public void Binds_To_Source_xStatic() { From 13c25983f70d2c606eaaa5d83f318413bf65773b Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 15 Nov 2020 17:45:04 +0200 Subject: [PATCH 08/35] try infer StaticResource type in CompiledBinding Soure --- .../AvaloniaXamlIlBindingPathTransformer.cs | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs index 0d54e97bd9..4d8c940bbc 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using XamlX; using XamlX.Ast; using XamlX.Transform; using XamlX.TypeSystem; @@ -28,6 +26,71 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers case XamlMarkupExtensionNode extension: startType = extension.Type?.GetClrType(); + + //let's try to infer StaticResource type from parent resources in xaml + if (extension.Value.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.MarkupExtensions.StaticResourceExtension" && + extension.Value is XamlAstConstructableObjectNode cn && + cn.Arguments.Count == 1 && cn.Arguments[0] is XamlAstTextNode keyNode) + { + bool matchProperty(IXamlAstNode node, IXamlType styledElementType, string propertyName) + { + return (node is XamlPropertyAssignmentNode p && + p.Property.DeclaringType == styledElementType && p.Property.Name == propertyName) + || + (node is XamlManipulationGroupNode m && m.Children.Count > 0 && + m.Children[0] is XamlPropertyAssignmentNode pm && + pm.Property.DeclaringType == styledElementType && pm.Property.Name == propertyName); + } + + string getResourceValue_xKey(XamlPropertyAssignmentNode node) + => node.Values.Count == 2 && node.Values[0] is XamlAstTextNode t ? t.Text : ""; + + IXamlType getResourceValue_Type(XamlPropertyAssignmentNode node, IXamlType xamlType) + => node.Values.Count == 2 ? node.Values[1].Type.GetClrType() : xamlType; + + IEnumerable getResourceValues(IXamlAstNode node) + { + if (node is XamlPropertyAssignmentNode propertyNode) + { + if (propertyNode.Values.Count == 1 && + propertyNode.Values[0] is XamlAstConstructableObjectNode obj && + obj.Type.GetClrType().FullName == "Avalonia.Controls.ResourceDictionary") + { + foreach (var r in obj.Children.SelectMany(c => getResourceValues(c))) + { + yield return r; + } + } + else + { + yield return propertyNode; + } + } + else if (node is XamlManipulationGroupNode m) + { + foreach (var r in m.Children.OfType()) + { + yield return r; + } + } + } + + string key = keyNode.Text; + + var styledElement = context.GetAvaloniaTypes().StyledElement; + var resource = context.ParentNodes() + .OfType() + .Where(o => styledElement.IsAssignableFrom(o.Type.GetClrType())) + .Select(o => o.Children.FirstOrDefault(p => matchProperty(p, styledElement, "Resources"))) + .Where(r => r != null) + .SelectMany(r => getResourceValues(r)) + .FirstOrDefault(r => getResourceValue_xKey(r) == key); + + if (resource != null) + { + startType = getResourceValue_Type(resource, startType); + } + } break; case XamlStaticExtensionNode staticExtension: From bc39305fd0a088a306e8c13358fa8fc426fe9fc0 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 15 Nov 2020 18:16:58 +0200 Subject: [PATCH 09/35] clean up --- .../AvaloniaXamlIlBindingPathParser.cs | 23 ++++++------------- ...ransformSyntheticCompiledBindingMembers.cs | 8 +------ 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs index 7944d8b569..24cded1d22 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs @@ -72,16 +72,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers v.Property is AvaloniaSyntheticCompiledBindingProperty prop && prop.Name == SyntheticCompiledBindingPropertyName.ElementName); - var sourceProperty = syntheticCompiledBindingProperties - .FirstOrDefault(v => - v.Property is AvaloniaSyntheticCompiledBindingProperty prop - && prop.Name == SyntheticCompiledBindingPropertyName.Source); - var relativeSourceProperty = syntheticCompiledBindingProperties .FirstOrDefault(v => v.Property is AvaloniaSyntheticCompiledBindingProperty prop && prop.Name == SyntheticCompiledBindingPropertyName.RelativeSource); + var sourceProperty = binding.Children.OfType() + .FirstOrDefault(v => + v.Property is XamlAstClrProperty prop + && prop.Name == "Source"); + if (elementNameProperty?.Values[0] is XamlAstTextNode elementName) { convertedNode = new BindingExpressionGrammar.NameNode { Name = elementName.Text }; @@ -91,14 +91,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]); } - if (sourceProperty?.Values[0] != null) + if (sourceProperty != null && convertedNode != null) { - if (convertedNode != null) - { - throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding); - } - - convertedNode = new RawSourceBindingExpressionNode(sourceProperty?.Values[0]); + throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding); } if (GetRelativeSourceObjectFromAssignment( @@ -223,10 +218,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { binding.Children.Remove(elementNameProperty); } - if (sourceProperty != null) - { - binding.Children.Remove(sourceProperty); - } if (relativeSourceProperty != null) { binding.Children.Remove(relativeSourceProperty); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs index 6cb6e4e2d1..ba05347a52 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs @@ -25,11 +25,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return new AvaloniaSyntheticCompiledBindingProperty(node, SyntheticCompiledBindingPropertyName.RelativeSource); } - else if (prop.Name == "Source") - { - //return new AvaloniaSyntheticCompiledBindingProperty(node, - // SyntheticCompiledBindingPropertyName.Source); - } } return node; @@ -39,8 +34,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers enum SyntheticCompiledBindingPropertyName { ElementName, - RelativeSource, - Source + RelativeSource } class AvaloniaSyntheticCompiledBindingProperty : XamlAstNode, IXamlAstPropertyReference From 303b6c15866fb61ae2e17093ddf5e85acc670ffb Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 16 Nov 2020 23:25:13 +0300 Subject: [PATCH 10/35] Remove ReactiveUI usage from sample apps --- Avalonia.sln | 27 +++++ samples/BindingDemo/App.xaml.cs | 2 - samples/BindingDemo/BindingDemo.csproj | 3 +- .../ViewModels/ExceptionErrorViewModel.cs | 4 +- .../ViewModels/IndeiErrorViewModel.cs | 4 +- .../ViewModels/MainWindowViewModel.cs | 12 +- .../ViewModels/NestedCommandViewModel.cs | 8 +- samples/BindingDemo/ViewModels/TestItem.cs | 4 +- samples/ControlCatalog.Desktop/Program.cs | 4 +- samples/ControlCatalog.NetCore/Program.cs | 2 - samples/ControlCatalog/ControlCatalog.csproj | 2 +- .../ControlCatalog/Pages/LabelsPage.axaml.cs | 1 - samples/ControlCatalog/Pages/ListBoxPage.xaml | 2 +- samples/ControlCatalog/Pages/MenuPage.xaml.cs | 1 - .../Pages/NumericUpDownPage.xaml.cs | 4 +- .../Pages/ScrollViewerPage.xaml.cs | 4 +- .../Pages/TabControlPage.xaml.cs | 4 +- .../ViewModels/ContextMenuPageViewModel.cs | 14 +-- .../ViewModels/ItemsRepeaterPageViewModel.cs | 6 +- .../ViewModels/ListBoxPageViewModel.cs | 27 +++-- .../ViewModels/MainWindowViewModel.cs | 28 ++--- .../ViewModels/MenuPageViewModel.cs | 14 +-- .../ViewModels/NotificationViewModel.cs | 10 +- .../ViewModels/SplitViewPageViewModel.cs | 4 +- .../ViewModels/TreeViewPageViewModel.cs | 16 +-- samples/MiniMvvm/MiniCommand.cs | 66 +++++++++++ samples/MiniMvvm/MiniMvvm.csproj | 6 + samples/MiniMvvm/PropertyChangedExtensions.cs | 108 ++++++++++++++++++ samples/MiniMvvm/ViewModelBase.cs | 26 +++++ samples/Previewer/Previewer.csproj | 1 - samples/RenderDemo/App.xaml.cs | 2 - samples/RenderDemo/MainWindow.xaml.cs | 2 +- samples/RenderDemo/RenderDemo.csproj | 3 +- .../ViewModels/AnimationsPageViewModel.cs | 4 +- .../ViewModels/MainWindowViewModel.cs | 17 ++- samples/Sandbox/Program.cs | 2 - samples/Sandbox/Sandbox.csproj | 1 - samples/VirtualizationDemo/Program.cs | 2 - .../ViewModels/ItemViewModel.cs | 4 +- .../ViewModels/MainWindowViewModel.cs | 24 ++-- .../VirtualizationDemo.csproj | 3 +- .../Direct3DInteropSample.csproj | 2 +- .../MainWindowViewModel.cs | 4 +- .../WindowsInteropTest.csproj | 6 +- .../Avalonia.AndroidTestApplication.csproj | 4 - 45 files changed, 349 insertions(+), 145 deletions(-) create mode 100644 samples/MiniMvvm/MiniCommand.cs create mode 100644 samples/MiniMvvm/MiniMvvm.csproj create mode 100644 samples/MiniMvvm/PropertyChangedExtensions.cs create mode 100644 samples/MiniMvvm/ViewModelBase.cs diff --git a/Avalonia.sln b/Avalonia.sln index 74a2dbb94b..75f1dd8407 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -230,6 +230,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroComGenerator", "src\to EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2116,6 +2118,30 @@ Global {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhone.Build.0 = Release|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhone.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhone.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|Any CPU.Build.0 = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhone.ActiveCfg = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhone.Build.0 = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2176,6 +2202,7 @@ Global {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} + {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index eb2da03a7e..8a5364c70b 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -1,7 +1,6 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using Avalonia.ReactiveUI; namespace BindingDemo { @@ -25,7 +24,6 @@ namespace BindingDemo public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .UseReactiveUI() .LogToTrace(); } } diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj index 817023fd71..d898b737a9 100644 --- a/samples/BindingDemo/BindingDemo.csproj +++ b/samples/BindingDemo/BindingDemo.csproj @@ -6,12 +6,11 @@ - + - diff --git a/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs b/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs index 0fe12a8ef7..7de083351e 100644 --- a/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs +++ b/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs @@ -1,9 +1,9 @@ -using ReactiveUI; +using MiniMvvm; using System; namespace BindingDemo.ViewModels { - public class ExceptionErrorViewModel : ReactiveObject + public class ExceptionErrorViewModel : ViewModelBase { private int _lessThan10; diff --git a/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs b/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs index caf75c846c..9ae8d9558f 100644 --- a/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs +++ b/samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs @@ -1,11 +1,11 @@ -using ReactiveUI; +using MiniMvvm; using System; using System.ComponentModel; using System.Collections; namespace BindingDemo.ViewModels { - public class IndeiErrorViewModel : ReactiveObject, INotifyDataErrorInfo + public class IndeiErrorViewModel : ViewModelBase, INotifyDataErrorInfo { private int _maximum = 10; private int _value; diff --git a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs index f0241cad48..18a7a01a69 100644 --- a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs @@ -5,14 +5,14 @@ using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; using System.Threading; -using ReactiveUI; +using MiniMvvm; using Avalonia.Controls; using Avalonia.Metadata; using Avalonia.Controls.Selection; namespace BindingDemo.ViewModels { - public class MainWindowViewModel : ReactiveObject + public class MainWindowViewModel : ViewModelBase { private string _booleanString = "True"; private double _doubleValue = 5.0; @@ -32,13 +32,13 @@ namespace BindingDemo.ViewModels Selection = new SelectionModel { SingleSelect = false }; - ShuffleItems = ReactiveCommand.Create(() => + ShuffleItems = MiniCommand.Create(() => { var r = new Random(); Items.Move(r.Next(Items.Count), 1); }); - StringValueCommand = ReactiveCommand.Create(param => + StringValueCommand = MiniCommand.Create(param => { BooleanFlag = !BooleanFlag; StringValue = param.ToString(); @@ -60,7 +60,7 @@ namespace BindingDemo.ViewModels public ObservableCollection Items { get; } public SelectionModel Selection { get; } - public ReactiveCommand ShuffleItems { get; } + public MiniCommand ShuffleItems { get; } public string BooleanString { @@ -93,7 +93,7 @@ namespace BindingDemo.ViewModels } public IObservable CurrentTimeObservable { get; } - public ReactiveCommand StringValueCommand { get; } + public MiniCommand StringValueCommand { get; } public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel(); public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel(); diff --git a/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs b/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs index 0e9139ab98..1c2222b0b0 100644 --- a/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs +++ b/samples/BindingDemo/ViewModels/NestedCommandViewModel.cs @@ -1,18 +1,18 @@ -using ReactiveUI; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; +using MiniMvvm; namespace BindingDemo.ViewModels { - public class NestedCommandViewModel : ReactiveObject + public class NestedCommandViewModel : ViewModelBase { public NestedCommandViewModel() { - Command = ReactiveCommand.Create(() => { }); + Command = MiniCommand.Create(() => { }); } public ICommand Command { get; } diff --git a/samples/BindingDemo/ViewModels/TestItem.cs b/samples/BindingDemo/ViewModels/TestItem.cs index 5a9f192f58..2f49a3c99f 100644 --- a/samples/BindingDemo/ViewModels/TestItem.cs +++ b/samples/BindingDemo/ViewModels/TestItem.cs @@ -1,8 +1,8 @@ -using ReactiveUI; +using MiniMvvm; namespace BindingDemo.ViewModels { - public class TestItem : ReactiveObject + public class TestItem : ViewModelBase { private string _stringValue = "String Value"; private string _detail; diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 5af646b180..7b8b27fff7 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -3,7 +3,6 @@ using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Platform; -using Avalonia.ReactiveUI; namespace ControlCatalog { @@ -19,8 +18,7 @@ namespace ControlCatalog public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .LogToTrace() - .UsePlatformDetect() - .UseReactiveUI(); + .UsePlatformDetect(); private static void ConfigureAssetAssembly(AppBuilder builder) { diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 675ea2e10f..1dc8c09c0e 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -10,7 +10,6 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Dialogs; using Avalonia.Headless; using Avalonia.LogicalTree; -using Avalonia.ReactiveUI; using Avalonia.Threading; namespace ControlCatalog.NetCore @@ -118,7 +117,6 @@ namespace ControlCatalog.NetCore AllowEglInitialization = true }) .UseSkia() - .UseReactiveUI() .UseManagedSystemDialogs() .LogToTrace(); diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 8a88b89b48..1aa926a2a6 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -24,8 +24,8 @@ - + diff --git a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs index b8503d6ae6..a14978fd2c 100644 --- a/samples/ControlCatalog/Pages/LabelsPage.axaml.cs +++ b/samples/ControlCatalog/Pages/LabelsPage.axaml.cs @@ -2,7 +2,6 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; using ControlCatalog.Models; -using ReactiveUI; namespace ControlCatalog.Pages { diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 3521ad71a9..f515db84d4 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -20,6 +20,6 @@ + SelectionMode="{Binding SelectionMode^}"/> diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 46dbe3dcad..5999510b6c 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -6,7 +6,6 @@ using System.Windows.Input; using Avalonia.Controls; using Avalonia.Markup.Xaml; using ControlCatalog.ViewModels; -using ReactiveUI; namespace ControlCatalog.Pages { diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs index 92da64d87e..31749edf08 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs @@ -6,7 +6,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Markup.Xaml; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.Pages { @@ -26,7 +26,7 @@ namespace ControlCatalog.Pages } - public class NumbersPageViewModel : ReactiveObject + public class NumbersPageViewModel : ViewModelBase { private IList _formats; private FormatObject _selectedFormat; diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs index 36d3768b13..dcd7a88a56 100644 --- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Markup.Xaml; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.Pages { - public class ScrollViewerPageViewModel : ReactiveObject + public class ScrollViewerPageViewModel : ViewModelBase { private bool _allowAutoHide; private ScrollBarVisibility _horizontalScrollVisibility; diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs index a38a3ab4cb..f49b13091b 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -6,7 +6,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Platform; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.Pages { @@ -56,7 +56,7 @@ namespace ControlCatalog.Pages return new Bitmap(assets.Open(new Uri(uri))); } - private class PageViewModel : ReactiveObject + private class PageViewModel : ViewModelBase { private Dock _tabPlacement; diff --git a/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs index 5c2f74d2d5..3f5d0cd93c 100644 --- a/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs @@ -3,7 +3,7 @@ using System.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { @@ -12,9 +12,9 @@ namespace ControlCatalog.ViewModels public Control View { get; set; } public ContextMenuPageViewModel() { - OpenCommand = ReactiveCommand.CreateFromTask(Open); - SaveCommand = ReactiveCommand.Create(Save); - OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + OpenCommand = MiniCommand.CreateFromTask(Open); + SaveCommand = MiniCommand.Create(Save); + OpenRecentCommand = MiniCommand.Create(OpenRecent); MenuItems = new[] { @@ -44,9 +44,9 @@ namespace ControlCatalog.ViewModels } public IReadOnlyList MenuItems { get; set; } - public ReactiveCommand OpenCommand { get; } - public ReactiveCommand SaveCommand { get; } - public ReactiveCommand OpenRecentCommand { get; } + public MiniCommand OpenCommand { get; } + public MiniCommand SaveCommand { get; } + public MiniCommand OpenRecentCommand { get; } public async Task Open() { diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs index f893a6e28e..ee1fa6ae77 100644 --- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs @@ -2,11 +2,11 @@ using System.Collections.ObjectModel; using System.Linq; using Avalonia.Media; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class ItemsRepeaterPageViewModel : ReactiveObject + public class ItemsRepeaterPageViewModel : ViewModelBase { private int _newItemIndex = 1; private int _newGenerationIndex = 0; @@ -59,7 +59,7 @@ namespace ControlCatalog.ViewModels })); } - public class Item : ReactiveObject + public class Item : ViewModelBase { private double _height = double.NaN; diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs index f75bc32105..7f2d6e9572 100644 --- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs @@ -4,18 +4,18 @@ using System.Linq; using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.Selection; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class ListBoxPageViewModel : ReactiveObject + public class ListBoxPageViewModel : ViewModelBase { private bool _multiple; private bool _toggle; private bool _alwaysSelected; private bool _autoScrollToSelectedItem = true; private int _counter; - private ObservableAsPropertyHelper _selectionMode; + private IObservable _selectionMode; public ListBoxPageViewModel() { @@ -29,14 +29,13 @@ namespace ControlCatalog.ViewModels x => x.Toggle, x => x.AlwaysSelected, (m, t, a) => - (m ? SelectionMode.Multiple : 0) | - (t ? SelectionMode.Toggle : 0) | - (a ? SelectionMode.AlwaysSelected : 0)) - .ToProperty(this, x => x.SelectionMode); + (m ? Avalonia.Controls.SelectionMode.Multiple : 0) | + (t ? Avalonia.Controls.SelectionMode.Toggle : 0) | + (a ? Avalonia.Controls.SelectionMode.AlwaysSelected : 0)); - AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem())); + AddItemCommand = MiniCommand.Create(() => Items.Add(GenerateItem())); - RemoveItemCommand = ReactiveCommand.Create(() => + RemoveItemCommand = MiniCommand.Create(() => { var items = Selection.SelectedItems.ToList(); @@ -46,7 +45,7 @@ namespace ControlCatalog.ViewModels } }); - SelectRandomItemCommand = ReactiveCommand.Create(() => + SelectRandomItemCommand = MiniCommand.Create(() => { var random = new Random(); @@ -60,7 +59,7 @@ namespace ControlCatalog.ViewModels public ObservableCollection Items { get; } public SelectionModel Selection { get; } - public SelectionMode SelectionMode => _selectionMode.Value; + public IObservable SelectionMode => _selectionMode; public bool Multiple { @@ -86,9 +85,9 @@ namespace ControlCatalog.ViewModels set => this.RaiseAndSetIfChanged(ref _autoScrollToSelectedItem, value); } - public ReactiveCommand AddItemCommand { get; } - public ReactiveCommand RemoveItemCommand { get; } - public ReactiveCommand SelectRandomItemCommand { get; } + public MiniCommand AddItemCommand { get; } + public MiniCommand RemoveItemCommand { get; } + public MiniCommand SelectRandomItemCommand { get; } private string GenerateItem() => $"Item {_counter++.ToString()}"; } diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 4356a032fa..4b3cfa9c9d 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -5,11 +5,11 @@ using Avalonia.Controls.Notifications; using Avalonia.Dialogs; using Avalonia.Platform; using System; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - class MainWindowViewModel : ReactiveObject + class MainWindowViewModel : ViewModelBase { private IManagedNotificationManager _notificationManager; @@ -27,22 +27,22 @@ namespace ControlCatalog.ViewModels { _notificationManager = notificationManager; - ShowCustomManagedNotificationCommand = ReactiveCommand.Create(() => + ShowCustomManagedNotificationCommand = MiniCommand.Create(() => { NotificationManager.Show(new NotificationViewModel(NotificationManager) { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" }); }); - ShowManagedNotificationCommand = ReactiveCommand.Create(() => + ShowManagedNotificationCommand = MiniCommand.Create(() => { NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information)); }); - ShowNativeNotificationCommand = ReactiveCommand.Create(() => + ShowNativeNotificationCommand = MiniCommand.Create(() => { NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error)); }); - AboutCommand = ReactiveCommand.CreateFromTask(async () => + AboutCommand = MiniCommand.CreateFromTask(async () => { var dialog = new AboutAvaloniaDialog(); @@ -51,12 +51,12 @@ namespace ControlCatalog.ViewModels await dialog.ShowDialog(mainWindow); }); - ExitCommand = ReactiveCommand.Create(() => + ExitCommand = MiniCommand.Create(() => { (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown(); }); - ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() => + ToggleMenuItemCheckedCommand = MiniCommand.Create(() => { IsMenuItemChecked = !IsMenuItemChecked; }); @@ -153,16 +153,16 @@ namespace ControlCatalog.ViewModels set { this.RaiseAndSetIfChanged(ref _isMenuItemChecked, value); } } - public ReactiveCommand ShowCustomManagedNotificationCommand { get; } + public MiniCommand ShowCustomManagedNotificationCommand { get; } - public ReactiveCommand ShowManagedNotificationCommand { get; } + public MiniCommand ShowManagedNotificationCommand { get; } - public ReactiveCommand ShowNativeNotificationCommand { get; } + public MiniCommand ShowNativeNotificationCommand { get; } - public ReactiveCommand AboutCommand { get; } + public MiniCommand AboutCommand { get; } - public ReactiveCommand ExitCommand { get; } + public MiniCommand ExitCommand { get; } - public ReactiveCommand ToggleMenuItemCheckedCommand { get; } + public MiniCommand ToggleMenuItemCheckedCommand { get; } } } diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index 9e7ae8b716..ecbd59c5d7 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -4,7 +4,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { @@ -13,9 +13,9 @@ namespace ControlCatalog.ViewModels public Control View { get; set; } public MenuPageViewModel() { - OpenCommand = ReactiveCommand.CreateFromTask(Open); - SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false)); - OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + OpenCommand = MiniCommand.CreateFromTask(Open); + SaveCommand = MiniCommand.Create(Save); + OpenRecentCommand = MiniCommand.Create(OpenRecent); var recentItems = new[] { @@ -65,9 +65,9 @@ namespace ControlCatalog.ViewModels public IReadOnlyList MenuItems { get; set; } public IReadOnlyList RecentItems { get; set; } - public ReactiveCommand OpenCommand { get; } - public ReactiveCommand SaveCommand { get; } - public ReactiveCommand OpenRecentCommand { get; } + public MiniCommand OpenCommand { get; } + public MiniCommand SaveCommand { get; } + public MiniCommand OpenRecentCommand { get; } public async Task Open() { diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs index 8724ba344b..2052481015 100644 --- a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs +++ b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs @@ -1,6 +1,6 @@ using System.Reactive; using Avalonia.Controls.Notifications; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { @@ -8,12 +8,12 @@ namespace ControlCatalog.ViewModels { public NotificationViewModel(INotificationManager manager) { - YesCommand = ReactiveCommand.Create(() => + YesCommand = MiniCommand.Create(() => { manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today.")); }); - NoCommand = ReactiveCommand.Create(() => + NoCommand = MiniCommand.Create(() => { manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit...")); }); @@ -22,9 +22,9 @@ namespace ControlCatalog.ViewModels public string Title { get; set; } public string Message { get; set; } - public ReactiveCommand YesCommand { get; } + public MiniCommand YesCommand { get; } - public ReactiveCommand NoCommand { get; } + public MiniCommand NoCommand { get; } } } diff --git a/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs index f27f605a8b..9e6932bb76 100644 --- a/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs @@ -1,10 +1,10 @@ using System; using Avalonia.Controls; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class SplitViewPageViewModel : ReactiveObject + public class SplitViewPageViewModel : ViewModelBase { private bool _isLeft = true; private int _displayMode = 3; //CompactOverlay diff --git a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs index 210e281ed6..c03379330f 100644 --- a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs @@ -3,11 +3,11 @@ using System.Collections.ObjectModel; using System.Linq; using System.Reactive; using Avalonia.Controls; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class TreeViewPageViewModel : ReactiveObject + public class TreeViewPageViewModel : ViewModelBase { private readonly Node _root; private SelectionMode _selectionMode; @@ -19,16 +19,16 @@ namespace ControlCatalog.ViewModels Items = _root.Children; SelectedItems = new ObservableCollection(); - AddItemCommand = ReactiveCommand.Create(AddItem); - RemoveItemCommand = ReactiveCommand.Create(RemoveItem); - SelectRandomItemCommand = ReactiveCommand.Create(SelectRandomItem); + AddItemCommand = MiniCommand.Create(AddItem); + RemoveItemCommand = MiniCommand.Create(RemoveItem); + SelectRandomItemCommand = MiniCommand.Create(SelectRandomItem); } public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } - public ReactiveCommand AddItemCommand { get; } - public ReactiveCommand RemoveItemCommand { get; } - public ReactiveCommand SelectRandomItemCommand { get; } + public MiniCommand AddItemCommand { get; } + public MiniCommand RemoveItemCommand { get; } + public MiniCommand SelectRandomItemCommand { get; } public SelectionMode SelectionMode { diff --git a/samples/MiniMvvm/MiniCommand.cs b/samples/MiniMvvm/MiniCommand.cs new file mode 100644 index 0000000000..c6a9273c20 --- /dev/null +++ b/samples/MiniMvvm/MiniCommand.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MiniMvvm +{ + public sealed class MiniCommand : MiniCommand, ICommand + { + private readonly Action _cb; + private bool _busy; + private Func _acb; + + public MiniCommand(Action cb) + { + _cb = cb; + } + + public MiniCommand(Func cb) + { + _acb = cb; + } + + private bool Busy + { + get => _busy; + set + { + _busy = value; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + + + public override event EventHandler CanExecuteChanged; + public override bool CanExecute(object parameter) => !_busy; + + public override async void Execute(object parameter) + { + if(Busy) + return; + try + { + Busy = true; + if (_cb != null) + _cb((T)parameter); + else + await _acb((T)parameter); + } + finally + { + Busy = false; + } + } + } + + public abstract class MiniCommand : ICommand + { + public static MiniCommand Create(Action cb) => new MiniCommand(_ => cb()); + public static MiniCommand Create(Action cb) => new MiniCommand(cb); + public static MiniCommand CreateFromTask(Func cb) => new MiniCommand(_ => cb()); + + public abstract bool CanExecute(object parameter); + public abstract void Execute(object parameter); + public abstract event EventHandler CanExecuteChanged; + } +} diff --git a/samples/MiniMvvm/MiniMvvm.csproj b/samples/MiniMvvm/MiniMvvm.csproj new file mode 100644 index 0000000000..6535b2bdbd --- /dev/null +++ b/samples/MiniMvvm/MiniMvvm.csproj @@ -0,0 +1,6 @@ + + + netstandard2.0 + + + diff --git a/samples/MiniMvvm/PropertyChangedExtensions.cs b/samples/MiniMvvm/PropertyChangedExtensions.cs new file mode 100644 index 0000000000..f1065c7530 --- /dev/null +++ b/samples/MiniMvvm/PropertyChangedExtensions.cs @@ -0,0 +1,108 @@ +using System; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Reactive.Linq; +using System.Reflection; + +namespace MiniMvvm +{ + public static class PropertyChangedExtensions + { + class PropertyObservable : IObservable + { + private readonly INotifyPropertyChanged _target; + private readonly PropertyInfo _info; + + public PropertyObservable(INotifyPropertyChanged target, PropertyInfo info) + { + _target = target; + _info = info; + } + + class Subscription : IDisposable + { + private readonly INotifyPropertyChanged _target; + private readonly PropertyInfo _info; + private readonly IObserver _observer; + + public Subscription(INotifyPropertyChanged target, PropertyInfo info, IObserver observer) + { + _target = target; + _info = info; + _observer = observer; + _target.PropertyChanged += OnPropertyChanged; + _observer.OnNext((T)_info.GetValue(_target)); + } + + private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == _info.Name) + _observer.OnNext((T)_info.GetValue(_target)); + } + + public void Dispose() + { + _target.PropertyChanged -= OnPropertyChanged; + _observer.OnCompleted(); + } + } + + public IDisposable Subscribe(IObserver observer) + { + return new Subscription(_target, _info, observer); + } + } + + public static IObservable WhenAnyValue(this TModel model, + Expression> expr) where TModel : INotifyPropertyChanged + { + var l = (LambdaExpression)expr; + var ma = (MemberExpression)l.Body; + var prop = (PropertyInfo)ma.Member; + return new PropertyObservable(model, prop); + } + + public static IObservable WhenAnyValue(this TModel model, + Expression> v1, + Func cb + ) where TModel : INotifyPropertyChanged + { + return model.WhenAnyValue(v1).Select(cb); + } + + public static IObservable WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2, + Func cb + ) where TModel : INotifyPropertyChanged => + Observable.CombineLatest( + model.WhenAnyValue(v1), + model.WhenAnyValue(v2), + cb); + + public static IObservable> WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2 + ) where TModel : INotifyPropertyChanged => + model.WhenAnyValue(v1, v2, (a1, a2) => (a1, a2)); + + public static IObservable WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2, + Expression> v3, + Func cb + ) where TModel : INotifyPropertyChanged => + Observable.CombineLatest( + model.WhenAnyValue(v1), + model.WhenAnyValue(v2), + model.WhenAnyValue(v3), + cb); + + public static IObservable> WhenAnyValue(this TModel model, + Expression> v1, + Expression> v2, + Expression> v3 + ) where TModel : INotifyPropertyChanged => + model.WhenAnyValue(v1, v2, v3, (a1, a2, a3) => (a1, a2, a3)); + } +} diff --git a/samples/MiniMvvm/ViewModelBase.cs b/samples/MiniMvvm/ViewModelBase.cs new file mode 100644 index 0000000000..7256b05cef --- /dev/null +++ b/samples/MiniMvvm/ViewModelBase.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Reactive.Joins; +using System.Runtime.CompilerServices; + +namespace MiniMvvm +{ + public class ViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (!EqualityComparer.Default.Equals(field, value)) + { + field = value; + RaisePropertyChanged(propertyName); + return true; + } + return false; + } + + + protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index cd3daf61e1..cfedb7ad9e 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -8,7 +8,6 @@ %(Filename) - diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index e6ec963d75..8054b06964 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -1,7 +1,6 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using Avalonia.ReactiveUI; namespace RenderDemo { @@ -32,7 +31,6 @@ namespace RenderDemo OverlayPopups = true, }) .UsePlatformDetect() - .UseReactiveUI() .LogToTrace(); } } diff --git a/samples/RenderDemo/MainWindow.xaml.cs b/samples/RenderDemo/MainWindow.xaml.cs index b45a605e04..877eb8016a 100644 --- a/samples/RenderDemo/MainWindow.xaml.cs +++ b/samples/RenderDemo/MainWindow.xaml.cs @@ -3,7 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using RenderDemo.ViewModels; -using ReactiveUI; +using MiniMvvm; namespace RenderDemo { diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj index d1654f4b54..0d33b4c111 100644 --- a/samples/RenderDemo/RenderDemo.csproj +++ b/samples/RenderDemo/RenderDemo.csproj @@ -9,12 +9,11 @@ - + - diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs index 7b89b7944c..f8ba01f3d1 100644 --- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs @@ -1,10 +1,10 @@ using System; -using ReactiveUI; +using MiniMvvm; using Avalonia.Animation; namespace RenderDemo.ViewModels { - public class AnimationsPageViewModel : ReactiveObject + public class AnimationsPageViewModel : ViewModelBase { private bool _isPlaying = true; diff --git a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs index eda5e80530..19917c20df 100644 --- a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs @@ -1,11 +1,10 @@ using System.Reactive; using System.Threading.Tasks; - -using ReactiveUI; +using MiniMvvm; namespace RenderDemo.ViewModels { - public class MainWindowViewModel : ReactiveObject + public class MainWindowViewModel : ViewModelBase { private bool drawDirtyRects = false; private bool drawFps = true; @@ -14,9 +13,9 @@ namespace RenderDemo.ViewModels public MainWindowViewModel() { - ToggleDrawDirtyRects = ReactiveCommand.Create(() => DrawDirtyRects = !DrawDirtyRects); - ToggleDrawFps = ReactiveCommand.Create(() => DrawFps = !DrawFps); - ResizeWindow = ReactiveCommand.CreateFromTask(ResizeWindowAsync); + ToggleDrawDirtyRects = MiniCommand.Create(() => DrawDirtyRects = !DrawDirtyRects); + ToggleDrawFps = MiniCommand.Create(() => DrawFps = !DrawFps); + ResizeWindow = MiniCommand.CreateFromTask(ResizeWindowAsync); } public bool DrawDirtyRects @@ -43,9 +42,9 @@ namespace RenderDemo.ViewModels set => this.RaiseAndSetIfChanged(ref height, value); } - public ReactiveCommand ToggleDrawDirtyRects { get; } - public ReactiveCommand ToggleDrawFps { get; } - public ReactiveCommand ResizeWindow { get; } + public MiniCommand ToggleDrawDirtyRects { get; } + public MiniCommand ToggleDrawFps { get; } + public MiniCommand ResizeWindow { get; } private async Task ResizeWindowAsync() { diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs index 7d41a8b8c0..1e74105196 100644 --- a/samples/Sandbox/Program.cs +++ b/samples/Sandbox/Program.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.ReactiveUI; namespace Sandbox { @@ -9,7 +8,6 @@ namespace Sandbox { AppBuilder.Configure() .UsePlatformDetect() - .UseReactiveUI() .LogToTrace() .StartWithClassicDesktopLifetime(args); } diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index 1a0a8a7ce5..0c19440a1e 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -8,7 +8,6 @@ - diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs index 46c05f74b2..febda46450 100644 --- a/samples/VirtualizationDemo/Program.cs +++ b/samples/VirtualizationDemo/Program.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.ReactiveUI; namespace VirtualizationDemo { @@ -8,7 +7,6 @@ namespace VirtualizationDemo public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .UseReactiveUI() .LogToTrace(); public static int Main(string[] args) diff --git a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs index cf34980b40..9ba505ffe5 100644 --- a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs @@ -1,9 +1,9 @@ using System; -using ReactiveUI; +using MiniMvvm; namespace VirtualizationDemo.ViewModels { - internal class ItemViewModel : ReactiveObject + internal class ItemViewModel : ViewModelBase { private string _prefix; private int _index; diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index 852c01399f..514df691ae 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -5,13 +5,13 @@ using System.Reactive; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; -using ReactiveUI; using Avalonia.Layout; using Avalonia.Controls.Selection; +using MiniMvvm; namespace VirtualizationDemo.ViewModels { - internal class MainWindowViewModel : ReactiveObject + internal class MainWindowViewModel : ViewModelBase { private int _itemCount = 200; private string _newItemString = "New Item"; @@ -26,15 +26,15 @@ namespace VirtualizationDemo.ViewModels public MainWindowViewModel() { this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems); - RecreateCommand = ReactiveCommand.Create(() => Recreate()); + RecreateCommand = MiniCommand.Create(() => Recreate()); - AddItemCommand = ReactiveCommand.Create(() => AddItem()); + AddItemCommand = MiniCommand.Create(() => AddItem()); - RemoveItemCommand = ReactiveCommand.Create(() => Remove()); + RemoveItemCommand = MiniCommand.Create(() => Remove()); - SelectFirstCommand = ReactiveCommand.Create(() => SelectItem(0)); + SelectFirstCommand = MiniCommand.Create(() => SelectItem(0)); - SelectLastCommand = ReactiveCommand.Create(() => SelectItem(Items.Count - 1)); + SelectLastCommand = MiniCommand.Create(() => SelectItem(Items.Count - 1)); } public string NewItemString @@ -90,11 +90,11 @@ namespace VirtualizationDemo.ViewModels public IEnumerable VirtualizationModes => Enum.GetValues(typeof(ItemVirtualizationMode)).Cast(); - public ReactiveCommand AddItemCommand { get; private set; } - public ReactiveCommand RecreateCommand { get; private set; } - public ReactiveCommand RemoveItemCommand { get; private set; } - public ReactiveCommand SelectFirstCommand { get; private set; } - public ReactiveCommand SelectLastCommand { get; private set; } + public MiniCommand AddItemCommand { get; private set; } + public MiniCommand RecreateCommand { get; private set; } + public MiniCommand RemoveItemCommand { get; private set; } + public MiniCommand SelectFirstCommand { get; private set; } + public MiniCommand SelectLastCommand { get; private set; } public void RandomizeSize() { diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj index 817023fd71..d898b737a9 100644 --- a/samples/VirtualizationDemo/VirtualizationDemo.csproj +++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj @@ -6,12 +6,11 @@ - + - diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj index bd6b6f170f..cd9963a2e5 100644 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -22,9 +22,9 @@ - + diff --git a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs index d39a21cd07..21679a99c5 100644 --- a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs +++ b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using ReactiveUI; +using MiniMvvm; namespace Direct3DInteropSample { - public class MainWindowViewModel : ReactiveObject + public class MainWindowViewModel : ViewModelBase { private double _rotationX; diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index c067d38595..8394d7cb13 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -136,10 +136,6 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Avalonia.Layout - - {6417b24e-49c2-4985-8db2-3ab9d898ec91} - Avalonia.ReactiveUI - {eb582467-6abb-43a1-b052-e981ba910e3a} Avalonia.Visuals @@ -190,4 +186,4 @@ - \ No newline at end of file + diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index b8697e0ca2..f880e48282 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -127,10 +127,6 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Avalonia.Layout - - {6417b24e-49c2-4985-8db2-3ab9d898ec91} - Avalonia.ReactiveUI - {eb582467-6abb-43a1-b052-e981ba910e3a} Avalonia.Visuals From ea90f05f83bea42dd47892f76d5de5012ff6afba Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 17 Nov 2020 14:34:44 +0100 Subject: [PATCH 11/35] Added Window.ShowActive for win32. --- .../Platform/IWindowBaseImpl.cs | 4 ++-- src/Avalonia.Controls/Window.cs | 20 ++++++++++++++++--- src/Avalonia.Controls/WindowBase.cs | 2 +- .../Remote/PreviewerWindowImpl.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 7 ++++--- src/Avalonia.X11/X11Window.cs | 2 +- src/Windows/Avalonia.Win32/PopupImpl.cs | 3 ++- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 14 +++++++------ .../ContextMenuTests.cs | 16 +++++++-------- .../WindowBaseTests.cs | 2 +- .../MockWindowingPlatform.cs | 2 +- 13 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index ecaf87d1ed..0d303a6666 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -5,9 +5,9 @@ namespace Avalonia.Platform public interface IWindowBaseImpl : ITopLevelImpl { /// - /// Shows the top level. + /// Shows the window. /// - void Show(); + void Show(bool activate); /// /// Hides the window. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 54574a7e1c..ab99cfad17 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -129,6 +129,12 @@ namespace Avalonia.Controls public static readonly StyledProperty SystemDecorationsProperty = AvaloniaProperty.Register(nameof(SystemDecorations), SystemDecorations.Full); + /// + /// Defines the property. + /// + public static readonly StyledProperty ShowActivatedProperty = + AvaloniaProperty.Register(nameof(ShowActivated), true); + /// /// Enables or disables the taskbar icon /// @@ -352,13 +358,21 @@ namespace Avalonia.Controls /// /// Sets the system decorations (title bar, border, etc) /// - /// public SystemDecorations SystemDecorations { get { return GetValue(SystemDecorationsProperty); } set { SetValue(SystemDecorationsProperty, value); } } + /// + /// Gets or sets a value that indicates whether a window is activated when first shown. + /// + public bool ShowActivated + { + get { return GetValue(ShowActivatedProperty); } + set { SetValue(ShowActivatedProperty, value); } + } + /// /// Enables or disables the taskbar icon /// @@ -650,7 +664,7 @@ namespace Avalonia.Controls Owner = parent; parent?.AddChild(this, false); - PlatformImpl?.Show(); + PlatformImpl?.Show(ShowActivated); Renderer?.Start(); SetWindowStartupLocation(Owner?.PlatformImpl); } @@ -720,7 +734,7 @@ namespace Avalonia.Controls PlatformImpl?.SetParent(owner.PlatformImpl); Owner = owner; owner.AddChild(this, true); - PlatformImpl?.Show(); + PlatformImpl?.Show(ShowActivated); Renderer?.Start(); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 1efd6c8c1d..cdcb499e98 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -162,7 +162,7 @@ namespace Avalonia.Controls LayoutManager.ExecuteInitialLayoutPass(); _hasExecutedInitialLayoutPass = true; } - PlatformImpl?.Show(); + PlatformImpl?.Show(true); Renderer?.Start(); OnOpened(EventArgs.Empty); } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 25c26be91e..787f44887f 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote ClientSize = new Size(1, 1); } - public void Show() + public void Show(bool activate) { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index f377b1bcd1..f6783dc0b7 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -77,7 +77,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public void Show() + public void Show(bool activate) { } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 8f4fa5e304..3a1f3bdaf7 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -75,9 +75,10 @@ namespace Avalonia.Headless public Action Closed { get; set; } public IMouseDevice MouseDevice { get; } - public void Show() + public void Show(bool activate) { - Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); + if (activate) + Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input); } public void Hide() @@ -148,7 +149,7 @@ namespace Avalonia.Headless public void ShowDialog(IWindowImpl parent) { - Show(); + Show(true); } public void SetSystemDecorations(bool enabled) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2cd3b973d8..d62e3bed23 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -815,7 +815,7 @@ namespace Avalonia.X11 XSetTransientForHint(_x11.Display, _handle, parent.Handle.Handle); } - public void Show() + public void Show(bool activate) { _wasMappedAtLeastOnce = true; XMapWindow(_x11.Display, _handle); diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 7fb146899b..dd3fd1342c 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -17,8 +17,9 @@ namespace Avalonia.Win32 [ThreadStatic] private static IntPtr s_parentHandle; - public override void Show() + public override void Show(bool activate) { + // Popups are always shown non-activated. UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate); // We need to steal focus if it's held by a child window of our toplevel window diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 5b16cae26e..af9531400e 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -229,7 +229,7 @@ namespace Avalonia.Win32 public IWindowImpl CreateEmbeddableWindow() { var embedded = new EmbeddedWindowImpl(); - embedded.Show(); + embedded.Show(true); return embedded; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 715c8fc01d..f594763413 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -240,7 +240,7 @@ namespace Avalonia.Win32 { if (IsWindowVisible(_hwnd)) { - ShowWindow(value); + ShowWindow(value, true); } else { @@ -550,10 +550,11 @@ namespace Avalonia.Win32 UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide); } - public virtual void Show() + public virtual void Show(bool activate) { SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, _parent != null ? _parent._hwnd : IntPtr.Zero); - ShowWindow(_showWindowState); + + ShowWindow(_showWindowState, activate); } public Action GotInputWhenDisabled { get; set; } @@ -891,7 +892,7 @@ namespace Avalonia.Win32 ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended); } - private void ShowWindow(WindowState state) + private void ShowWindow(WindowState state, bool activate) { ShowWindowCommand? command; @@ -901,7 +902,7 @@ namespace Avalonia.Win32 { case WindowState.Minimized: newWindowProperties.IsFullScreen = false; - command = ShowWindowCommand.Minimize; + command = activate ? ShowWindowCommand.Minimize : ShowWindowCommand.ShowMinNoActive; break; case WindowState.Maximized: newWindowProperties.IsFullScreen = false; @@ -910,7 +911,8 @@ namespace Avalonia.Win32 case WindowState.Normal: newWindowProperties.IsFullScreen = false; - command = ShowWindowCommand.Restore; + command = IsWindowVisible(_hwnd) ? ShowWindowCommand.Restore : + activate ? ShowWindowCommand.Normal : ShowWindowCommand.ShowNoActivate; break; case WindowState.FullScreen: diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index c179aef9ac..39a3250686 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -79,7 +79,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show()).Verifiable(); + popupImpl.Setup(x => x.Show(true)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var sut = new ContextMenu(); @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests _mouse.Click(target); Assert.False(sut.IsOpen); - popupImpl.Verify(x => x.Show(), Times.Once); + popupImpl.Verify(x => x.Show(true), Times.Once); popupImpl.Verify(x => x.Hide(), Times.Once); } } @@ -109,7 +109,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show()).Verifiable(); + popupImpl.Setup(x => x.Show(true)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); var sut = new ContextMenu(); @@ -130,7 +130,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(sut.IsOpen); popupImpl.Verify(x => x.Hide(), Times.Once); - popupImpl.Verify(x => x.Show(), Times.Exactly(2)); + popupImpl.Verify(x => x.Show(true), Times.Exactly(2)); } } @@ -177,7 +177,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show()).Verifiable(); + popupImpl.Setup(x => x.Show(true)).Verifiable(); bool eventCalled = false; var sut = new ContextMenu(); @@ -193,7 +193,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(eventCalled); Assert.False(sut.IsOpen); - popupImpl.Verify(x => x.Show(), Times.Never); + popupImpl.Verify(x => x.Show(true), Times.Never); } } @@ -297,7 +297,7 @@ namespace Avalonia.Controls.UnitTests { using (Application()) { - popupImpl.Setup(x => x.Show()).Verifiable(); + popupImpl.Setup(x => x.Show(true)).Verifiable(); popupImpl.Setup(x => x.Hide()).Verifiable(); bool eventCalled = false; @@ -321,7 +321,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(eventCalled); Assert.True(sut.IsOpen); - popupImpl.Verify(x => x.Show(), Times.Once()); + popupImpl.Verify(x => x.Show(true), Times.Once()); popupImpl.Verify(x => x.Hide(), Times.Never); } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 84f212d1b3..8109b037c5 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -137,7 +137,7 @@ namespace Avalonia.Controls.UnitTests var target = new TestWindowBase(windowImpl.Object); target.IsVisible = true; - windowImpl.Verify(x => x.Show()); + windowImpl.Verify(x => x.Show(true)); } } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 9ec8ca2d36..8a24a8366f 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -58,7 +58,7 @@ namespace Avalonia.UnitTests windowImpl.Object.Resized?.Invoke(clientSize); }); - windowImpl.Setup(x => x.Show()).Callback(() => + windowImpl.Setup(x => x.Show(true)).Callback(() => { windowImpl.Object.Activated?.Invoke(); }); From 286ca73fdd417c363991b3d790e57d030835826b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2020 15:15:30 +0100 Subject: [PATCH 12/35] Prevent hangs on win32 in case of clipboard errors. --- src/Windows/Avalonia.Win32/ClipboardImpl.cs | 37 +++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 7d9e0a8bd2..0475a9d607 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -12,6 +12,9 @@ namespace Avalonia.Win32 { internal class ClipboardImpl : IClipboard { + private const int OleRetryCount = 10; + private const int OleRetryDelay = 100; + private async Task OpenClipboard() { while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero)) @@ -72,20 +75,32 @@ namespace Avalonia.Win32 { Dispatcher.UIThread.VerifyAccess(); var wrapper = new DataObject(data); + var i = OleRetryCount; + while (true) { - if (UnmanagedMethods.OleSetClipboard(wrapper) == 0) + var hr = UnmanagedMethods.OleSetClipboard(wrapper); + + if (hr == 0) break; - await Task.Delay(100); + + if (--i == 0) + Marshal.ThrowExceptionForHR(hr); + + await Task.Delay(OleRetryDelay); } } public async Task GetFormatsAsync() { Dispatcher.UIThread.VerifyAccess(); + var i = OleRetryCount; + while (true) { - if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0) + var hr = UnmanagedMethods.OleGetClipboard(out var dataObject); + + if (hr == 0) { var wrapper = new OleDataObject(dataObject); var formats = wrapper.GetDataFormats().ToArray(); @@ -93,16 +108,23 @@ namespace Avalonia.Win32 return formats; } - await Task.Delay(100); + if (--i == 0) + Marshal.ThrowExceptionForHR(hr); + + await Task.Delay(OleRetryDelay); } } public async Task GetDataAsync(string format) { Dispatcher.UIThread.VerifyAccess(); + var i = OleRetryCount; + while (true) { - if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0) + var hr = UnmanagedMethods.OleGetClipboard(out var dataObject); + + if (hr == 0) { var wrapper = new OleDataObject(dataObject); var rv = wrapper.Get(format); @@ -110,7 +132,10 @@ namespace Avalonia.Win32 return rv; } - await Task.Delay(100); + if (--i == 0) + Marshal.ThrowExceptionForHR(hr); + + await Task.Delay(OleRetryDelay); } } } From a088910f313635144559d3eabc9b007d6a15ac98 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Nov 2020 19:06:37 +0000 Subject: [PATCH 13/35] Add OSX implementation. --- native/Avalonia.Native/src/OSX/window.mm | 8 ++++---- src/Avalonia.Native/PopupImpl.cs | 4 ++-- src/Avalonia.Native/WindowImplBase.cs | 4 ++-- src/Avalonia.Native/avn.idl | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index e3996a1fae..8419258fe9 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -106,13 +106,13 @@ public: return Window; } - virtual HRESULT Show() override + virtual HRESULT Show(bool activate) override { @autoreleasepool { SetPosition(lastPositionSet); UpdateStyle(); - if(ShouldTakeFocusOnShow()) + if(ShouldTakeFocusOnShow() && activate) { [Window makeKeyAndOrderFront:Window]; [NSApp activateIgnoringOtherApps:YES]; @@ -561,11 +561,11 @@ private: } } - virtual HRESULT Show () override + virtual HRESULT Show (bool activate) override { @autoreleasepool { - WindowBaseImpl::Show(); + WindowBaseImpl::Show(activate); HideOrShowTrafficLights(); diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index 60c552a937..c36675afcd 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -60,14 +60,14 @@ namespace Avalonia.Native } } - public override void Show() + public override void Show(bool activate) { var parent = _parent; while (parent is PopupImpl p) parent = p._parent; if (parent is WindowImpl w) w.Native.TakeFocusFromChildren(); - base.Show(); + base.Show(false); } public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 88c3144884..927b739b0c 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -351,9 +351,9 @@ namespace Avalonia.Native } - public virtual void Show() + public virtual void Show(bool activate) { - _native.Show(); + _native.Show(activate.AsComBool()); } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 3f33476c3d..4f7e5833a3 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -225,7 +225,7 @@ interface IAvnString : IUnknown [uuid(e5aca675-02b7-4129-aa79-d6e417210bda)] interface IAvnWindowBase : IUnknown { - HRESULT Show(); + HRESULT Show(bool activate); HRESULT Hide(); HRESULT Close(); HRESULT Activate(); From 43078d720a6ca398795cbb081e4b588c2ca94425 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Nov 2020 19:07:17 +0000 Subject: [PATCH 14/35] fix apicompat baselines. --- .../ApiCompatBaseline.txt | 6 +--- src/Avalonia.Controls/ApiCompatBaseline.txt | 29 +++---------------- .../ApiCompatBaseline.txt | 5 +--- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt index 82472c505a..fcc74cf864 100644 --- a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt @@ -1,5 +1 @@ -Compat issues with assembly Avalonia.Controls.DataGrid: -MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.DataGridTextColumn.FontFamilyProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public System.String Avalonia.Controls.DataGridTextColumn.FontFamily.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Controls.DataGridTextColumn.FontFamily.set(System.String)' does not exist in the implementation but it does exist in the contract. -Total Issues: 3 +Total Issues: 0 diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 16c801201c..7488ef8149 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -1,26 +1,5 @@ Compat issues with assembly Avalonia.Controls: -TypesMustExist : Type 'Avalonia.Controls.IndexPath' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'Avalonia.Controls.ISelectedItemInfo' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'Avalonia.Controls.ISelectionModel' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.DirectProperty Avalonia.DirectProperty Avalonia.Controls.ListBox.SelectionProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Controls.ISelectionModel Avalonia.Controls.ListBox.Selection.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Controls.ListBox.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'Avalonia.Controls.SelectionModel' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'Avalonia.Controls.SelectionModelChildrenRequestedEventArgs' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'Avalonia.Controls.SelectionModelSelectionChangedEventArgs' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.SplitView.ContentProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.SplitView.PaneProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Controls.IControl Avalonia.Controls.SplitView.Content.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Controls.SplitView.Content.set(Avalonia.Controls.IControl)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Controls.IControl Avalonia.Controls.SplitView.Pane.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Controls.SplitView.Pane.set(Avalonia.Controls.IControl)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.DirectProperty Avalonia.DirectProperty Avalonia.Controls.TreeView.SelectionProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent Avalonia.Interactivity.RoutedEvent Avalonia.Controls.TreeView.SelectionChangedEvent' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public Avalonia.Controls.ISelectionModel Avalonia.Controls.TreeView.Selection.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Controls.TreeView.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.String[] Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.Args' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.String[] Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.Args.get()' is present in the implementation but not in the contract. -MembersMustExist : Member 'public Avalonia.DirectProperty Avalonia.DirectProperty Avalonia.Controls.Primitives.SelectingItemsControl.SelectionProperty' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'protected Avalonia.Controls.ISelectionModel Avalonia.Controls.Primitives.SelectingItemsControl.Selection.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'protected void Avalonia.Controls.Primitives.SelectingItemsControl.Selection.set(Avalonia.Controls.ISelectionModel)' does not exist in the implementation but it does exist in the contract. -Total Issues: 24 +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show()' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show()' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the implementation but not in the contract. +Total Issues: 3 diff --git a/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt b/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt index c87dcacda4..fcc74cf864 100644 --- a/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt +++ b/src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt @@ -1,4 +1 @@ -Compat issues with assembly Avalonia.Markup.Xaml: -MembersMustExist : Member 'public Avalonia.Data.Binding Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSource.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'public void Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSource.set(Avalonia.Data.Binding)' does not exist in the implementation but it does exist in the contract. -Total Issues: 2 +Total Issues: 0 From d438e0e954c8b3e8d6e29eb1a996f0f86b5d77c1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Nov 2020 09:41:19 +0100 Subject: [PATCH 15/35] Prevent infinite loop on OpenClipboard. --- src/Windows/Avalonia.Win32/ClipboardImpl.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 0475a9d607..047b7c361f 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -17,8 +17,12 @@ namespace Avalonia.Win32 private async Task OpenClipboard() { + var i = OleRetryCount; + while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero)) { + if (--i == 0) + throw new TimeoutException("Timeout opening clipboard."); await Task.Delay(100); } From 960ae38ecdc1a6b5a75dce50a2f53c54fa1b9d1f Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 28 Nov 2020 16:51:30 +0100 Subject: [PATCH 16/35] Fix TextBox crashing when PointerMoved gets called and text presenter has 0 size. --- src/Avalonia.Controls/TextBox.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 0fe3ac62e4..28426ee70f 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -867,7 +867,10 @@ namespace Avalonia.Controls { var point = e.GetPosition(_presenter); - point = new Point(MathUtilities.Clamp(point.X, 0, _presenter.Bounds.Width - 1), MathUtilities.Clamp(point.Y, 0, _presenter.Bounds.Height - 1)); + point = new Point( + MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)), + MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0))); + CaretIndex = SelectionEnd = _presenter.GetCaretIndex(point); } } From 3d131473164a352539e80f3abd6de057093485c4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Nov 2020 01:14:14 +0000 Subject: [PATCH 17/35] Sign assemblies --- build/SharedVersion.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/SharedVersion.props b/build/SharedVersion.props index a5c0aa1bea..43ec995ed9 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -16,7 +16,7 @@ https://github.com/AvaloniaUI/Avalonia/releases git $(MSBuildThisFileDirectory)\avalonia.snk - false + true $(DefineConstants);SIGNED_BUILD From 9a591f517eb59bb5c77dd15f81690cbdd40565b3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Nov 2020 15:10:06 +0000 Subject: [PATCH 18/35] sign test assemblies --- .../Avalonia.Direct2D1.RenderTests.csproj | 1 + tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj | 3 ++- tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj index 1cfc1a6ab8..c59e59be63 100644 --- a/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj +++ b/tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj @@ -27,4 +27,5 @@ + diff --git a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj index 584eaf6fa9..ef69865e32 100644 --- a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj +++ b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj @@ -8,6 +8,7 @@ + @@ -23,4 +24,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index 6b5c7a66ff..d4abf9416a 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -28,4 +28,5 @@ + From 9eb7a1002fd29ed6b208e4492d9d7c4730bcc253 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Nov 2020 15:17:25 +0000 Subject: [PATCH 19/35] fix test signing --- tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj | 3 ++- .../Avalonia.Controls.DataGrid.UnitTests.csproj | 1 + .../Avalonia.Controls.UnitTests.csproj | 1 + .../Avalonia.Direct2D1.UnitTests.csproj | 3 ++- .../Avalonia.Visuals.UnitTests.csproj | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 0d87f0eb03..badfd09430 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -10,6 +10,7 @@ + @@ -17,4 +18,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj index 396dfee691..c0c9303767 100644 --- a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj +++ b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index 7a6d77ef46..6b17427eda 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj b/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj index e346ce944d..42229ba456 100644 --- a/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj +++ b/tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj @@ -7,6 +7,7 @@ + @@ -22,4 +23,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj index aee54e87a9..13a04be5db 100644 --- a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj +++ b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj @@ -13,6 +13,7 @@ + From 8ac71278091c0c2922f5e41c2e0857cb7f8b9191 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Nov 2020 16:05:48 +0000 Subject: [PATCH 20/35] sign more test assemblies --- .../Avalonia.Animation.UnitTests.csproj | 1 + tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj | 3 ++- .../Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj | 1 + .../Avalonia.Markup.Xaml.UnitTests.csproj | 1 + .../Avalonia.Styling.UnitTests.csproj | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj index dd50eff2b6..5b686dea4c 100644 --- a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj +++ b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index 27f3223c6c..d49a859b89 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -7,6 +7,7 @@ + @@ -25,4 +26,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj index ec5b2f0ed1..7d1285c025 100644 --- a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj +++ b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index ad3592294d..44b604197d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -10,6 +10,7 @@ + diff --git a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj index 2b7f6263a7..a07ec050a6 100644 --- a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj +++ b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj @@ -11,6 +11,7 @@ + From 315d8be0ead4db017d2adaeb77e4c733629259ea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Nov 2020 16:34:46 +0000 Subject: [PATCH 21/35] sign more test assemblies --- tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj | 1 + tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj b/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj index 463bc50008..74cc6e292b 100644 --- a/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj +++ b/tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj @@ -8,6 +8,7 @@ + diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj index 65df5e7cb0..14d0f4debf 100644 --- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj +++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj @@ -29,4 +29,5 @@ + From 289e787938a6e4d898ad46e01440fa0df3d8bc03 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Dec 2020 12:14:39 +0100 Subject: [PATCH 22/35] Implement FluentTheme class. For easier selection of fluent theme in App.xaml. --- samples/RenderDemo/App.xaml | 2 +- samples/Sandbox/App.axaml | 2 +- src/Avalonia.Themes.Fluent/FluentTheme.cs | 114 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 + 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Themes.Fluent/FluentTheme.cs create mode 100644 src/Avalonia.Themes.Fluent/Properties/AssemblyInfo.cs diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml index eeeaf5b0ae..7cdcea2e1d 100644 --- a/samples/RenderDemo/App.xaml +++ b/samples/RenderDemo/App.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="RenderDemo.App"> - + diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml index a351ba93e9..9f97822a97 100644 --- a/samples/Sandbox/App.axaml +++ b/samples/Sandbox/App.axaml @@ -3,6 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sandbox.App"> - + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs new file mode 100644 index 0000000000..09e9561cb1 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; + +#nullable enable + +namespace Avalonia.Themes.Fluent +{ + public enum FluentColorScheme + { + Light, + Dark, + } + + /// + /// Includes the fluent theme in an application. + /// + public class FluentTheme : IStyle, IResourceProvider + { + private readonly Uri _baseUri; + private IStyle[]? _loaded; + private bool _isLoading; + + /// + /// Initializes a new instance of the class. + /// + /// The base URL for the XAML context. + public FluentTheme(Uri baseUri) + { + _baseUri = baseUri; + } + + /// + /// Initializes a new instance of the class. + /// + /// The XAML service provider. + public FluentTheme(IServiceProvider serviceProvider) + { + _baseUri = ((IUriContext)serviceProvider.GetService(typeof(IUriContext))).BaseUri; + } + + /// + /// Gets or sets the color scheme to use (light, dark). + /// + public FluentColorScheme Scheme { get; set; } + + public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner; + + /// + /// Gets the loaded style. + /// + public IStyle Loaded + { + get + { + if (_loaded == null) + { + _isLoading = true; + var loaded = (IStyle)AvaloniaXamlLoader.Load(GetUri(), _baseUri); + _loaded = new[] { loaded }; + _isLoading = false; + } + + return _loaded?[0]!; + } + } + + bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false; + + IReadOnlyList IStyle.Children => _loaded ?? Array.Empty(); + + public event EventHandler OwnerChanged + { + add + { + if (Loaded is IResourceProvider rp) + { + rp.OwnerChanged += value; + } + } + remove + { + if (Loaded is IResourceProvider rp) + { + rp.OwnerChanged -= value; + } + } + } + + public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host); + + public bool TryGetResource(object key, out object? value) + { + if (!_isLoading && Loaded is IResourceProvider p) + { + return p.TryGetResource(key, out value); + } + + value = null; + return false; + } + + void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner); + void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); + + private Uri GetUri() => Scheme switch + { + FluentColorScheme.Dark => new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml", UriKind.Absolute), + _ => new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml", UriKind.Absolute), + }; + } +} diff --git a/src/Avalonia.Themes.Fluent/Properties/AssemblyInfo.cs b/src/Avalonia.Themes.Fluent/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0bad226ba7 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using Avalonia.Metadata; + +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Themes.Fluent")] From f08bfb329dfc601afc5a3ae1b78f3add2c68f7ea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Dec 2020 12:18:45 +0100 Subject: [PATCH 23/35] Update API compat baseline. --- src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt index 68dc477cbc..44e250b889 100644 --- a/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt +++ b/src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt @@ -1,3 +1,4 @@ Compat issues with assembly Avalonia.Themes.Fluent: -TypesMustExist : Type 'Avalonia.Themes.Fluent.FluentTheme' does not exist in the implementation but it does exist in the contract. -Total Issues: 1 +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Themes.Fluent.FluentTheme' does not inherit from base type 'Avalonia.Styling.Styles' in the implementation but it does in the contract. +MembersMustExist : Member 'public void Avalonia.Themes.Fluent.FluentTheme..ctor()' does not exist in the implementation but it does exist in the contract. +Total Issues: 2 From 31e4d9b5156e7f0d9d7b41101fd53e30df5a5f9a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Dec 2020 13:53:38 +0100 Subject: [PATCH 24/35] Renamed Theme -> Mode in FluentTheme. --- samples/Sandbox/App.axaml | 2 +- src/Avalonia.Themes.Fluent/FluentTheme.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml index 9f97822a97..f601f9f78f 100644 --- a/samples/Sandbox/App.axaml +++ b/samples/Sandbox/App.axaml @@ -3,6 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sandbox.App"> - + diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 09e9561cb1..43b71567fa 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -8,7 +8,7 @@ using Avalonia.Styling; namespace Avalonia.Themes.Fluent { - public enum FluentColorScheme + public enum FluentThemeMode { Light, Dark, @@ -42,9 +42,9 @@ namespace Avalonia.Themes.Fluent } /// - /// Gets or sets the color scheme to use (light, dark). + /// Gets or sets the mode of the fluent theme (light, dark). /// - public FluentColorScheme Scheme { get; set; } + public FluentThemeMode Mode { get; set; } public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner; @@ -105,9 +105,9 @@ namespace Avalonia.Themes.Fluent void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner); void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); - private Uri GetUri() => Scheme switch + private Uri GetUri() => Mode switch { - FluentColorScheme.Dark => new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml", UriKind.Absolute), + FluentThemeMode.Dark => new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml", UriKind.Absolute), _ => new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml", UriKind.Absolute), }; } From 3a247bde2afc4cc7a31f525a95e3aaf3fd9aaa19 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 2 Dec 2020 21:59:44 +0100 Subject: [PATCH 25/35] LayoutState should call LayoutStateCore. See upstream implementation at https://github.com/microsoft/microsoft-ui-xaml/blob/c6c8d084231f8cc4ffa978305e9f2a0f93a9a39c/dev/Repeater/LayoutContext.cpp#L13-L18 --- src/Avalonia.Layout/LayoutContext.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Layout/LayoutContext.cs b/src/Avalonia.Layout/LayoutContext.cs index 45a8048ea2..dadce58c0c 100644 --- a/src/Avalonia.Layout/LayoutContext.cs +++ b/src/Avalonia.Layout/LayoutContext.cs @@ -14,7 +14,11 @@ namespace Avalonia.Layout /// /// Gets or sets an object that represents the state of a layout. /// - public object LayoutState { get; set; } + public object LayoutState + { + get => LayoutStateCore; + set => LayoutStateCore = value; + } /// /// Implements the behavior of in a derived or custom LayoutContext. From f4eb53330c4b9a17abf575726fa36fa52abce2d0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 2 Dec 2020 18:18:17 -0500 Subject: [PATCH 26/35] Add missing DataGridRowHeader template for DataGrid --- .../Themes/Default.xaml | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml index cf062e0920..09d19c8e43 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml @@ -137,12 +137,37 @@ + +