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
diff --git a/build.cake b/build.cake
index e5a4e3470d..d7efa69399 100644
--- a/build.cake
+++ b/build.cake
@@ -2,9 +2,10 @@
// ADDINS
///////////////////////////////////////////////////////////////////////////////
-#tool "nuget:?package=NuGet.CommandLine&version=4.3.0"
-#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720"
-
+#addin "nuget:?package=NuGet.Core&version=2.14.0"
+#tool "nuget:?package=NuGet.CommandLine&version=4.7.1"
+#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.3"
+
///////////////////////////////////////////////////////////////////////////////
// TOOLS
///////////////////////////////////////////////////////////////////////////////
diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
new file mode 100644
index 0000000000..04c6f100a6
--- /dev/null
+++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
@@ -0,0 +1,44 @@
+// 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;
+ }
+
+ ///
+ /// 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..bb50a37764
--- /dev/null
+++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs
@@ -0,0 +1,44 @@
+// 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;
+ }
+
+ ///
+ /// 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/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;
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;
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()
{
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
index bda55276a8..b782311729 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() => this.WhenActivated(disposables => { });
+ }
+
+ public class ActivatableUserControl : ReactiveUserControl
+ {
+ public ActivatableUserControl() => this.WhenActivated(disposables => { });
+ }
+
+ 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
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 @@
-
+