From 1eee17345bf2958f0179794a4b21fb9d15469196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Thu, 6 Jul 2017 17:45:21 +0100 Subject: [PATCH 1/5] Implemented Window.WindowStartupLocation and WindowBase.Owner. --- src/Avalonia.Controls/Window.cs | 28 +++++++ src/Avalonia.Controls/WindowBase.cs | 15 ++++ .../WindowStartupLocation.cs | 23 ++++++ src/Avalonia.DotNetCoreRuntime/AppBuilder.cs | 3 + .../WindowTests.cs | 77 ++++++++++++++++++- 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Controls/WindowStartupLocation.cs diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3802f2b6ea..3535510ce3 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -183,6 +183,15 @@ namespace Avalonia.Controls set { SetValue(IconProperty, value); } } + /// + /// Gets or sets the startup location of the window. + /// + public WindowStartupLocation WindowStartupLocation + { + get; + set; + } + /// Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize; @@ -246,6 +255,7 @@ namespace Avalonia.Controls s_windows.Add(this); EnsureInitialized(); + SetWindowStartupLocation(); IsVisible = true; LayoutManager.Instance.ExecuteInitialLayoutPass(this); @@ -285,6 +295,7 @@ namespace Avalonia.Controls s_windows.Add(this); EnsureInitialized(); + SetWindowStartupLocation(); IsVisible = true; LayoutManager.Instance.ExecuteInitialLayoutPass(this); @@ -321,6 +332,23 @@ namespace Avalonia.Controls } } + void SetWindowStartupLocation() + { + if (WindowStartupLocation == WindowStartupLocation.CenterScreen) + { + var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; + Position = new Point(positionAsSize.Width, positionAsSize.Height); + } + else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) + { + if (Owner != null) + { + var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2; + Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height); + } + } + } + /// void INameScope.Register(string name, object element) { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index fbdf64b14a..dd1e8fbef1 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -29,6 +29,12 @@ namespace Avalonia.Controls public static readonly DirectProperty IsActiveProperty = AvaloniaProperty.RegisterDirect(nameof(IsActive), o => o.IsActive); + /// + /// Defines the property. + /// + public static readonly StyledProperty OwnerProperty = + AvaloniaProperty.Register(nameof(Owner)); + private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; @@ -100,6 +106,15 @@ namespace Avalonia.Controls private set; } + /// + /// Gets or sets the owner of the window. + /// + public WindowBase Owner + { + get { return GetValue(OwnerProperty); } + set { SetValue(OwnerProperty, value); } + } + /// /// Activates the window. /// diff --git a/src/Avalonia.Controls/WindowStartupLocation.cs b/src/Avalonia.Controls/WindowStartupLocation.cs new file mode 100644 index 0000000000..1818636076 --- /dev/null +++ b/src/Avalonia.Controls/WindowStartupLocation.cs @@ -0,0 +1,23 @@ +namespace Avalonia.Controls +{ + /// + /// Determines the startup location of the window. + /// + public enum WindowStartupLocation + { + /// + /// The startup location is defined by the Position property. + /// + Manual, + + /// + /// The startup location is the center of the screen. + /// + CenterScreen, + + /// + /// The startup location is the center of the owner window. If the owner window is not specified, the startup location will be . + /// + CenterOwner + } +} diff --git a/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs b/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs index 2b9b3083b1..bf8d7a20fd 100644 --- a/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs +++ b/src/Avalonia.DotNetCoreRuntime/AppBuilder.cs @@ -10,6 +10,9 @@ using Avalonia.Shared.PlatformSupport; namespace Avalonia { + /// + /// Initializes platform-specific services for an . + /// public sealed class AppBuilder : AppBuilderBase { /// diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index e0dd908bbb..83f86ce5a0 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -68,7 +68,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void IsVisible_Should_Be_False_Atfer_Hide() + public void IsVisible_Should_Be_False_After_Hide() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -82,7 +82,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void IsVisible_Should_Be_False_Atfer_Close() + public void IsVisible_Should_Be_False_After_Close() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -96,7 +96,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void IsVisible_Should_Be_False_Atfer_Impl_Signals_Close() + public void IsVisible_Should_Be_False_After_Impl_Signals_Close() { var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Closed); @@ -191,5 +191,76 @@ namespace Avalonia.Controls.UnitTests // AvaloniaLocator scopes. ((IList)Window.OpenWindows).Clear(); } + + [Fact] + public void Window_Should_Be_Centered_When_Window_Startup_Location_Is_Center_Screen() + { + var windowImpl = new Mock(); + windowImpl.SetupProperty(x => x.Position); + windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); + windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); + windowImpl.Setup(x => x.Scaling).Returns(1); + + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(); + window.WindowStartupLocation = WindowStartupLocation.CenterScreen; + window.Position = new Point(60, 40); + + window.Show(); + + var expectedPosition = new Point( + window.PlatformImpl.MaxClientSize.Width / 2 - window.ClientSize.Width / 2, + window.PlatformImpl.MaxClientSize.Height / 2 - window.ClientSize.Height / 2); + + Assert.Equal(window.Position, expectedPosition); + } + } + + [Fact] + public void Window_Should_Be_Centered_Relative_To_Owner_When_Window_Startup_Location_Is_Center_Owner() + { + var parentWindowImpl = new Mock(); + parentWindowImpl.SetupProperty(x => x.Position); + parentWindowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); + parentWindowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); + parentWindowImpl.Setup(x => x.Scaling).Returns(1); + + var windowImpl = new Mock(); + windowImpl.SetupProperty(x => x.Position); + windowImpl.Setup(x => x.ClientSize).Returns(new Size(320, 200)); + windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); + windowImpl.Setup(x => x.Scaling).Returns(1); + + var parentWindowServices = TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => parentWindowImpl.Object)); + + var windowServices = TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); + + using (UnitTestApplication.Start(parentWindowServices)) + { + var parentWindow = new Window(); + parentWindow.Position = new Point(60, 40); + + parentWindow.Show(); + + using (UnitTestApplication.Start(windowServices)) + { + var window = new Window(); + window.WindowStartupLocation = WindowStartupLocation.CenterOwner; + window.Position = new Point(60, 40); + window.Owner = parentWindow; + + window.Show(); + + var expectedPosition = new Point( + parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2, + parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2); + + Assert.Equal(window.Position, expectedPosition); + } + } + } } } From 87e8cb892384127b7be314a7da8678bc1deefe44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 8 Jul 2017 18:14:06 +0100 Subject: [PATCH 2/5] Fixed PossibleNullReferenceException. --- src/Avalonia.Controls/Window.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 3535510ce3..7f0cab3237 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -336,8 +336,11 @@ namespace Avalonia.Controls { if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { - var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; - Position = new Point(positionAsSize.Width, positionAsSize.Height); + if (PlatformImpl != null) + { + var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; + Position = new Point(positionAsSize.Width, positionAsSize.Height); + } } else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) { From e54d0c75d1c64f26be1aa6e67fede778729e0040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sun, 9 Jul 2017 14:03:22 +0100 Subject: [PATCH 3/5] Property changes. --- src/Avalonia.Controls/Window.cs | 13 +++++++++++-- src/Avalonia.Controls/WindowBase.cs | 9 +++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 7f0cab3237..f60768980d 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -78,9 +78,16 @@ namespace Avalonia.Controls public static readonly StyledProperty IconProperty = AvaloniaProperty.Register(nameof(Icon)); + /// + /// Defines the proeprty. + /// + public static readonly DirectProperty WindowStartupLocationProperty = + AvaloniaProperty.RegisterDirect(nameof(WindowStartupLocation), o => o.WindowStartupLocation); + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; + private WindowStartupLocation _windowStartupLoction; /// /// Initializes static members of the class. @@ -188,8 +195,8 @@ namespace Avalonia.Controls /// public WindowStartupLocation WindowStartupLocation { - get; - set; + get { return _windowStartupLoction; } + set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLoction, value); } } /// @@ -336,6 +343,8 @@ namespace Avalonia.Controls { if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { + // This should be using a Screen API, but we don't have one yet and + // PlatformImpl.MaxClientSize is the best we have. if (PlatformImpl != null) { var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index dd1e8fbef1..dd1e0ae842 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -32,12 +32,13 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty OwnerProperty = - AvaloniaProperty.Register(nameof(Owner)); + public static readonly DirectProperty OwnerProperty = + AvaloniaProperty.RegisterDirect(nameof(Owner), o => o.Owner); private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; + private WindowBase _owner; static WindowBase() { @@ -111,8 +112,8 @@ namespace Avalonia.Controls /// public WindowBase Owner { - get { return GetValue(OwnerProperty); } - set { SetValue(OwnerProperty, value); } + get { return _owner; } + set { SetAndRaise(OwnerProperty, ref _owner, value); } } /// From efb9fd4c5732db991a6e70e7c410226bea68202c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sun, 6 Aug 2017 18:02:16 +0100 Subject: [PATCH 4/5] Added missing setters. --- src/Avalonia.Controls/Window.cs | 5 ++++- src/Avalonia.Controls/WindowBase.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index f60768980d..a94609122a 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -82,7 +82,10 @@ namespace Avalonia.Controls /// Defines the proeprty. /// public static readonly DirectProperty WindowStartupLocationProperty = - AvaloniaProperty.RegisterDirect(nameof(WindowStartupLocation), o => o.WindowStartupLocation); + AvaloniaProperty.RegisterDirect( + nameof(WindowStartupLocation), + o => o.WindowStartupLocation, + (o, v) => o.WindowStartupLocation = v); private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index dd1e0ae842..73d6d8c7cc 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -33,7 +33,10 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly DirectProperty OwnerProperty = - AvaloniaProperty.RegisterDirect(nameof(Owner), o => o.Owner); + AvaloniaProperty.RegisterDirect( + nameof(Owner), + o => o.Owner, + (o, v) => o.Owner = v); private bool _hasExecutedInitialLayoutPass; private bool _isActive; From 2cc4b41acc7521f72fe552cfbf079c85f06199dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 3 Oct 2017 22:12:11 +0100 Subject: [PATCH 5/5] Use Screen API. --- src/Avalonia.Controls/Screens.cs | 2 +- src/Avalonia.Controls/Window.cs | 11 ++++------- src/Windows/Avalonia.Win32/ScreenImpl.cs | 4 ++-- .../Avalonia.Controls.UnitTests/WindowTests.cs | 18 ++++++++++++------ 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index b8ddce3aea..2bfddc048b 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls return currMaxScreen; } - public Screen SceenFromPoint(Point point) + public Screen ScreenFromPoint(Point point) { return All.FirstOrDefault(x=>x.Bounds.Contains(point)); } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 4cbd73841b..7890a7dd5a 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -376,13 +376,10 @@ namespace Avalonia.Controls { if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { - // This should be using a Screen API, but we don't have one yet and - // PlatformImpl.MaxClientSize is the best we have. - if (PlatformImpl != null) - { - var positionAsSize = PlatformImpl.MaxClientSize / 2 - ClientSize / 2; - Position = new Point(positionAsSize.Width, positionAsSize.Height); - } + var screen = Screens.ScreenFromPoint(Bounds.Position); + + if (screen != null) + Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position; } else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) { diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 4f4331e461..113b2811dc 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -41,8 +41,8 @@ namespace Avalonia.Win32 Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top); Rect avaloniaWorkArea = - new Rect(workingArea.left, workingArea.top, workingArea.right - bounds.left, - workingArea.bottom - bounds.top); + new Rect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, + workingArea.bottom - workingArea.top); screens[index] = new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, monitor); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 4de1cfe356..6168421919 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -239,32 +239,38 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Window_Should_Be_Centered_When_Window_Startup_Location_Is_Center_Screen() + public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { + var screen1 = new Mock(new Rect(new Size(1920, 1080)), new Rect(new Size(1920, 1040)), true); + var screen2 = new Mock(new Rect(new Size(1366, 768)), new Rect(new Size(1366, 728)), false); + + var screens = new Mock(); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); + var windowImpl = new Mock(); windowImpl.SetupProperty(x => x.Position); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); - windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1920, 1080)); windowImpl.Setup(x => x.Scaling).Returns(1); + windowImpl.Setup(x => x.Screen).Returns(screens.Object); using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var window = new Window(); + var window = new Window(windowImpl.Object); window.WindowStartupLocation = WindowStartupLocation.CenterScreen; window.Position = new Point(60, 40); window.Show(); var expectedPosition = new Point( - window.PlatformImpl.MaxClientSize.Width / 2 - window.ClientSize.Width / 2, - window.PlatformImpl.MaxClientSize.Height / 2 - window.ClientSize.Height / 2); + screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2, + screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2); Assert.Equal(window.Position, expectedPosition); } } [Fact] - public void Window_Should_Be_Centered_Relative_To_Owner_When_Window_Startup_Location_Is_Center_Owner() + public void Window_Should_Be_Centered_Relative_To_Owner_When_WindowStartupLocation_Is_CenterOwner() { var parentWindowImpl = new Mock(); parentWindowImpl.SetupProperty(x => x.Position);