浏览代码

Adding various other runtime application configuration extensions to improve boilerplate main startup. Updated most Sample applications to use simpler bootstrapping extensions.

Jason Jarvis 9 年之前
父节点
当前提交
97388703b3

+ 7 - 17
samples/BindingTest/App.xaml.cs

@@ -13,30 +13,20 @@ namespace BindingTest
         public App()
         {
             RegisterServices();
-            InitializeSubsystems((int)Environment.OSVersion.Platform);
-            InitializeComponent();
-            InitializeLogging();
-        }
-
-        public static void AttachDevTools(Window window)
-        {
-            DevTools.Attach(window);
         }
 
         private static void Main()
         {
-            var app = new App();
-            var window = new MainWindow();
-            window.Show();
-            app.Run(window);
-        }
+			InitializeLogging();
 
-        private void InitializeComponent()
-        {
-            PerspexXamlLoader.Load(this);
+			new App()
+				.UseWin32Subsystem()
+				.UseDirect2D()
+				.LoadFromXaml()
+				.RunWithMainWindow<MainWindow>();
         }
 
-        private void InitializeLogging()
+		private static void InitializeLogging()
         {
 #if DEBUG
             SerilogLogger.Initialize(new LoggerConfiguration()

+ 3 - 2
samples/BindingTest/MainWindow.xaml.cs

@@ -1,4 +1,5 @@
 using BindingTest.ViewModels;
+using Perspex;
 using Perspex.Controls;
 using Perspex.Markup.Xaml;
 
@@ -10,12 +11,12 @@ namespace BindingTest
         {
             this.InitializeComponent();
             this.DataContext = new MainWindowViewModel();
-            App.AttachDevTools(this);
+			this.AttachDevTools();
         }
 
         private void InitializeComponent()
         {
-            PerspexXamlLoader.Load(this);
+			this.LoadFromXaml();
         }
     }
 }

+ 4 - 48
samples/ControlCatalog/App.xaml.cs

@@ -9,58 +9,14 @@ using Serilog;
 
 namespace ControlCatalog
 {
+	// Eventually we should move this into a PCL library so we can access
+	// from mobile platforms
+	//
     class App : Application
     {
         public App()
         {
             RegisterServices();
-            InitializeSubsystems(GetPlatformId());
-            InitializeLogging();
-            InitializeComponent();
         }
-
-        public static void AttachDevTools(Window window)
-        {
-#if DEBUG
-            DevTools.Attach(window);
-#endif
-        }
-
-        static void Main(string[] args)
-        {
-            var app = new App();
-            var window = new MainWindow();
-            window.Show();
-            app.Run(window);
-        }
-
-        private void InitializeComponent()
-        {
-            PerspexXamlLoader.Load(this);
-        }
-
-        private void InitializeLogging()
-        {
-#if DEBUG
-            SerilogLogger.Initialize(new LoggerConfiguration()
-                .MinimumLevel.Warning()
-                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
-                .CreateLogger());
-#endif
-        }
-
-        private int GetPlatformId()
-        {
-            var args = Environment.GetCommandLineArgs();
-
-            if (args.Contains("--gtk"))
-            {
-                return (int)PlatformID.Unix;
-            }
-            else
-            {
-                return (int)Environment.OSVersion.Platform;
-            }
-        }
-    }
+	}
 }

+ 1 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -103,6 +103,7 @@
     <Compile Include="Pages\SliderPage.xaml.cs">
       <DependentUpon>SliderPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>

+ 4 - 3
samples/ControlCatalog/MainWindow.xaml.cs

@@ -1,4 +1,5 @@
-using Perspex.Controls;
+using Perspex;
+using Perspex.Controls;
 using Perspex.Markup.Xaml;
 
 namespace ControlCatalog
@@ -8,12 +9,12 @@ namespace ControlCatalog
         public MainWindow()
         {
             this.InitializeComponent();
-            App.AttachDevTools(this);
+			this.AttachDevTools();
         }
 
         private void InitializeComponent()
         {
-            PerspexXamlLoader.Load(this);
+			this.LoadFromXaml();
         }
     }
 }

+ 127 - 0
samples/ControlCatalog/Program.cs

@@ -0,0 +1,127 @@
+using Perspex.Logging.Serilog;
+using Serilog;
+using System;
+using System.Linq;
+using Perspex;
+
+namespace ControlCatalog
+{
+	internal class Program
+	{
+		static void Main(string[] args)
+		{
+			InitializeLogging();
+
+			new App()
+				.ConfigureRenderSystem(args)
+				.LoadFromXaml()
+				.RunWithMainWindow<MainWindow>();
+		}
+
+		// This will be made into a runtime configuration extension soon!
+		private static void InitializeLogging()
+		{
+#if DEBUG
+			SerilogLogger.Initialize(new LoggerConfiguration()
+				.MinimumLevel.Warning()
+				.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
+				.CreateLogger());
+#endif
+		}
+
+	}
+
+	/////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	// Experimental: Would like to move this into a shared location once I figure out the best place for it 
+	// considering all common libraries are PCL and do not have access to Environment.OSVersion.Platform
+	// nor do they have access to the platform specific render/subsystem extensions.
+	// 
+	// Perhaps via DI we register each system with a priority/rank
+	//
+	public static class RenderSystemExtensions
+	{
+		[Flags]
+		enum RenderSystem
+		{
+			None = 0,
+			GTK = 1,
+			Skia = 2,
+			Direct2D = 4
+		};
+
+		/// <summary>
+		/// Default (Optimal) render system for a particular platform
+		/// </summary>
+		/// <returns></returns>
+		private static RenderSystem DefaultRenderSystem()
+		{
+			switch (Environment.OSVersion.Platform)
+			{
+				case PlatformID.MacOSX:
+					return RenderSystem.GTK;
+
+				case PlatformID.Unix:
+					return RenderSystem.GTK;
+
+				case PlatformID.Win32Windows:
+					return RenderSystem.Direct2D;
+			}
+
+			return RenderSystem.None;
+		}
+
+		/// <summary>
+		/// Returns an array of avalidable rendering systems in priority order
+		/// </summary>
+		/// <returns></returns>
+		private static RenderSystem[] AvailableRenderSystems()
+		{
+			switch (Environment.OSVersion.Platform)
+			{
+				case PlatformID.MacOSX:
+					return new RenderSystem[] { RenderSystem.GTK, RenderSystem.Skia };
+
+				case PlatformID.Unix:
+					return new RenderSystem[] { RenderSystem.GTK, RenderSystem.Skia };
+
+				case PlatformID.Win32Windows:
+					return new RenderSystem[] { RenderSystem.Direct2D, RenderSystem.Skia, RenderSystem.GTK };
+			}
+
+			return new RenderSystem[0];
+		}
+
+		/// <summary>
+		/// Selects the optimal render system for desktop platforms. Supports cmd line overrides
+		/// </summary>
+		/// <param name="app"></param>
+		/// <param name="args"></param>
+		public static AppT ConfigureRenderSystem<AppT>(this AppT app, string[] args) where AppT : Application
+		{
+			// So this all works great under Windows where it can support
+			// ALL configurations. But on OSX/Unix we cannot use Direct2D
+			//
+			if (args.Contains("--gtk") || DefaultRenderSystem() == RenderSystem.GTK)
+			{
+				app.UseGtkSubsystem();
+				app.UseCairo();
+			}
+			else
+			{
+				app.UseWin32Subsystem();
+
+				// not available until we do the SkiaSharp merge
+				//if (args.Contains("--skia") || DefaultRenderSystem() == RenderSystem.Skia)
+				//{
+				//	app.UseSkia();
+				//}
+				//else
+				{
+					app.UseDirect2D();
+				}
+			}
+
+			return app;
+		}
+	}
+}

+ 24 - 5
samples/TestApplication/Program.cs

@@ -31,11 +31,30 @@ namespace TestApplication
             // The version of ReactiveUI currently included is for WPF and so expects a WPF
             // dispatcher. This makes sure it's initialized.
             System.Windows.Threading.Dispatcher foo = System.Windows.Threading.Dispatcher.CurrentDispatcher;
-            new App();
-            MainWindow.RootNamespace = "TestApplication";
-            var wnd = MainWindow.Create();
-            DevTools.Attach(wnd);
-            Application.Current.Run(wnd);
+
+            var app = new App();
+
+			if (args.Contains("--gtk"))
+			{
+				app.UseGtkSubsystem();
+				app.UseCairo();
+			}
+			else
+			{
+				app.UseWin32Subsystem();
+
+				// not available until we do the SkiaSharp merge
+				//if (args.Contains("--skia"))
+				//{
+				//	app.UseSkia();
+				//}
+				//else
+				{
+					app.UseDirect2D();
+				}
+			}
+
+			app.Run();
         }
     }
 }

+ 31 - 15
samples/TestApplicationShared/App.cs

@@ -8,6 +8,9 @@ using Perspex.Controls.Templates;
 using Perspex.Markup.Xaml;
 using Perspex.Styling;
 using Perspex.Themes.Default;
+using Perspex.Diagnostics;
+using Perspex.Platform;
+using Perspex.Shared.PlatformSupport;
 
 namespace TestApplication
 {
@@ -15,22 +18,35 @@ namespace TestApplication
     {
         public App()
         {
+			// TODO: I believe this has to happen before we select sub systems. Can we
+			// move this safely into Application itself? Is there anything in here
+			// that is platform specific??
+			//
             RegisterServices();
-            InitializeSubsystems((int)Environment.OSVersion.Platform);            
-            Styles.Add(new DefaultTheme());
+        }
 
-            var loader = new PerspexXamlLoader();
-            var baseLight = (IStyle)loader.Load(
-                new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default"));
-            Styles.Add(baseLight);
+		public void Run()
+		{
+			Styles.Add(new DefaultTheme());
 
-            Styles.Add(new SampleTabStyle());
-            DataTemplates = new DataTemplates
-            {
-                new FuncTreeDataTemplate<Node>(
-                    x => new TextBlock {Text = x.Name},
-                    x => x.Children),
-            };
-        }
-    }
+			var loader = new PerspexXamlLoader();
+			var baseLight = (IStyle)loader.Load(
+				new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default"));
+			Styles.Add(baseLight);
+
+			Styles.Add(new SampleTabStyle());
+			DataTemplates = new DataTemplates
+			{
+				new FuncTreeDataTemplate<Node>(
+					x => new TextBlock {Text = x.Name},
+					x => x.Children),
+			};
+
+			MainWindow.RootNamespace = "TestApplication";
+			var wnd = MainWindow.Create();
+			DevTools.Attach(wnd);
+
+			Run(wnd);
+		}
+	}
 }

+ 1 - 22
samples/XamlTestApplication/App.cs

@@ -7,26 +7,5 @@ using Serilog;
 
 namespace XamlTestApplication
 {
-    public class App : XamlTestApp
-    {
-        public App()
-        {
-            InitializeLogging();
-        }
-
-        protected override void RegisterPlatform()
-        {
-            InitializeSubsystems((int)Environment.OSVersion.Platform);
-        }
-
-        private void InitializeLogging()
-        {
-#if DEBUG
-            SerilogLogger.Initialize(new LoggerConfiguration()
-                .MinimumLevel.Warning()
-                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
-                .CreateLogger());
-#endif
-        }
-    }
+	// No longer needed!
 }

+ 22 - 22
samples/XamlTestApplication/Program.cs

@@ -1,39 +1,39 @@
 // Copyright (c) The Perspex Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
-using System;
+using System.Linq;
 using System.Diagnostics;
 using System.Windows.Threading;
 using Perspex;
-using Perspex.Collections;
-using Perspex.Controls;
-using Perspex.Controls.Templates;
-using ReactiveUI;
-using XamlTestApplication.Views;
+using Serilog;
+using Perspex.Logging.Serilog;
 
 namespace XamlTestApplication
 {
     internal class Program
     {
-        private static void Main()
+        private static void Main(string[] args)
         {
-            var sw = new Stopwatch();
-            sw.Start();
-
+			// this sucks. Can we fix this? Do we even need it anymore?
             var foo = Dispatcher.CurrentDispatcher;
 
-            App application = new App
-            {
-
-            };
-
-            var window = new MainWindow();
-            window.Show();
+			InitializeLogging();
 
-            sw.Stop();
-            Debug.WriteLine($"Startup: {sw.ElapsedMilliseconds}ms");
-
-            Application.Current.Run(window);
+			 new XamlTestApp()
+					.UseWin32Subsystem()
+					.UseDirect2D()
+					.LoadFromXaml()
+					.RunWithMainWindow<Views.MainWindow>();
         }
-    }
+
+		private static void InitializeLogging()
+		{
+#if DEBUG
+			SerilogLogger.Initialize(new LoggerConfiguration()
+				.MinimumLevel.Warning()
+				.WriteTo.Trace(outputTemplate: "{Area}: {Message}")
+				.CreateLogger());
+#endif
+		}
+	}
 }

+ 3 - 12
samples/XamlTestApplicationPcl/XamlTestApp.cs

@@ -1,23 +1,14 @@
 using Perspex;
 using Perspex.Markup.Xaml;
+using XamlTestApplication.Views;
 
 namespace XamlTestApplication
 {
-    public abstract class XamlTestApp : Application
+    public class XamlTestApp : Application
     {
-        protected abstract void RegisterPlatform();
-
         public XamlTestApp()
         {
             RegisterServices();
-            RegisterPlatform();
-            InitializeComponent();
-        }
-
-        private void InitializeComponent()
-        {
-            var loader = new PerspexXamlLoader();
-            loader.Load(typeof(XamlTestApp), this);
         }
-    }
+	}
 }

+ 12 - 0
src/Markup/Perspex.Markup.Xaml/PerspexXamlLoader.cs

@@ -187,3 +187,15 @@ namespace Perspex.Markup.Xaml
         }
     }
 }
+
+namespace Perspex
+{
+	public static class XamlObjectExtensions
+	{
+		public static ObjectT LoadFromXaml<ObjectT>(this ObjectT obj)
+		{
+			Markup.Xaml.PerspexXamlLoader.Load(obj);
+			return obj;
+		}
+	}
+}

+ 322 - 309
src/Perspex.Controls/Window.cs

@@ -14,314 +14,327 @@ using System.Collections.Generic;
 
 namespace Perspex.Controls
 {
-    /// <summary>
-    /// Determines how a <see cref="Window"/> will size itself to fit its content.
-    /// </summary>
-    public enum SizeToContent
-    {
-        /// <summary>
-        /// The window will not automatically size itself to fit its content.
-        /// </summary>
-        Manual,
-
-        /// <summary>
-        /// The window will size itself horizontally to fit its content.
-        /// </summary>
-        Width,
-
-        /// <summary>
-        /// The window will size itself vertically to fit its content.
-        /// </summary>
-        Height,
-
-        /// <summary>
-        /// The window will size itself horizontally and vertically to fit its content.
-        /// </summary>
-        WidthAndHeight,
-    }
-
-    /// <summary>
-    /// A top-level window.
-    /// </summary>
-    public class Window : TopLevel, IStyleable, IFocusScope, ILayoutRoot, INameScope
-    {
-        private static IList<Window> s_windows = new List<Window>();
-
-        /// <summary>
-        /// Retrieves an enumeration of all Windows in the currently running application.
-        /// </summary>
-        public static IList<Window> OpenWindows => s_windows;
-
-        /// <summary>
-        /// Defines the <see cref="SizeToContent"/> property.
-        /// </summary>
-        public static readonly StyledProperty<SizeToContent> SizeToContentProperty =
-            PerspexProperty.Register<Window, SizeToContent>(nameof(SizeToContent));
-
-        /// <summary>
-        /// Enables of disables system window decorations (title bar, buttons, etc)
-        /// </summary>
-        public static readonly StyledProperty<bool> HasSystemDecorationsProperty =
-            PerspexProperty.Register<Window, bool>(nameof(HasSystemDecorations), true);
-
-        /// <summary>
-        /// Defines the <see cref="Title"/> property.
-        /// </summary>
-        public static readonly StyledProperty<string> TitleProperty =
-            PerspexProperty.Register<Window, string>(nameof(Title), "Window");
-
-        private readonly NameScope _nameScope = new NameScope();
-        private object _dialogResult;
-        private readonly Size _maxPlatformClientSize;
-
-        /// <summary>
-        /// Initializes static members of the <see cref="Window"/> class.
-        /// </summary>
-        static Window()
-        {
-            BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
-            TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue));
-            HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
+	/// <summary>
+	/// Determines how a <see cref="Window"/> will size itself to fit its content.
+	/// </summary>
+	public enum SizeToContent
+	{
+		/// <summary>
+		/// The window will not automatically size itself to fit its content.
+		/// </summary>
+		Manual,
+
+		/// <summary>
+		/// The window will size itself horizontally to fit its content.
+		/// </summary>
+		Width,
+
+		/// <summary>
+		/// The window will size itself vertically to fit its content.
+		/// </summary>
+		Height,
+
+		/// <summary>
+		/// The window will size itself horizontally and vertically to fit its content.
+		/// </summary>
+		WidthAndHeight,
+	}
+
+	/// <summary>
+	/// A top-level window.
+	/// </summary>
+	public class Window : TopLevel, IStyleable, IFocusScope, ILayoutRoot, INameScope
+	{
+		private static IList<Window> s_windows = new List<Window>();
+
+		/// <summary>
+		/// Retrieves an enumeration of all Windows in the currently running application.
+		/// </summary>
+		public static IList<Window> OpenWindows => s_windows;
+
+		/// <summary>
+		/// Defines the <see cref="SizeToContent"/> property.
+		/// </summary>
+		public static readonly StyledProperty<SizeToContent> SizeToContentProperty =
+			PerspexProperty.Register<Window, SizeToContent>(nameof(SizeToContent));
+
+		/// <summary>
+		/// Enables of disables system window decorations (title bar, buttons, etc)
+		/// </summary>
+		public static readonly StyledProperty<bool> HasSystemDecorationsProperty =
+			PerspexProperty.Register<Window, bool>(nameof(HasSystemDecorations), true);
+
+		/// <summary>
+		/// Defines the <see cref="Title"/> property.
+		/// </summary>
+		public static readonly StyledProperty<string> TitleProperty =
+			PerspexProperty.Register<Window, string>(nameof(Title), "Window");
+
+		private readonly NameScope _nameScope = new NameScope();
+		private object _dialogResult;
+		private readonly Size _maxPlatformClientSize;
+
+		/// <summary>
+		/// Initializes static members of the <see cref="Window"/> class.
+		/// </summary>
+		static Window()
+		{
+			BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
+			TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue));
+			HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
                 (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue));
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="Window"/> class.
-        /// </summary>
-        public Window()
-            : base(PlatformManager.CreateWindow())
-        {
-            _maxPlatformClientSize = this.PlatformImpl.MaxClientSize;
-        }
-
-        /// <inheritdoc/>
-        event EventHandler<NameScopeEventArgs> INameScope.Registered
-        {
-            add { _nameScope.Registered += value; }
-            remove { _nameScope.Registered -= value; }
-        }
-
-        /// <inheritdoc/>
-        event EventHandler<NameScopeEventArgs> INameScope.Unregistered
-        {
-            add { _nameScope.Unregistered += value; }
-            remove { _nameScope.Unregistered -= value; }
-        }
-
-        /// <summary>
-        /// Gets the platform-specific window implementation.
-        /// </summary>
-        public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl;
-
-        /// <summary>
-        /// Gets or sets a value indicating how the window will size itself to fit its content.
-        /// </summary>
-        public SizeToContent SizeToContent
-        {
-            get { return GetValue(SizeToContentProperty); }
-            set { SetValue(SizeToContentProperty, value); }
-        }
-
-        /// <summary>
-        /// Gets or sets the title of the window.
-        /// </summary>
-        public string Title
-        {
-            get { return GetValue(TitleProperty); }
-            set { SetValue(TitleProperty, value); }
-        }
-
-        /// <summary>
-        /// Enables of disables system window decorations (title bar, buttons, etc)
-        /// </summary>
-        /// 
-        public bool HasSystemDecorations
-        {
-            get { return GetValue(HasSystemDecorationsProperty); }
-            set { SetValue(HasSystemDecorationsProperty, value); }
-        }
-
-        /// <summary>
-        /// Gets or sets the minimized/maximized state of the window.
-        /// </summary>
-        public WindowState WindowState
-        {
-            get { return this.PlatformImpl.WindowState; }
-            set { this.PlatformImpl.WindowState = value; }
-        }
-
-        /// <inheritdoc/>
-        Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
-
-        /// <inheritdoc/>
-        Type IStyleable.StyleKey => typeof(Window);
-
-        /// <summary>
-        /// Closes the window.
-        /// </summary>
-        public void Close()
-        {
-            s_windows.Remove(this);
-            PlatformImpl.Dispose();
-        }
-
-        protected override void HandleApplicationExiting()
-        {
-            base.HandleApplicationExiting();
-            Close();
-        }
-
-        /// <summary>
-        /// Closes a dialog window with the specified result.
-        /// </summary>
-        /// <param name="dialogResult">The dialog result.</param>
-        /// <remarks>
-        /// When the window is shown with the <see cref="ShowDialog{TResult}"/> method, the
-        /// resulting task will produce the <see cref="_dialogResult"/> value when the window
-        /// is closed.
-        /// </remarks>
-        public void Close(object dialogResult)
-        {
-            _dialogResult = dialogResult;
-            Close();
-        }
-
-        /// <summary>
-        /// Hides the window but does not close it.
-        /// </summary>
-        public void Hide()
-        {
-            using (BeginAutoSizing())
-            {
-                PlatformImpl.Hide();
-            }
-        }
-
-        /// <summary>
-        /// Shows the window.
-        /// </summary>
-        public void Show()
-        {
-            s_windows.Add(this);
-
-            EnsureInitialized();
-            LayoutManager.Instance.ExecuteInitialLayoutPass(this);
-
-            using (BeginAutoSizing())
-            {
-                PlatformImpl.Show();
-            }
-        }
-
-        /// <summary>
-        /// Shows the window as a dialog.
-        /// </summary>
-        /// <returns>
-        /// A task that can be used to track the lifetime of the dialog.
-        /// </returns>
-        public Task ShowDialog()
-        {
-            return ShowDialog<object>();
-        }
-
-        /// <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 retrive the result of the dialog when it closes.
-        /// </returns>
-        public Task<TResult> ShowDialog<TResult>()
-        {
-            s_windows.Add(this);
-
-            EnsureInitialized();
-            LayoutManager.Instance.ExecuteInitialLayoutPass(this);
-
-            using (BeginAutoSizing())
-            {
-                var modal = PlatformImpl.ShowDialog();
-                var result = new TaskCompletionSource<TResult>();
-
-                Observable.FromEventPattern(this, nameof(Closed))
-                    .Take(1)
-                    .Subscribe(_ =>
-                    {
-                        modal.Dispose();
-                        result.SetResult((TResult)_dialogResult);
-                    });
-
-                return result.Task;
-            }
-        }
-
-        /// <inheritdoc/>
-        void INameScope.Register(string name, object element)
-        {
-            _nameScope.Register(name, element);
-        }
-
-        /// <inheritdoc/>
-        object INameScope.Find(string name)
-        {
-            return _nameScope.Find(name);
-        }
-
-        /// <inheritdoc/>
-        void INameScope.Unregister(string name)
-        {
-            _nameScope.Unregister(name);
-        }
-
-        /// <inheritdoc/>
-        protected override Size MeasureOverride(Size availableSize)
-        {
-            var sizeToContent = SizeToContent;
-            var size = ClientSize;
-            var desired = base.MeasureOverride(availableSize.Constrain(_maxPlatformClientSize));
-
-            switch (sizeToContent)
-            {
-                case SizeToContent.Width:
-                    size = new Size(desired.Width, ClientSize.Height);
-                    break;
-                case SizeToContent.Height:
-                    size = new Size(ClientSize.Width, desired.Height);
-                    break;
-                case SizeToContent.WidthAndHeight:
-                    size = new Size(desired.Width, desired.Height);
-                    break;
-                case SizeToContent.Manual:
-                    size = ClientSize;
-                    break;
-                default:
-                    throw new InvalidOperationException("Invalid value for SizeToContent.");
-            }
-
-            return size;
-        }
-
-        /// <inheritdoc/>
-        protected override void HandleResized(Size clientSize)
-        {
-            if (!AutoSizing)
-            {
-                SizeToContent = SizeToContent.Manual;
-            }
-
-            base.HandleResized(clientSize);
-        }
-
-        private void EnsureInitialized()
-        {
-            if (!this.IsInitialized)
-            {
-                var init = (ISupportInitialize)this;
-                init.BeginInit();
-                init.EndInit();
-            }
-        }
-    }
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Window"/> class.
+		/// </summary>
+		public Window()
+			: base(PlatformManager.CreateWindow())
+		{
+			_maxPlatformClientSize = this.PlatformImpl.MaxClientSize;
+		}
+
+		/// <inheritdoc/>
+		event EventHandler<NameScopeEventArgs> INameScope.Registered
+		{
+			add { _nameScope.Registered += value; }
+			remove { _nameScope.Registered -= value; }
+		}
+
+		/// <inheritdoc/>
+		event EventHandler<NameScopeEventArgs> INameScope.Unregistered
+		{
+			add { _nameScope.Unregistered += value; }
+			remove { _nameScope.Unregistered -= value; }
+		}
+
+		/// <summary>
+		/// Gets the platform-specific window implementation.
+		/// </summary>
+		public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl;
+
+		/// <summary>
+		/// Gets or sets a value indicating how the window will size itself to fit its content.
+		/// </summary>
+		public SizeToContent SizeToContent
+		{
+			get { return GetValue(SizeToContentProperty); }
+			set { SetValue(SizeToContentProperty, value); }
+		}
+
+		/// <summary>
+		/// Gets or sets the title of the window.
+		/// </summary>
+		public string Title
+		{
+			get { return GetValue(TitleProperty); }
+			set { SetValue(TitleProperty, value); }
+		}
+
+		/// <summary>
+		/// Enables of disables system window decorations (title bar, buttons, etc)
+		/// </summary>
+		/// 
+		public bool HasSystemDecorations
+		{
+			get { return GetValue(HasSystemDecorationsProperty); }
+			set { SetValue(HasSystemDecorationsProperty, value); }
+		}
+
+		/// <summary>
+		/// Gets or sets the minimized/maximized state of the window.
+		/// </summary>
+		public WindowState WindowState
+		{
+			get { return this.PlatformImpl.WindowState; }
+			set { this.PlatformImpl.WindowState = value; }
+		}
+
+		/// <inheritdoc/>
+		Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
+
+		/// <inheritdoc/>
+		Type IStyleable.StyleKey => typeof(Window);
+
+		/// <summary>
+		/// Closes the window.
+		/// </summary>
+		public void Close()
+		{
+			s_windows.Remove(this);
+			PlatformImpl.Dispose();
+		}
+
+		protected override void HandleApplicationExiting()
+		{
+			base.HandleApplicationExiting();
+			Close();
+		}
+
+		/// <summary>
+		/// Closes a dialog window with the specified result.
+		/// </summary>
+		/// <param name="dialogResult">The dialog result.</param>
+		/// <remarks>
+		/// When the window is shown with the <see cref="ShowDialog{TResult}"/> method, the
+		/// resulting task will produce the <see cref="_dialogResult"/> value when the window
+		/// is closed.
+		/// </remarks>
+		public void Close(object dialogResult)
+		{
+			_dialogResult = dialogResult;
+			Close();
+		}
+
+		/// <summary>
+		/// Hides the window but does not close it.
+		/// </summary>
+		public void Hide()
+		{
+			using (BeginAutoSizing())
+			{
+				PlatformImpl.Hide();
+			}
+		}
+
+		/// <summary>
+		/// Shows the window.
+		/// </summary>
+		public void Show()
+		{
+			s_windows.Add(this);
+
+			EnsureInitialized();
+			LayoutManager.Instance.ExecuteInitialLayoutPass(this);
+
+			using (BeginAutoSizing())
+			{
+				PlatformImpl.Show();
+			}
+		}
+
+		/// <summary>
+		/// Shows the window as a dialog.
+		/// </summary>
+		/// <returns>
+		/// A task that can be used to track the lifetime of the dialog.
+		/// </returns>
+		public Task ShowDialog()
+		{
+			return ShowDialog<object>();
+		}
+
+		/// <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 retrive the result of the dialog when it closes.
+		/// </returns>
+		public Task<TResult> ShowDialog<TResult>()
+		{
+			s_windows.Add(this);
+
+			EnsureInitialized();
+			LayoutManager.Instance.ExecuteInitialLayoutPass(this);
+
+			using (BeginAutoSizing())
+			{
+				var modal = PlatformImpl.ShowDialog();
+				var result = new TaskCompletionSource<TResult>();
+
+				Observable.FromEventPattern(this, nameof(Closed))
+					.Take(1)
+					.Subscribe(_ =>
+					{
+						modal.Dispose();
+						result.SetResult((TResult)_dialogResult);
+					});
+
+				return result.Task;
+			}
+		}
+
+		/// <inheritdoc/>
+		void INameScope.Register(string name, object element)
+		{
+			_nameScope.Register(name, element);
+		}
+
+		/// <inheritdoc/>
+		object INameScope.Find(string name)
+		{
+			return _nameScope.Find(name);
+		}
+
+		/// <inheritdoc/>
+		void INameScope.Unregister(string name)
+		{
+			_nameScope.Unregister(name);
+		}
+
+		/// <inheritdoc/>
+		protected override Size MeasureOverride(Size availableSize)
+		{
+			var sizeToContent = SizeToContent;
+			var size = ClientSize;
+			var desired = base.MeasureOverride(availableSize.Constrain(_maxPlatformClientSize));
+
+			switch (sizeToContent)
+			{
+				case SizeToContent.Width:
+					size = new Size(desired.Width, ClientSize.Height);
+					break;
+				case SizeToContent.Height:
+					size = new Size(ClientSize.Width, desired.Height);
+					break;
+				case SizeToContent.WidthAndHeight:
+					size = new Size(desired.Width, desired.Height);
+					break;
+				case SizeToContent.Manual:
+					size = ClientSize;
+					break;
+				default:
+					throw new InvalidOperationException("Invalid value for SizeToContent.");
+			}
+
+			return size;
+		}
+
+		/// <inheritdoc/>
+		protected override void HandleResized(Size clientSize)
+		{
+			if (!AutoSizing)
+			{
+				SizeToContent = SizeToContent.Manual;
+			}
+
+			base.HandleResized(clientSize);
+		}
+
+		private void EnsureInitialized()
+		{
+			if (!this.IsInitialized)
+			{
+				var init = (ISupportInitialize)this;
+				init.BeginInit();
+				init.EndInit();
+			}
+		}
+	}
+}
+
+namespace Perspex
+{
+	public static class WindowApplicationExtensions
+	{
+		public static void RunWithMainWindow<WindowT>(this Application app) where WindowT : Perspex.Controls.Window, new()
+		{
+			var window = new WindowT();
+			window.Show();
+			app.Run(window);
+		}
+	}
 }

+ 12 - 1
src/Perspex.Diagnostics/DevTools.xaml.cs

@@ -13,9 +13,20 @@ using Perspex.Markup.Xaml;
 using Perspex.VisualTree;
 using ReactiveUI;
 
+namespace Perspex
+{
+	public static class WindowExtensions
+	{
+		public static void AttachDevTools(this Window window)
+		{
+			Perspex.Diagnostics.DevTools.Attach(window);
+		}
+	}
+}
+
 namespace Perspex.Diagnostics
 {
-    public class DevTools : UserControl
+	public class DevTools : UserControl
     {
         private static Dictionary<Window, Window> s_open = new Dictionary<Window, Window>();
         private IDisposable _keySubscription;