diff --git a/nuget/build.ps1 b/nuget/build.ps1 index a26fe74706..2663012664 100644 --- a/nuget/build.ps1 +++ b/nuget/build.ps1 @@ -1 +1 @@ -.\build-version.ps1 0.1.1-alpha2 \ No newline at end of file +.\build-version.ps1 0.2.0-alpha3 \ No newline at end of file diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index 62914fdc13..0ba9e86d67 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -79,6 +79,10 @@ MainWindow.paml + + TestUserControl.paml + + @@ -89,6 +93,7 @@ MSBuild:Compile + diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index 903d1f1050..c782786e42 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -1,5 +1,6 @@  + xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest" + xmlns:local="clr-namespace:BindingTest;assembly=BindingTest"> @@ -55,5 +56,10 @@ + + + \ No newline at end of file diff --git a/samples/BindingTest/TestUserControl.paml b/samples/BindingTest/TestUserControl.paml new file mode 100644 index 0000000000..2c5609f8c9 --- /dev/null +++ b/samples/BindingTest/TestUserControl.paml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/samples/BindingTest/TestUserControl.paml.cs b/samples/BindingTest/TestUserControl.paml.cs new file mode 100644 index 0000000000..1cf4342f19 --- /dev/null +++ b/samples/BindingTest/TestUserControl.paml.cs @@ -0,0 +1,18 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace BindingTest +{ + public class TestUserControl : UserControl + { + public TestUserControl() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index 057666d5c4..650f69bc36 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -32,6 +32,7 @@ namespace BindingTest.ViewModels public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } public ReactiveCommand ShuffleItems { get; } + public TestUserControlViewModel UserControl { get; } = new TestUserControlViewModel(); public string BooleanString { diff --git a/samples/BindingTest/ViewModels/TestUserControlViewModel.cs b/samples/BindingTest/ViewModels/TestUserControlViewModel.cs new file mode 100644 index 0000000000..9e5879e1c1 --- /dev/null +++ b/samples/BindingTest/ViewModels/TestUserControlViewModel.cs @@ -0,0 +1,9 @@ +using ReactiveUI; + +namespace BindingTest.ViewModels +{ + public class TestUserControlViewModel : ReactiveObject + { + public string Content { get; } = "User Control Content"; + } +} diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index b8ac6c8ac3..b220fb4e46 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -214,22 +214,28 @@ namespace Perspex.Markup.Xaml.Data { Contract.Requires(target != null); - var dataContextHost = targetIsDataContext ? - target.InheritanceParent as IObservablePropertyBag : target; - - if (dataContextHost != null) + if (!targetIsDataContext) { var result = new ExpressionObserver( - () => dataContextHost.GetValue(Control.DataContextProperty), + () => target.GetValue(Control.DataContextProperty), path); - dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x => + + /// TODO: Instead of doing this, make the ExpressionObserver accept an "update" + /// observable as doing it this way can will cause a leak in Binding as this + /// observable is never unsubscribed. + target.GetObservable(Control.DataContextProperty).Subscribe(x => result.UpdateRoot()); + return result; } else { - throw new InvalidOperationException( - "Cannot bind to DataContext of object with no parent."); + return new ExpressionObserver( + target.GetObservable(Visual.VisualParentProperty) + .OfType() + .Select(x => x.GetObservable(Control.DataContextProperty)) + .Switch(), + path); } } diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 3e3b46ba66..f2673838c0 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 3e3b46ba66941da925092e2977003d0553cfc907 +Subproject commit f2673838c0422ff0f6fdb3e4b34a5302971f59b5 diff --git a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs index 81c670ba3e..e8df013d50 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs @@ -172,6 +172,8 @@ namespace Perspex.Markup.Data /// /// Causes the root object to be re-read from the root getter. /// + /// TODO: Instead of doing this, make the object accept an "update" observable + /// as doing it this way can cause a leak in Binding. public void UpdateRoot() { if (_count > 0 && _rootGetter != null) diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index ac82076637..65aa4ae4f9 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -98,48 +98,52 @@ namespace Perspex.Controls.Presenters /// True if the scroll offset was changed; otherwise false. public bool BringDescendentIntoView(IVisual target, Rect targetRect) { - if (Child != null) + if (Child == null) { - var transform = target.TransformToVisual(Child); - var rect = targetRect * transform; - var offset = Offset; - var result = false; + return false; + } - if (rect.Bottom > offset.Y + Viewport.Height) - { - offset = offset.WithY((rect.Bottom - Viewport.Height) + Child.Margin.Top); - result = true; - } + var transform = target.TransformToVisual(Child); - if (rect.Y < offset.Y) - { - offset = offset.WithY(rect.Y); - result = true; - } + if (transform == null) + { + return false; + } - if (rect.Right > offset.X + Viewport.Width) - { - offset = offset.WithX((rect.Right - Viewport.Width) + Child.Margin.Left); - result = true; - } + var rect = targetRect * transform.Value; + var offset = Offset; + var result = false; - if (rect.X < offset.X) - { - offset = offset.WithX(rect.X); - result = true; - } + if (rect.Bottom > offset.Y + Viewport.Height) + { + offset = offset.WithY((rect.Bottom - Viewport.Height) + Child.Margin.Top); + result = true; + } - if (result) - { - Offset = offset; - } + if (rect.Y < offset.Y) + { + offset = offset.WithY(rect.Y); + result = true; + } - return result; + if (rect.Right > offset.X + Viewport.Width) + { + offset = offset.WithX((rect.Right - Viewport.Width) + Child.Margin.Left); + result = true; } - else + + if (rect.X < offset.X) { - return false; + offset = offset.WithX(rect.X); + result = true; + } + + if (result) + { + Offset = offset; } + + return result; } /// diff --git a/src/Perspex.SceneGraph/IVisual.cs b/src/Perspex.SceneGraph/IVisual.cs index 3acb3ac27c..de14703144 100644 --- a/src/Perspex.SceneGraph/IVisual.cs +++ b/src/Perspex.SceneGraph/IVisual.cs @@ -97,7 +97,10 @@ namespace Perspex /// of the specified . /// /// The visual to translate the coordinates to. - /// A containing the transform. - Matrix TransformToVisual(IVisual visual); + /// + /// A containing the transform or null if the visuals don't share a + /// common ancestor. + /// + Matrix? TransformToVisual(IVisual visual); } } diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 6779cd7269..597528d0a7 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -300,13 +300,22 @@ namespace Perspex /// of the specified . /// /// The visual to translate the coordinates to. - /// A containing the transform. - public Matrix TransformToVisual(IVisual visual) + /// + /// A containing the transform or null if the visuals don't share a + /// common ancestor. + /// + public Matrix? TransformToVisual(IVisual visual) { var common = this.FindCommonVisualAncestor(visual); - var thisOffset = GetOffsetFrom(common, this); - var thatOffset = GetOffsetFrom(common, visual); - return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); + + if (common != null) + { + var thisOffset = GetOffsetFrom(common, this); + var thatOffset = GetOffsetFrom(common, visual); + return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); + } + + return null; } /// diff --git a/src/Perspex.Themes.Default/DefaultTheme.paml b/src/Perspex.Themes.Default/DefaultTheme.paml index 534dad5a07..5902e568b3 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.paml +++ b/src/Perspex.Themes.Default/DefaultTheme.paml @@ -23,6 +23,7 @@ + diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj index cd60cda586..2ec676592d 100644 --- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -140,6 +140,9 @@ Designer + + Designer + Designer diff --git a/src/Perspex.Themes.Default/ToolTip.paml b/src/Perspex.Themes.Default/ToolTip.paml new file mode 100644 index 0000000000..ed59c08a70 --- /dev/null +++ b/src/Perspex.Themes.Default/ToolTip.paml @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs index 2ca7ad8c3a..e598e4cb6e 100644 --- a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs @@ -11,15 +11,22 @@ namespace Perspex.Designer.AppHost { class WindowHost : UserControl { - public WindowHost() + private readonly bool _supportScroll; + + public WindowHost(bool supportScroll) { - AutoScroll = true; - VerticalScroll.Enabled = true; - HorizontalScroll.Enabled = true; + _supportScroll = supportScroll; + if (_supportScroll) + { + AutoScroll = true; + VerticalScroll.Enabled = true; + HorizontalScroll.Enabled = true; + } SetStyle(ControlStyles.AllPaintingInWmPaint, true); - Text = "ScrollableArea"; Controls.Add(_windowHost); _windowHost.Anchor = AnchorStyles.None; + if (!supportScroll) + _windowHost.Visible = false; _timer.Tick += delegate { ReloadSettings(); @@ -93,7 +100,7 @@ namespace Perspex.Designer.AppHost _hWnd = hWnd; if (_hWnd != IntPtr.Zero) { - WinApi.SetParent(hWnd, _windowHost.Handle); + WinApi.SetParent(hWnd, _supportScroll ? _windowHost.Handle : Handle); FixWindow(); } } @@ -102,25 +109,34 @@ namespace Perspex.Designer.AppHost { if (_hWnd != IntPtr.Zero) { - 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 (_supportScroll) + { + 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)) + if ( + !(pt.Y == 0 && pt.X == 0 && _desiredWidth == _windowHost.Width && + _desiredHeight == _windowHost.Height)) + { + _windowHost.Width = _desiredWidth; + _windowHost.Height = _desiredHeight; + WinApi.MoveWindow(_hWnd, 0, 0, _desiredWidth, _desiredHeight, true); + } + FixPosition(); + } + else { - _windowHost.Width = _desiredWidth; - _windowHost.Height = _desiredHeight; - WinApi.MoveWindow(_hWnd, 0, 0, _desiredWidth, _desiredHeight, true); + WinApi.MoveWindow(_hWnd, 0, 0, Width, Height, true); } - FixPosition(); } } protected override void OnResize(EventArgs e) { - FixPosition(); + FixWindow(); base.OnResize(e); } } diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs index 9e617f1560..c936b2faa8 100644 --- a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs +++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs @@ -33,7 +33,7 @@ namespace Perspex.Designer.InProcDesigner InitializeComponent(); DataContext = _appModel; _appModel.PropertyChanged += ModelPropertyChanged; - WindowHostControl.Child = _host = new WindowHost(); + WindowHostControl.Child = _host = new WindowHost(true); HandleVisibility(); HandleWindow(); diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml b/src/Windows/Perspex.Designer/PerspexDesigner.xaml index a5a14d170e..96679be499 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml @@ -6,7 +6,7 @@ mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - + diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs index 2f944d7251..1b394a0f7d 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs @@ -73,41 +73,21 @@ namespace Perspex.Designer { if (e.PropertyName == nameof(ProcessHost.WindowHandle)) { - if (NativeContainer.Content != null) + if (NativeContainer.Child != null) { - var wndHost = ((HwndHost) NativeContainer.Content); - NativeContainer.Content = null; - wndHost?.Dispose(); + var child = NativeContainer.Child; + NativeContainer.Child = null; + child.Dispose(); } - if (_host.WindowHandle != IntPtr.Zero) - { - var host = new NativeWindowHost(_host.WindowHandle); - NativeContainer.Content = host; - } - } - } + NativeContainer.Child = new WindowHost(false); + var wndHost = ((WindowHost) NativeContainer.Child); + wndHost.SetWindow(_host.WindowHandle); - 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/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs index 02438baefd..95675fef4b 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -140,6 +140,27 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Assert.Equal("Bar", parent.Child.DataContext); } + [Fact] + public void DataContext_Binding_Should_Track_Parent() + { + var parent = new Decorator + { + DataContext = new { Foo = "foo" }, + }; + + var child = new Control(); + + var binding = new Binding + { + Path = "Foo", + }; + + binding.Bind(child, Control.DataContextProperty); + Assert.Null(child.DataContext); + parent.Child = child; + Assert.Equal("foo", child.DataContext); + } + [Fact] public void Should_Use_DefaultValueConverter_When_No_Converter_Specified() {