Browse Source

Merge pull request #9562 from AvaloniaUI/feature/window-show-hide-with-isvisible

IsVisible can control window show and hide.
pull/9464/head
Max Katz 3 years ago
committed by GitHub
parent
commit
ad855938fb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 267
      src/Avalonia.Controls/Window.cs
  2. 58
      src/Avalonia.Controls/WindowBase.cs
  3. 47
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

267
src/Avalonia.Controls/Window.cs

@ -176,6 +176,8 @@ namespace Avalonia.Controls
private object? _dialogResult;
private readonly Size _maxPlatformClientSize;
private WindowStartupLocation _windowStartupLocation;
private bool _shown;
private bool _showingAsDialog;
/// <summary>
/// Initializes static members of the <see cref="Window"/> class.
@ -508,6 +510,8 @@ namespace Avalonia.Controls
Owner = null;
PlatformImpl?.Dispose();
_showingAsDialog = false;
}
private bool ShouldCancelClose(CancelEventArgs? args = null)
@ -563,29 +567,33 @@ namespace Avalonia.Controls
/// </summary>
public override void Hide()
{
if (!IsVisible)
using (FreezeVisibilityChangeHandling())
{
return;
}
if (!_shown)
{
return;
}
Renderer?.Stop();
Renderer?.Stop();
if (Owner is Window owner)
{
owner.RemoveChild(this);
}
if (Owner is Window owner)
{
owner.RemoveChild(this);
}
if (_children.Count > 0)
{
foreach (var child in _children.ToArray())
if (_children.Count > 0)
{
child.child.Hide();
foreach (var child in _children.ToArray())
{
child.child.Hide();
}
}
}
Owner = null;
PlatformImpl?.Hide();
IsVisible = false;
Owner = null;
PlatformImpl?.Hide();
IsVisible = false;
_shown = false;
}
}
/// <summary>
@ -599,81 +607,124 @@ namespace Avalonia.Controls
ShowCore(null);
}
protected override void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!IgnoreVisibilityChanges)
{
var isVisible = e.GetNewValue<bool>();
if (_shown != isVisible)
{
if(!_shown)
{
Show();
}
else
{
if (_showingAsDialog)
{
Close(false);
}
else
{
Hide();
}
}
}
}
}
/// <summary>
/// Shows the window as a child of <paramref name="parent"/>.
/// Shows the window as a child of <paramref name="owner"/>.
/// </summary>
/// <param name="parent">Window that will be a parent of the shown window.</param>
/// <param name="owner">Window that will be the owner of the shown window.</param>
/// <exception cref="InvalidOperationException">
/// The window has already been closed.
/// </exception>
public void Show(Window parent)
public void Show(Window owner)
{
if (parent is null)
if (owner is null)
{
throw new ArgumentNullException(nameof(parent), "Showing a child window requires valid parent.");
throw new ArgumentNullException(nameof(owner), "Showing a child window requires valid parent.");
}
ShowCore(parent);
ShowCore(owner);
}
private void ShowCore(Window? parent)
private void EnsureStateBeforeShow()
{
if (PlatformImpl == null)
{
throw new InvalidOperationException("Cannot re-show a closed window.");
}
}
private void EnsureParentStateBeforeShow(Window owner)
{
if (owner.PlatformImpl == null)
{
throw new InvalidOperationException("Cannot show a window with a closed owner.");
}
if (parent != null)
if (owner == this)
{
if (parent.PlatformImpl == null)
{
throw new InvalidOperationException("Cannot show a window with a closed parent.");
}
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.");
}
throw new InvalidOperationException("A Window cannot be its own owner.");
}
if (IsVisible)
if (!owner.IsVisible)
{
return;
throw new InvalidOperationException("Cannot show window with non-visible owner.");
}
}
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
private void ShowCore(Window? owner)
{
using (FreezeVisibilityChangeHandling())
{
EnsureStateBeforeShow();
EnsureInitialized();
ApplyStyling();
IsVisible = true;
if (owner != null)
{
EnsureParentStateBeforeShow(owner);
}
var initialSize = new Size(
double.IsNaN(Width) ? Math.Max(MinWidth, ClientSize.Width) : Width,
double.IsNaN(Height) ? Math.Max(MinHeight, ClientSize.Height) : Height);
if (_shown)
{
return;
}
if (initialSize != ClientSize)
{
PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
}
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
LayoutManager.ExecuteInitialLayoutPass();
EnsureInitialized();
ApplyStyling();
_shown = true;
IsVisible = true;
if (PlatformImpl != null && parent?.PlatformImpl is not null)
{
PlatformImpl.SetParent(parent.PlatformImpl);
}
var initialSize = new Size(
double.IsNaN(Width) ? Math.Max(MinWidth, ClientSize.Width) : Width,
double.IsNaN(Height) ? Math.Max(MinHeight, ClientSize.Height) : Height);
Owner = parent;
parent?.AddChild(this, false);
if (initialSize != ClientSize)
{
PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
}
SetWindowStartupLocation(parent?.PlatformImpl);
LayoutManager.ExecuteInitialLayoutPass();
PlatformImpl?.Show(ShowActivated, false);
Renderer?.Start();
OnOpened(EventArgs.Empty);
if (PlatformImpl != null && owner?.PlatformImpl is not null)
{
PlatformImpl.SetParent(owner.PlatformImpl);
}
Owner = owner;
owner?.AddChild(this, false);
SetWindowStartupLocation(owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated, false);
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
}
/// <summary>
@ -703,68 +754,66 @@ namespace Avalonia.Controls
/// </returns>
public Task<TResult> ShowDialog<TResult>(Window owner)
{
if (owner == null)
{
throw new ArgumentNullException(nameof(owner));
}
else if (owner.PlatformImpl == null)
{
throw new InvalidOperationException("Cannot show a window with a closed owner.");
}
else if (owner == this)
using (FreezeVisibilityChangeHandling())
{
throw new InvalidOperationException("A Window cannot be its own owner.");
}
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.");
}
EnsureStateBeforeShow();
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
if (owner == null)
{
throw new ArgumentNullException(nameof(owner));
}
EnsureInitialized();
ApplyStyling();
IsVisible = true;
EnsureParentStateBeforeShow(owner);
var initialSize = new Size(
double.IsNaN(Width) ? ClientSize.Width : Width,
double.IsNaN(Height) ? ClientSize.Height : Height);
if (_shown)
{
throw new InvalidOperationException("The window is already being shown.");
}
if (initialSize != ClientSize)
{
PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
}
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
LayoutManager.ExecuteInitialLayoutPass();
EnsureInitialized();
ApplyStyling();
_shown = true;
_showingAsDialog = true;
IsVisible = true;
var result = new TaskCompletionSource<TResult>();
var initialSize = new Size(
double.IsNaN(Width) ? ClientSize.Width : Width,
double.IsNaN(Height) ? ClientSize.Height : Height);
PlatformImpl?.SetParent(owner.PlatformImpl);
Owner = owner;
owner.AddChild(this, true);
if (initialSize != ClientSize)
{
PlatformImpl?.Resize(initialSize, PlatformResizeReason.Layout);
}
SetWindowStartupLocation(owner.PlatformImpl);
LayoutManager.ExecuteInitialLayoutPass();
PlatformImpl?.Show(ShowActivated, true);
var result = new TaskCompletionSource<TResult>();
Renderer?.Start();
PlatformImpl?.SetParent(owner.PlatformImpl!);
Owner = owner;
owner.AddChild(this, true);
Observable.FromEventPattern<EventHandler, EventArgs>(
x => Closed += x,
x => Closed -= x)
.Take(1)
.Subscribe(_ =>
{
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)!));
});
SetWindowStartupLocation(owner.PlatformImpl);
OnOpened(EventArgs.Empty);
return result.Task;
PlatformImpl?.Show(ShowActivated, true);
Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>(
x => Closed += x,
x => Closed -= x)
.Take(1)
.Subscribe(_ =>
{
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)!));
});
OnOpened(EventArgs.Empty);
return result.Task;
}
}
private void UpdateEnabled()

58
src/Avalonia.Controls/WindowBase.cs

@ -42,9 +42,11 @@ namespace Avalonia.Controls
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
private int _ignoreVisibilityChanges;
private WindowBase? _owner;
protected bool IgnoreVisibilityChanges => _ignoreVisibilityChanges > 0;
static WindowBase()
{
IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
@ -66,6 +68,11 @@ namespace Avalonia.Controls
impl.PositionChanged = HandlePositionChanged;
}
protected IDisposable FreezeVisibilityChangeHandling()
{
return new IgnoreVisibilityChangesDisposable(this);
}
/// <summary>
/// Fired when the window is activated.
/// </summary>
@ -125,18 +132,12 @@ namespace Avalonia.Controls
/// </summary>
public virtual void Hide()
{
_ignoreVisibilityChange = true;
try
using (FreezeVisibilityChangeHandling())
{
Renderer?.Stop();
PlatformImpl?.Hide();
IsVisible = false;
}
finally
{
_ignoreVisibilityChange = false;
}
}
/// <summary>
@ -144,9 +145,7 @@ namespace Avalonia.Controls
/// </summary>
public virtual void Show()
{
_ignoreVisibilityChange = true;
try
using (FreezeVisibilityChangeHandling())
{
EnsureInitialized();
ApplyStyling();
@ -157,14 +156,11 @@ namespace Avalonia.Controls
LayoutManager.ExecuteInitialLayoutPass();
_hasExecutedInitialLayoutPass = true;
}
PlatformImpl?.Show(true, false);
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
finally
{
_ignoreVisibilityChange = false;
}
}
/// <summary>
@ -202,23 +198,17 @@ namespace Avalonia.Controls
protected override void HandleClosed()
{
_ignoreVisibilityChange = true;
try
using (FreezeVisibilityChangeHandling())
{
IsVisible = false;
if (this is IFocusScope scope)
{
FocusManager.Instance?.RemoveFocusScope(scope);
}
base.HandleClosed();
}
finally
{
_ignoreVisibilityChange = false;
}
}
/// <summary>
@ -318,9 +308,9 @@ namespace Avalonia.Controls
Deactivated?.Invoke(this, EventArgs.Empty);
}
private void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
protected virtual void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_ignoreVisibilityChange)
if (_ignoreVisibilityChanges == 0)
{
if ((bool)e.NewValue!)
{
@ -332,5 +322,21 @@ namespace Avalonia.Controls
}
}
}
private readonly struct IgnoreVisibilityChangesDisposable : IDisposable
{
private readonly WindowBase _windowBase;
public IgnoreVisibilityChangesDisposable(WindowBase windowBase)
{
_windowBase = windowBase;
_windowBase._ignoreVisibilityChanges++;
}
public void Dispose()
{
_windowBase._ignoreVisibilityChanges--;
}
}
}
}

47
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -403,7 +403,7 @@ namespace Avalonia.Controls.UnitTests
parent.Close();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(parent));
Assert.Equal("Cannot show a window with a closed parent.", ex.Message);
Assert.Equal("Cannot show a window with a closed owner.", ex.Message);
}
}
@ -431,7 +431,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Window();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(parent));
Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
Assert.Equal("Cannot show window with non-visible owner.", ex.Message);
}
}
@ -444,7 +444,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Window();
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog(parent));
Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
Assert.Equal("Cannot show window with non-visible owner.", ex.Message);
}
}
@ -456,7 +456,7 @@ namespace Avalonia.Controls.UnitTests
var target = new Window();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(target));
Assert.Equal("A Window cannot be its own parent.", ex.Message);
Assert.Equal("A Window cannot be its own owner.", ex.Message);
}
}
@ -986,7 +986,46 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
}
[Fact]
public void IsVisible_Should_Open_Window()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new Window();
var raised = false;
target.Opened += (s, e) => raised = true;
target.IsVisible = true;
Assert.True(raised);
}
}
[Fact]
public void IsVisible_Should_Close_DialogWindow()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
parent.Show();
var target = new Window();
var raised = false;
var task = target.ShowDialog<bool>(parent);
target.Closed += (sender, args) => raised = true;
target.IsVisible = false;
Assert.True(raised);
Assert.False(task.Result);
}
}
protected virtual void Show(Window window)
{
window.Show();

Loading…
Cancel
Save