From de5dd5096efb3a52803d4f7cb207b9565020916b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Thu, 3 Sep 2020 00:09:52 +0200 Subject: [PATCH 01/23] Schedule opening popup if cannot be opened right now --- src/Avalonia.Controls/Primitives/Popup.cs | 21 ++++++++++++-- .../Primitives/PopupTests.cs | 28 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index a676892384..1e5e80d144 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -128,6 +128,7 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); + private bool _isOpenRequested = false; private bool _isOpen; private bool _ignoreIsOpenChanged; private PopupOpenState? _openState; @@ -361,17 +362,19 @@ namespace Avalonia.Controls.Primitives if (placementTarget == null) { - throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null"); + _isOpenRequested = true; + return; } var topLevel = placementTarget.VisualRoot as TopLevel; if (topLevel == null) { - throw new InvalidOperationException( - "Attempted to open a popup not attached to a TopLevel"); + _isOpenRequested = true; + return; } + _isOpenRequested = false; var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver); var handlerCleanup = new CompositeDisposable(5); @@ -492,6 +495,17 @@ namespace Avalonia.Controls.Primitives return new Size(); } + + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + if (_isOpenRequested) + { + Open(); + } + } + /// protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { @@ -552,6 +566,7 @@ namespace Avalonia.Controls.Primitives private void CloseCore() { + _isOpenRequested = false; if (_openState is null) { using (BeginIgnoringIsOpen()) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d9176ca55d..73871cf79f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -22,6 +22,34 @@ namespace Avalonia.Controls.UnitTests.Primitives { protected bool UsePopupHost; + + [Fact] + public void Popup_Without_TopLevel_Shouldnt_Call_Open() + { + int openedEvent = 0; + var target = new Popup(); + target.Opened += (s, a) => openedEvent++; + target.IsOpen = true; + + Assert.Equal(0, openedEvent); + } + + [Fact] + public void Opening_Popup_Shouldnt_Throw_When_Not_In_Visual_Tree() + { + var target = new Popup(); + target.IsOpen = true; + } + + [Fact] + public void Opening_Popup_Shouldnt_Throw_When_In_Tree_Without_TopLevel() + { + Control c = new Control(); + var target = new Popup(); + ((ISetLogicalParent)target).SetParent(c); + target.IsOpen = true; + } + [Fact] public void Setting_Child_Should_Set_Child_Controls_LogicalParent() { From db98c340d65532b136750cae5f5379c54b7c64a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Onak?= Date: Thu, 3 Sep 2020 20:47:52 +0200 Subject: [PATCH 02/23] Add test to check if popup is displayed correctly after window is shown --- .../Primitives/PopupTests.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 73871cf79f..5bad646aa7 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -21,7 +21,22 @@ namespace Avalonia.Controls.UnitTests.Primitives public class PopupTests { protected bool UsePopupHost; - + + [Fact] + public void Popup_Open_Without_Target_Should_Attach_Itself_Later() + { + using (CreateServices()) + { + int openedEvent = 0; + var target = new Popup(); + target.Opened += (s, a) => openedEvent++; + target.IsOpen = true; + + var window = PreparedWindow(target); + window.Show(); + Assert.Equal(1, openedEvent); + } + } [Fact] public void Popup_Without_TopLevel_Shouldnt_Call_Open() From f33e12c22996fbe3a23938462f5fe66de41df9fd Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 11 Sep 2020 04:05:05 -0400 Subject: [PATCH 03/23] DataGridTextColumn: Make FontFamily, FontSize, FontStyle, FontWeight and Foreground properties bindable --- .../ApiCompatBaseline.txt | 5 + .../DataGridTextColumn.cs | 231 +++++------------- .../Utils/DataGridHelper.cs | 22 ++ 3 files changed, 92 insertions(+), 166 deletions(-) create mode 100644 src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs diff --git a/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt new file mode 100644 index 0000000000..82472c505a --- /dev/null +++ b/src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt @@ -0,0 +1,5 @@ +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 diff --git a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs index d31204b9e6..1cf6ab68ac 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs @@ -20,11 +20,6 @@ namespace Avalonia.Controls { private const string DATAGRID_TextColumnCellTextBlockMarginKey = "DataGridTextColumnCellTextBlockMargin"; - private double? _fontSize; - private FontStyle? _fontStyle; - private FontWeight? _fontWeight; - private IBrush _foreground; - /// /// Initializes a new instance of the class. /// @@ -36,18 +31,24 @@ namespace Avalonia.Controls /// /// Identifies the FontFamily dependency property. /// - public static readonly StyledProperty FontFamilyProperty = - AvaloniaProperty.Register(nameof(FontFamily)); + public static readonly AttachedProperty FontFamilyProperty = + TextBlock.FontFamilyProperty.AddOwner(); /// /// Gets or sets the font name. /// - public string FontFamily + public FontFamily FontFamily { - get { return GetValue(FontFamilyProperty); } - set { SetValue(FontFamilyProperty, value); } + get => GetValue(FontFamilyProperty); + set => SetValue(FontFamilyProperty, value); } + /// + /// Identifies the FontSize dependency property. + /// + public static readonly AttachedProperty FontSizeProperty = + TextBlock.FontSizeProperty.AddOwner(); + /// /// Gets or sets the font size. /// @@ -55,74 +56,66 @@ namespace Avalonia.Controls [DefaultValue(double.NaN)] public double FontSize { - get - { - return _fontSize ?? Double.NaN; - } - set - { - if (_fontSize != value) - { - _fontSize = value; - NotifyPropertyChanged(nameof(FontSize)); - } - } + get => GetValue(FontSizeProperty); + set => SetValue(FontSizeProperty, value); } + /// + /// Identifies the FontStyle dependency property. + /// + public static readonly AttachedProperty FontStyleProperty = + TextBlock.FontStyleProperty.AddOwner(); + /// /// Gets or sets the font style. /// public FontStyle FontStyle { - get - { - return _fontStyle ?? FontStyle.Normal; - } - set - { - if (_fontStyle != value) - { - _fontStyle = value; - NotifyPropertyChanged(nameof(FontStyle)); - } - } + get => GetValue(FontStyleProperty); + set => SetValue(FontStyleProperty, value); } + /// + /// Identifies the FontWeight dependency property. + /// + public static readonly AttachedProperty FontWeightProperty = + TextBlock.FontWeightProperty.AddOwner(); + /// /// Gets or sets the font weight or thickness. /// public FontWeight FontWeight { - get - { - return _fontWeight ?? FontWeight.Normal; - } - set - { - if (_fontWeight != value) - { - _fontWeight = value; - NotifyPropertyChanged(nameof(FontWeight)); - } - } + get => GetValue(FontWeightProperty); + set => SetValue(FontWeightProperty, value); } + /// + /// Identifies the Foreground dependency property. + /// + public static readonly AttachedProperty ForegroundProperty = + TextBlock.ForegroundProperty.AddOwner(); + /// /// Gets or sets a brush that describes the foreground of the column cells. /// public IBrush Foreground { - get - { - return _foreground; - } - set + get => GetValue(ForegroundProperty); + set => SetValue(ForegroundProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == FontFamilyProperty + || change.Property == FontSizeProperty + || change.Property == FontStyleProperty + || change.Property == FontWeightProperty + || change.Property == ForegroundProperty) { - if (_foreground != value) - { - _foreground = value; - NotifyPropertyChanged(nameof(Foreground)); - } + NotifyPropertyChanged(change.Property.Name); } } @@ -154,26 +147,7 @@ namespace Avalonia.Controls Background = new SolidColorBrush(Colors.Transparent) }; - if (IsSet(FontFamilyProperty)) - { - textBox.FontFamily = FontFamily; - } - if (_fontSize.HasValue) - { - textBox.FontSize = _fontSize.Value; - } - if (_fontStyle.HasValue) - { - textBox.FontStyle = _fontStyle.Value; - } - if (_fontWeight.HasValue) - { - textBox.FontWeight = _fontWeight.Value; - } - if (_foreground != null) - { - textBox.Foreground = _foreground; - } + SyncProperties(textBox); return textBox; } @@ -192,26 +166,8 @@ namespace Avalonia.Controls VerticalAlignment = VerticalAlignment.Center }; - if (IsSet(FontFamilyProperty)) - { - textBlockElement.FontFamily = FontFamily; - } - if (_fontSize.HasValue) - { - textBlockElement.FontSize = _fontSize.Value; - } - if (_fontStyle.HasValue) - { - textBlockElement.FontStyle = _fontStyle.Value; - } - if (_fontWeight.HasValue) - { - textBlockElement.FontWeight = _fontWeight.Value; - } - if (_foreground != null) - { - textBlockElement.Foreground = _foreground; - } + SyncProperties(textBlockElement); + if (Binding != null) { textBlockElement.Bind(TextBlock.TextProperty, Binding); @@ -261,99 +217,42 @@ namespace Avalonia.Controls throw new ArgumentNullException("element"); } - if(element is TextBox textBox) + if (element is AvaloniaObject content) { if (propertyName == nameof(FontFamily)) { - textBox.FontFamily = FontFamily; + DataGridHelper.SyncColumnProperty(this, content, FontFamilyProperty); } else if (propertyName == nameof(FontSize)) { - SetTextFontSize(textBox, TextBox.FontSizeProperty); + DataGridHelper.SyncColumnProperty(this, content, FontSizeProperty); } else if (propertyName == nameof(FontStyle)) { - textBox.FontStyle = FontStyle; + DataGridHelper.SyncColumnProperty(this, content, FontStyleProperty); } else if (propertyName == nameof(FontWeight)) { - textBox.FontWeight = FontWeight; + DataGridHelper.SyncColumnProperty(this, content, FontWeightProperty); } else if (propertyName == nameof(Foreground)) { - textBox.Foreground = Foreground; - } - else - { - if (FontFamily != null) - { - textBox.FontFamily = FontFamily; - } - SetTextFontSize(textBox, TextBox.FontSizeProperty); - textBox.FontStyle = FontStyle; - textBox.FontWeight = FontWeight; - if (Foreground != null) - { - textBox.Foreground = Foreground; - } - } - - } - else if (element is TextBlock textBlock) - { - if (propertyName == nameof(FontFamily)) - { - textBlock.FontFamily = FontFamily; - } - else if (propertyName == nameof(FontSize)) - { - SetTextFontSize(textBlock, TextBlock.FontSizeProperty); - } - else if (propertyName == nameof(FontStyle)) - { - textBlock.FontStyle = FontStyle; - } - else if (propertyName == nameof(FontWeight)) - { - textBlock.FontWeight = FontWeight; - } - else if (propertyName == nameof(Foreground)) - { - textBlock.Foreground = Foreground; - } - else - { - if (FontFamily != null) - { - textBlock.FontFamily = FontFamily; - } - SetTextFontSize(textBlock, TextBlock.FontSizeProperty); - textBlock.FontStyle = FontStyle; - textBlock.FontWeight = FontWeight; - if (Foreground != null) - { - textBlock.Foreground = Foreground; - } + DataGridHelper.SyncColumnProperty(this, content, ForegroundProperty); } } else { - throw DataGridError.DataGrid.ValueIsNotAnInstanceOfEitherOr("element", typeof(TextBox), typeof(TextBlock)); + throw DataGridError.DataGrid.ValueIsNotAnInstanceOf("element", typeof(AvaloniaObject)); } } - private void SetTextFontSize(AvaloniaObject textElement, AvaloniaProperty fontSizeProperty) + private void SyncProperties(AvaloniaObject content) { - double newFontSize = FontSize; - if (double.IsNaN(newFontSize)) - { - textElement.ClearValue(fontSizeProperty); - } - else - { - textElement.SetValue(fontSizeProperty, newFontSize); - } + DataGridHelper.SyncColumnProperty(this, content, FontFamilyProperty); + DataGridHelper.SyncColumnProperty(this, content, FontSizeProperty); + DataGridHelper.SyncColumnProperty(this, content, FontStyleProperty); + DataGridHelper.SyncColumnProperty(this, content, FontWeightProperty); + DataGridHelper.SyncColumnProperty(this, content, ForegroundProperty); } - } } diff --git a/src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs new file mode 100644 index 0000000000..d553a82fa0 --- /dev/null +++ b/src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs @@ -0,0 +1,22 @@ +namespace Avalonia.Controls +{ + public static class DataGridHelper + { + internal static void SyncColumnProperty(AvaloniaObject column, AvaloniaObject content, AvaloniaProperty property) + { + SyncColumnProperty(column, content, property, property); + } + + internal static void SyncColumnProperty(AvaloniaObject column, AvaloniaObject content, AvaloniaProperty contentProperty, AvaloniaProperty columnProperty) + { + if (!column.IsSet(columnProperty)) + { + content.ClearValue(contentProperty); + } + else + { + content.SetValue(contentProperty, column.GetValue(columnProperty)); + } + } + } +} From ab5c3dd190088d80464edbdd76bb366b7ae52bab Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 11 Sep 2020 04:06:10 -0400 Subject: [PATCH 04/23] DataGridCheckBoxColumn: Make IsThreeState property bindable --- .../DataGridCheckBoxColumn.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index f1bbea9949..e2a067ac61 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -17,9 +17,7 @@ namespace Avalonia.Controls /// public class DataGridCheckBoxColumn : DataGridBoundColumn { - private bool _beganEditWithKeyboard; - private bool _isThreeState; private CheckBox _currentCheckBox; private DataGrid _owningGrid; @@ -31,6 +29,12 @@ namespace Avalonia.Controls BindingTarget = CheckBox.IsCheckedProperty; } + /// + /// Defines the property. + /// + public static StyledProperty IsThreeStateProperty = + CheckBox.IsThreeStateProperty.AddOwner(); + /// /// Gets or sets a value that indicates whether the hosted controls allow three states or two. /// @@ -39,17 +43,17 @@ namespace Avalonia.Controls /// public bool IsThreeState { - get - { - return _isThreeState; - } - set + get => GetValue(IsThreeStateProperty); + set => SetValue(IsThreeStateProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == IsThreeStateProperty) { - if (_isThreeState != value) - { - _isThreeState = value; - NotifyPropertyChanged(nameof(IsThreeState)); - } + NotifyPropertyChanged(change.Property.Name); } } @@ -203,9 +207,9 @@ namespace Avalonia.Controls { throw new ArgumentNullException("element"); } - if(element is CheckBox checkBox) + if (element is CheckBox checkBox) { - checkBox.IsThreeState = IsThreeState; + DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty); } else { @@ -229,7 +233,7 @@ namespace Avalonia.Controls { checkBox.HorizontalAlignment = HorizontalAlignment.Center; checkBox.VerticalAlignment = VerticalAlignment.Center; - checkBox.IsThreeState = IsThreeState; + DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty); } private bool EnsureOwningGrid() From fae969644a110f1a78b9d9307de9eeefa7cc86c7 Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 11 Sep 2020 04:11:02 -0400 Subject: [PATCH 05/23] Update DataGrid sample page to test column bindings --- samples/ControlCatalog/Pages/DataGridPage.xaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index d045626c2c..cacc2204bd 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -11,12 +11,17 @@ - + DataGrid A control for displaying and interacting with a data source. - + + + + + + @@ -39,13 +44,13 @@ - + - - - + + +