Browse Source

Reworked dialogs for GTK/Win32

pull/2091/head
Nikita Tsukanov 7 years ago
parent
commit
5cd7c1f6f4
  1. 4
      samples/ControlCatalog/Pages/DialogsPage.xaml
  2. 8
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  3. 2
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  4. 5
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  5. 31
      src/Avalonia.Controls/SystemDialog.cs
  6. 38
      src/Avalonia.Controls/Window.cs
  7. 5
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  8. 6
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  9. 4
      src/Avalonia.Native/WindowImpl.cs
  10. 4
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  11. 2
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  12. 13
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  13. 18
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  14. 25
      src/Windows/Avalonia.Win32/WindowImpl.cs
  15. 7
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

4
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -3,10 +3,6 @@
<Button Name="OpenFile">Open File</Button>
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>
<StackPanel Orientation="Horizontal">
<CheckBox Name="IsModal" IsChecked="True"/>
<TextBlock>Modal to window</TextBlock>
</StackPanel>
<Button Name="DecoratedWindow">Decorated window</Button>
</StackPanel>
</UserControl>

8
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -31,12 +31,12 @@ namespace ControlCatalog.Pages
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("DecoratedWindow").Click += delegate
{
new DecoratedWindow().Show();
};
{
new DecoratedWindow().ShowDialog(GetWindow());
};
}
Window GetWindow() => this.FindControl<CheckBox>("IsModal").IsChecked.Value ? (Window)this.VisualRoot : null;
Window GetWindow() => (Window)this.VisualRoot;
private void InitializeComponent()
{

2
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@ -83,7 +83,7 @@ namespace ControlCatalog.Pages
public async Task Open()
{
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync();
var result = await dialog.ShowAsync(App.Current.MainWindow);
if (result != null)
{

5
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -30,10 +30,7 @@ namespace Avalonia.Platform
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <returns>
/// An <see cref="IDisposable"/> that should be used to close the window.
/// </returns>
IDisposable ShowDialog();
void ShowDialog(IWindowImpl parent);
/// <summary>
/// Enables or disables system window decorations (title bar, buttons, etc)

31
src/Avalonia.Controls/SystemDialog.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -18,28 +19,40 @@ namespace Avalonia.Controls
public class SaveFileDialog : FileDialog
{
public string DefaultExtension { get; set; }
public string DefaultExtension { get; set; }
public async Task<string> ShowAsync(Window window)
=>
((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, window?.PlatformImpl)) ??
new string[0]).FirstOrDefault();
public async Task<string> ShowAsync(Window parent)
{
if(parent == null)
throw new ArgumentNullException(nameof(parent));
return ((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>()
.ShowFileDialogAsync(this, parent?.PlatformImpl)) ??
new string[0]).FirstOrDefault();
}
}
public class OpenFileDialog : FileDialog
{
public bool AllowMultiple { get; set; }
public Task<string[]> ShowAsync(Window window = null)
=> AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, window?.PlatformImpl);
public Task<string[]> ShowAsync(Window parent)
{
if(parent == null)
throw new ArgumentNullException(nameof(parent));
return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, parent?.PlatformImpl);
}
}
public class OpenFolderDialog : FileSystemDialog
{
public string DefaultDirectory { get; set; }
public Task<string> ShowAsync(Window window = null)
=> AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, window?.PlatformImpl);
public Task<string> ShowAsync(Window parent)
{
if(parent == null)
throw new ArgumentNullException(nameof(parent));
return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, parent?.PlatformImpl);
}
}
public abstract class SystemDialog

38
src/Avalonia.Controls/Window.cs

@ -397,11 +397,23 @@ namespace Avalonia.Controls
/// <returns>
/// A task that can be used to track the lifetime of the dialog.
/// </returns>
public Task ShowDialog()
public Task ShowDialog(Window parent)
{
return ShowDialog<object>();
return ShowDialog<object>(parent);
}
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result produced by the dialog.
/// </typeparam>
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task<TResult> ShowDialog<TResult>(Window parent) => ShowDialog<TResult>(parent.PlatformImpl);
/// <summary>
/// Shows the window as a dialog.
/// </summary>
@ -411,8 +423,10 @@ namespace Avalonia.Controls
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task<TResult> ShowDialog<TResult>()
public Task<TResult> ShowDialog<TResult>(IWindowImpl parent)
{
if(parent == null)
throw new ArgumentNullException(nameof(parent));
if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
@ -427,24 +441,18 @@ namespace Avalonia.Controls
using (BeginAutoSizing())
{
var affectedWindows = Application.Current.Windows.Where(w => w.IsEnabled && w != this).ToList();
var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault();
SetIsEnabled(affectedWindows, false);
var modal = PlatformImpl?.ShowDialog();
PlatformImpl?.ShowDialog(parent);
var result = new TaskCompletionSource<TResult>();
Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>(
x => this.Closed += x,
x => this.Closed -= x)
.Take(1)
.Subscribe(_ =>
{
modal?.Dispose();
SetIsEnabled(affectedWindows, true);
activated?.Activate();
parent.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
@ -452,14 +460,6 @@ namespace Avalonia.Controls
}
}
void SetIsEnabled(IEnumerable<Window> windows, bool isEnabled)
{
foreach (var window in windows)
{
window.IsEnabled = isEnabled;
}
}
void SetWindowStartupLocation()
{
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)

5
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -86,9 +86,8 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public IDisposable ShowDialog()
public void ShowDialog(IWindowImpl parent)
{
return Disposable.Empty;
}
public void SetSystemDecorations(bool enabled)
@ -111,4 +110,4 @@ namespace Avalonia.DesignerSupport.Remote
{
}
}
}
}

6
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -87,7 +87,9 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public IDisposable ShowDialog() => Disposable.Empty;
public void ShowDialog(IWindowImpl parent)
{
}
public void SetSystemDecorations(bool enabled)
{
@ -157,4 +159,4 @@ namespace Avalonia.DesignerSupport.Remote
public Screen[] AllScreens { get; } =
{new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)};
}
}
}

4
src/Avalonia.Native/WindowImpl.cs

@ -46,9 +46,9 @@ namespace Avalonia.Native
public IAvnWindow Native => _native;
public IDisposable ShowDialog()
public void ShowDialog(IWindowImpl window)
{
return _native.ShowDialog();
_native.ShowDialog();
}
public void CanResize(bool value)

4
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -67,6 +67,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_modal(GtkWindow window, bool modal);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_transient_for(GtkWindow window, IntPtr parent);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import
public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow);
@ -408,6 +411,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_new GtkWindowNew;
public static D.gtk_window_set_icon GtkWindowSetIcon;
public static D.gtk_window_set_modal GtkWindowSetModal;
public static D.gtk_window_set_transient_for GtkWindowSetTransientFor;
public static D.gdk_set_allowed_backends GdkSetAllowedBackends;
public static D.gtk_init GtkInit;
public static D.gtk_window_present GtkWindowPresent;

2
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -422,7 +422,7 @@ namespace Avalonia.Gtk3
Native.GdkWindowSetCursor(Native.GtkWidgetGetWindow(GtkWidget), cursor?.Handle ?? IntPtr.Zero);
}
public void Show() => Native.GtkWindowPresent(GtkWidget);
public virtual void Show() => Native.GtkWindowPresent(GtkWidget);
public void Hide() => Native.GtkWidgetHide(GtkWidget);

13
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@ -63,11 +63,18 @@ namespace Avalonia.Gtk3
public Action<WindowState> WindowStateChanged { get; set; }
public IDisposable ShowDialog()
public void ShowDialog(IWindowImpl parent)
{
Native.GtkWindowSetModal(GtkWidget, true);
Show();
return new EmptyDisposable();
Native.GtkWindowSetTransientFor(GtkWidget, ((WindowImpl)parent).GtkWidget.DangerousGetHandle());
Native.GtkWindowPresent(GtkWidget);
}
public override void Show()
{
Native.GtkWindowSetModal(GtkWidget, false);
Native.GtkWindowSetTransientFor(GtkWidget, IntPtr.Zero);
Native.GtkWindowPresent(GtkWidget);
}
public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(GtkWidget, enabled);

18
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -780,8 +780,8 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLong64b(IntPtr hWnd, int nIndex, IntPtr value);
public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value)
{
@ -791,7 +791,19 @@ namespace Avalonia.Win32.Interop
}
else
{
return SetWindowLongPtr(hWnd, nIndex, value);
return (uint)SetWindowLong64b(hWnd, nIndex, new IntPtr((uint)value)).ToInt32();
}
}
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr handle)
{
if (IntPtr.Size == 4)
{
return new IntPtr(SetWindowLong32b(hWnd, nIndex, (uint)handle.ToInt32()));
}
else
{
return SetWindowLong64b(hWnd, nIndex, handle);
}
}

25
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -42,6 +42,8 @@ namespace Avalonia.Win32
private OleDropTarget _dropTarget;
private Size _minSize;
private Size _maxSize;
private WindowImpl _parent;
private readonly List<WindowImpl> _disabledBy = new List<WindowImpl>();
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
@ -165,10 +167,10 @@ namespace Avalonia.Win32
private set;
}
public bool IsEnabled
void UpdateEnabled()
{
get { return UnmanagedMethods.IsWindowEnabled(_hwnd); }
set { UnmanagedMethods.EnableWindow(_hwnd, value); }
EnableWindow(_hwnd, _disabledBy.Count == 0);
}
public Size MaxClientSize
@ -248,6 +250,12 @@ namespace Avalonia.Win32
public void Hide()
{
if (_parent != null)
{
_parent._disabledBy.Remove(this);
_parent.UpdateEnabled();
_parent = null;
}
UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Hide);
}
@ -359,6 +367,7 @@ namespace Avalonia.Win32
public virtual void Show()
{
SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, IntPtr.Zero);
ShowWindow(_showWindowState);
}
@ -412,11 +421,13 @@ namespace Avalonia.Win32
}
}
public virtual IDisposable ShowDialog()
public void ShowDialog(IWindowImpl parent)
{
Show();
return Disposable.Empty;
_parent = (WindowImpl)parent;
_parent._disabledBy.Add(this);
_parent.UpdateEnabled();
SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, ((WindowImpl)parent)._hwnd);
ShowWindow(_showWindowState);
}
public void SetCursor(IPlatformHandle cursor)

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

@ -62,9 +62,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
parent.Show();
var window = new Window();
var task = window.ShowDialog();
var task = window.ShowDialog(parent);
Assert.True(window.IsVisible);
}
@ -258,12 +260,13 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Mock<IWindowImpl>();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1);
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>();
var task = target.ShowDialog<bool>(parent.Object);
windowImpl.Object.Closed();

Loading…
Cancel
Save