diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props
index c3b136d41d..1911c02677 100644
--- a/build/ReactiveUI.props
+++ b/build/ReactiveUI.props
@@ -1,5 +1,5 @@
-
+
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 083182a370..524362fcf9 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
@@ -104,7 +105,6 @@ namespace Avalonia.Controls
private static readonly HashSet _loadedQueue = new HashSet();
private static readonly HashSet _loadedProcessingQueue = new HashSet();
- private bool _isAttachedToVisualTree = false;
private bool _isLoaded = false;
private DataTemplates? _dataTemplates;
private IControl? _focusAdorner;
@@ -347,7 +347,7 @@ namespace Avalonia.Controls
internal void OnLoadedCore()
{
if (_isLoaded == false &&
- _isAttachedToVisualTree)
+ ((ILogical)this).IsAttachedToLogicalTree)
{
_isLoaded = true;
OnLoaded();
@@ -395,7 +395,6 @@ namespace Avalonia.Controls
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
- _isAttachedToVisualTree = true;
InitializeIfNeeded();
@@ -406,7 +405,6 @@ namespace Avalonia.Controls
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
- _isAttachedToVisualTree = false;
OnUnloadedCore();
}
diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
index 5c713804e9..9f69b4ee6e 100644
--- a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
+++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
@@ -2,6 +2,7 @@ using System;
using System.Reactive.Linq;
using Avalonia.VisualTree;
using Avalonia.Controls;
+using Avalonia.Interactivity;
using ReactiveUI;
namespace Avalonia.ReactiveUI
@@ -25,27 +26,28 @@ namespace Avalonia.ReactiveUI
public IObservable GetActivationForView(IActivatableView view)
{
if (!(view is IVisual visual)) return Observable.Return(false);
- if (view is WindowBase window) return GetActivationForWindowBase(window);
+ if (view is Control control) return GetActivationForControl(control);
return GetActivationForVisual(visual);
}
///
- /// Listens to Opened and Closed events for Avalonia windows.
+ /// Listens to Loaded and Unloaded
+ /// events for Avalonia Control.
///
- private IObservable GetActivationForWindowBase(WindowBase window)
+ private IObservable GetActivationForControl(Control control)
{
- var windowLoaded = Observable
- .FromEventPattern(
- x => window.Opened += x,
- x => window.Opened -= x)
+ var controlLoaded = Observable
+ .FromEventPattern(
+ x => control.Loaded += x,
+ x => control.Loaded -= x)
.Select(args => true);
- var windowUnloaded = Observable
- .FromEventPattern(
- x => window.Closed += x,
- x => window.Closed -= x)
+ var controlUnloaded = Observable
+ .FromEventPattern(
+ x => control.Unloaded += x,
+ x => control.Unloaded -= x)
.Select(args => false);
- return windowLoaded
- .Merge(windowUnloaded)
+ return controlLoaded
+ .Merge(controlUnloaded)
.DistinctUntilChanged();
}
diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
index 775014d419..2d848d4cd7 100644
--- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs
+++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
@@ -50,7 +50,7 @@ namespace Avalonia.ReactiveUI
/// ReactiveUI routing documentation website for more info.
///
///
- public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
+ public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger, IStyleable
{
///
/// for the property.
@@ -126,6 +126,8 @@ namespace Avalonia.ReactiveUI
///
public IViewLocator? ViewLocator { get; set; }
+ Type IStyleable.StyleKey => typeof(TransitioningContentControl);
+
///
/// Invoked when ReactiveUI router navigates to a view model.
///
diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
index 869238b377..0750fef067 100644
--- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
+++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
@@ -2,7 +2,7 @@ using System;
using System.Reactive.Disposables;
using Avalonia.Controls;
-
+using Avalonia.Styling;
using ReactiveUI;
using Splat;
@@ -13,7 +13,7 @@ namespace Avalonia.ReactiveUI
/// the ViewModel property and display it. This control is very useful
/// inside a DataTemplate to display the View associated with a ViewModel.
///
- public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger
+ public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IStyleable
{
///
/// for the property.
@@ -78,6 +78,8 @@ namespace Avalonia.ReactiveUI
///
public IViewLocator? ViewLocator { get; set; }
+ Type IStyleable.StyleKey => typeof(TransitioningContentControl);
+
///
/// Invoked when ReactiveUI router navigates to a view model.
///
diff --git a/tests/Avalonia.Controls.UnitTests/LoadedTests.cs b/tests/Avalonia.Controls.UnitTests/LoadedTests.cs
new file mode 100644
index 0000000000..aaf0dce30e
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/LoadedTests.cs
@@ -0,0 +1,71 @@
+using Avalonia.Platform;
+using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Moq;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests;
+
+public class LoadedTests
+{
+ [Fact]
+ public void Window_Loads_And_Unloads()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ int loadedCount = 0, unloadedCount = 0;
+ var target = new Window();
+
+ target.Loaded += (_, _) => loadedCount++;
+ target.Unloaded += (_, _) => unloadedCount++;
+
+ Assert.Equal(0, loadedCount);
+ Assert.Equal(0, unloadedCount);
+
+ target.Show();
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+ Assert.True(target.IsLoaded);
+
+ Assert.Equal(1, loadedCount);
+ Assert.Equal(0, unloadedCount);
+
+ target.Close();
+
+ Assert.Equal(1, loadedCount);
+ Assert.Equal(1, unloadedCount);
+ Assert.False(target.IsLoaded);
+ }
+ }
+
+ [Fact]
+ public void Control_Loads_And_Unloads()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ int loadedCount = 0, unloadedCount = 0;
+ var window = new Window();
+ window.Show();
+
+ var target = new Button();
+
+ target.Loaded += (_, _) => loadedCount++;
+ target.Unloaded += (_, _) => unloadedCount++;
+
+ Assert.Equal(0, loadedCount);
+ Assert.Equal(0, unloadedCount);
+
+ window.Content = target;
+ Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
+ Assert.True(target.IsLoaded);
+
+ Assert.Equal(1, loadedCount);
+ Assert.Equal(0, unloadedCount);
+
+ window.Content = null;
+
+ Assert.Equal(1, loadedCount);
+ Assert.Equal(1, unloadedCount);
+ Assert.False(target.IsLoaded);
+ }
+ }
+}
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
index c10f0ffcdb..196375fb40 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs
@@ -7,6 +7,7 @@ using System.Reactive;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Collections.Generic;
+using System.IO;
using System.Runtime.Serialization;
using System.Threading;
using Avalonia.Controls.ApplicationLifetimes;
@@ -17,6 +18,7 @@ using Avalonia.UnitTests;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia;
+using Avalonia.Threading;
using ReactiveUI;
using DynamicData;
using Xunit;
@@ -93,13 +95,23 @@ namespace Avalonia.ReactiveUI.UnitTests
var suspension = new AutoSuspendHelper(application.ApplicationLifetime);
RxApp.SuspensionHost.CreateNewAppState = () => new AppState { Example = "Foo" };
RxApp.SuspensionHost.ShouldPersistState.Subscribe(_ => shouldPersistReceived = true);
- RxApp.SuspensionHost.SetupDefaultSuspendResume(new DummySuspensionDriver());
+ RxApp.SuspensionHost.SetupDefaultSuspendResume(new FakeSuspensionDriver());
suspension.OnFrameworkInitializationCompleted();
lifetime.Shutdown();
+
Assert.True(shouldPersistReceived);
Assert.Equal("Foo", RxApp.SuspensionHost.GetAppState().Example);
}
}
+
+ private class FakeSuspensionDriver : ISuspensionDriver
+ {
+ public IObservable