From 7a027792cbfc4426bea431ea4b24268ef68c68a3 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 30 Aug 2021 18:39:21 +0300 Subject: [PATCH 01/10] add failing test --- .../Avalonia.Base.UnitTests.csproj | 1 + .../Logging/LoggingTests.cs | 214 ++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 32f90d5cbf..c4c1f49346 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -14,6 +14,7 @@ + diff --git a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs new file mode 100644 index 0000000000..193d5e3a45 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Logging; +using Avalonia.Markup.Xaml; +using Avalonia.UnitTests; +using Avalonia.Utilities; +using Xunit; + +namespace Avalonia.Base.UnitTests.Logging +{ + public class LoggingTests + { + [Fact] + public void Control_Should_Not_Log_Binding_Errors_When_Detached_From_Visual_Tree() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + +"; + + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + using var logSink = new StubLogSink(LogEventLevel.Warning); + var panel = window.FindControl("panel"); + var rect = window.FindControl("rect"); + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + panel.Children.Remove(rect); + Assert.Equal(0, logSink.Results.Count); + } + } + } + + class StubLogSink : ILogSink, IDisposable + { + LogEventLevel _level; + public StubLogSink(LogEventLevel level) + { + _level = level; + Logger.Sink = this; + } + public void Dispose() + { + Logger.Sink = null; + } + public List Results { get; set; } = new List(); + + public bool IsEnabled(LogEventLevel level, string area) + { + return true; + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValue0)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2)); + } + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues) + { + if (level >= _level) + { + Results.Add(Format(area, messageTemplate, source, propertyValues)); + } + } + #region Copy-Pasta + private static string Format( + string area, + string template, + object source, + T0 v0 = default, + T1 v1 = default, + T2 v2 = default) + { + var result = new StringBuilder(template.Length); + var r = new CharacterReader(template.AsSpan()); + var i = 0; + + result.Append('['); + result.Append(area); + result.Append("] "); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i++ switch + { + 0 => v0, + 1 => v1, + 2 => v2, + _ => null + }); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source is object) + { + result.Append(" ("); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + + private static string Format( + string area, + string template, + object source, + object[] v) + { + var result = new StringBuilder(template.Length); + var r = new CharacterReader(template.AsSpan()); + var i = 0; + + result.Append('['); + result.Append(area); + result.Append(']'); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i < v.Length ? v[i++] : null); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source is object) + { + result.Append('('); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + #endregion + } +} From 22432a002dc9ba86429d582c08103e17f3e50433 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 31 Aug 2021 13:14:59 +0300 Subject: [PATCH 02/10] test --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 6d0302ace4..e89bf55404 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -57,7 +57,7 @@ namespace Avalonia.LogicalTree private void Detached(object sender, LogicalTreeAttachmentEventArgs e) { _value = null; - PublishNext(null); + // PublishNext(null); } private void Update() From 68370e37a5d5340613369e49f8aa9f9e5b7f31d1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 16:14:15 +0200 Subject: [PATCH 03/10] fixes(DataGrid): Issue #6508 does not re-enter the edit mode after pressing ESC --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index ab1aff9220..cb1ce0c731 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -4489,8 +4489,19 @@ namespace Avalonia.Controls element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext); if (element != null) { - // Subscribe to the new element's events - element.Initialized += EditingElement_Initialized; + + if (element.IsInitialized) + { + Threading.Dispatcher.UIThread.Post(() => + { + PreparingCellForEditPrivate(element as Control); + }); + } + else + { + // Subscribe to the new element's events + element.Initialized += EditingElement_Initialized; + } } } else From 063e5e7be3b94d4a3f929acbd2ac552534877e41 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 11:42:16 +0300 Subject: [PATCH 04/10] test --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index e89bf55404..501a26c848 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -57,7 +57,10 @@ namespace Avalonia.LogicalTree private void Detached(object sender, LogicalTreeAttachmentEventArgs e) { _value = null; - // PublishNext(null); + if (_relativeTo.IsAttachedToLogicalTree) + { + PublishNext(null); + } } private void Update() From 01518b37e78b44dff6f7839672b6fe5575771a08 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 12:30:23 +0300 Subject: [PATCH 05/10] test --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 6 ++---- src/Avalonia.Visuals/Visual.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 501a26c848..3f5b1ee0b5 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -57,10 +57,8 @@ namespace Avalonia.LogicalTree private void Detached(object sender, LogicalTreeAttachmentEventArgs e) { _value = null; - if (_relativeTo.IsAttachedToLogicalTree) - { - PublishNext(null); - } + PublishNext(null); + } private void Update() diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 2c96d87bb6..163c3db5fd 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -493,7 +493,7 @@ namespace Avalonia // In theory this should only need to check for logical tree attachment, but in practise // due to ContentControlMixin only taking effect when the template has finished being // applied, some controls are attached to the visual tree before the logical tree. - if (((ILogical)this).IsAttachedToLogicalTree || ((IVisual)this).IsAttachedToVisualTree) + if (((ILogical)this).IsAttachedToLogicalTree) { if (e is BindingChainException b && string.IsNullOrEmpty(b.ExpressionErrorPoint) && From 5c1fd27d1f0f31cf2c5c5a60aa634c7d3fc58914 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 15:00:08 +0300 Subject: [PATCH 06/10] wip --- src/Avalonia.Visuals/Visual.cs | 5 +- .../Logging/LoggingTests.cs | 191 +----------------- 2 files changed, 12 insertions(+), 184 deletions(-) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 163c3db5fd..322b630e83 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -489,10 +489,7 @@ namespace Avalonia protected internal sealed override void LogBindingError(AvaloniaProperty property, Exception e) { - // Don't log a binding error unless the control is attached to a logical or visual tree. - // In theory this should only need to check for logical tree attachment, but in practise - // due to ContentControlMixin only taking effect when the template has finished being - // applied, some controls are attached to the visual tree before the logical tree. + // Don't log a binding error unless the control is attached to a logical tree. if (((ILogical)this).IsAttachedToLogicalTree) { if (e is BindingChainException b && diff --git a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs index 193d5e3a45..9e1885463d 100644 --- a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Controls.Shapes; -using Avalonia.Logging; using Avalonia.Markup.Xaml; using Avalonia.UnitTests; -using Avalonia.Utilities; using Xunit; namespace Avalonia.Base.UnitTests.Logging @@ -28,187 +23,23 @@ namespace Avalonia.Base.UnitTests.Logging "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); - using var logSink = new StubLogSink(LogEventLevel.Warning); + var calledTimes = 0; + using var logSink = TestLogSink.Start((l, a, s, m, d) => + { + if (l >= Avalonia.Logging.LogEventLevel.Warning) + { + calledTimes++; + } + }); var panel = window.FindControl("panel"); var rect = window.FindControl("rect"); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); panel.Children.Remove(rect); - Assert.Equal(0, logSink.Results.Count); - } - } - } - - class StubLogSink : ILogSink, IDisposable - { - LogEventLevel _level; - public StubLogSink(LogEventLevel level) - { - _level = level; - Logger.Sink = this; - } - public void Dispose() - { - Logger.Sink = null; - } - public List Results { get; set; } = new List(); - - public bool IsEnabled(LogEventLevel level, string area) - { - return true; - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValue0)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValue0, propertyValue1, propertyValue2)); - } - } - - public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues) - { - if (level >= _level) - { - Results.Add(Format(area, messageTemplate, source, propertyValues)); - } - } - #region Copy-Pasta - private static string Format( - string area, - string template, - object source, - T0 v0 = default, - T1 v1 = default, - T2 v2 = default) - { - var result = new StringBuilder(template.Length); - var r = new CharacterReader(template.AsSpan()); - var i = 0; - - result.Append('['); - result.Append(area); - result.Append("] "); - - while (!r.End) - { - var c = r.Take(); - - if (c != '{') - { - result.Append(c); - } - else - { - if (r.Peek != '{') - { - result.Append('\''); - result.Append(i++ switch - { - 0 => v0, - 1 => v1, - 2 => v2, - _ => null - }); - result.Append('\''); - r.TakeUntil('}'); - r.Take(); - } - else - { - result.Append('{'); - r.Take(); - } - } + Assert.Equal(0, calledTimes); } - - if (source is object) - { - result.Append(" ("); - result.Append(source.GetType().Name); - result.Append(" #"); - result.Append(source.GetHashCode()); - result.Append(')'); - } - - return result.ToString(); } + } - private static string Format( - string area, - string template, - object source, - object[] v) - { - var result = new StringBuilder(template.Length); - var r = new CharacterReader(template.AsSpan()); - var i = 0; - - result.Append('['); - result.Append(area); - result.Append(']'); - - while (!r.End) - { - var c = r.Take(); - - if (c != '{') - { - result.Append(c); - } - else - { - if (r.Peek != '{') - { - result.Append('\''); - result.Append(i < v.Length ? v[i++] : null); - result.Append('\''); - r.TakeUntil('}'); - r.Take(); - } - else - { - result.Append('{'); - r.Take(); - } - } - } - - if (source is object) - { - result.Append('('); - result.Append(source.GetType().Name); - result.Append(" #"); - result.Append(source.GetHashCode()); - result.Append(')'); - } - return result.ToString(); - } - #endregion - } } From 77f4a6e808a37664c5a4aa23e171129f02dd9971 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 15:17:16 +0300 Subject: [PATCH 07/10] more tests --- .../Logging/LoggingTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs index 9e1885463d..d160713023 100644 --- a/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs @@ -39,6 +39,34 @@ namespace Avalonia.Base.UnitTests.Logging Assert.Equal(0, calledTimes); } } + + [Fact] + public void Control_Should_Log_Binding_Errors_When_No_Ancestor_With_Such_Name() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + +"; + var calledTimes = 0; + using var logSink = TestLogSink.Start((l, a, s, m, d) => + { + if (l >= Avalonia.Logging.LogEventLevel.Warning && s is Rectangle) + { + calledTimes++; + } + }); + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + Assert.Equal(1, calledTimes); + } + } } From df5a9c7afc1726beb5a1338c7175ee56243afea2 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 1 Sep 2021 15:18:17 +0300 Subject: [PATCH 08/10] wip --- src/Avalonia.Styling/LogicalTree/ControlLocator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 3f5b1ee0b5..6d0302ace4 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -58,7 +58,6 @@ namespace Avalonia.LogicalTree { _value = null; PublishNext(null); - } private void Update() From c919109be60d96900961cb1917314903eada4bfb Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 10 Sep 2021 11:41:07 +0200 Subject: [PATCH 09/10] fixes(DataGrid): Issue #6567 New line in Editable DataGrid, Not editable --- .../DataGridDataConnection.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs index a94acdec57..fade597ca1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs @@ -233,7 +233,7 @@ namespace Avalonia.Controls else { editableCollectionView.EditItem(dataItem); - return editableCollectionView.IsEditingItem; + return editableCollectionView.IsEditingItem || editableCollectionView.IsAddingNew; } } @@ -314,7 +314,14 @@ namespace Avalonia.Controls CommittingEdit = true; try { - editableCollectionView.CommitEdit(); + if (editableCollectionView.IsAddingNew) + { + editableCollectionView.CommitNew(); + } + else + { + editableCollectionView.CommitEdit(); + } } finally { From 7a8650ae06867b16c716740b117bf61aef87653a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 10 Sep 2021 18:05:37 +0200 Subject: [PATCH 10/10] fixes(Datagrid): Avoid using Threading.Dispatcher.UIThread.Post( --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index cb1ce0c731..9d231ff8b2 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -4489,13 +4489,11 @@ namespace Avalonia.Controls element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext); if (element != null) { - + + dataGridCell.Content = element; if (element.IsInitialized) { - Threading.Dispatcher.UIThread.Post(() => - { - PreparingCellForEditPrivate(element as Control); - }); + PreparingCellForEditPrivate(element as Control); } else { @@ -4508,9 +4506,10 @@ namespace Avalonia.Controls { // Generate Element and apply column style if available element = dataGridColumn.GenerateElementInternal(dataGridCell, dataGridRow.DataContext); + dataGridCell.Content = element; } - dataGridCell.Content = element; + } private void PreparingCellForEditPrivate(Control editingElement)