diff --git a/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs new file mode 100644 index 0000000000..649b5d48ee --- /dev/null +++ b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Perspex.Designer.AppHost +{ + public class HostedAppModel : INotifyPropertyChanged + { + private IntPtr _nativeWindowHandle; + private string _error; + private string _errorDetails; + + public IntPtr NativeWindowHandle + { + get { return _nativeWindowHandle; } + set + { + if (value.Equals(_nativeWindowHandle)) return; + _nativeWindowHandle = value; + OnPropertyChanged(); + } + } + + public string Error + { + get { return _error; } + private set + { + if (value == _error) return; + _error = value; + OnPropertyChanged(); + } + } + + public string ErrorDetails + { + get { return _errorDetails; } + private set + { + if (value == _errorDetails) return; + _errorDetails = value; + OnPropertyChanged(); + } + } + + public void SetError(string error, string details = null) + { + Error = error; + ErrorDetails = details; + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs index 32f005fd26..54fab7d205 100644 --- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -5,10 +5,11 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading; -using System.Threading.Tasks; -using System.Windows.Data; using System.Windows.Forms; +using System.Windows.Forms.Integration; +using System.Xml; using Perspex.Designer.Comm; +using Perspex.Designer.InProcDesigner; using Perspex.Designer.Metadata; using Timer = System.Windows.Forms.Timer; @@ -21,8 +22,9 @@ namespace Perspex.Designer.AppHost private string _lastXaml; private string _currentXaml; private Func _xamlReader; - private WindowHost _host; private bool _initSuccess; + private HostedAppModel _appModel = new HostedAppModel(); + private Control _window; public PerspexAppHost(CommChannel channel) { @@ -222,17 +224,47 @@ namespace Perspex.Designer.AppHost LookupType("OmniXaml.XamlLoader").GetConstructors().First().Invoke(new object[] {xamlFactory}); _xamlReader = (stream) => xamlLoader.Load(stream); - _host = new WindowHost(); + + _window = new Control + { + Controls = + { + new ElementHost() + { + Child = new InProcDesignerView(_appModel), + Dock = DockStyle.Fill + } + } + }; + _window.CreateControl(); + new Timer {Interval = 10, Enabled = true}.Tick += delegate { dispatcher.RunJobs(); }; new Timer {Interval = 200, Enabled = true}.Tick += delegate { XamlUpdater(); }; - _comm.SendMessage(new WindowCreatedMessage(_host.Handle)); + _comm.SendMessage(new WindowCreatedMessage(_window.Handle)); _initSuccess = true; } + bool ValidateXml(string xml) + { + try + { + var rdr = new XmlTextReader(new StringReader(xml)); + while (rdr.Read()) + { + + } + } + catch + { + return false; + } + return true; + } + void XamlUpdater() { if (!_initSuccess) @@ -240,6 +272,14 @@ namespace Perspex.Designer.AppHost if(_lastXaml == _currentXaml) return; _lastXaml = _currentXaml; + + if (!ValidateXml(_currentXaml)) + { + _appModel.SetError("Invalid markup"); + return; + } + + try { const string windowType = "Perspex.Controls.Window"; @@ -254,13 +294,13 @@ namespace Perspex.Designer.AppHost var hWnd = (IntPtr)window.PlatformImpl.Handle; - _host.SetWindow(hWnd); + _appModel.NativeWindowHandle = hWnd; window.Show(); + _appModel.SetError(null); } catch (Exception e) { - _host.SetWindow(IntPtr.Zero); - _host.PlaceholderText = e.ToString(); + _appModel.SetError("XAML load error", e.ToString()); } } diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs index 260cb96ff7..1c377b6404 100644 --- a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs @@ -9,29 +9,58 @@ using System.Windows.Forms; namespace Perspex.Designer.AppHost { - class WindowHost : Control + class WindowHost : UserControl { public WindowHost() { - BackColor = Color.AliceBlue; - _textBox = new TextBox {Dock = DockStyle.Fill, ReadOnly = true, Multiline = true, Visible = false}; - Controls.Add(_textBox); + AutoScroll = true; + VerticalScroll.Enabled = true; + HorizontalScroll.Enabled = true; + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + Text = "ScrollableArea"; + Controls.Add(_windowHost); + _windowHost.Anchor = AnchorStyles.None; + _timer.Tick += delegate { FixWindow(); }; } - + + private Control _windowHost = new Control() {Text = "WindowWrapper"}; + private Timer _timer = new Timer {Enabled = true, Interval = 50}; private IntPtr _hWnd; - private TextBox _textBox; + private int _desiredWidth; + private int _desiredHeight; + + private const int WM_HSCROLL = 0x114; + private const int WM_VSCROLL = 0x115; - public string PlaceholderText + protected override void WndProc(ref Message m) { - get { return _textBox.Text; } - set + if ((m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL) + && (((int) m.WParam & 0xFFFF) == 5)) { - _textBox.Text = value; - FixWindow(); + // Change SB_THUMBTRACK to SB_THUMBPOSITION + m.WParam = (IntPtr) (((int) m.WParam & ~0xFFFF) | 4); } + base.WndProc(ref m); } - + void FixPosition() + { + AutoScrollMinSize = new Size(_desiredWidth, _desiredHeight); + var width = Width - AutoScrollMargin.Width; + var height = Height - AutoScrollMargin.Height; + var x = Math.Max(0, (width - _windowHost.Width)/2); + var y = Math.Max(0, (height - _windowHost.Height)/2); + _windowHost.Location = new Point(x, y); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _timer.Dispose(); + } + base.Dispose(disposing); + } public void SetWindow(IntPtr hWnd) { @@ -40,21 +69,34 @@ namespace Perspex.Designer.AppHost _hWnd = hWnd; if (_hWnd != IntPtr.Zero) { - WinApi.SetParent(hWnd, Handle); + WinApi.SetParent(hWnd, _windowHost.Handle); FixWindow(); } } - + void FixWindow() { if (_hWnd != IntPtr.Zero) - WinApi.MoveWindow(_hWnd, 0, 0, Width, Height, true); - _textBox.Visible = _hWnd == IntPtr.Zero && !string.IsNullOrWhiteSpace(_textBox.Text); + { + WinApi.RECT rc; + WinApi.GetWindowRect(_hWnd, out rc); + _desiredWidth = rc.Right - rc.Left; + _desiredHeight = rc.Bottom - rc.Top; + var pt = _windowHost.PointToClient(new Point(rc.Left, rc.Top)); + + if (pt.Y == 0 && pt.X == 0 && _desiredWidth == _windowHost.Width && _desiredHeight == _windowHost.Height) + return; + _windowHost.Width = _desiredWidth; + _windowHost.Height = _desiredHeight; + WinApi.MoveWindow(_hWnd, 0, 0, _desiredWidth, _desiredHeight, true); + FixPosition(); + + } } protected override void OnResize(EventArgs e) { - FixWindow(); + FixPosition(); base.OnResize(e); } } diff --git a/src/Windows/Perspex.Designer/Comm/ProcessHost.cs b/src/Windows/Perspex.Designer/Comm/ProcessHost.cs index c850521af8..e2e93a149c 100644 --- a/src/Windows/Perspex.Designer/Comm/ProcessHost.cs +++ b/src/Windows/Perspex.Designer/Comm/ProcessHost.cs @@ -152,7 +152,7 @@ namespace Perspex.Designer.Comm { try { - _proc.Kill(); + _proc?.Kill(); _proc = null; } catch diff --git a/src/Windows/Perspex.Designer/DesignerAppHost.cs b/src/Windows/Perspex.Designer/DesignerAppHost.cs deleted file mode 100644 index 8ee5819321..0000000000 --- a/src/Windows/Perspex.Designer/DesignerAppHost.cs +++ /dev/null @@ -1,13 +0,0 @@ -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/InProcDesigner/InProcDesignerView.xaml b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml new file mode 100644 index 0000000000..38e09688cb --- /dev/null +++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml @@ -0,0 +1,20 @@ + + + + + + + (View details) + + + + + + diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs new file mode 100644 index 0000000000..801b088730 --- /dev/null +++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs @@ -0,0 +1,84 @@ +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; +using Perspex.Designer.AppHost; + +namespace Perspex.Designer.InProcDesigner +{ + /// + /// Interaction logic for InProcDesignerView.xaml + /// + public partial class InProcDesignerView : UserControl + { + private readonly HostedAppModel _appModel; + private readonly WindowHost _host; + + public InProcDesignerView(HostedAppModel appModel) + { + _appModel = appModel; + InitializeComponent(); + DataContext = _appModel; + _appModel.PropertyChanged += ModelPropertyChanged; + WindowHostControl.Child = _host = new WindowHost(); + HandleVisibility(); + HandleWindow(); + + } + + private void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(HostedAppModel.Error) || e.PropertyName == nameof(HostedAppModel.ErrorDetails)) + HandleVisibility(); + if (e.PropertyName == nameof(HostedAppModel.NativeWindowHandle)) + HandleWindow(); + } + + private void HandleWindow() + { + _host.SetWindow(_appModel.NativeWindowHandle); + } + + private void HandleVisibility() + { + ErrorPanel.Visibility = string.IsNullOrEmpty(_appModel.Error) + ? Visibility.Collapsed + : Visibility.Visible; + DetailsBlock.Visibility = string.IsNullOrWhiteSpace(_appModel.ErrorDetails) + ? Visibility.Collapsed + : Visibility.Visible; + } + + + + public InProcDesignerView() + { + + } + + private void Hyperlink_OnClick(object sender, RoutedEventArgs e) + { + var wnd = new Window() + { + Content = new TextBox() + { + IsReadOnly = true, + AcceptsReturn = true, + TextWrapping = TextWrapping.Wrap, + Text = _appModel.ErrorDetails + } + }; + wnd.ShowDialog(); + } + } +} diff --git a/src/Windows/Perspex.Designer/Perspex.Designer.csproj b/src/Windows/Perspex.Designer/Perspex.Designer.csproj index 4cc78fd9be..f5dd8bd8ac 100644 --- a/src/Windows/Perspex.Designer/Perspex.Designer.csproj +++ b/src/Windows/Perspex.Designer/Perspex.Designer.csproj @@ -46,6 +46,10 @@ + + ..\..\..\packages\JetBrains.Annotations.9.2.0\lib\net20\JetBrains.Annotations.dll + True + @@ -66,9 +70,10 @@ App.xaml + - Component + UserControl @@ -80,7 +85,9 @@ DemoWindow.xaml - + + InProcDesignerView.xaml + PerspexDesigner.xaml @@ -91,6 +98,7 @@ + @@ -102,6 +110,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml b/src/Windows/Perspex.Designer/PerspexDesigner.xaml index fa25a9586c..673b4ca52c 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml @@ -10,5 +10,8 @@ + + + diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs index 45ece63e89..f5f6d85b0d 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs @@ -80,13 +80,33 @@ namespace Perspex.Designer } if (_host.WindowHandle != IntPtr.Zero) { - var host = new WindowHost(); - host.SetWindow(_host.WindowHandle); - NativeContainer.Content = new WindowsFormsHost() {Child = host}; + var host = new NativeWindowHost(_host.WindowHandle); + NativeContainer.Content = host; } } } + class NativeWindowHost :HwndHost + { + private readonly IntPtr _hWnd; + + public NativeWindowHost(IntPtr hWnd) + { + _hWnd = hWnd; + } + + protected override HandleRef BuildWindowCore(HandleRef hwndParent) + { + WinApi.SetParent(_hWnd, hwndParent.Handle); + return new HandleRef(this, _hWnd); + } + + protected override void DestroyWindowCore(HandleRef hwnd) + { + WinApi.SendMessage(hwnd.Handle, WinApi.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + } + } + public void KillProcess() { _host.Kill(); diff --git a/src/Windows/Perspex.Designer/WinApi.cs b/src/Windows/Perspex.Designer/WinApi.cs index 04e9dbed29..c751254945 100644 --- a/src/Windows/Perspex.Designer/WinApi.cs +++ b/src/Windows/Perspex.Designer/WinApi.cs @@ -18,5 +18,16 @@ namespace Perspex.Designer public const uint WM_CLOSE = 0x0010; + [DllImport("user32.dll", SetLastError = true)] + public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; // x position of upper-left corner + public int Top; // y position of upper-left corner + public int Right; // x position of lower-right corner + public int Bottom; // y position of lower-right corner + } } } diff --git a/src/Windows/Perspex.Designer/packages.config b/src/Windows/Perspex.Designer/packages.config new file mode 100644 index 0000000000..5796357235 --- /dev/null +++ b/src/Windows/Perspex.Designer/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file