diff --git a/Gtk/Perspex.Gtk/WindowImpl.cs b/Gtk/Perspex.Gtk/WindowImpl.cs index 502eaaa36a..c736b9d946 100644 --- a/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/Gtk/Perspex.Gtk/WindowImpl.cs @@ -7,6 +7,7 @@ namespace Perspex.Gtk { using System; + using System.Threading.Tasks; using Perspex.Controls; using Perspex.Input.Raw; using Perspex.Platform; @@ -79,6 +80,16 @@ namespace Perspex.Gtk this.Title = title; } + public IDisposable ShowDialog() + { + throw new NotImplementedException(); + } + + void ITopLevelImpl.Activate() + { + this.Activate(); + } + protected override bool OnButtonPressEvent(Gdk.EventButton evnt) { var e = new RawMouseEventArgs( diff --git a/Perspex.Controls/Platform/ITopLevelImpl.cs b/Perspex.Controls/Platform/ITopLevelImpl.cs index 96c642d158..2802dad621 100644 --- a/Perspex.Controls/Platform/ITopLevelImpl.cs +++ b/Perspex.Controls/Platform/ITopLevelImpl.cs @@ -29,6 +29,8 @@ namespace Perspex.Platform Action Resized { get; set; } + void Activate(); + void Invalidate(Rect rect); void SetOwner(TopLevel owner); diff --git a/Perspex.Controls/Platform/IWindowImpl.cs b/Perspex.Controls/Platform/IWindowImpl.cs index cd84fc0f6d..83586d3e9f 100644 --- a/Perspex.Controls/Platform/IWindowImpl.cs +++ b/Perspex.Controls/Platform/IWindowImpl.cs @@ -6,7 +6,8 @@ namespace Perspex.Platform { - using Perspex.Controls; + using System; + using System.Threading.Tasks; public interface IWindowImpl : ITopLevelImpl { @@ -14,6 +15,8 @@ namespace Perspex.Platform void Show(); + IDisposable ShowDialog(); + void Hide(); } } diff --git a/Perspex.Controls/TopLevel.cs b/Perspex.Controls/TopLevel.cs index 2a3b19f13e..8cc0d46f74 100644 --- a/Perspex.Controls/TopLevel.cs +++ b/Perspex.Controls/TopLevel.cs @@ -24,6 +24,9 @@ namespace Perspex.Controls public static readonly PerspexProperty ClientSizeProperty = PerspexProperty.Register("ClientSize"); + public static readonly PerspexProperty IsActiveProperty = + PerspexProperty.Register("IsActive"); + private Dispatcher dispatcher; private IRenderManager renderManager; @@ -113,6 +116,12 @@ namespace Perspex.Controls set { this.SetValue(ClientSizeProperty, value); } } + public bool IsActive + { + get { return this.GetValue(IsActiveProperty); } + private set { this.SetValue(IsActiveProperty, value); } + } + public ILayoutManager LayoutManager { get; @@ -140,6 +149,11 @@ namespace Perspex.Controls return this.PlatformImpl.PointToScreen(p); } + public void Activate() + { + this.PlatformImpl.Activate(); + } + protected IDisposable BeginAutoSizing() { this.autoSizing = true; @@ -174,6 +188,7 @@ namespace Perspex.Controls } FocusManager.Instance.SetFocusScope(this); + this.IsActive = true; } private void HandleClosed() @@ -186,6 +201,8 @@ namespace Perspex.Controls private void HandleDeactivated() { + this.IsActive = false; + if (this.Deactivated != null) { this.Deactivated(this, EventArgs.Empty); diff --git a/Perspex.Controls/Window.cs b/Perspex.Controls/Window.cs index 9ea156a08a..00605c7f36 100644 --- a/Perspex.Controls/Window.cs +++ b/Perspex.Controls/Window.cs @@ -7,6 +7,8 @@ namespace Perspex.Controls { using System; + using System.Reactive.Linq; + using System.Threading.Tasks; using Perspex.Media; using Perspex.Platform; using Perspex.Styling; @@ -17,6 +19,8 @@ namespace Perspex.Controls public static readonly PerspexProperty TitleProperty = PerspexProperty.Register("Title", "Window"); + private object dialogResult; + static Window() { BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); @@ -43,6 +47,17 @@ namespace Perspex.Controls get { return typeof(Window); } } + public void Close() + { + this.PlatformImpl.Dispose(); + } + + public void Close(object dialogResult) + { + this.dialogResult = dialogResult; + this.Close(); + } + public void Hide() { using (this.BeginAutoSizing()) @@ -60,5 +75,31 @@ namespace Perspex.Controls this.PlatformImpl.Show(); } } + + public Task ShowDialog() + { + return this.ShowDialog(); + } + + public Task ShowDialog() + { + this.ExecuteLayoutPass(); + + using (this.BeginAutoSizing()) + { + var modal = this.PlatformImpl.ShowDialog(); + var result = new TaskCompletionSource(); + + Observable.FromEventPattern(this, nameof(Closed)) + .Take(1) + .Subscribe(_ => + { + modal.Dispose(); + result.SetResult((TResult)this.dialogResult); + }); + + return result.Task; + } + } } } diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 4cc0d1f537..1221d69366 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -11,12 +11,11 @@ using Perspex.Layout; using Perspex.Media; using Perspex.Media.Imaging; using Perspex.Rendering; -using Perspex.Styling; using Perspex.Threading; #if PERSPEX_GTK using Perspex.Gtk; #else -using Perspex.Win32; +using ReactiveUI; #endif using Splat; @@ -189,7 +188,10 @@ namespace TestApplication private static TabItem ButtonsTab() { - return new TabItem + var showDialog = ReactiveCommand.Create(); + Button showDialogButton; + + var result = new TabItem { Header = "Buttons", Content = new StackPanel @@ -201,10 +203,11 @@ namespace TestApplication MinWidth = 120, Children = new Controls { - new Button + (showDialogButton = new Button { Content = "Button", - }, + Command = showDialog, + }), new Button { Content = "Button", @@ -246,6 +249,31 @@ namespace TestApplication } }, }; + + showDialog.Subscribe(async _ => + { + var close = ReactiveCommand.Create(); + + var dialog = new Window + { + Content = new StackPanel + { + Width = 200, + Height = 200, + Children = new Controls + { + new Button { Content = "Yes", Command = close, CommandParameter = "Yes" }, + new Button { Content = "No", Command = close, CommandParameter = "No" }, + } + } + }; + + close.Subscribe(x => dialog.Close(x)); + + showDialogButton.Content = await dialog.ShowDialog(); + }); + + return result; } private static TabItem TextTab() @@ -582,6 +610,5 @@ namespace TestApplication return result; } - } } diff --git a/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 2dde3b6a53..44e655fabd 100644 --- a/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -516,6 +516,9 @@ namespace Perspex.Win32.Interop [DllImport("user32.dll", SetLastError = true)] public static extern bool DestroyWindow(IntPtr hwnd); + [DllImport("user32.dll")] + public static extern bool EnableWindow(IntPtr hWnd, bool bEnable); + [DllImport("user32.dll")] public static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint); @@ -558,6 +561,9 @@ namespace Perspex.Win32.Interop [DllImport("user32.dll")] public static extern bool InvalidateRect(IntPtr hWnd, ref RECT lpRect, bool bErase); + [DllImport("user32.dll")] + public static extern bool IsWindowEnabled(IntPtr hWnd); + [DllImport("user32.dll")] public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent); @@ -579,6 +585,9 @@ namespace Perspex.Win32.Interop [DllImport("user32.dll")] public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetActiveWindow(IntPtr hWnd); + [DllImport("user32.dll")] public static extern IntPtr SetCapture(IntPtr hWnd); diff --git a/Windows/Perspex.Win32/WindowImpl.cs b/Windows/Perspex.Win32/WindowImpl.cs index dbf138a459..25ffd322b4 100644 --- a/Windows/Perspex.Win32/WindowImpl.cs +++ b/Windows/Perspex.Win32/WindowImpl.cs @@ -7,9 +7,14 @@ namespace Perspex.Win32 { using System; + using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Reactive.Disposables; + using System.Reactive.Linq; using System.Runtime.InteropServices; + using System.Threading.Tasks; using Perspex.Controls; using Perspex.Input.Raw; using Perspex.Platform; @@ -18,6 +23,8 @@ namespace Perspex.Win32 public class WindowImpl : IWindowImpl { + private static List instances = new List(); + private UnmanagedMethods.WndProc wndProcDelegate; private string className; @@ -31,6 +38,7 @@ namespace Perspex.Win32 public WindowImpl() { this.CreateWindow(); + instances.Add(this); } public Action Activated { get; set; } @@ -83,6 +91,17 @@ namespace Perspex.Win32 private set; } + public bool IsEnabled + { + get { return UnmanagedMethods.IsWindowEnabled(this.hwnd); } + set { UnmanagedMethods.EnableWindow(this.hwnd, value); } + } + + public void Activate() + { + UnmanagedMethods.SetActiveWindow(this.hwnd); + } + public IPopupImpl CreatePopup() { return new PopupImpl(); @@ -90,6 +109,7 @@ namespace Perspex.Win32 public void Dispose() { + instances.Remove(this); UnmanagedMethods.DestroyWindow(this.hwnd); } @@ -133,6 +153,37 @@ namespace Perspex.Win32 UnmanagedMethods.ShowWindow(this.hwnd, UnmanagedMethods.ShowWindowCommand.Normal); } + public virtual IDisposable ShowDialog() + { + var disabled = instances.Where(x => x != this && x.IsEnabled).ToList(); + TopLevel activated = null; + + foreach (var window in disabled) + { + if (window.owner.IsActive) + { + activated = window.owner; + } + + window.IsEnabled = false; + } + + this.Show(); + + return Disposable.Create(() => + { + foreach (var window in disabled) + { + window.IsEnabled = true; + } + + if (activated != null) + { + activated.Activate(); + } + }); + } + protected virtual IntPtr CreateWindowOverride(ushort atom) { return UnmanagedMethods.CreateWindowEx(