diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index b308c59789..ec841db5b2 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 51cf1d4dde..c4febff434 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Net; using System.Reflection; using Avalonia.Controls; @@ -22,6 +23,15 @@ namespace Avalonia.DesignerSupport.Remote { public string AppPath { get; set; } public Uri Transport { get; set; } + public string Method { get; set; } = Methods.AvaloniaRemote; + public string SessionId { get; set; } = Guid.NewGuid().ToString(); + } + + static class Methods + { + public const string AvaloniaRemote = "avalonia-remote"; + public const string Win32 = "win32"; + } static Exception Die(string error) @@ -35,11 +45,13 @@ namespace Avalonia.DesignerSupport.Remote return new Exception("APPEXIT"); } + static void Log(string message) => Console.WriteLine(message); + static Exception PrintUsage() { - Console.Error.WriteLine("Usage: --transport transport_spec app"); + Console.Error.WriteLine("Usage: --transport transport_spec --session-id sid --method method app"); Console.Error.WriteLine(); - Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe"); + Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ --session-id 123 --method avalonia-remote MyApp.exe"); Console.Error.Flush(); return Die(null); } @@ -59,6 +71,10 @@ namespace Avalonia.DesignerSupport.Remote } else if (arg == "--transport") next = a => rv.Transport = new Uri(a, UriKind.Absolute); + else if (arg == "--method") + next = a => rv.Method = a; + else if (arg == "--session-id") + next = a => rv.SessionId = a; else if (rv.AppPath == null) rv.AppPath = arg; else @@ -84,18 +100,22 @@ namespace Avalonia.DesignerSupport.Remote PrintUsage(); return null; } - + interface IAppInitializer { - Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj); + Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj); } class AppInitializer : IAppInitializer where T : AppBuilderBase, new() { - public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj) + public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, + CommandLineArgs args, object obj) { var builder = (AppBuilderBase) obj; - builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); + if (args.Method == Methods.AvaloniaRemote) + builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); + if (args.Method == Methods.Win32) + builder.UseWindowingSubsystem("Avalonia.Win32"); builder.SetupWithoutStarting(); return builder.Instance; } @@ -120,13 +140,16 @@ namespace Avalonia.DesignerSupport.Remote BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (builderMethod == null) throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}"); - + Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}"); var appBuilder = builderMethod.Invoke(null, null); + Log($"Initializing application in design mode"); var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); - var app = initializer.GetConfiguredApp(transport, appBuilder); + var app = initializer.GetConfiguredApp(transport, args, appBuilder); s_transport = transport; transport.OnMessage += OnTransportMessage; transport.OnException += (t, e) => Die(e.ToString()); + Log("Sending StartDesignerSessionMessage"); + transport.Send(new StartDesignerSessionMessage {SessionId = args.SessionId}); app.Run(new NeverClose()); } @@ -139,7 +162,8 @@ namespace Avalonia.DesignerSupport.Remote s_viewportAllocatedMessage }; } - + + private static Window s_currentWindow; private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.Post(() => { if (obj is ClientSupportedPixelFormatsMessage formats) @@ -156,8 +180,17 @@ namespace Avalonia.DesignerSupport.Remote { try { - DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); - s_transport.Send(new UpdateXamlResultMessage()); + s_currentWindow?.Close(); + } + catch + { + //Ignore + } + s_currentWindow = null; + try + { + s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath); + s_transport.Send(new UpdateXamlResultMessage(){Handle = s_currentWindow.PlatformImpl?.Handle?.Handle.ToString()}); } catch (Exception e) { diff --git a/src/Avalonia.Remote.Protocol/DesignMessages.cs b/src/Avalonia.Remote.Protocol/DesignMessages.cs index d6e898f320..f3683da21c 100644 --- a/src/Avalonia.Remote.Protocol/DesignMessages.cs +++ b/src/Avalonia.Remote.Protocol/DesignMessages.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Remote.Protocol.Designer +using System; + +namespace Avalonia.Remote.Protocol.Designer { [AvaloniaRemoteMessageGuid("9AEC9A2E-6315-4066-B4BA-E9A9EFD0F8CC")] public class UpdateXamlMessage @@ -11,7 +13,13 @@ public class UpdateXamlResultMessage { public string Error { get; set; } + public string Handle { get; set; } + } + + [AvaloniaRemoteMessageGuid("854887CF-2694-4EB6-B499-7461B6FB96C7")] + public class StartDesignerSessionMessage + { + public string SessionId { get; set; } } - } \ No newline at end of file diff --git a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs index af62f2da59..3d2bd09485 100644 --- a/src/Avalonia.Remote.Protocol/TcpTransportBase.cs +++ b/src/Avalonia.Remote.Protocol/TcpTransportBase.cs @@ -72,7 +72,7 @@ namespace Avalonia.Remote.Protocol { var c = new TcpClient(); await c.ConnectAsync(address, port); - return CreateTransport(_resolver, c.GetStream(), c.Dispose); + return CreateTransport(_resolver, c.GetStream(), ((IDisposable)c).Dispose); } } } \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems b/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems index 6f13747cb6..cca6f5acf1 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems @@ -11,6 +11,7 @@ + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 92d973238e..14f6211d68 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -53,7 +53,6 @@ - Component diff --git a/src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs b/src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs new file mode 100644 index 0000000000..538a51c01e --- /dev/null +++ b/src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs @@ -0,0 +1,90 @@ +// Copyright (c) The Avalonia 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.ComponentModel; +using System.Runtime.InteropServices; +using Avalonia.Platform; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class EmbeddedWindowImpl : WindowImpl, IEmbeddableWindowImpl + { + private static IntPtr DefaultParentWindow = CreateParentWindow(); + private static UnmanagedMethods.WndProc _wndProcDelegate; + + protected override IntPtr CreateWindowOverride(ushort atom) + { + var hWnd = UnmanagedMethods.CreateWindowEx( + 0, + atom, + null, + (int)UnmanagedMethods.WindowStyles.WS_CHILD, + 0, + 0, + 640, + 480, + DefaultParentWindow, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero); + return hWnd; + } + + protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + if (msg == (uint)UnmanagedMethods.WindowsMessage.WM_KILLFOCUS) + LostFocus?.Invoke(); + return base.WndProc(hWnd, msg, wParam, lParam); + } + + public event Action LostFocus; + + private static IntPtr CreateParentWindow() + { + _wndProcDelegate = new UnmanagedMethods.WndProc(ParentWndProc); + + var wndClassEx = new UnmanagedMethods.WNDCLASSEX + { + cbSize = Marshal.SizeOf(), + hInstance = UnmanagedMethods.GetModuleHandle(null), + lpfnWndProc = _wndProcDelegate, + lpszClassName = "AvaloniaEmbeddedWindow-" + Guid.NewGuid(), + }; + + var atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx); + + if (atom == 0) + { + throw new Win32Exception(); + } + + var hwnd = UnmanagedMethods.CreateWindowEx( + 0, + atom, + null, + (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW, + UnmanagedMethods.CW_USEDEFAULT, + UnmanagedMethods.CW_USEDEFAULT, + UnmanagedMethods.CW_USEDEFAULT, + UnmanagedMethods.CW_USEDEFAULT, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero); + + if (hwnd == IntPtr.Zero) + { + throw new Win32Exception(); + } + + return hwnd; + } + + private static IntPtr ParentWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Embedding/EmbeddedWindowImpl.cs b/src/Windows/Avalonia.Win32/Embedding/EmbeddedWindowImpl.cs deleted file mode 100644 index d27c28cb67..0000000000 --- a/src/Windows/Avalonia.Win32/Embedding/EmbeddedWindowImpl.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using Avalonia.Platform; -using Avalonia.Win32.Interop; - -namespace Avalonia.Win32 -{ - class EmbeddedWindowImpl : WindowImpl, IEmbeddableWindowImpl - { - private static readonly System.Windows.Forms.UserControl WinFormsControl = new System.Windows.Forms.UserControl(); - - public static IntPtr DefaultParentWindow = WinFormsControl.Handle; - - protected override IntPtr CreateWindowOverride(ushort atom) - { - var hWnd = UnmanagedMethods.CreateWindowEx( - 0, - atom, - null, - (int)UnmanagedMethods.WindowStyles.WS_CHILD, - 0, - 0, - 640, - 480, - DefaultParentWindow, - IntPtr.Zero, - IntPtr.Zero, - IntPtr.Zero); - return hWnd; - } - - protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) - { - if(msg == (uint)UnmanagedMethods.WindowsMessage.WM_KILLFOCUS) - LostFocus?.Invoke(); - return base.WndProc(hWnd, msg, wParam, lParam); - } - - public event Action LostFocus; - } -} diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index e265749249..a490e59cb7 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -196,13 +196,9 @@ namespace Avalonia.Win32 public IEmbeddableWindowImpl CreateEmbeddableWindow() { -#if NETSTANDARD - throw new NotSupportedException(); -#else var embedded = new EmbeddedWindowImpl(); embedded.Show(); return embedded; -#endif } public IPopupImpl CreatePopup() diff --git a/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs b/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs index d3f80dd621..bef53bb685 100644 --- a/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs +++ b/src/tools/Avalonia.Designer.HostApp.NetFX/Program.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -8,7 +11,37 @@ namespace Avalonia.Designer.HostApp.NetFX { class Program { + private static string s_appDir; public static void Main(string[] args) - => Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); + { + s_appDir = Directory.GetCurrentDirectory(); + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + foreach (var dll in Directory.GetFiles(s_appDir, "*.dll")) + { + try + { + Console.WriteLine("Loading " + dll); + Assembly.LoadFile(dll); + } + catch + { + + } + } + Exec(args); + } + + static void Exec(string[] args) + { + Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(args); + } + + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + string assemblyPath = Path.Combine(s_appDir, new AssemblyName(args.Name).Name + ".dll"); + if (File.Exists(assemblyPath) == false) return null; + Assembly assembly = Assembly.LoadFrom(assemblyPath); + return assembly; + } } }