From eee5da30a0214869b2f99f7bcd29b0e0510084e8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 3 Sep 2015 00:12:12 +0300 Subject: [PATCH] Designer: Initial out-of-process renderer implementation --- Perspex.sln | 7 + src/Windows/Perspex.Designer/App.config | 6 + src/Windows/Perspex.Designer/App.xaml | 8 + src/Windows/Perspex.Designer/App.xaml.cs | 28 ++++ .../AppHost/PerspexAppHost.cs | 120 ++++++++++++++ .../Perspex.Designer/AppHost/WindowHost.cs | 44 +++++ .../Perspex.Designer/Comm/CommChannel.cs | 108 +++++++++++++ .../Perspex.Designer/Comm/InitMessage.cs | 18 +++ .../Perspex.Designer/Comm/ProcessHost.cs | 152 ++++++++++++++++++ .../Perspex.Designer/Comm/StateMessage.cs | 19 +++ .../Comm/UpdateXamlMessage.cs | 19 +++ .../Comm/WindowCreatedMessage.cs | 19 +++ src/Windows/Perspex.Designer/DemoWindow.xaml | 23 +++ .../Perspex.Designer/DemoWindow.xaml.cs | 34 ++++ .../Perspex.Designer/DesignerAppHost.cs | 13 ++ .../Perspex.Designer/Perspex.Designer.csproj | 109 +++++++++++++ .../Perspex.Designer/PerspexDesigner.xaml | 14 ++ .../Perspex.Designer/PerspexDesigner.xaml.cs | 98 +++++++++++ src/Windows/Perspex.Designer/Program.cs | 51 ++++++ .../Properties/AssemblyInfo.cs | 36 +++++ src/Windows/Perspex.Designer/README.txt | 3 + src/Windows/Perspex.Designer/WinApi.cs | 17 ++ 22 files changed, 946 insertions(+) create mode 100644 src/Windows/Perspex.Designer/App.config create mode 100644 src/Windows/Perspex.Designer/App.xaml create mode 100644 src/Windows/Perspex.Designer/App.xaml.cs create mode 100644 src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs create mode 100644 src/Windows/Perspex.Designer/AppHost/WindowHost.cs create mode 100644 src/Windows/Perspex.Designer/Comm/CommChannel.cs create mode 100644 src/Windows/Perspex.Designer/Comm/InitMessage.cs create mode 100644 src/Windows/Perspex.Designer/Comm/ProcessHost.cs create mode 100644 src/Windows/Perspex.Designer/Comm/StateMessage.cs create mode 100644 src/Windows/Perspex.Designer/Comm/UpdateXamlMessage.cs create mode 100644 src/Windows/Perspex.Designer/Comm/WindowCreatedMessage.cs create mode 100644 src/Windows/Perspex.Designer/DemoWindow.xaml create mode 100644 src/Windows/Perspex.Designer/DemoWindow.xaml.cs create mode 100644 src/Windows/Perspex.Designer/DesignerAppHost.cs create mode 100644 src/Windows/Perspex.Designer/Perspex.Designer.csproj create mode 100644 src/Windows/Perspex.Designer/PerspexDesigner.xaml create mode 100644 src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs create mode 100644 src/Windows/Perspex.Designer/Program.cs create mode 100644 src/Windows/Perspex.Designer/Properties/AssemblyInfo.cs create mode 100644 src/Windows/Perspex.Designer/README.txt create mode 100644 src/Windows/Perspex.Designer/WinApi.cs diff --git a/Perspex.sln b/Perspex.sln index 3c1892f48d..a5940bb913 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -90,6 +90,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Cairo", "src\Gtk\Pe EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.ReactiveUI", "src\Perspex.ReactiveUI\Perspex.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Designer", "src\Windows\Perspex.Designer\Perspex.Designer.csproj", "{EC42600F-049B-43FF-AED1-8314D61B2749}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -212,6 +214,10 @@ Global {6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Debug|Any CPU.Build.0 = Debug|Any CPU {6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|Any CPU.ActiveCfg = Release|Any CPU {6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|Any CPU.Build.0 = Release|Any CPU + {EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC42600F-049B-43FF-AED1-8314D61B2749}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC42600F-049B-43FF-AED1-8314D61B2749}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC42600F-049B-43FF-AED1-8314D61B2749}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -234,5 +240,6 @@ Global {78CAFE33-DBEB-4132-8A28-81CFE8A4933C} = {9B9E3891-2366-4253-A952-D08BCEB71098} {54F237D5-A70A-4752-9656-0C70B1A7B047} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} {FB05AC90-89BA-4F2F-A924-F37875FB547C} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} + {EC42600F-049B-43FF-AED1-8314D61B2749} = {B39A8919-9F95-48FE-AD7B-76E08B509888} EndGlobalSection EndGlobal diff --git a/src/Windows/Perspex.Designer/App.config b/src/Windows/Perspex.Designer/App.config new file mode 100644 index 0000000000..d1428ad712 --- /dev/null +++ b/src/Windows/Perspex.Designer/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Windows/Perspex.Designer/App.xaml b/src/Windows/Perspex.Designer/App.xaml new file mode 100644 index 0000000000..2c853d351e --- /dev/null +++ b/src/Windows/Perspex.Designer/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/src/Windows/Perspex.Designer/App.xaml.cs b/src/Windows/Perspex.Designer/App.xaml.cs new file mode 100644 index 0000000000..929a440192 --- /dev/null +++ b/src/Windows/Perspex.Designer/App.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Perspex.Designer +{ + /// + /// Interaction logic for App.xaml + /// + internal partial class App + { + public App() + { + InitializeComponent(); + } + } +} diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs new file mode 100644 index 0000000000..88861628a6 --- /dev/null +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using Perspex.Designer.Comm; + +namespace Perspex.Designer.AppHost +{ + class PerspexAppHost + { + private string _appDir; + private CommChannel _comm; + private Func _xamlReader; + private WindowHost _host; + + public PerspexAppHost(CommChannel channel) + { + _comm = channel; + } + + public void Start() + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + _comm.OnMessage += Channel_OnMessage; + _comm.Start(); + } + + private void Channel_OnMessage(object obj) + { + var init = obj as InitMessage; + if (init != null) + { + Init(init.TargetExe); + } + var updateXaml = obj as UpdateXamlMessage; + if (updateXaml != null) + UpdateXaml(updateXaml.Xaml); + } + + private void UpdateXaml(string xaml) + { + dynamic window = Activator.CreateInstance(LookupType("Perspex.Controls.Window")); + _xamlReader(new MemoryStream(Encoding.UTF8.GetBytes(xaml)), window); + var hWnd = (IntPtr) window.PlatformImpl.Handle; + _host.SetWindow(hWnd); + window.Show(); + } + + void UpdateState(string state) + { + _comm.SendMessage(new StateMessage(state)); + } + + Type LookupType(string name) + { + var asms = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var asm in asms) + { + var found = asm.GetType(name, false, true); + if (found != null) + return found; + } + throw new TypeLoadException("Unable to find type " + name); + } + + private MethodInfo LookupStaticMethod(string typeName, string method) + { + var type = LookupType(typeName); + var methods = type.GetMethods(); + return methods.First(m => m.Name == method); + } + + private void Init(string targetExe) + { + _appDir = Path.GetFullPath(Path.GetDirectoryName(targetExe)); + Directory.SetCurrentDirectory(_appDir); + UpdateState("Loading assemblies"); + foreach(var asm in Directory.GetFiles(_appDir).Where(f=>f.ToLower().EndsWith(".dll")||f.ToLower().EndsWith(".exe"))) + try + { + Assembly.LoadFrom(asm); + } + catch { } + UpdateState("Looking up Perspex types"); + + var app = Activator.CreateInstance(LookupType("Perspex.Application")); + app.GetType() + .GetMethod("RegisterServices", BindingFlags.NonPublic | BindingFlags.Instance) + .Invoke(app, null); + app.GetType().GetProperty("Styles").GetSetMethod(true) + .Invoke(app, new[] {Activator.CreateInstance(LookupType("Perspex.Themes.Default.DefaultTheme"))}); + + + LookupStaticMethod("Perspex.Direct2D1.Direct2D1Platform", "Initialize").Invoke(null, null); + LookupStaticMethod("Perspex.Win32.Win32Platform", "InitializeEmbedded").Invoke(null, null); + + + var xamlFactory = Activator.CreateInstance(LookupType("Perspex.Markup.Xaml.Context.PerspexParserFactory")); + + dynamic xamlLoader = + LookupType("OmniXaml.XamlLoader").GetConstructors().First().Invoke(new object[] {xamlFactory}); + + _xamlReader = (stream, root) => xamlLoader.Load(stream, root); + _host = new WindowHost(); + _comm.SendMessage(new WindowCreatedMessage(_host.Handle)); + } + + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + string assemblyPath = Path.Combine(_appDir, new AssemblyName(args.Name).Name + ".dll"); + if (File.Exists(assemblyPath) == false) return null; + Assembly assembly = Assembly.LoadFrom(assemblyPath); + return assembly; + } + } +} diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs new file mode 100644 index 0000000000..90daca18e4 --- /dev/null +++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Perspex.Designer.AppHost +{ + class WindowHost : Control + { + public WindowHost() + { + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + } + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.FillRectangle(Brushes.AliceBlue, ClientRectangle); + } + + private IntPtr _hWnd; + + public void SetWindow(IntPtr hWnd) + { + _hWnd = hWnd; + WinApi.SetParent(hWnd, Handle); + FixWindow(); + } + + void FixWindow() + { + WinApi.MoveWindow(_hWnd, 0, 0, Width, Height, true); + } + + protected override void OnResize(EventArgs e) + { + FixWindow(); + base.OnResize(e); + } + } +} diff --git a/src/Windows/Perspex.Designer/Comm/CommChannel.cs b/src/Windows/Perspex.Designer/Comm/CommChannel.cs new file mode 100644 index 0000000000..a408298db2 --- /dev/null +++ b/src/Windows/Perspex.Designer/Comm/CommChannel.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Threading; + +namespace Perspex.Designer.Comm +{ + internal class CommChannel : IDisposable + { + private readonly BinaryReader _input; + private readonly BinaryWriter _output; + private SynchronizationContext _dispatcher; + TaskCompletionSource _terminating = new TaskCompletionSource(); + private BlockingCollection _outputQueue = new BlockingCollection(); + public event Action OnMessage; + public event Action Disposed; + public CommChannel(Stream input, Stream output) + { + _input = new BinaryReader(input); + _output = new BinaryWriter(output); + _dispatcher = SynchronizationContext.Current; + + } + + public void Start() + { + new Thread(WriterThread) { IsBackground = true }.Start(); + new Thread(ReaderThread) { IsBackground = true }.Start(); + } + + public void SendMessage(object message) + { + var fmt = new BinaryFormatter(); + var ms = new MemoryStream(); + fmt.Serialize(ms, message); + _outputQueue.Add(ms.ToArray()); + } + + void WriterThread() + { + while (!_terminating.Task.IsCompleted) + { + var item = _outputQueue.Take(); + if(_terminating.Task.IsCompleted) + return; + try + { + _output.Write(item.Length); + _output.Write(item); + _output.Flush(); + } + catch (Exception e) + { + Dispose(); + } + } + } + + private async Task ReadAsyncOrNull(int count) + { + var readTask = Task.Factory.StartNew(() => _input.ReadBytes(count)); + await Task.WhenAny(readTask, _terminating.Task); + if (_terminating.Task.IsCompleted) + return null; + return await readTask; + } + + async void ReaderThread() + { + var fmt = new BinaryFormatter(); + while (!_terminating.Task.IsCompleted) + { + try + { + var lenb = await ReadAsyncOrNull(4); + if (lenb == null) + return; + var data = await ReadAsyncOrNull(BitConverter.ToInt32(lenb, 0)); + if (data == null) + return; + var message = fmt.Deserialize(new MemoryStream(data)); + _dispatcher.Post(_ => OnMessage?.Invoke(message), null); + } + catch + { + Dispose(); + return; + } + } + + } + + public void Dispose() + { + if(_terminating.Task.IsCompleted) + return; + _terminating.TrySetResult(true); + _outputQueue.Add(null); + Disposed?.Invoke(); + } + } +} diff --git a/src/Windows/Perspex.Designer/Comm/InitMessage.cs b/src/Windows/Perspex.Designer/Comm/InitMessage.cs new file mode 100644 index 0000000000..71a562d9e5 --- /dev/null +++ b/src/Windows/Perspex.Designer/Comm/InitMessage.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Designer.Comm +{ + [Serializable] + class InitMessage : UpdateXamlMessage + { + public string TargetExe { get; private set; } + public InitMessage(string targetExe, string xaml) : base(xaml) + { + TargetExe = targetExe; + } + } +} diff --git a/src/Windows/Perspex.Designer/Comm/ProcessHost.cs b/src/Windows/Perspex.Designer/Comm/ProcessHost.cs new file mode 100644 index 0000000000..4b61d62b18 --- /dev/null +++ b/src/Windows/Perspex.Designer/Comm/ProcessHost.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Threading; + +namespace Perspex.Designer.Comm +{ + class ProcessHost : INotifyPropertyChanged + { + private CommChannel _comm; + private string _state; + public string State + { + get { return _state; } + private set + { + if (_state == value) + return; + _state = value; + OnPropertyChanged(); + } + } + + private bool _isAlive; + private readonly SynchronizationContext _dispatcher; + private Process _proc; + + public bool IsAlive + { + get { return _isAlive; } + set + { + if (_isAlive == value) + return; + _isAlive = value; + OnPropertyChanged(); + } + } + + private IntPtr _windowHandle; + public IntPtr WindowHandle + { + get { return _windowHandle; } + set + { + if (_windowHandle == value) + return; + _windowHandle = value; + OnPropertyChanged(); + } + } + + public ProcessHost() + { + _dispatcher = SynchronizationContext.Current; + } + + void OnExited(object sender, EventArgs eventArgs) + { + _comm.Dispose(); + _dispatcher.Post(_ => + { + IsAlive = false; + WindowHandle = IntPtr.Zero; + State = "Designer process crashed"; + }, null); + } + + public void Start(string targetExe, string initialXaml) + { + if (_proc != null) + { + _proc.Exited -= OnExited; + _proc.Kill(); + OnExited(null, null); + State = "Restarting..."; + } + + var msg = new InitMessage(Path.GetFullPath(targetExe), initialXaml); + var exe = typeof (ProcessHost).Assembly.GetModules()[0].FullyQualifiedName; + _proc = new Process() + { + StartInfo = new ProcessStartInfo(exe) + { + UseShellExecute = false, + RedirectStandardInput = true, + RedirectStandardOutput = true + }, + EnableRaisingEvents = true + }; + _proc.Exited += OnExited; + try + { + _proc.Start(); + State = "Launching designer process..."; + _comm = new CommChannel(_proc.StandardOutput.BaseStream, _proc.StandardInput.BaseStream); + _comm.OnMessage += OnMessage; + _comm.Start(); + _comm.SendMessage(msg); + return; + } + catch (Exception e) + { + State = e.ToString(); + } + IsAlive = true; + + } + + public void UpdateXaml(string xaml) + { + _comm?.SendMessage(new UpdateXamlMessage(xaml)); + } + + private void OnMessage(object obj) + { + var stateMessage = obj as StateMessage; + if (stateMessage != null) + State = stateMessage.State; + var windowMessage = obj as WindowCreatedMessage; + if (windowMessage != null) + WindowHandle = windowMessage.Handle; + } + + public void Kill() + { + try + { + _proc.Kill(); + _proc = null; + } + catch + { + // + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Windows/Perspex.Designer/Comm/StateMessage.cs b/src/Windows/Perspex.Designer/Comm/StateMessage.cs new file mode 100644 index 0000000000..1c844bddfa --- /dev/null +++ b/src/Windows/Perspex.Designer/Comm/StateMessage.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Designer.Comm +{ + [Serializable] + class StateMessage + { + public StateMessage(string state) + { + State = state; + } + + public string State { get; private set; } + } +} diff --git a/src/Windows/Perspex.Designer/Comm/UpdateXamlMessage.cs b/src/Windows/Perspex.Designer/Comm/UpdateXamlMessage.cs new file mode 100644 index 0000000000..eb0fc27dd9 --- /dev/null +++ b/src/Windows/Perspex.Designer/Comm/UpdateXamlMessage.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Designer.Comm +{ + [Serializable] + class UpdateXamlMessage + { + public UpdateXamlMessage(string xaml) + { + Xaml = xaml; + } + + public string Xaml { get; private set; } + } +} diff --git a/src/Windows/Perspex.Designer/Comm/WindowCreatedMessage.cs b/src/Windows/Perspex.Designer/Comm/WindowCreatedMessage.cs new file mode 100644 index 0000000000..e8570cee9f --- /dev/null +++ b/src/Windows/Perspex.Designer/Comm/WindowCreatedMessage.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Designer.Comm +{ + [Serializable] + class WindowCreatedMessage + { + public WindowCreatedMessage(IntPtr handle) + { + Handle = handle; + } + + public IntPtr Handle { get; private set; } + } +} diff --git a/src/Windows/Perspex.Designer/DemoWindow.xaml b/src/Windows/Perspex.Designer/DemoWindow.xaml new file mode 100644 index 0000000000..69cfe8cefe --- /dev/null +++ b/src/Windows/Perspex.Designer/DemoWindow.xaml @@ -0,0 +1,23 @@ + + + + Target Exe: + + + + + + + + + + diff --git a/src/Windows/Perspex.Designer/DemoWindow.xaml.cs b/src/Windows/Perspex.Designer/DemoWindow.xaml.cs new file mode 100644 index 0000000000..102bf4a412 --- /dev/null +++ b/src/Windows/Perspex.Designer/DemoWindow.xaml.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Perspex.Designer +{ + + internal partial class DemoWindow + { + public DemoWindow() + { + InitializeComponent(); + } + + public DemoWindow(string targetExe, string targetPath) : this() + { + TargetExe.Text = targetExe; + if (targetExe != null) + Xaml.Text = File.ReadAllText(targetPath); + } + } +} diff --git a/src/Windows/Perspex.Designer/DesignerAppHost.cs b/src/Windows/Perspex.Designer/DesignerAppHost.cs new file mode 100644 index 0000000000..8ee5819321 --- /dev/null +++ b/src/Windows/Perspex.Designer/DesignerAppHost.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Designer +{ + class DesignerAppHost + { + + } +} diff --git a/src/Windows/Perspex.Designer/Perspex.Designer.csproj b/src/Windows/Perspex.Designer/Perspex.Designer.csproj new file mode 100644 index 0000000000..f415b0af8a --- /dev/null +++ b/src/Windows/Perspex.Designer/Perspex.Designer.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {EC42600F-049B-43FF-AED1-8314D61B2749} + Exe + Properties + Perspex.Designer + Perspex.Designer + v4.5 + 512 + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + App.xaml + + + + Component + + + + + + + + + DemoWindow.xaml + + + + PerspexDesigner.xaml + + + + + + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + + + + \ No newline at end of file diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml b/src/Windows/Perspex.Designer/PerspexDesigner.xaml new file mode 100644 index 0000000000..87fff98123 --- /dev/null +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs new file mode 100644 index 0000000000..ff9557a23b --- /dev/null +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Forms.Integration; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Perspex.Designer.AppHost; +using Perspex.Designer.Comm; + +namespace Perspex.Designer +{ + /// + /// Interaction logic for PerpexDesigner.xaml + /// + public partial class PerspexDesigner + { + public static readonly DependencyProperty TargetExeProperty = DependencyProperty.Register( + "TargetExe", typeof (string), typeof (PerspexDesigner), new FrameworkPropertyMetadata(TargetExeChanged)); + + private static void TargetExeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((PerspexDesigner) d).OnTargetExeChanged(); + } + public string TargetExe + { + get { return (string) GetValue(TargetExeProperty); } + set { SetValue(TargetExeProperty, value); } + } + + public static readonly DependencyProperty XamlProperty = DependencyProperty.Register( + "Xaml", typeof (string), typeof (PerspexDesigner), new FrameworkPropertyMetadata(XamlChanged)); + + private static void XamlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((PerspexDesigner) d).OnXamlChanged(); + } + + public string Xaml + { + get { return (string) GetValue(XamlProperty); } + set { SetValue(XamlProperty, value); } + } + + private readonly ProcessHost _host = new ProcessHost(); + + + public PerspexDesigner() + { + InitializeComponent(); + BindingOperations.SetBinding(State, TextBlock.TextProperty, + new Binding(nameof(ProcessHost.State)) {Source = _host}); + + _host.PropertyChanged += _host_PropertyChanged; + } + + private void _host_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ProcessHost.WindowHandle) && _host.WindowHandle != IntPtr.Zero) + { + var host = new WindowHost(); + host.SetWindow(_host.WindowHandle); + NativeContainer.Content = new WindowsFormsHost() {Child = host}; + } + } + + public void KillProcess() + { + _host.Kill(); + } + + private void OnTargetExeChanged() + { + if(string.IsNullOrEmpty(Xaml)) + return; + _host.Start(TargetExe, Xaml); + } + + private void OnXamlChanged() + { + if (!_host.IsAlive) + _host.Start(TargetExe, Xaml); + else + _host.UpdateXaml(Xaml); + } + + } +} diff --git a/src/Windows/Perspex.Designer/Program.cs b/src/Windows/Perspex.Designer/Program.cs new file mode 100644 index 0000000000..3a4191f6f7 --- /dev/null +++ b/src/Windows/Perspex.Designer/Program.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Windows.Threading; +using Perspex.Designer.AppHost; +using Perspex.Designer.Comm; + +namespace Perspex.Designer +{ + class Program + { + [STAThread] + static void Main(string[] args) + { + if (!Console.IsInputRedirected || !Console.IsOutputRedirected) + DemoMain(args); + else + HostedMain(); + + + } + + static void HostedMain() + { + //Initialize sync context + SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); + var comm = new CommChannel(Console.OpenStandardInput(), Console.OpenStandardOutput()); + comm.Disposed += () => Process.GetCurrentProcess().Kill(); + var service = new PerspexAppHost(comm); + service.Start(); + Application.Run(); + } + + private static void DemoMain(string[] args) + { + SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); + var app = new App(); + const string targetExe = "--exe="; + const string xaml = "--xaml="; + + app.Run(new DemoWindow( + args.Where(a => a.StartsWith(targetExe)).Select(a => a.Substring(targetExe.Length)).FirstOrDefault(), + args.Where(a => a.StartsWith(xaml)).Select(a => a.Substring(xaml.Length)).FirstOrDefault())); + } + } +} diff --git a/src/Windows/Perspex.Designer/Properties/AssemblyInfo.cs b/src/Windows/Perspex.Designer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8a85c81a30 --- /dev/null +++ b/src/Windows/Perspex.Designer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Designer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Designer")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ec42600f-049b-43ff-aed1-8314d61b2749")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Windows/Perspex.Designer/README.txt b/src/Windows/Perspex.Designer/README.txt new file mode 100644 index 0000000000..565bfa965b --- /dev/null +++ b/src/Windows/Perspex.Designer/README.txt @@ -0,0 +1,3 @@ +To load data from XamlTestApplication run with: + +--exe=..\..\..\..\..\samples\XamlTestApplication\bin\Debug --xaml=..\..\..\..\..\samples\XamlTestApplication\Views\MainWindow.xaml \ No newline at end of file diff --git a/src/Windows/Perspex.Designer/WinApi.cs b/src/Windows/Perspex.Designer/WinApi.cs new file mode 100644 index 0000000000..f9cd76e85b --- /dev/null +++ b/src/Windows/Perspex.Designer/WinApi.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Designer +{ + class WinApi + { + [DllImport("user32.dll", SetLastError = true)] + public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); + [DllImport("user32.dll")] + public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent); + } +}