From 70440517ca2dda3f46d87d94f9e7f326d79299ca Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 20 Jun 2018 23:53:54 +0200 Subject: [PATCH 001/106] Initial implementation of StringFormat. XAML tests failing because of https://github.com/cwensley/Portable.Xaml/pull/106 --- .../Data/Converters/StringFormatConverter.cs | 30 ++++ .../MarkupExtensions/BindingExtension.cs | 3 + src/Markup/Avalonia.Markup/Data/Binding.cs | 21 ++- .../Data/BindingTests_Converters.cs | 146 ++++++++++++++++++ .../Xaml/BindingTests.cs | 22 +++ 5 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Data/Converters/StringFormatConverter.cs create mode 100644 tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs diff --git a/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs new file mode 100644 index 0000000000..67ecacd1c7 --- /dev/null +++ b/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Globalization; + +namespace Avalonia.Data.Converters +{ + public class StringFormatConverter : IValueConverter + { + public StringFormatConverter(string format, IValueConverter inner) + { + Contract.Requires(format != null); + + Format = format; + Inner = inner; + } + + public IValueConverter Inner { get; } + public string Format { get; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + value = Inner?.Convert(value, targetType, parameter, culture) ?? value; + return string.Format(Format, value, culture); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException("Two way bindings are not supported with a string format"); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs index 98203deebe..4349b5e82e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -45,6 +45,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions Path = pathInfo.Path, Priority = Priority, Source = Source, + StringFormat = StringFormat, RelativeSource = pathInfo.RelativeSource ?? RelativeSource, DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider)) }; @@ -226,6 +227,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public object Source { get; set; } + public string StringFormat { get; set; } + public RelativeSource RelativeSource { get; set; } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 96fc2986e8..a39dbe72cd 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -84,6 +84,11 @@ namespace Avalonia.Data /// public object Source { get; set; } + /// + /// Gets or sets the string format. + /// + public string StringFormat { get; set; } + public WeakReference DefaultAnchor { get; set; } /// @@ -158,11 +163,23 @@ namespace Avalonia.Data fallback = null; } + var converter = Converter; + var targetType = targetProperty?.PropertyType ?? typeof(object); + + // We only respect `StringFormat` if the type of the property we're assigning to will + // accept a string. Note that this is slightly different to WPF in that WPF only applies + // `StringFormat` for target type `string` (not `object`). + if (!string.IsNullOrWhiteSpace(StringFormat) && + (targetType == typeof(string) || targetType == typeof(object))) + { + converter = new StringFormatConverter(StringFormat, converter); + } + var subject = new BindingExpression( observer, - targetProperty?.PropertyType ?? typeof(object), + targetType, fallback, - Converter ?? DefaultValueConverter.Instance, + converter ?? DefaultValueConverter.Instance, ConverterParameter, Priority); diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs new file mode 100644 index 0000000000..814618d764 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -0,0 +1,146 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.Data.Core; +using Xunit; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class BindingTests_Converters + { + [Fact] + public void Converter_Should_Be_Used() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + Converter = StringConverters.NullOrEmpty, + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.TextProperty).Observable; + + Assert.Same(StringConverters.NullOrEmpty, expressionObserver.Converter); + } + + public class When_Binding_To_String + { + [Fact] + public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.TextProperty).Observable; + + Assert.IsType(expressionObserver.Converter); + } + } + + public class When_Binding_To_Object + { + [Fact] + public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.TagProperty).Observable; + + Assert.IsType(expressionObserver.Converter); + } + } + + public class When_Binding_To_Non_String_Or_Object + { + [Fact] + public void StringFormatConverter_Should_Not_Be_Used_When_Binding_Has_StringFormat() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + var expressionObserver = (BindingExpression)target.Initiate( + textBlock, + TextBlock.MarginProperty).Observable; + + Assert.Same(DefaultValueConverter.Instance, expressionObserver.Converter); + } + } + + [Fact] + public void StringFormat_Should_Be_Applied() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + StringFormat = "Hello {0}", + }; + + textBlock.Bind(TextBlock.TextProperty, target); + + Assert.Equal("Hello foo", textBlock.Text); + } + + [Fact] + public void StringFormat_Should_Be_Applied_After_Converter() + { + var textBlock = new TextBlock + { + DataContext = new Class1(), + }; + + var target = new Binding(nameof(Class1.Foo)) + { + Converter = StringConverters.NotNullOrEmpty, + StringFormat = "Hello {0}", + }; + + textBlock.Bind(TextBlock.TextProperty, target); + + Assert.Equal("Hello True", textBlock.Text); + } + + private class Class1 + { + public string Foo { get; set; } = "foo"; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index 568c6482f5..14952bca09 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -281,5 +281,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock)); } } + + [Fact] + public void Binding_To_TextBlock_Text_With_StringConverter_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + textBlock.DataContext = new { Foo = "world" }; + window.ApplyTemplate(); + + Assert.Equal("Hello world", textBlock.Text); + } + } } } \ No newline at end of file From d8615643dd9b0ec242c943da22c88372e0fb8b02 Mon Sep 17 00:00:00 2001 From: Karnah Date: Thu, 21 Jun 2018 12:47:38 +0500 Subject: [PATCH 002/106] Fixed NullReferenceException for ImageBush with null Source --- .../Media/DrawingContextImpl.cs | 2 +- .../Media/ImageBrushTests.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index bdbbdab2b9..479db51be4 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -394,7 +394,7 @@ namespace Avalonia.Direct2D1.Media { return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize); } - else if (imageBrush != null) + else if (imageBrush?.Source != null) { return new ImageBrushImpl( imageBrush, diff --git a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs b/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs index 0107002274..d1f51a1a0c 100644 --- a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs @@ -32,6 +32,23 @@ namespace Avalonia.Direct2D1.RenderTests.Media get { return System.IO.Path.Combine(OutputPath, "github_icon_small.png"); } } + [Fact] + public async Task ImageBrush_NullSource() + { + Decorator target = new Decorator + { + Width = 200, + Height = 200, + Child = new Rectangle + { + Margin = new Thickness(8), + Fill = new ImageBrush() + } + }; + + await RenderToFile(target); + } + [Fact] public async Task ImageBrush_Tile_Fill() { From 740b6073f0de1f14da38b620302fb5797130077e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 16:42:24 +0200 Subject: [PATCH 003/106] Escape braces in format string. --- tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index 14952bca09..18cb3c74fc 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -291,7 +291,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml - + "; var loader = new AvaloniaXamlLoader(); var window = (Window)loader.Load(xaml); From 6a14747eb658d59928a8ab3f9c294e29bfcfb949 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 18:24:18 +0200 Subject: [PATCH 004/106] StringFormatConverter -> StringFormatValueConverter --- ...StringFormatConverter.cs => StringFormatValueConverter.cs} | 4 ++-- src/Markup/Avalonia.Markup/Data/Binding.cs | 2 +- .../Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Avalonia.Base/Data/Converters/{StringFormatConverter.cs => StringFormatValueConverter.cs} (85%) diff --git a/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs similarity index 85% rename from src/Avalonia.Base/Data/Converters/StringFormatConverter.cs rename to src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs index 67ecacd1c7..3703929356 100644 --- a/src/Avalonia.Base/Data/Converters/StringFormatConverter.cs +++ b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs @@ -3,9 +3,9 @@ using System.Globalization; namespace Avalonia.Data.Converters { - public class StringFormatConverter : IValueConverter + public class StringFormatValueConverter : IValueConverter { - public StringFormatConverter(string format, IValueConverter inner) + public StringFormatValueConverter(string format, IValueConverter inner) { Contract.Requires(format != null); diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index a39dbe72cd..62cff4d768 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -172,7 +172,7 @@ namespace Avalonia.Data if (!string.IsNullOrWhiteSpace(StringFormat) && (targetType == typeof(string) || targetType == typeof(object))) { - converter = new StringFormatConverter(StringFormat, converter); + converter = new StringFormatValueConverter(StringFormat, converter); } var subject = new BindingExpression( diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index 814618d764..e98f9f895c 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -51,7 +51,7 @@ namespace Avalonia.Markup.UnitTests.Data textBlock, TextBlock.TextProperty).Observable; - Assert.IsType(expressionObserver.Converter); + Assert.IsType(expressionObserver.Converter); } } @@ -74,7 +74,7 @@ namespace Avalonia.Markup.UnitTests.Data textBlock, TextBlock.TagProperty).Observable; - Assert.IsType(expressionObserver.Converter); + Assert.IsType(expressionObserver.Converter); } } From 09454783577847b58b780bdd695a3d08a80e5150 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 18:38:39 +0200 Subject: [PATCH 005/106] Fix grammar. --- .../Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index e98f9f895c..123aadfda5 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -35,7 +35,7 @@ namespace Avalonia.Markup.UnitTests.Data public class When_Binding_To_String { [Fact] - public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + public void StringFormatConverter_Should_Be_Used_When_Binding_Has_StringFormat() { var textBlock = new TextBlock { @@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data public class When_Binding_To_Object { [Fact] - public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat() + public void StringFormatConverter_Should_Be_Used_When_Binding_Has_StringFormat() { var textBlock = new TextBlock { From 63a628731e533d5aa41c367b9cfae19c4db83944 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Jun 2018 18:56:37 +0200 Subject: [PATCH 006/106] Added XML docs for StringFormatValueConverter. --- .../Converters/StringFormatValueConverter.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs index 3703929356..643f26a3c1 100644 --- a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs @@ -3,8 +3,18 @@ using System.Globalization; namespace Avalonia.Data.Converters { + /// + /// A value converter which calls + /// public class StringFormatValueConverter : IValueConverter { + /// + /// Initializes a new instance of the class. + /// + /// The format string. + /// + /// An optional inner converter to be called before the format takes place. + /// public StringFormatValueConverter(string format, IValueConverter inner) { Contract.Requires(format != null); @@ -13,15 +23,24 @@ namespace Avalonia.Data.Converters Inner = inner; } + /// + /// Gets an inner value converter which will be called before the string format takes place. + /// public IValueConverter Inner { get; } + + /// + /// Gets the format string. + /// public string Format { get; } + /// public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { value = Inner?.Convert(value, targetType, parameter, culture) ?? value; return string.Format(Format, value, culture); } + /// public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException("Two way bindings are not supported with a string format"); From d0bc9f19a3067f0fbf0891f2c45be731baecb58f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 22 Jun 2018 09:56:27 +0200 Subject: [PATCH 007/106] Use correct parameter order! --- src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs index 643f26a3c1..fc695762b8 100644 --- a/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs @@ -37,7 +37,7 @@ namespace Avalonia.Data.Converters public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { value = Inner?.Convert(value, targetType, parameter, culture) ?? value; - return string.Format(Format, value, culture); + return string.Format(culture, Format, value); } /// From 48dd3b94d1ad0d1ea8dbc2c4e6c859196862c821 Mon Sep 17 00:00:00 2001 From: Karnah Date: Tue, 26 Jun 2018 11:28:45 +0500 Subject: [PATCH 008/106] Using transparent brush when image source is null --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++++ .../Avalonia.RenderTests/Media/ImageBrushTests.cs | 1 + .../ImageBrush/ImageBrush_NullSource.expected.png | Bin 0 -> 250 bytes .../ImageBrush/ImageBrush_NullSource.expected.png | Bin 0 -> 250 bytes 4 files changed, 5 insertions(+) create mode 100644 tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NullSource.expected.png create mode 100644 tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_NullSource.expected.png diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index b7ce6eedc4..d2c4bd0aa1 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -491,6 +491,10 @@ namespace Avalonia.Skia { ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage); } + else + { + paint.Color = new SKColor(255, 255, 255, 0); + } return paintWrapper; } diff --git a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs b/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs index d1f51a1a0c..3a41585d04 100644 --- a/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs +++ b/tests/Avalonia.RenderTests/Media/ImageBrushTests.cs @@ -47,6 +47,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media }; await RenderToFile(target); + CompareImages(); } [Fact] diff --git a/tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NullSource.expected.png b/tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NullSource.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4c9409ee63e2d96335b03121247027d66340b190 GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`kiXH>#WAE} r&f9~Gj6hzK!vFPo9u6SpAO$s&h0homeHw~bKo0bD^>bP0l+XkKkl+`x literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_NullSource.expected.png b/tests/TestFiles/Skia/Media/ImageBrush/ImageBrush_NullSource.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4c9409ee63e2d96335b03121247027d66340b190 GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`kiXH>#WAE} r&f9~Gj6hzK!vFPo9u6SpAO$s&h0homeHw~bKo0bD^>bP0l+XkKkl+`x literal 0 HcmV?d00001 From 6d4694b471a7f2a38520d7ac8540c5cb4df78b28 Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Mon, 2 Jul 2018 09:00:32 +0200 Subject: [PATCH 009/106] Add checks for null in Windows SystemDialogImpl --- src/Windows/Avalonia.Win32/SystemDialogImpl.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs index d555e93a88..8a5617789b 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs @@ -37,13 +37,16 @@ namespace Avalonia.Win32 var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? ""; frm.SetDefaultExtension(defaultExtension); frm.SetFileName(dialog.InitialFileName ?? ""); - frm.SetTitle(dialog.Title); + frm.SetTitle(dialog.Title ?? ""); var filters = new List(); - foreach (var filter in dialog.Filters) + if (dialog.Filters != null) { - var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); - filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask }); + foreach (var filter in dialog.Filters) + { + var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); + filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask }); + } } if (filters.Count == 0) filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" }); From 54a919a7284894f6ed99e28097ea97d45721ce7f Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Mon, 2 Jul 2018 09:16:45 +0200 Subject: [PATCH 010/106] Ensure that selected items in Windows FileDialog are filesystem items --- src/Windows/Avalonia.Win32/SystemDialogImpl.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs index 8a5617789b..020e6aa0ea 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs @@ -13,6 +13,9 @@ namespace Avalonia.Win32 class SystemDialogImpl : ISystemDialogImpl { + private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | + UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT; + public unsafe Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) { var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; @@ -29,7 +32,7 @@ namespace Avalonia.Win32 uint options; frm.GetOptions(out options); - options |= (uint)(UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); + options |= (uint)(DefaultDialogOptions); if (openDialog?.AllowMultiple == true) options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT; frm.SetOptions(options); @@ -109,7 +112,7 @@ namespace Avalonia.Win32 var frm = (UnmanagedMethods.IFileDialog)unk; uint options; frm.GetOptions(out options); - options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); + options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | DefaultDialogOptions); frm.SetOptions(options); if (dialog.InitialDirectory != null) From 8ab89b1c3581eccd041fe652e2b87c1cb1bcb5bd Mon Sep 17 00:00:00 2001 From: CommonGuy Date: Mon, 2 Jul 2018 17:24:57 +0200 Subject: [PATCH 011/106] FOS_PICKFOLDERS should not be a default options for Windows SystemDialogs --- src/Windows/Avalonia.Win32/SystemDialogImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs index 020e6aa0ea..dec5e3a544 100644 --- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs +++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs @@ -13,7 +13,7 @@ namespace Avalonia.Win32 class SystemDialogImpl : ISystemDialogImpl { - private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | + private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT; public unsafe Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) From cf3bdf8507f86a8e5d6d4c6f9818c42feef3d82c Mon Sep 17 00:00:00 2001 From: Rayyan Tahir Date: Tue, 3 Jul 2018 17:31:44 +0500 Subject: [PATCH 012/106] When TextBox IsReadOnly property is true, no need to show caret. --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 1 + src/Avalonia.Controls/TextBox.cs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index fb0d8fb7b1..96c1665afe 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -9,6 +9,7 @@ Gap="16"> + Date: Tue, 3 Jul 2018 15:19:16 -0500 Subject: [PATCH 013/106] Get rid of static deferredSetter used for PriorityValues. Use a single DeferredSetter for both styled and direct properties. --- src/Avalonia.Base/AvaloniaObject.cs | 20 +++++++++----------- src/Avalonia.Base/IPriorityValueOwner.cs | 3 +++ src/Avalonia.Base/PriorityValue.cs | 8 +++----- src/Avalonia.Base/ValueStore.cs | 20 +++++++++++++++----- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 761c0618da..9fd2acbe5f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -22,7 +22,7 @@ namespace Avalonia /// /// This class is analogous to DependencyObject in WPF. /// - public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IPriorityValueOwner + public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged { /// /// The parent object that inherited values are inherited from. @@ -414,9 +414,8 @@ namespace Avalonia VerifyAccess(); _values?.Revalidate(property); } - - /// - void IPriorityValueOwner.Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) + + internal void PriorityValueChanged(AvaloniaProperty property, int priority, object oldValue, object newValue) { oldValue = (oldValue == AvaloniaProperty.UnsetValue) ? GetDefaultValue(property) : @@ -439,9 +438,8 @@ namespace Avalonia (BindingPriority)priority); } } - - /// - void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) + + internal void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) { UpdateDataValidation(property, notification); } @@ -566,15 +564,15 @@ namespace Avalonia T value) { Contract.Requires(setterCallback != null); - return DirectPropertyDeferredSetter.SetAndNotify( + return _values.Setter.SetAndNotify( property, ref field, - (object val, ref T backing, Action notify) => + ((object value, int) update, ref T backing, Action notify) => { - setterCallback((T)val, ref backing, notify); + setterCallback((T)update.value, ref backing, notify); return true; }, - value); + (value, 0)); } /// diff --git a/src/Avalonia.Base/IPriorityValueOwner.cs b/src/Avalonia.Base/IPriorityValueOwner.cs index 5f63f6ef91..3a931d6b35 100644 --- a/src/Avalonia.Base/IPriorityValueOwner.cs +++ b/src/Avalonia.Base/IPriorityValueOwner.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Data; +using Avalonia.Utilities; namespace Avalonia { @@ -31,5 +32,7 @@ namespace Avalonia /// Ensures that the current thread is the UI thread. /// void VerifyAccess(); + + DeferredSetter Setter { get; } } } diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index c474f9098e..bc09a41e5f 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -21,7 +21,7 @@ namespace Avalonia /// priority binding that doesn't return . Where there /// are multiple bindings registered with the same priority, the most recently added binding /// has a higher priority. Each time the value changes, the - /// method on the + /// method on the /// owner object is fired with the old and new values. /// internal class PriorityValue @@ -30,7 +30,6 @@ namespace Avalonia private readonly SingleOrDictionary _levels = new SingleOrDictionary(); private readonly Func _validate; - private static readonly DeferredSetter delayedSetter = new DeferredSetter(); private (object value, int priority) _value; /// @@ -243,7 +242,7 @@ namespace Avalonia /// The priority level that the value came from. private void UpdateValue(object value, int priority) { - delayedSetter.SetAndNotify(this, + Owner.Setter.SetAndNotify(Property, ref _value, UpdateCore, (value, priority)); @@ -256,14 +255,13 @@ namespace Avalonia { var val = update.value; var notification = val as BindingNotification; - object castValue; if (notification != null) { val = (notification.HasValue) ? notification.Value : null; } - if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue)) + if (TypeUtilities.TryConvertImplicit(_valueType, val, out object castValue)) { var old = backing.value; diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 8283edab80..5e34e8a43f 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Data; +using Avalonia.Utilities; namespace Avalonia { @@ -91,12 +92,12 @@ namespace Avalonia public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) { - ((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification); + _owner.BindingNotificationReceived(property, notification); } public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) { - ((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue); + _owner.PriorityValueChanged(property, priority, oldValue, newValue); } public IDictionary GetSetValues() => throw new NotImplementedException(); @@ -148,13 +149,11 @@ namespace Avalonia validate2 = v => validate(_owner, v); } - PriorityValue result = new PriorityValue( + return new PriorityValue( this, property, property.PropertyType, validate2); - - return result; } private object Validate(AvaloniaProperty property, object value) @@ -168,5 +167,16 @@ namespace Avalonia return value; } + + private DeferredSetter _defferedSetter; + + public DeferredSetter Setter + { + get + { + return _defferedSetter ?? + (_defferedSetter = new DeferredSetter()); + } + } } } From 00f8dfabc050d5e65071062e5bc18ebd4e3c73bf Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 3 Jul 2018 16:09:23 -0500 Subject: [PATCH 014/106] Use SingleOrQueue instead of Queue in DeferredSetter. Make sure tests pass. --- src/Avalonia.Base/AvaloniaObject.cs | 49 +++++------------ src/Avalonia.Base/IPriorityValueOwner.cs | 2 +- src/Avalonia.Base/PriorityValue.cs | 9 +++- src/Avalonia.Base/Utilities/DeferredSetter.cs | 9 ++-- src/Avalonia.Base/Utilities/SingleOrQueue.cs | 54 +++++++++++++++++++ src/Avalonia.Base/ValueStore.cs | 8 +-- .../PriorityValueTests.cs | 52 ++++++++++-------- .../Utilities/SingleOrQueueTests.cs | 50 +++++++++++++++++ 8 files changed, 165 insertions(+), 68 deletions(-) create mode 100644 src/Avalonia.Base/Utilities/SingleOrQueue.cs create mode 100644 tests/Avalonia.Base.UnitTests/Utilities/SingleOrQueueTests.cs diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 9fd2acbe5f..db4e52ee0f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -45,21 +45,8 @@ namespace Avalonia /// private EventHandler _propertyChanged; - private DeferredSetter _directDeferredSetter; private ValueStore _values; - - /// - /// Delayed setter helper for direct properties. Used to fix #855. - /// - private DeferredSetter DirectPropertyDeferredSetter - { - get - { - return _directDeferredSetter ?? - (_directDeferredSetter = new DeferredSetter()); - } - } - + private ValueStore Values => _values ?? (_values = new ValueStore(this)); /// /// Initializes a new instance of the class. @@ -223,9 +210,9 @@ namespace Avalonia { return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this); } - else if (_values != null) + else if (Values != null) { - var result = _values.GetValue(property); + var result = Values.GetValue(property); if (result == AvaloniaProperty.UnsetValue) { @@ -263,7 +250,7 @@ namespace Avalonia Contract.Requires(property != null); VerifyAccess(); - return _values?.IsAnimating(property) ?? false; + return Values?.IsAnimating(property) ?? false; } /// @@ -280,7 +267,7 @@ namespace Avalonia Contract.Requires(property != null); VerifyAccess(); - return _values?.IsSet(property) ?? false; + return Values?.IsSet(property) ?? false; } /// @@ -376,12 +363,7 @@ namespace Avalonia description, priority); - if (_values == null) - { - _values = new ValueStore(this); - } - - return _values.AddBinding(property, source, priority); + return Values.AddBinding(property, source, priority); } } @@ -412,7 +394,7 @@ namespace Avalonia public void Revalidate(AvaloniaProperty property) { VerifyAccess(); - _values?.Revalidate(property); + Values?.Revalidate(property); } internal void PriorityValueChanged(AvaloniaProperty property, int priority, object oldValue, object newValue) @@ -454,7 +436,7 @@ namespace Avalonia /// Gets all priority values set on the object. /// /// A collection of property/value tuples. - internal IDictionary GetSetValues() => _values?.GetSetValues(); + internal IDictionary GetSetValues() => Values?.GetSetValues(); /// /// Forces revalidation of properties when a property value changes. @@ -564,15 +546,15 @@ namespace Avalonia T value) { Contract.Requires(setterCallback != null); - return _values.Setter.SetAndNotify( + return Values.Setter.SetAndNotify( property, ref field, - ((object value, int) update, ref T backing, Action notify) => + (object update, ref T backing, Action notify) => { - setterCallback((T)update.value, ref backing, notify); + setterCallback((T)update, ref backing, notify); return true; }, - (value, 0)); + value); } /// @@ -735,13 +717,8 @@ namespace Avalonia originalValue?.GetType().FullName ?? "(null)")); } - if (_values == null) - { - _values = new ValueStore(this); - } - LogPropertySet(property, value, priority); - _values.AddValue(property, value, (int)priority); + Values.AddValue(property, value, (int)priority); } /// diff --git a/src/Avalonia.Base/IPriorityValueOwner.cs b/src/Avalonia.Base/IPriorityValueOwner.cs index 3a931d6b35..8eaefac4f2 100644 --- a/src/Avalonia.Base/IPriorityValueOwner.cs +++ b/src/Avalonia.Base/IPriorityValueOwner.cs @@ -33,6 +33,6 @@ namespace Avalonia /// void VerifyAccess(); - DeferredSetter Setter { get; } + DeferredSetter Setter { get; } } } diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index bc09a41e5f..03094e2236 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -248,6 +248,12 @@ namespace Avalonia (value, priority)); } + private bool UpdateCore( + object update, + ref (object value, int priority) backing, + Action notify) + => UpdateCore(((object, int))update, ref backing, notify); + private bool UpdateCore( (object value, int priority) update, ref (object value, int priority) backing, @@ -255,13 +261,14 @@ namespace Avalonia { var val = update.value; var notification = val as BindingNotification; + object castValue; if (notification != null) { val = (notification.HasValue) ? notification.Value : null; } - if (TypeUtilities.TryConvertImplicit(_valueType, val, out object castValue)) + if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue)) { var old = backing.value; diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs index fdfa160134..9e3cf023c7 100644 --- a/src/Avalonia.Base/Utilities/DeferredSetter.cs +++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs @@ -8,6 +8,7 @@ namespace Avalonia.Utilities { /// /// A utility class to enable deferring assignment until after property-changed notifications are sent. + /// Used to fix #855. /// /// The type of the object that represents the property. /// The type of value with which to track the delayed assignment. @@ -37,13 +38,13 @@ namespace Avalonia.Utilities { public bool Notifying { get; set; } - private Queue pendingValues; + private SingleOrQueue pendingValues; - public Queue PendingValues + public SingleOrQueue PendingValues { get { - return pendingValues ?? (pendingValues = new Queue()); + return pendingValues ?? (pendingValues = new SingleOrQueue()); } } } @@ -89,7 +90,7 @@ namespace Avalonia.Utilities /// If the property has any pending assignments. private bool HasPendingSet(TProperty property) { - return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0; + return setRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty; } /// diff --git a/src/Avalonia.Base/Utilities/SingleOrQueue.cs b/src/Avalonia.Base/Utilities/SingleOrQueue.cs new file mode 100644 index 0000000000..f33adaff37 --- /dev/null +++ b/src/Avalonia.Base/Utilities/SingleOrQueue.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Utilities +{ + public class SingleOrQueue + { + private T _head; + private Queue _tail; + + private Queue Tail => _tail ?? (_tail = new Queue()); + + private bool HasTail => _tail != null; + + public bool Empty { get; private set; } = true; + + public void Enqueue(T value) + { + if (Empty) + { + _head = value; + } + else + { + Tail.Enqueue(value); + } + + Empty = false; + } + + public T Dequeue() + { + if (Empty) + { + throw new InvalidOperationException("Cannot dequeue from an empty queue!"); + } + + var result = _head; + + if (HasTail && Tail.Count != 0) + { + _head = Tail.Dequeue(); + } + else + { + _head = default; + Empty = true; + } + + return result; + } + } +} diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 5e34e8a43f..0e404500a1 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -116,7 +116,7 @@ namespace Avalonia public bool IsAnimating(AvaloniaProperty property) { - return _values.TryGetValue(property, out var value) ? (value as PriorityValue)?.IsAnimating ?? false : false; + return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating; } public bool IsSet(AvaloniaProperty property) @@ -168,14 +168,14 @@ namespace Avalonia return value; } - private DeferredSetter _defferedSetter; + private DeferredSetter _defferedSetter; - public DeferredSetter Setter + public DeferredSetter Setter { get { return _defferedSetter ?? - (_defferedSetter = new DeferredSetter()); + (_defferedSetter = new DeferredSetter()); } } } diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs index d6650aa104..61ba9bc463 100644 --- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs @@ -1,11 +1,12 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Utilities; +using Moq; using System; using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; -using Moq; using Xunit; namespace Avalonia.Base.UnitTests @@ -21,7 +22,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Initial_Value_Should_Be_UnsetValue() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); Assert.Same(AvaloniaProperty.UnsetValue, target.Value); } @@ -29,7 +30,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void First_Binding_Sets_Value() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); target.Add(Single("foo"), 0); @@ -39,7 +40,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Changing_Binding_Should_Set_Value() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var subject = new BehaviorSubject("foo"); target.Add(subject, 0); @@ -51,7 +52,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Setting_Direct_Value_Should_Override_Binding() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); target.Add(Single("foo"), 0); target.SetValue("bar", 0); @@ -62,7 +63,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Binding_Firing_Should_Override_Direct_Value() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); @@ -76,7 +77,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Earlier_Binding_Firing_Should_Not_Override_Later() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var nonActive = new BehaviorSubject("na"); var source = new BehaviorSubject("initial"); @@ -92,7 +93,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Binding_Completing_Should_Revert_To_Direct_Value() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); @@ -108,7 +109,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Binding_With_Lower_Priority_Has_Precedence() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); @@ -120,7 +121,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Later_Binding_With_Same_Priority_Should_Take_Precedence() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); @@ -133,7 +134,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -146,7 +147,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void UnsetValue_Should_Fall_Back_To_Next_Binding() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(subject, 0); @@ -162,7 +163,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Adding_Value_Should_Call_OnNext() { - var owner = new Mock(); + var owner = GetMockOwner(); var target = new PriorityValue(owner.Object, TestProperty, typeof(string)); target.Add(Single("foo"), 0); @@ -173,7 +174,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Changing_Value_Should_Call_OnNext() { - var owner = new Mock(); + var owner = GetMockOwner(); var target = new PriorityValue(owner.Object, TestProperty, typeof(string)); var subject = new BehaviorSubject("foo"); @@ -186,7 +187,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Disposing_A_Binding_Should_Revert_To_Next_Value() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); @@ -199,7 +200,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Disposing_A_Binding_Should_Remove_BindingEntry() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); @@ -212,7 +213,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Revert_To_Previous_Binding() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -226,7 +227,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Revert_To_Lower_Priority() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 1); @@ -240,7 +241,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Completing_A_Binding_Should_Remove_BindingEntry() { - var target = new PriorityValue(null, TestProperty, typeof(string)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); @@ -254,7 +255,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Direct_Value_Should_Be_Coerced() { - var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(int), x => Math.Min((int)x, 10)); target.SetValue(5, 0); Assert.Equal(5, target.Value); @@ -265,7 +266,7 @@ namespace Avalonia.Base.UnitTests [Fact] public void Bound_Value_Should_Be_Coerced() { - var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(int), x => Math.Min((int)x, 10)); var source = new Subject(); target.Add(source, 0); @@ -279,7 +280,7 @@ namespace Avalonia.Base.UnitTests public void Revalidate_Should_ReCoerce_Value() { var max = 10; - var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, max)); + var target = new PriorityValue(GetMockOwner().Object, TestProperty, typeof(int), x => Math.Min((int)x, max)); var source = new Subject(); target.Add(source, 0); @@ -302,5 +303,12 @@ namespace Avalonia.Base.UnitTests { return Observable.Never().StartWith(value); } + + private static Mock GetMockOwner() + { + var owner = new Mock(); + owner.SetupGet(o => o.Setter).Returns(new DeferredSetter()); + return owner; + } } } diff --git a/tests/Avalonia.Base.UnitTests/Utilities/SingleOrQueueTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/SingleOrQueueTests.cs new file mode 100644 index 0000000000..f1ab265bc9 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Utilities/SingleOrQueueTests.cs @@ -0,0 +1,50 @@ +using Avalonia.Utilities; +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Avalonia.Base.UnitTests.Utilities +{ + public class SingleOrQueueTests + { + [Fact] + public void New_SingleOrQueue_Is_Empty() + { + Assert.True(new SingleOrQueue().Empty); + } + + [Fact] + public void Dequeue_Throws_When_Empty() + { + var queue = new SingleOrQueue(); + + Assert.Throws(() => queue.Dequeue()); + } + + [Fact] + public void Enqueue_Adds_Element() + { + var queue = new SingleOrQueue(); + + queue.Enqueue(1); + + Assert.False(queue.Empty); + + Assert.Equal(1, queue.Dequeue()); + } + + [Fact] + public void Multiple_Elements_Dequeued_In_Correct_Order() + { + var queue = new SingleOrQueue(); + + queue.Enqueue(1); + queue.Enqueue(2); + queue.Enqueue(3); + Assert.Equal(1, queue.Dequeue()); + Assert.Equal(2, queue.Dequeue()); + Assert.Equal(3, queue.Dequeue()); + } + } +} From 27589e87a8033d7060bc20210ea9f0a541256fce Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 3 Jul 2018 23:15:51 +0200 Subject: [PATCH 015/106] Don't create virtualizer before panel. Otherwise, an `ItemVirtualizerNone` is created which can materialize all items, even if `ItemVirtualizationMode.Simple` is set. Hopefully fixes #1707. --- .../Presenters/ItemVirtualizer.cs | 5 +++ .../Presenters/ItemsPresenter.cs | 36 +++++++++++-------- .../ItemsPresenterTests_Virtualization.cs | 9 +++++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index fee326dacc..e293cff211 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs @@ -161,6 +161,11 @@ namespace Avalonia.Controls.Presenters /// An . public static ItemVirtualizer Create(ItemsPresenter owner) { + if (owner.Panel == null) + { + return null; + } + var virtualizingPanel = owner.Panel as IVirtualizingPanel; var scrollable = (ILogicalScrollable)owner; ItemVirtualizer result = null; diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 590bfa25ac..f8d62a1cbf 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -22,7 +22,6 @@ namespace Avalonia.Controls.Presenters nameof(VirtualizationMode), defaultValue: ItemVirtualizationMode.None); - private ItemVirtualizer _virtualizer; private bool _canHorizontallyScroll; private bool _canVerticallyScroll; @@ -76,21 +75,27 @@ namespace Avalonia.Controls.Presenters /// bool ILogicalScrollable.IsLogicalScrollEnabled { - get { return _virtualizer?.IsLogicalScrollEnabled ?? false; } + get { return Virtualizer?.IsLogicalScrollEnabled ?? false; } } /// - Size IScrollable.Extent => _virtualizer.Extent; + Size IScrollable.Extent => Virtualizer?.Extent ?? Size.Empty; /// Vector IScrollable.Offset { - get { return _virtualizer.Offset; } - set { _virtualizer.Offset = CoerceOffset(value); } + get { return Virtualizer?.Offset ?? new Vector(); } + set + { + if (Virtualizer != null) + { + Virtualizer.Offset = CoerceOffset(value); + } + } } /// - Size IScrollable.Viewport => _virtualizer.Viewport; + Size IScrollable.Viewport => Virtualizer?.Viewport ?? Bounds.Size; /// Action ILogicalScrollable.InvalidateScroll { get; set; } @@ -101,6 +106,8 @@ namespace Avalonia.Controls.Presenters /// Size ILogicalScrollable.PageScrollSize => new Size(0, 1); + internal ItemVirtualizer Virtualizer { get; private set; } + /// bool ILogicalScrollable.BringIntoView(IControl target, Rect targetRect) { @@ -110,29 +117,30 @@ namespace Avalonia.Controls.Presenters /// IControl ILogicalScrollable.GetControlInDirection(NavigationDirection direction, IControl from) { - return _virtualizer?.GetControlInDirection(direction, from); + return Virtualizer?.GetControlInDirection(direction, from); } public override void ScrollIntoView(object item) { - _virtualizer?.ScrollIntoView(item); + Virtualizer?.ScrollIntoView(item); } /// protected override Size MeasureOverride(Size availableSize) { - return _virtualizer?.MeasureOverride(availableSize) ?? Size.Empty; + return Virtualizer?.MeasureOverride(availableSize) ?? Size.Empty; } protected override Size ArrangeOverride(Size finalSize) { - return _virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty; + return Virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty; } /// protected override void PanelCreated(IPanel panel) { - _virtualizer = ItemVirtualizer.Create(this); + Virtualizer?.Dispose(); + Virtualizer = ItemVirtualizer.Create(this); ((ILogicalScrollable)this).InvalidateScroll?.Invoke(); if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty)) @@ -149,7 +157,7 @@ namespace Avalonia.Controls.Presenters protected override void ItemsChanged(NotifyCollectionChangedEventArgs e) { - _virtualizer?.ItemsChanged(Items, e); + Virtualizer?.ItemsChanged(Items, e); } private Vector CoerceOffset(Vector value) @@ -162,8 +170,8 @@ namespace Avalonia.Controls.Presenters private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e) { - _virtualizer?.Dispose(); - _virtualizer = ItemVirtualizer.Create(this); + Virtualizer?.Dispose(); + Virtualizer = ItemVirtualizer.Create(this); ((ILogicalScrollable)this).InvalidateScroll?.Invoke(); } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 7eb44c5354..5aa6d50425 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -194,6 +194,15 @@ namespace Avalonia.Controls.UnitTests.Presenters } } + [Fact] + public void Should_Not_Create_Virtualizer_Before_Panel() + { + var target = CreateTarget(); + + Assert.Null(target.Panel); + Assert.Null(target.Virtualizer); + } + [Fact] public void Changing_VirtualizationMode_None_To_Simple_Should_Update_Control() { From 1ad7f27cf9a559ac4b51aa6d7cd8ffb6a769c5cb Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 3 Jul 2018 17:01:51 -0500 Subject: [PATCH 016/106] Make DeferredSetter tied to AvaloniaProperty and use a regular Dictionary. Remove SettingStatus entries for bindings from the dictionary for bindings that don't create a queue of pending values. --- src/Avalonia.Base/IPriorityValueOwner.cs | 2 +- src/Avalonia.Base/Utilities/DeferredSetter.cs | 58 ++++++++++++++----- src/Avalonia.Base/Utilities/SingleOrQueue.cs | 2 +- src/Avalonia.Base/ValueStore.cs | 6 +- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Base/IPriorityValueOwner.cs b/src/Avalonia.Base/IPriorityValueOwner.cs index 8eaefac4f2..8cbf212381 100644 --- a/src/Avalonia.Base/IPriorityValueOwner.cs +++ b/src/Avalonia.Base/IPriorityValueOwner.cs @@ -33,6 +33,6 @@ namespace Avalonia /// void VerifyAccess(); - DeferredSetter Setter { get; } + DeferredSetter Setter { get; } } } diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs index 9e3cf023c7..15c85338c7 100644 --- a/src/Avalonia.Base/Utilities/DeferredSetter.cs +++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs @@ -10,10 +10,8 @@ namespace Avalonia.Utilities /// A utility class to enable deferring assignment until after property-changed notifications are sent. /// Used to fix #855. /// - /// The type of the object that represents the property. /// The type of value with which to track the delayed assignment. - class DeferredSetter - where TProperty: class + class DeferredSetter { private struct NotifyDisposable : IDisposable { @@ -47,20 +45,37 @@ namespace Avalonia.Utilities return pendingValues ?? (pendingValues = new SingleOrQueue()); } } + + public bool IsSimpleSet => pendingValues?.HasTail != true; } - private readonly ConditionalWeakTable setRecords = new ConditionalWeakTable(); + private Dictionary _setRecords; + private Dictionary SetRecords + => _setRecords ?? (_setRecords = new Dictionary()); + + private SettingStatus GetOrCreateStatus(AvaloniaProperty property) + { + if (!SetRecords.TryGetValue(property, out var status)) + { + status = new SettingStatus(); + SetRecords.Add(property, status); + } + + return status; + } /// /// Mark the property as currently notifying. /// /// The property to mark as notifying. /// Returns a disposable that when disposed, marks the property as done notifying. - private NotifyDisposable MarkNotifying(TProperty property) + private NotifyDisposable MarkNotifying(AvaloniaProperty property) { Contract.Requires(!IsNotifying(property)); - - return new NotifyDisposable(setRecords.GetOrCreateValue(property)); + + SettingStatus status = GetOrCreateStatus(property); + + return new NotifyDisposable(status); } /// @@ -68,19 +83,19 @@ namespace Avalonia.Utilities /// /// The property. /// If the property is currently notifying listeners. - private bool IsNotifying(TProperty property) - => setRecords.TryGetValue(property, out var value) && value.Notifying; + private bool IsNotifying(AvaloniaProperty property) + => SetRecords.TryGetValue(property, out var value) && value.Notifying; /// /// Add a pending assignment for the property. /// /// The property. /// The value to assign. - private void AddPendingSet(TProperty property, TSetRecord value) + private void AddPendingSet(AvaloniaProperty property, TSetRecord value) { Contract.Requires(IsNotifying(property)); - setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value); + GetOrCreateStatus(property).PendingValues.Enqueue(value); } /// @@ -88,9 +103,9 @@ namespace Avalonia.Utilities /// /// The property to check. /// If the property has any pending assignments. - private bool HasPendingSet(TProperty property) + private bool HasPendingSet(AvaloniaProperty property) { - return setRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty; + return SetRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty; } /// @@ -98,9 +113,17 @@ namespace Avalonia.Utilities /// /// The property to check. /// The first pending assignment for the property. - private TSetRecord GetFirstPendingSet(TProperty property) + private TSetRecord GetFirstPendingSet(AvaloniaProperty property) + { + return GetOrCreateStatus(property).PendingValues.Dequeue(); + } + + private void CleanupSetStatus(AvaloniaProperty property) { - return setRecords.GetOrCreateValue(property).PendingValues.Dequeue(); + if (SetRecords.TryGetValue(property, out var status) && status.IsSimpleSet) + { + SetRecords.Remove(property); + } } public delegate bool SetterDelegate(TSetRecord record, ref TValue backing, Action notifyCallback); @@ -116,7 +139,7 @@ namespace Avalonia.Utilities /// /// The value to try to set. public bool SetAndNotify( - TProperty property, + AvaloniaProperty property, ref TValue backing, SetterDelegate setterCallback, TSetRecord value) @@ -145,6 +168,9 @@ namespace Avalonia.Utilities } }); } + + CleanupSetStatus(property); + return updated; } else if(!object.Equals(value, backing)) diff --git a/src/Avalonia.Base/Utilities/SingleOrQueue.cs b/src/Avalonia.Base/Utilities/SingleOrQueue.cs index f33adaff37..37eada3d96 100644 --- a/src/Avalonia.Base/Utilities/SingleOrQueue.cs +++ b/src/Avalonia.Base/Utilities/SingleOrQueue.cs @@ -11,7 +11,7 @@ namespace Avalonia.Utilities private Queue Tail => _tail ?? (_tail = new Queue()); - private bool HasTail => _tail != null; + public bool HasTail => _tail != null; public bool Empty { get; private set; } = true; diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 0e404500a1..c8e2124182 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -168,14 +168,14 @@ namespace Avalonia return value; } - private DeferredSetter _defferedSetter; + private DeferredSetter _defferedSetter; - public DeferredSetter Setter + public DeferredSetter Setter { get { return _defferedSetter ?? - (_defferedSetter = new DeferredSetter()); + (_defferedSetter = new DeferredSetter()); } } } From e2020f30120d72f57cfcaa6ef05ddd38db77de9c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 3 Jul 2018 17:06:40 -0500 Subject: [PATCH 017/106] Fix mock setup. --- tests/Avalonia.Base.UnitTests/PriorityValueTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs index 61ba9bc463..2f1b7862a7 100644 --- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs @@ -307,7 +307,7 @@ namespace Avalonia.Base.UnitTests private static Mock GetMockOwner() { var owner = new Mock(); - owner.SetupGet(o => o.Setter).Returns(new DeferredSetter()); + owner.SetupGet(o => o.Setter).Returns(new DeferredSetter()); return owner; } } From 06dbc915839a3161ebeb7e4e4da93b48f3f59a53 Mon Sep 17 00:00:00 2001 From: "rayyantahir2010@hotmail.com" Date: Wed, 4 Jul 2018 12:11:57 +0500 Subject: [PATCH 018/106] Moved caret visibility decision to helper method and called that method in OnTemplateApplied also --- src/Avalonia.Controls/TextBox.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index a4c6a6cdf7..ec32326cd9 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -262,7 +262,7 @@ namespace Avalonia.Controls if (IsFocused) { - _presenter.ShowCaret(); + DecideCaretVisibility(); } } @@ -282,15 +282,20 @@ namespace Avalonia.Controls } else { - if (!IsReadOnly) - _presenter?.ShowCaret(); - else - _presenter?.HideCaret(); + DecideCaretVisibility(); } e.Handled = true; } + private void DecideCaretVisibility() + { + if (!IsReadOnly || IsReadOnlyCaretVisible) + _presenter?.ShowCaret(); + else + _presenter?.HideCaret(); + } + protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); From 387de6a3f70e3eb87256b29d0ccc4d94555d126d Mon Sep 17 00:00:00 2001 From: "rayyantahir2010@hotmail.com" Date: Wed, 4 Jul 2018 12:29:08 +0500 Subject: [PATCH 019/106] Fix build failure --- src/Avalonia.Controls/TextBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index ec32326cd9..f5900f99e4 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -124,7 +124,7 @@ namespace Avalonia.Controls ScrollViewer.HorizontalScrollBarVisibilityProperty, horizontalScrollBarVisibility, BindingPriority.Style); - _undoRedoHelper = new UndoRedoHelper(this); + _undoRedoHelper = new UndoRedoHelper(this); } public bool AcceptsReturn @@ -290,7 +290,7 @@ namespace Avalonia.Controls private void DecideCaretVisibility() { - if (!IsReadOnly || IsReadOnlyCaretVisible) + if (!IsReadOnly) _presenter?.ShowCaret(); else _presenter?.HideCaret(); From 33172127cc04025fb4e6e567d357706a68b367d2 Mon Sep 17 00:00:00 2001 From: "rayyantahir2010@hotmail.com" <=> Date: Wed, 4 Jul 2018 12:54:54 +0500 Subject: [PATCH 020/106] Added the ability to copy from a readonly textbox --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 2 +- src/Avalonia.Controls/Presenters/TextPresenter.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 96c1665afe..a717518655 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -9,7 +9,7 @@ Gap="16"> - + Date: Thu, 5 Jul 2018 08:53:12 +0200 Subject: [PATCH 021/106] Call AddOwner on Slider.Orientation instead of declaring a new property --- src/Avalonia.Controls/Slider.cs | 5 +-- .../SliderTests.cs | 39 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/SliderTests.cs diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 2ef0af2852..31113812d1 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); + ScrollBar.OrientationProperty.AddOwner(); /// /// Defines the property. @@ -41,8 +41,7 @@ namespace Avalonia.Controls /// static Slider() { - PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical"); - PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal"); + OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); Thumb.DragStartedEvent.AddClassHandler(x => x.OnThumbDragStarted, RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler(x => x.OnThumbDragDelta, RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble); diff --git a/tests/Avalonia.Controls.UnitTests/SliderTests.cs b/tests/Avalonia.Controls.UnitTests/SliderTests.cs new file mode 100644 index 0000000000..dc47d9eb89 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/SliderTests.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class SliderTests + { + [Fact] + public void Default_Orientation_Should_Be_Horizontal() + { + var slider = new Slider(); + Assert.Equal(Orientation.Horizontal, slider.Orientation); + } + + [Fact] + public void Should_Set_Horizontal_Class() + { + var slider = new Slider + { + Orientation = Orientation.Horizontal + }; + + Assert.Contains(slider.Classes, ":horizontal".Equals); + } + + [Fact] + public void Should_Set_Vertical_Class() + { + var slider = new Slider + { + Orientation = Orientation.Vertical + }; + + Assert.Contains(slider.Classes, ":vertical".Equals); + } + } +} From 95ac57d648b0d48c7e20c4da43ac10e72e6f345a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 6 Jul 2018 14:27:15 -0500 Subject: [PATCH 022/106] Add some SingleOrQueue documentation. --- src/Avalonia.Base/Utilities/SingleOrQueue.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Base/Utilities/SingleOrQueue.cs b/src/Avalonia.Base/Utilities/SingleOrQueue.cs index 37eada3d96..04c1279148 100644 --- a/src/Avalonia.Base/Utilities/SingleOrQueue.cs +++ b/src/Avalonia.Base/Utilities/SingleOrQueue.cs @@ -4,6 +4,10 @@ using System.Text; namespace Avalonia.Utilities { + /// + /// FIFO Queue optimized for holding zero or one items. + /// + /// The type of items held in the queue. public class SingleOrQueue { private T _head; @@ -11,6 +15,9 @@ namespace Avalonia.Utilities private Queue Tail => _tail ?? (_tail = new Queue()); + /// + /// True if this queue has at some point had more than one element. + /// public bool HasTail => _tail != null; public bool Empty { get; private set; } = true; From 45f37dfd1eb7f8af1392447a9b0ad77da82fc964 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 6 Jul 2018 15:11:44 -0500 Subject: [PATCH 023/106] Remove cleanup since it was slowing down ControlCatalog. --- src/Avalonia.Base/Utilities/DeferredSetter.cs | 12 ------------ src/Avalonia.Base/Utilities/SingleOrQueue.cs | 5 +---- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs index 15c85338c7..ae6f599005 100644 --- a/src/Avalonia.Base/Utilities/DeferredSetter.cs +++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs @@ -45,8 +45,6 @@ namespace Avalonia.Utilities return pendingValues ?? (pendingValues = new SingleOrQueue()); } } - - public bool IsSimpleSet => pendingValues?.HasTail != true; } private Dictionary _setRecords; @@ -118,14 +116,6 @@ namespace Avalonia.Utilities return GetOrCreateStatus(property).PendingValues.Dequeue(); } - private void CleanupSetStatus(AvaloniaProperty property) - { - if (SetRecords.TryGetValue(property, out var status) && status.IsSimpleSet) - { - SetRecords.Remove(property); - } - } - public delegate bool SetterDelegate(TSetRecord record, ref TValue backing, Action notifyCallback); /// @@ -169,8 +159,6 @@ namespace Avalonia.Utilities }); } - CleanupSetStatus(property); - return updated; } else if(!object.Equals(value, backing)) diff --git a/src/Avalonia.Base/Utilities/SingleOrQueue.cs b/src/Avalonia.Base/Utilities/SingleOrQueue.cs index 04c1279148..4a66b72a56 100644 --- a/src/Avalonia.Base/Utilities/SingleOrQueue.cs +++ b/src/Avalonia.Base/Utilities/SingleOrQueue.cs @@ -15,10 +15,7 @@ namespace Avalonia.Utilities private Queue Tail => _tail ?? (_tail = new Queue()); - /// - /// True if this queue has at some point had more than one element. - /// - public bool HasTail => _tail != null; + private bool HasTail => _tail != null; public bool Empty { get; private set; } = true; From 4f84db9080d19beca0bcc1206370e822b0aac21e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 10 Jul 2018 22:06:50 +0100 Subject: [PATCH 024/106] Add fix for monomac borderless windows being transparent to mouse clicks when they shouldnt be. --- src/OSX/Avalonia.MonoMac/WindowImpl.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index a2f8df6791..3900af8709 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Platform; using MonoMac.AppKit; using MonoMac.CoreGraphics; +using Avalonia.Threading; namespace Avalonia.MonoMac { @@ -16,7 +17,14 @@ namespace Avalonia.MonoMac public WindowImpl() { - UpdateStyle(); + // Post UpdateStyle to UIThread otherwise for as yet unknown reason. + // The window becomes transparent to mouse clicks except a 100x100 square + // at the top left. (danwalmsley) + Dispatcher.UIThread.Post(() => + { + UpdateStyle(); + }); + Window.SetCanBecomeKeyAndMain(); Window.DidResize += delegate From b163892a7d6cb839e26ce39eb682b8b264563623 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 11 Jul 2018 11:18:59 +0200 Subject: [PATCH 025/106] Make TemplateBinding.Converter work. Also: - Add some unit tests - Make `TwoWay` bindings back to the parent control work at `LocalValue` priority Fixes 1737 --- .../Avalonia.Markup/Data/TemplateBinding.cs | 6 +- .../Data/TemplateBindingTests.cs | 124 ++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs diff --git a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs index 9a5aa41e1f..fab9bdbf55 100644 --- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs @@ -100,7 +100,9 @@ namespace Avalonia.Data CultureInfo.CurrentCulture); } - _target.TemplatedParent.SetValue(Property, value, BindingPriority.TemplatedParent); + // Use LocalValue priority here, as TemplatedParent doesn't make sense on controls + // that aren't template children. + _target.TemplatedParent.SetValue(Property, value, BindingPriority.LocalValue); } } @@ -171,7 +173,7 @@ namespace Avalonia.Data { if (e.Property == Property) { - PublishNext(_target.TemplatedParent.GetValue(Property)); + PublishValue(); } } } diff --git a/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs new file mode 100644 index 0000000000..ef3864abd7 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs @@ -0,0 +1,124 @@ +using System; +using System.Globalization; +using System.Linq; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Data.Converters; +using Avalonia.VisualTree; +using Xunit; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class TemplateBindingTests + { + [Fact] + public void OneWay_Binding_Should_Be_Set_Up() + { + var source = new Button + { + Template = new FuncControlTemplate