From 3518f19b2aa5d688781a06217e7090bb2efb575c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 16 Nov 2020 11:22:05 +0100 Subject: [PATCH 1/5] Revert "ItemsControl+ItemVirtualizerSimple don not recreate containers when Items or ItemTemplate are replaced" --- src/Avalonia.Controls/ItemsControl.cs | 6 +- .../Presenters/ItemVirtualizerSimple.cs | 4 - .../Presenters/ItemsPresenterBase.cs | 4 +- .../ItemsControlTests.cs | 92 -------------- .../ListBoxTests.cs | 112 ------------------ 5 files changed, 2 insertions(+), 216 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index f955df5f21..4dc8aec6f3 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -449,11 +449,7 @@ namespace Avalonia.Controls if (_itemContainerGenerator != null) { _itemContainerGenerator.ItemTemplate = (IDataTemplate)e.NewValue; - - if (e.OldValue != null && Presenter != null) - { - Presenter.ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } + // TODO: Rebuild the item containers. } } diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 51dbc969a8..7d50ef7d33 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -200,10 +200,6 @@ namespace Avalonia.Controls.Presenters break; case NotifyCollectionChangedAction.Reset: - Owner.ItemContainerGenerator.Clear(); - VirtualizingPanel.Children.Clear(); - FirstIndex = NextIndex = 0; - RecycleContainersOnRemove(); CreateAndRemoveContainers(); panel.ForceInvalidateMeasure(); diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 6c408bbed9..52f173fc71 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -57,8 +57,6 @@ namespace Avalonia.Controls.Presenters set { - var itemsReplaced = (_items != value); - _itemsSubscription?.Dispose(); _itemsSubscription = null; @@ -69,7 +67,7 @@ namespace Avalonia.Controls.Presenters SetAndRaise(ItemsProperty, ref _items, value); - if (_createdPanel && itemsReplaced) + if (_createdPanel) { ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 157eefb84a..684486cbae 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -596,98 +596,6 @@ namespace Avalonia.Controls.UnitTests root.Child = target; } - [Fact] - public void Presenter_Items_Should_Be_In_Sync_When_Replacing_Items() - { - var target = new ItemsControl - { - Template = GetTemplate(), - Items = new[] - { - new Item("Item1") - } - }; - - var root = new TestRoot { Child = target }; - var otherPanel = new StackPanel(); - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - int dematerializedEventCallCount = 0; - target.ItemContainerGenerator.Dematerialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value); - dematerializedEventCallCount++; - }; - - int materializedEventCallCount = 0; - target.ItemContainerGenerator.Materialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item2", ((Item)e.Containers[0].Item).Value); - materializedEventCallCount++; - }; - - target.Items = new[] - { - new Item("Item2") - }; - - //Ensure that events are called one time only - Assert.Equal(1, dematerializedEventCallCount); - Assert.Equal(1, materializedEventCallCount); - } - - [Fact] - public void Presenter_Items_Should_Be_In_Sync_When_Replacing_ItemTemplate() - { - var target = new ItemsControl - { - Template = GetTemplate(), - Items = new[] - { - new Item("Item1") - }, - ItemTemplate = new FuncDataTemplate((x, ns) => new TextBlock()) - }; - - var root = new TestRoot { Child = target }; - var otherPanel = new StackPanel(); - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - int dematerializedEventCallCount = 0; - target.ItemContainerGenerator.Dematerialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value); - var contentPresenter = ((ContentPresenter)e.Containers[0].ContainerControl); - contentPresenter.UpdateChild(); - Assert.IsType(contentPresenter.Child); - dematerializedEventCallCount++; - }; - - int materializedEventCallCount = 0; - target.ItemContainerGenerator.Materialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value); - var contentPresenter = ((ContentPresenter)e.Containers[0].ContainerControl); - contentPresenter.UpdateChild(); - Assert.IsType(contentPresenter.Child); - materializedEventCallCount++; - }; - - target.ItemTemplate = - new FuncDataTemplate((x, ns) => new Canvas()); - - Assert.Equal(1, dematerializedEventCallCount); - Assert.Equal(1, materializedEventCallCount); - } - private class Item { public Item(string value) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 364cb01c65..145fce4fed 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -454,118 +454,6 @@ namespace Avalonia.Controls.UnitTests } } - - [Fact] - public void ListBox_Presenter_Items_Should_Be_In_Sync_When_Replacing_Items() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var wnd = new Window() { Width = 100, Height = 100, IsVisible = true }; - - var target = new ListBox() - { - VerticalAlignment = Layout.VerticalAlignment.Top, - AutoScrollToSelectedItem = true, - Width = 50, - VirtualizationMode = ItemVirtualizationMode.Simple, - Items = new[] - { - new Item("Item1") - }, - }; - wnd.Content = target; - - var lm = wnd.LayoutManager; - - lm.ExecuteInitialLayoutPass(); - - int dematerializedEventCallCount = 0; - target.ItemContainerGenerator.Dematerialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value); - dematerializedEventCallCount++; - }; - - int materializedEventCallCount = 0; - target.ItemContainerGenerator.Materialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item2", ((Item)e.Containers[0].Item).Value); - materializedEventCallCount++; - }; - - target.Items = new[] - { - new Item("Item2") - }; - - //assert that materialize/dematerialize events are called exactly one time - Assert.Equal(1, dematerializedEventCallCount); - Assert.Equal(1, materializedEventCallCount); - } - } - - [Fact] - public void ListBox_Items_Should_Be_In_Sync_When_Replacing_ItemTemplate() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var wnd = new Window() { Width = 100, Height = 100, IsVisible = true }; - - var target = new ListBox() - { - VerticalAlignment = Layout.VerticalAlignment.Top, - AutoScrollToSelectedItem = true, - Width = 50, - VirtualizationMode = ItemVirtualizationMode.Simple, - Items = new[] - { - new Item("Item1") - }, - ItemTemplate = - new FuncDataTemplate((x, ns) => new Canvas()) - }; - - wnd.Content = target; - - var lm = wnd.LayoutManager; - - lm.ExecuteInitialLayoutPass(); - - int dematerializedEventCallCount = 0; - target.ItemContainerGenerator.Dematerialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value); - Assert.IsType(((ListBoxItem)e.Containers[0].ContainerControl).Presenter.Child); - dematerializedEventCallCount++; - }; - - int materializedEventCallCount = 0; - ListBoxItem materializedListBoxItem = null; - target.ItemContainerGenerator.Materialized += (s, e) => - { - Assert.IsType(e.Containers[0].Item); - Assert.Equal("Item1", ((Item)e.Containers[0].Item).Value); - materializedListBoxItem = ((ListBoxItem)e.Containers[0].ContainerControl); - materializedEventCallCount++; - }; - - target.ItemTemplate = - new FuncDataTemplate((x, ns) => new TextBlock()); - - //ensure events are called only one time - Assert.Equal(1, dematerializedEventCallCount); - Assert.Equal(1, materializedEventCallCount); - - wnd.LayoutManager.ExecuteLayoutPass(); - - //ensure that new template has been applied - Assert.IsType(materializedListBoxItem.Presenter.Child); - } - } - private FuncControlTemplate ListBoxTemplate() { return new FuncControlTemplate((parent, scope) => From 1dfb76d993fed57ac3ac64da4bcc1af475639983 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 16 Nov 2020 19:43:24 +0300 Subject: [PATCH 2/5] Fixed COM calling convention for 32 bit windows --- src/Avalonia.MicroCom/MicroComVtblBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.MicroCom/MicroComVtblBase.cs b/src/Avalonia.MicroCom/MicroComVtblBase.cs index e06e2d4934..2f0607c0a8 100644 --- a/src/Avalonia.MicroCom/MicroComVtblBase.cs +++ b/src/Avalonia.MicroCom/MicroComVtblBase.cs @@ -7,10 +7,10 @@ namespace Avalonia.MicroCom public unsafe class MicroComVtblBase { private List _methods = new List(); - [UnmanagedFunctionPointerAttribute(CallingConvention.ThisCall)] + [UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] private delegate int AddRefDelegate(Ccw* ccw); - [UnmanagedFunctionPointerAttribute(CallingConvention.ThisCall)] + [UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] private delegate int QueryInterfaceDelegate(Ccw* ccw, Guid* guid, void** ppv); public static IntPtr Vtable { get; } = new MicroComVtblBase().CreateVTable(); From be8a3e83fa9fd50c29e12c33af4951c05105f231 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 16 Nov 2020 21:12:46 +0000 Subject: [PATCH 3/5] fix OSX NRE. --- src/Avalonia.Native/WindowImplBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 20b6b8ecc5..150ab2703e 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -174,7 +174,7 @@ namespace Avalonia.Native void IAvnWindowBaseEvents.Resized(AvnSize* size) { - if (_parent._native != null) + if (_parent?._native != null) { var s = new Size(size->Width, size->Height); _parent._savedLogicalSize = s; From c24f65a5f78cbe0dd11d926d28ea8d8a198d90a0 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 16 Nov 2020 22:51:18 +0100 Subject: [PATCH 4/5] Fix even more broken cases for closing child/dialog windows on win32. --- .../ControlCatalog/Pages/DialogsPage.xaml.cs | 29 +++++++++++++--- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 19 ++--------- src/Windows/Avalonia.Win32/WindowImpl.cs | 34 +++++++++++++++++++ 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index cf6c771e34..49921fb7f6 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using Avalonia.Controls; using Avalonia.Dialogs; +using Avalonia.Layout; using Avalonia.Markup.Xaml; #pragma warning disable 4014 @@ -112,11 +113,29 @@ namespace ControlCatalog.Pages private Window CreateSampleWindow() { - var window = new Window(); - window.Height = 200; - window.Width = 200; - window.Content = new TextBlock { Text = "Hello world!" }; - window.WindowStartupLocation = WindowStartupLocation.CenterOwner; + Button button; + + var window = new Window + { + Height = 200, + Width = 200, + Content = new StackPanel + { + Spacing = 4, + Children = + { + new TextBlock { Text = "Hello world!" }, + (button = new Button + { + HorizontalAlignment = HorizontalAlignment.Center, + Content = "Click to close" + }) + } + }, + WindowStartupLocation = WindowStartupLocation.CenterOwner + }; + + button.Click += (_, __) => window.Close(); return window; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index d770f4b211..78de681403 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -65,23 +65,10 @@ namespace Avalonia.Win32 return IntPtr.Zero; } - // Based on https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Window.cs#L4270-L4337 - // We need to enable parent window before destroying child window to prevent OS from activating a random window behind us. - // This is described here: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks - // Our window closed callback will set enabled state to a correct value after child window gets destroyed. - // We need to verify if parent is still alive (perhaps it got destroyed somehow). - if (_parent != null && IsWindow(_parent._hwnd)) - { - var wasActive = GetActiveWindow() == _hwnd; - - _parent.SetEnabled(true); + BeforeCloseCleanup(false); - // We also need to activate our parent window since again OS might try to activate a window behind if it is not set. - if (wasActive) - { - SetActiveWindow(_parent._hwnd); - } - } + // Used to distinguish between programmatic and regular close requests. + _isCloseRequested = true; break; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index c603128a18..2483356e9a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -83,6 +83,7 @@ namespace Avalonia.Win32 private POINT _maxTrackSize; private WindowImpl _parent; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; + private bool _isCloseRequested; public WindowImpl() { @@ -506,6 +507,13 @@ namespace Avalonia.Win32 if (_hwnd != IntPtr.Zero) { + // Detect if we are being closed programmatically - this would mean that WM_CLOSE was not called + // and we didn't prepare this window for destruction. + if (!_isCloseRequested) + { + BeforeCloseCleanup(true); + } + DestroyWindow(_hwnd); _hwnd = IntPtr.Zero; } @@ -948,6 +956,32 @@ namespace Avalonia.Win32 SetFocus(_hwnd); } } + + private void BeforeCloseCleanup(bool isDisposing) + { + // Based on https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Window.cs#L4270-L4337 + // We need to enable parent window before destroying child window to prevent OS from activating a random window behind us (or last active window). + // This is described here: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks + // We need to verify if parent is still alive (perhaps it got destroyed somehow). + if (_parent != null && IsWindow(_parent._hwnd)) + { + var wasActive = GetActiveWindow() == _hwnd; + + // We can only set enabled state if we are not disposing - generally Dispose happens after enabled state has been set. + // Ignoring this would cause us to enable a window that might be disabled. + if (!isDisposing) + { + // Our window closed callback will set enabled state to a correct value after child window gets destroyed. + _parent.SetEnabled(true); + } + + // We also need to activate our parent window since again OS might try to activate a window behind if it is not set. + if (wasActive) + { + SetActiveWindow(_parent._hwnd); + } + } + } private void MaximizeWithoutCoveringTaskbar() { From 1428badc6b17c1922151d31af42d34d9d875ae97 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 17 Nov 2020 01:46:22 +0300 Subject: [PATCH 5/5] Fixed CPP header codegen --- src/tools/MicroComGenerator/CppGen.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/MicroComGenerator/CppGen.cs b/src/tools/MicroComGenerator/CppGen.cs index 68192ebffe..b053088ca9 100644 --- a/src/tools/MicroComGenerator/CppGen.cs +++ b/src/tools/MicroComGenerator/CppGen.cs @@ -14,7 +14,10 @@ namespace MicroComGenerator name = "unsigned char"; else if(name == "uint") name = "unsigned int"; - return name + new string('*', type.PointerLevel); + + type = type.Clone(); + type.Name = name; + return type.Format(); } public static string GenerateCpp(AstIdlNode idl)