Browse Source

Reworked dialogs for GTK/Win32

pull/2091/head
Nikita Tsukanov 8 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="OpenFile">Open File</Button>
<Button Name="SaveFile">Save File</Button> <Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</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> <Button Name="DecoratedWindow">Decorated window</Button>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

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

@ -31,12 +31,12 @@ namespace ControlCatalog.Pages
}.ShowAsync(GetWindow()); }.ShowAsync(GetWindow());
}; };
this.FindControl<Button>("DecoratedWindow").Click += delegate 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() private void InitializeComponent()
{ {

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

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

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

@ -30,10 +30,7 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Shows the window as a dialog. /// Shows the window as a dialog.
/// </summary> /// </summary>
/// <returns> void ShowDialog(IWindowImpl parent);
/// An <see cref="IDisposable"/> that should be used to close the window.
/// </returns>
IDisposable ShowDialog();
/// <summary> /// <summary>
/// Enables or disables system window decorations (title bar, buttons, etc) /// 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -18,28 +19,40 @@ namespace Avalonia.Controls
public class SaveFileDialog : FileDialog public class SaveFileDialog : FileDialog
{ {
public string DefaultExtension { get; set; } public string DefaultExtension { get; set; }
public async Task<string> ShowAsync(Window window) public async Task<string> ShowAsync(Window parent)
=> {
((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, window?.PlatformImpl)) ?? if(parent == null)
new string[0]).FirstOrDefault(); throw new ArgumentNullException(nameof(parent));
return ((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>()
.ShowFileDialogAsync(this, parent?.PlatformImpl)) ??
new string[0]).FirstOrDefault();
}
} }
public class OpenFileDialog : FileDialog public class OpenFileDialog : FileDialog
{ {
public bool AllowMultiple { get; set; } public bool AllowMultiple { get; set; }
public Task<string[]> ShowAsync(Window window = null) public Task<string[]> ShowAsync(Window parent)
=> AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, window?.PlatformImpl); {
if(parent == null)
throw new ArgumentNullException(nameof(parent));
return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, parent?.PlatformImpl);
}
} }
public class OpenFolderDialog : FileSystemDialog public class OpenFolderDialog : FileSystemDialog
{ {
public string DefaultDirectory { get; set; } public string DefaultDirectory { get; set; }
public Task<string> ShowAsync(Window window = null) public Task<string> ShowAsync(Window parent)
=> AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, window?.PlatformImpl); {
if(parent == null)
throw new ArgumentNullException(nameof(parent));
return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, parent?.PlatformImpl);
}
} }
public abstract class SystemDialog public abstract class SystemDialog

38
src/Avalonia.Controls/Window.cs

@ -397,11 +397,23 @@ namespace Avalonia.Controls
/// <returns> /// <returns>
/// A task that can be used to track the lifetime of the dialog. /// A task that can be used to track the lifetime of the dialog.
/// </returns> /// </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> /// <summary>
/// Shows the window as a dialog. /// Shows the window as a dialog.
/// </summary> /// </summary>
@ -411,8 +423,10 @@ namespace Avalonia.Controls
/// <returns>. /// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes. /// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns> /// </returns>
public Task<TResult> ShowDialog<TResult>() public Task<TResult> ShowDialog<TResult>(IWindowImpl parent)
{ {
if(parent == null)
throw new ArgumentNullException(nameof(parent));
if (IsVisible) if (IsVisible)
{ {
throw new InvalidOperationException("The window is already being shown."); throw new InvalidOperationException("The window is already being shown.");
@ -427,24 +441,18 @@ namespace Avalonia.Controls
using (BeginAutoSizing()) 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>(); var result = new TaskCompletionSource<TResult>();
Renderer?.Start(); Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>( Observable.FromEventPattern<EventHandler, EventArgs>(
x => this.Closed += x, x => this.Closed += x,
x => this.Closed -= x) x => this.Closed -= x)
.Take(1) .Take(1)
.Subscribe(_ => .Subscribe(_ =>
{ {
modal?.Dispose(); parent.Activate();
SetIsEnabled(affectedWindows, true);
activated?.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult))); 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() void SetWindowStartupLocation()
{ {
if (WindowStartupLocation == WindowStartupLocation.CenterScreen) 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) 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) public void SetSystemDecorations(bool enabled)
{ {
@ -157,4 +159,4 @@ namespace Avalonia.DesignerSupport.Remote
public Screen[] AllScreens { get; } = public Screen[] AllScreens { get; } =
{new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)}; {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 IAvnWindow Native => _native;
public IDisposable ShowDialog() public void ShowDialog(IWindowImpl window)
{ {
return _native.ShowDialog(); _native.ShowDialog();
} }
public void CanResize(bool value) 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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_modal(GtkWindow window, bool modal); 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 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import
public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow); 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_new GtkWindowNew;
public static D.gtk_window_set_icon GtkWindowSetIcon; public static D.gtk_window_set_icon GtkWindowSetIcon;
public static D.gtk_window_set_modal GtkWindowSetModal; 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.gdk_set_allowed_backends GdkSetAllowedBackends;
public static D.gtk_init GtkInit; public static D.gtk_init GtkInit;
public static D.gtk_window_present GtkWindowPresent; 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); 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); 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 Action<WindowState> WindowStateChanged { get; set; }
public IDisposable ShowDialog() public void ShowDialog(IWindowImpl parent)
{ {
Native.GtkWindowSetModal(GtkWidget, true); Native.GtkWindowSetModal(GtkWidget, true);
Show(); Native.GtkWindowSetTransientFor(GtkWidget, ((WindowImpl)parent).GtkWidget.DangerousGetHandle());
return new EmptyDisposable(); 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); 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")] [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value); private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")]
private static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value); private static extern IntPtr SetWindowLong64b(IntPtr hWnd, int nIndex, IntPtr value);
public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value) public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value)
{ {
@ -791,7 +791,19 @@ namespace Avalonia.Win32.Interop
} }
else 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 OleDropTarget _dropTarget;
private Size _minSize; private Size _minSize;
private Size _maxSize; private Size _maxSize;
private WindowImpl _parent;
private readonly List<WindowImpl> _disabledBy = new List<WindowImpl>();
#if USE_MANAGED_DRAG #if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag; private readonly ManagedWindowResizeDragHelper _managedDrag;
@ -165,10 +167,10 @@ namespace Avalonia.Win32
private set; private set;
} }
public bool IsEnabled
void UpdateEnabled()
{ {
get { return UnmanagedMethods.IsWindowEnabled(_hwnd); } EnableWindow(_hwnd, _disabledBy.Count == 0);
set { UnmanagedMethods.EnableWindow(_hwnd, value); }
} }
public Size MaxClientSize public Size MaxClientSize
@ -248,6 +250,12 @@ namespace Avalonia.Win32
public void Hide() public void Hide()
{ {
if (_parent != null)
{
_parent._disabledBy.Remove(this);
_parent.UpdateEnabled();
_parent = null;
}
UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Hide); UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Hide);
} }
@ -359,6 +367,7 @@ namespace Avalonia.Win32
public virtual void Show() public virtual void Show()
{ {
SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, IntPtr.Zero);
ShowWindow(_showWindowState); ShowWindow(_showWindowState);
} }
@ -412,11 +421,13 @@ namespace Avalonia.Win32
} }
} }
public virtual IDisposable ShowDialog() public void ShowDialog(IWindowImpl parent)
{ {
Show(); _parent = (WindowImpl)parent;
_parent._disabledBy.Add(this);
return Disposable.Empty; _parent.UpdateEnabled();
SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, ((WindowImpl)parent)._hwnd);
ShowWindow(_showWindowState);
} }
public void SetCursor(IPlatformHandle cursor) 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)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Window();
parent.Show();
var window = new Window(); var window = new Window();
var task = window.ShowDialog(); var task = window.ShowDialog(parent);
Assert.True(window.IsVisible); Assert.True(window.IsVisible);
} }
@ -258,12 +260,13 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Mock<IWindowImpl>();
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1); windowImpl.Setup(x => x.Scaling).Returns(1);
var target = new Window(windowImpl.Object); var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(); var task = target.ShowDialog<bool>(parent.Object);
windowImpl.Object.Closed(); windowImpl.Object.Closed();

Loading…
Cancel
Save