From ea3f85e1263cfb75ce2452e205477080d1c9b531 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 09:34:18 +0200 Subject: [PATCH 1/4] Don't allow using a closed window as a parent/owner. --- src/Avalonia.Controls/Window.cs | 10 +++++++ .../WindowTests.cs | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 700c3d9bad..2d369dae8c 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -635,6 +635,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot re-show a closed window."); } + if (parent != null && parent.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + } + if (IsVisible) { return; @@ -709,6 +714,11 @@ namespace Avalonia.Controls throw new ArgumentNullException(nameof(owner)); } + if (owner.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed owner."); + } + if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e8311b79ac..2d4559ddba 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -387,6 +387,36 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Calling_Show_With_Closed_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + parent.Close(); + + var ex = Assert.Throws(() => target.Show(parent)); + Assert.Equal("Cannot Show a Window with a closed parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Closed_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + parent.Close(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); + Assert.Equal("Cannot Show a Window with a closed owner.", ex.Message); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From 69852a56f588fe0e744aa11e7577eeb351f29670 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 12:16:44 +0200 Subject: [PATCH 2/4] Don't allow self as parent/owner window. --- src/Avalonia.Controls/Window.cs | 17 +++++++++++-- .../WindowTests.cs | 24 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 2d369dae8c..9f6c605d46 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -635,9 +635,17 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot re-show a closed window."); } - if (parent != null && parent.PlatformImpl == null) + if (parent != null) { - throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + if (parent.PlatformImpl == null) + { + throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + } + + if (parent == this) + { + throw new InvalidOperationException("A Window cannot be its own parent."); + } } if (IsVisible) @@ -719,6 +727,11 @@ namespace Avalonia.Controls throw new InvalidOperationException("Cannot Show a Window with a closed owner."); } + if (owner == this) + { + throw new InvalidOperationException("A Window cannot be its own owner."); + } + if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 2d4559ddba..5ba529292f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -417,6 +417,30 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Calling_Show_With_Self_As_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window(); + + var ex = Assert.Throws(() => target.Show(target)); + Assert.Equal("A Window cannot be its own parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Self_As_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new Window(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(target)); + Assert.Equal("A Window cannot be its own owner.", ex.Message); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From 15cdee1bee18cc7466f68951f7bfa863fee748be Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 13:27:01 +0200 Subject: [PATCH 3/4] Ensure parent/owner windows are visible when showing child. --- src/Avalonia.Controls/Window.cs | 24 +++++---- .../WindowTests.cs | 50 +++++++++++++++---- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 9f6c605d46..ddda083aa8 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -639,13 +639,16 @@ namespace Avalonia.Controls { if (parent.PlatformImpl == null) { - throw new InvalidOperationException("Cannot Show a Window with a closed parent."); + throw new InvalidOperationException("Cannot show a window with a closed parent."); } - - if (parent == this) + else if (parent == this) { throw new InvalidOperationException("A Window cannot be its own parent."); } + else if (!parent.IsVisible) + { + throw new InvalidOperationException("Cannot show window with non-visible parent."); + } } if (IsVisible) @@ -721,21 +724,22 @@ namespace Avalonia.Controls { throw new ArgumentNullException(nameof(owner)); } - - if (owner.PlatformImpl == null) + else if (owner.PlatformImpl == null) { - throw new InvalidOperationException("Cannot Show a Window with a closed owner."); + throw new InvalidOperationException("Cannot show a window with a closed owner."); } - - if (owner == this) + else if (owner == this) { throw new InvalidOperationException("A Window cannot be its own owner."); } - - if (IsVisible) + else if (IsVisible) { throw new InvalidOperationException("The window is already being shown."); } + else if (!owner.IsVisible) + { + throw new InvalidOperationException("Cannot show window with non-visible parent."); + } RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 5ba529292f..88c6c86c46 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -279,10 +279,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = Mock.Of(); + var parent = new Window(); var renderer = new Mock(); var target = new Window(CreateImpl(renderer)); + parent.Show(); target.ShowDialog(parent); renderer.Verify(x => x.Start(), Times.Once); @@ -294,10 +295,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = Mock.Of(); + var parent = new Window(); var target = new Window(); var raised = false; + parent.Show(); target.Opened += (s, e) => raised = true; target.ShowDialog(parent); @@ -326,14 +328,15 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = new Mock(); + var parent = new Window(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); + parent.Show(); var target = new Window(windowImpl.Object); - var task = target.ShowDialog(parent.Object); + var task = target.ShowDialog(parent); windowImpl.Object.Closed(); @@ -366,14 +369,16 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var parent = new Mock(); + var parent = new Window(); var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); + parent.Show(); + var target = new Window(windowImpl.Object); - var task = target.ShowDialog(parent.Object); + var task = target.ShowDialog(parent); windowImpl.Object.Closed(); await task; @@ -381,7 +386,7 @@ namespace Avalonia.Controls.UnitTests var openedRaised = false; target.Opened += (s, e) => openedRaised = true; - var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent.Object)); + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); Assert.Equal("Cannot re-show a closed window.", ex.Message); Assert.False(openedRaised); } @@ -398,7 +403,7 @@ namespace Avalonia.Controls.UnitTests parent.Close(); var ex = Assert.Throws(() => target.Show(parent)); - Assert.Equal("Cannot Show a Window with a closed parent.", ex.Message); + Assert.Equal("Cannot show a window with a closed parent.", ex.Message); } } @@ -413,7 +418,33 @@ namespace Avalonia.Controls.UnitTests parent.Close(); var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); - Assert.Equal("Cannot Show a Window with a closed owner.", ex.Message); + Assert.Equal("Cannot show a window with a closed owner.", ex.Message); + } + } + + [Fact] + public void Calling_Show_With_Invisible_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + var ex = Assert.Throws(() => target.Show(parent)); + Assert.Equal("Cannot show window with non-visible parent.", ex.Message); + } + } + + [Fact] + public async Task Calling_ShowDialog_With_Invisible_Parent_Window_Should_Throw() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parent = new Window(); + var target = new Window(); + + var ex = await Assert.ThrowsAsync(() => target.ShowDialog(parent)); + Assert.Equal("Cannot show window with non-visible parent.", ex.Message); } } @@ -740,6 +771,7 @@ namespace Avalonia.Controls.UnitTests protected override void Show(Window window) { var owner = new Window(); + owner.Show(); window.ShowDialog(owner); } } From a42334d128eb919c6f29de6c7d51cd333b4221a0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Jul 2021 14:00:09 +0200 Subject: [PATCH 4/4] Hide child windows when hiding parent/owner. --- src/Avalonia.Controls/Window.cs | 8 +++++ .../WindowTests.cs | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ddda083aa8..ae314a33ce 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -592,6 +592,14 @@ namespace Avalonia.Controls owner.RemoveChild(this); } + if (_children.Count > 0) + { + foreach (var child in _children.ToArray()) + { + child.child.Hide(); + } + } + Owner = null; PlatformImpl?.Hide(); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 88c6c86c46..6b9921d83d 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -472,6 +472,42 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Hiding_Parent_Window_Should_Close_Children() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var parent = new Window(); + var child = new Window(); + + parent.Show(); + child.Show(parent); + + parent.Hide(); + + Assert.False(parent.IsVisible); + Assert.False(child.IsVisible); + } + } + + [Fact] + public void Hiding_Parent_Window_Should_Close_Dialog_Children() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var parent = new Window(); + var child = new Window(); + + parent.Show(); + child.ShowDialog(parent); + + parent.Hide(); + + Assert.False(parent.IsVisible); + Assert.False(child.IsVisible); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() {