Skip to content
22 changes: 22 additions & 0 deletions src/ReactiveUI.Tests/Platforms/wpf/Mocks/WpfTestWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Windows;
using System.Windows.Controls;

namespace ReactiveUI.Tests.Wpf
{
public class WpfTestWindow : Window, IActivatableView
{
public WpfTestWindow()
{
RootGrid = new Grid();

AddChild(RootGrid);
}

public Grid RootGrid { get; }
}
}
312 changes: 182 additions & 130 deletions src/ReactiveUI.Tests/Platforms/wpf/WpfActivationForViewFetcherTest.cs
Original file line number Diff line number Diff line change
@@ -1,130 +1,182 @@
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Reactive.Concurrency;
using System.Windows;

using DynamicData;
using Xunit;

using FactAttribute = Xunit.WpfFactAttribute;

namespace ReactiveUI.Tests.Wpf
{
public class WpfActivationForViewFetcherTest
{
[Fact]
public void FrameworkElementIsActivatedAndDeactivated()
{
var uc = new WpfTestUserControl();
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(activated);

var unloaded = new RoutedEventArgs();
unloaded.RoutedEvent = FrameworkElement.UnloadedEvent;

uc.RaiseEvent(unloaded);

new[] { true, false }.AssertAreEqual(activated);
}

[Fact]
public void IsHitTestVisibleActivatesFrameworkElement()
{
var uc = new WpfTestUserControl();
uc.IsHitTestVisible = false;
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

// Loaded has happened.
new[] { true }.AssertAreEqual(activated);

uc.IsHitTestVisible = true;

// IsHitTestVisible true, we don't want the event to repeat unnecessarily.
new[] { true }.AssertAreEqual(activated);

var unloaded = new RoutedEventArgs();
unloaded.RoutedEvent = FrameworkElement.UnloadedEvent;

uc.RaiseEvent(unloaded);

// We had both a loaded/hit test visible change/unloaded happen.
new[] { true, false }.AssertAreEqual(activated);
}

[Fact]
public void IsHitTestVisibleDeactivatesFrameworkElement()
{
var uc = new WpfTestUserControl();
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(activated);

uc.IsHitTestVisible = false;

new[] { true, false }.AssertAreEqual(activated);
}

[Fact]
public void FrameworkElementIsActivatedAndDeactivatedWithHitTest()
{
var uc = new WpfTestUserControl();
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(activated);

// this should deactivate it
uc.IsHitTestVisible = false;

new[] { true, false }.AssertAreEqual(activated);

// this should activate it
uc.IsHitTestVisible = true;

new[] { true, false, true }.AssertAreEqual(activated);

var unloaded = new RoutedEventArgs();
unloaded.RoutedEvent = FrameworkElement.UnloadedEvent;

uc.RaiseEvent(unloaded);

new[] { true, false, true, false }.AssertAreEqual(activated);
}
}
}
// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System;
using System.Reactive.Concurrency;
using System.Windows;

using DynamicData;
using Xunit;

using FactAttribute = Xunit.WpfFactAttribute;

namespace ReactiveUI.Tests.Wpf
{
public class WpfActivationForViewFetcherTest
{
[Fact]
public void FrameworkElementIsActivatedAndDeactivated()
{
var uc = new WpfTestUserControl();
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(activated);

var unloaded = new RoutedEventArgs();
unloaded.RoutedEvent = FrameworkElement.UnloadedEvent;

uc.RaiseEvent(unloaded);

new[] { true, false }.AssertAreEqual(activated);
}

[Fact]
public void WindowIsActivatedAndDeactivated()
{
var window = new WpfTestWindow();
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(window);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

window.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(activated);

window.Close();

new[] { true, false }.AssertAreEqual(activated);
}

[StaFact]
public void WindowAndFrameworkElementAreActivatedAndDeactivated()
{
var window = new WpfTestWindow();
var uc = new WpfTestUserControl();

window.RootGrid.Children.Add(uc);

var activation = new ActivationForViewFetcher();

var windowObs = activation.GetActivationForView(window);
windowObs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var windowActivated).Subscribe();

var ucObs = activation.GetActivationForView(uc);
ucObs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var controlActivated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

window.RaiseEvent(loaded);
uc.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(windowActivated);
new[] { true }.AssertAreEqual(controlActivated);

window.Dispatcher.InvokeShutdown();

new[] { true, false }.AssertAreEqual(windowActivated);
new[] { true, false }.AssertAreEqual(controlActivated);
}

[Fact]
public void IsHitTestVisibleActivatesFrameworkElement()
{
var uc = new WpfTestUserControl();
uc.IsHitTestVisible = false;
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

// Loaded has happened.
new[] { true }.AssertAreEqual(activated);

uc.IsHitTestVisible = true;

// IsHitTestVisible true, we don't want the event to repeat unnecessarily.
new[] { true }.AssertAreEqual(activated);

var unloaded = new RoutedEventArgs();
unloaded.RoutedEvent = FrameworkElement.UnloadedEvent;

uc.RaiseEvent(unloaded);

// We had both a loaded/hit test visible change/unloaded happen.
new[] { true, false }.AssertAreEqual(activated);
}

[Fact]
public void IsHitTestVisibleDeactivatesFrameworkElement()
{
var uc = new WpfTestUserControl();
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(activated);

uc.IsHitTestVisible = false;

new[] { true, false }.AssertAreEqual(activated);
}

[Fact]
public void FrameworkElementIsActivatedAndDeactivatedWithHitTest()
{
var uc = new WpfTestUserControl();
var activation = new ActivationForViewFetcher();

var obs = activation.GetActivationForView(uc);
obs.ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var activated).Subscribe();

var loaded = new RoutedEventArgs();
loaded.RoutedEvent = FrameworkElement.LoadedEvent;

uc.RaiseEvent(loaded);

new[] { true }.AssertAreEqual(activated);

// this should deactivate it
uc.IsHitTestVisible = false;

new[] { true, false }.AssertAreEqual(activated);

// this should activate it
uc.IsHitTestVisible = true;

new[] { true, false, true }.AssertAreEqual(activated);

var unloaded = new RoutedEventArgs();
unloaded.RoutedEvent = FrameworkElement.UnloadedEvent;

uc.RaiseEvent(unloaded);

new[] { true, false, true, false }.AssertAreEqual(activated);
}
}
}
40 changes: 40 additions & 0 deletions src/ReactiveUI.Wpf/ActivationForViewFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reactive.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Threading;

namespace ReactiveUI
{
Expand Down Expand Up @@ -55,10 +56,49 @@ public IObservable<bool> GetActivationForView(IActivatableView view)
x => fe.Unloaded += x,
x => fe.Unloaded -= x);

var windowActivation = GetActivationForWindow(view);

var dispatcherActivation = GetActivationForDispatcher(fe);

return viewLoaded
.Merge(viewUnloaded)
.Merge(hitTestVisible)
.Merge(windowActivation)
.Merge(dispatcherActivation)
.DistinctUntilChanged();
}

private static IObservable<bool> GetActivationForWindow(IActivatableView view)
{
if (view is not Window window)
{
return Observable<bool>.Empty;
}

var viewClosed = Observable.FromEvent<EventHandler, bool>(
eventHandler =>
{
void Handler(object? sender, EventArgs e) => eventHandler(false);
return Handler;
},
x => window.Closed += x,
x => window.Closed -= x);

return viewClosed;
}

private static IObservable<bool> GetActivationForDispatcher(DispatcherObject dispatcherObject)
{
var dispatcherShutdownStarted = Observable.FromEvent<EventHandler, bool>(
eventHandler =>
{
void Handler(object? sender, EventArgs e) => eventHandler(false);
return Handler;
},
x => dispatcherObject.Dispatcher.ShutdownStarted += x,
x => dispatcherObject.Dispatcher.ShutdownStarted -= x);

return dispatcherShutdownStarted;
}
}
}