From a955f73a3e15303aac3b34e892c3963b1594a7af Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 19 Oct 2018 01:43:23 +0300 Subject: [PATCH 1/7] failing unittest for nested multibinding --- .../Data/MultiBindingTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs index fb23a38cce..78c538d99d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs @@ -41,6 +41,37 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal("1,2,3", result); } + [Fact] + public async Task Nested_MultiBinding_Should_Be_Set_Up() + { + var source = new { A = 1, B = 2, C = 3 }; + var binding = new MultiBinding + { + Converter = new ConcatConverter(), + Bindings = + { + new Binding { Path = "A" }, + new MultiBinding + { + Converter = new ConcatConverter(), + Bindings = + { + new Binding { Path = "B"}, + new Binding { Path = "C"} + } + } + } + }; + + var target = new Mock().As(); + target.Setup(x => x.GetValue(Control.DataContextProperty)).Returns(source); + + var observable = binding.Initiate(target.Object, null).Observable; + var result = await observable.Take(1); + + Assert.Equal("1,2,3", result); + } + [Fact] public void Should_Return_FallbackValue_When_Converter_Returns_UnsetValue() { From 0a5329f6a361b937aa2589fdf1ca93691a2a1cf5 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 19 Oct 2018 01:47:22 +0300 Subject: [PATCH 2/7] fix nested multibinding work again --- src/Markup/Avalonia.Markup/Data/MultiBinding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 4c47ef32e0..4921b7c9d7 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -61,7 +61,7 @@ namespace Avalonia.Data var targetType = targetProperty?.PropertyType ?? typeof(object); var children = Bindings.Select(x => x.Initiate(target, null)); - var input = children.Select(x => x.Subject).CombineLatest().Select(x => ConvertValue(x, targetType)); + var input = children.Select(x => x.Observable).CombineLatest().Select(x => ConvertValue(x, targetType)); var mode = Mode == BindingMode.Default ? targetProperty?.GetMetadata(target.GetType()).DefaultBindingMode : Mode; From d2be495bdecb5f249ff8855d6921fb14ba185a82 Mon Sep 17 00:00:00 2001 From: artyom Date: Sun, 21 Oct 2018 20:56:55 +0300 Subject: [PATCH 3/7] Add ReactiveUserControl and ReactiveWindow --- .../ReactiveUserControl.cs | 45 ++++++++++ src/Avalonia.ReactiveUI/ReactiveWindow.cs | 45 ++++++++++ .../AvaloniaActivationForViewFetcherTest.cs | 82 ++++++++++++++++--- 3 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 src/Avalonia.ReactiveUI/ReactiveUserControl.cs create mode 100644 src/Avalonia.ReactiveUI/ReactiveWindow.cs diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs new file mode 100644 index 0000000000..5df6399c38 --- /dev/null +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -0,0 +1,45 @@ +// 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 Avalonia; +using Avalonia.VisualTree; +using Avalonia.Controls; +using ReactiveUI; + +namespace Avalonia +{ + /// + /// A ReactiveUI UserControl that implements + /// and will activate your ViewModel automatically if it supports activation. + /// + /// ViewModel type. + public class ReactiveUserControl : UserControl, IViewFor where TViewModel : class + { + public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty + .Register, TViewModel>(nameof(ViewModel)); + + /// + /// Initializes a new instance of the class. + /// + public ReactiveUserControl() + { + DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel; + this.WhenActivated(disposables => { /* activate ViewModel */ }); + } + + /// + /// The ViewModel. + /// + public TViewModel ViewModel + { + get => GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + object IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TViewModel)value; + } + } +} \ No newline at end of file diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs new file mode 100644 index 0000000000..c8b0c59d67 --- /dev/null +++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs @@ -0,0 +1,45 @@ +// 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 Avalonia; +using Avalonia.VisualTree; +using Avalonia.Controls; +using ReactiveUI; + +namespace Avalonia +{ + /// + /// A ReactiveUI Window that implements + /// and will activate your ViewModel automatically if it supports activation. + /// + /// ViewModel type. + public class ReactiveWindow : Window, IViewFor where TViewModel : class + { + public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty + .Register, TViewModel>(nameof(ViewModel)); + + /// + /// Initializes a new instance of the class. + /// + public ReactiveWindow() + { + DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel; + this.WhenActivated(disposables => { /* activate ViewModel */ }); + } + + /// + /// The ViewModel. + /// + public TViewModel ViewModel + { + get => GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + object IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TViewModel)value; + } + } +} \ No newline at end of file diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index bda55276a8..cdf19c067d 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -23,7 +23,8 @@ namespace Avalonia public TestUserControlWithWhenActivated() { - this.WhenActivated(disposables => { + this.WhenActivated(disposables => + { Active = true; Disposable .Create(() => Active = false) @@ -38,7 +39,8 @@ namespace Avalonia public TestWindowWithWhenActivated() { - this.WhenActivated(disposables => { + this.WhenActivated(disposables => + { Active = true; Disposable .Create(() => Active = false) @@ -47,6 +49,42 @@ namespace Avalonia } } + public class ActivatableViewModel : ISupportsActivation + { + public ViewModelActivator Activator { get; } + + public bool IsActivated { get; private set; } + + public ActivatableViewModel() + { + Activator = new ViewModelActivator(); + this.WhenActivated(disposables => + { + IsActivated = true; + Disposable + .Create(() => IsActivated = false) + .DisposeWith(disposables); + }); + } + } + + public class ActivatableWindow : ReactiveWindow + { + public ActivatableWindow() { } + } + + public class ActivatableUserControl : ReactiveUserControl + { + public ActivatableUserControl() { } + } + + public AvaloniaActivationForViewFetcherTest() + { + Locator.CurrentMutable.RegisterConstant( + new AvaloniaActivationForViewFetcher(), + typeof(IActivationForViewFetcher)); + } + [Fact] public void Visual_Element_Is_Activated_And_Deactivated() { @@ -86,10 +124,6 @@ namespace Avalonia [Fact] public void Activation_For_View_Fetcher_Should_Support_When_Activated() { - Locator.CurrentMutable.RegisterConstant( - new AvaloniaActivationForViewFetcher(), - typeof(IActivationForViewFetcher)); - var userControl = new TestUserControlWithWhenActivated(); Assert.False(userControl.Active); @@ -104,10 +138,6 @@ namespace Avalonia [Fact] public void Activation_For_View_Fetcher_Should_Support_Windows() { - Locator.CurrentMutable.RegisterConstant( - new AvaloniaActivationForViewFetcher(), - typeof(IActivationForViewFetcher)); - using (var application = UnitTestApplication.Start(TestServices.MockWindowingPlatform)) { var window = new TestWindowWithWhenActivated(); @@ -120,5 +150,37 @@ namespace Avalonia Assert.False(window.Active); } } + + [Fact] + public void Activatable_Window_View_Model_Is_Activated_And_Deactivated() + { + using (var application = UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var viewModel = new ActivatableViewModel(); + var window = new ActivatableWindow { ViewModel = viewModel }; + Assert.False(viewModel.IsActivated); + + window.Show(); + Assert.True(viewModel.IsActivated); + + window.Close(); + Assert.False(viewModel.IsActivated); + } + } + + [Fact] + public void Activatable_User_Control_View_Model_Is_Activated_And_Deactivated() + { + var root = new TestRoot(); + var viewModel = new ActivatableViewModel(); + var control = new ActivatableUserControl { ViewModel = viewModel }; + Assert.False(viewModel.IsActivated); + + root.Child = control; + Assert.True(viewModel.IsActivated); + + root.Child = null; + Assert.False(viewModel.IsActivated); + } } } \ No newline at end of file From 3aabd354ade22470f6c233e92ef77f668ae0f676 Mon Sep 17 00:00:00 2001 From: artyom Date: Tue, 23 Oct 2018 00:29:29 +0300 Subject: [PATCH 4/7] Use activation behavior like on other XAML platforms --- src/Avalonia.ReactiveUI/ReactiveUserControl.cs | 1 - src/Avalonia.ReactiveUI/ReactiveWindow.cs | 1 - .../AvaloniaActivationForViewFetcherTest.cs | 4 ++-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 5df6399c38..04c6f100a6 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -24,7 +24,6 @@ namespace Avalonia public ReactiveUserControl() { DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel; - this.WhenActivated(disposables => { /* activate ViewModel */ }); } /// diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs index c8b0c59d67..bb50a37764 100644 --- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs +++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs @@ -24,7 +24,6 @@ namespace Avalonia public ReactiveWindow() { DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel; - this.WhenActivated(disposables => { /* activate ViewModel */ }); } /// diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs index cdf19c067d..b782311729 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs @@ -70,12 +70,12 @@ namespace Avalonia public class ActivatableWindow : ReactiveWindow { - public ActivatableWindow() { } + public ActivatableWindow() => this.WhenActivated(disposables => { }); } public class ActivatableUserControl : ReactiveUserControl { - public ActivatableUserControl() { } + public ActivatableUserControl() => this.WhenActivated(disposables => { }); } public AvaloniaActivationForViewFetcherTest() From e6ef3268193a805b1966938a1daae3a4b3ca20db Mon Sep 17 00:00:00 2001 From: nopara73 Date: Tue, 23 Oct 2018 19:03:14 +0200 Subject: [PATCH 5/7] Resolve g_application_id_is_valid Gtk3 warning --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 9604e9975b..8444e509fd 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -66,7 +66,7 @@ namespace Avalonia.Gtk3 DisplayClassName = Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp)))); - using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid())) + using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid().ToString("N")}")) App = Native.GtkApplicationNew(utf, 0); //Mark current thread as UI thread s_tlsMarker = true; From 4aaee5360202dad62c8f68c3552a606e99cce809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 23 Oct 2018 23:15:26 +0200 Subject: [PATCH 6/7] Updated Cake and script tools and addins --- build.cake | 41 ++--------------------------------------- tools/packages.config | 2 +- 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/build.cake b/build.cake index 56653109ae..d653e9da58 100644 --- a/build.cake +++ b/build.cake @@ -3,8 +3,8 @@ /////////////////////////////////////////////////////////////////////////////// #addin "nuget:?package=NuGet.Core&version=2.14.0" -#tool "nuget:?package=NuGet.CommandLine&version=4.3.0" -#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720" +#tool "nuget:?package=NuGet.CommandLine&version=4.7.1" +#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3" /////////////////////////////////////////////////////////////////////////////// // TOOLS @@ -311,42 +311,6 @@ Task("Publish-NuGet-Impl") Information("Publish-NuGet Task failed, but continuing with next Task..."); }); -Task("Inspect-Impl") - .WithCriteria((context, data) => data.Parameters.IsRunningOnWindows) - .Does(() => -{ - var badIssues = new []{"PossibleNullReferenceException"}; - var whitelist = new []{"tests", "src\\android", "src\\ios", - "src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"}; - Information("Running code inspections"); - - var exitCode = StartProcess(Context.Tools.Resolve("inspectcode.exe"), - new ProcessSettings - { - Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln", - RedirectStandardOutput = true - }); - - Information("Analyzing report"); - var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml")); - var failBuild = false; - foreach(var xml in doc.Descendants("Issue")) - { - var typeId = xml.Attribute("TypeId").Value.ToString(); - if(badIssues.Contains(typeId)) - { - var file = xml.Attribute("File").Value.ToString().ToLower(); - if(whitelist.Any(wh => file.StartsWith(wh))) - continue; - var line = xml.Attribute("Line").Value.ToString(); - Error(typeId + " - " + file + " on line " + line); - failBuild = true; - } - } - if(failBuild) - throw new Exception("Issues found"); -}); - /////////////////////////////////////////////////////////////////////////////// // TARGETS /////////////////////////////////////////////////////////////////////////////// @@ -364,7 +328,6 @@ Task("Run-Tests") Task("Package") .IsDependentOn("Run-Tests") - .IsDependentOn("Inspect-Impl") .IsDependentOn("Create-NuGet-Packages-Impl"); Task("AppVeyor") diff --git a/tools/packages.config b/tools/packages.config index 3c65df896f..05018882e8 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,4 +1,4 @@ - + From 70812de8e234d4ef141a386f5be90efed46b24c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 23 Oct 2018 23:26:53 +0200 Subject: [PATCH 7/7] Install castxml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d80d24f32c..b0c0c807cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ mono: - 5.2.0 dotnet: 2.1.200 script: + - sudo apt-get update + - sudo apt-get install castxml - ./build.sh --target "Travis" --configuration "Release" notifications: email: false