Skip to content

Commit

Permalink
Fixing ReactivePage, ReactiveUserControl and DependencyProperties cha…
Browse files Browse the repository at this point in the history
…nge notification for WinUI 3 (#2902)

* Added support for WinUI3 desktop

* ReactivePage, ReactiveUserControl, DependencyProperties now work. Also, The ReactiveUI.WinUI is getting loaded instead of .net startandard dll.

* Updated DispatcherQueueScheduler xml docs

* Update .gitignore
  • Loading branch information
harvinders committed Aug 23, 2021
1 parent 66a4d38 commit b647a43
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 78 deletions.
4 changes: 3 additions & 1 deletion src/ReactiveUI.Uwp/Common/BooleanToVisibilityHint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

using System;
using System.Diagnostics.CodeAnalysis;
#if NETFX_CORE || HAS_UNO
#if HAS_WINUI
using Microsoft.UI.Xaml;
#elif NETFX_CORE || HAS_UNO
using Windows.UI.Xaml;
#else
using System.Windows;
Expand Down
7 changes: 5 additions & 2 deletions src/ReactiveUI.Uwp/Common/BooleanToVisibilityTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
// See the LICENSE file in the project root for full license information.

using System;
#if NETFX_CORE || HAS_UNO
#if HAS_WINUI
using Microsoft.UI.Xaml;
#elif NETFX_CORE || HAS_UNO
using Windows.UI.Xaml;
#else
using System.Windows;
Expand Down Expand Up @@ -48,7 +50,8 @@ public bool TryConvert(object? from, Type toType, object? conversionHint, out ob
if (toType == typeof(Visibility) && from is bool fromBool)
{
var fromAsBool = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !fromBool : fromBool;
#if !NETFX_CORE && !HAS_UNO

#if !NETFX_CORE && !HAS_UNO && !HAS_WINUI
var notVisible = (hint & BooleanToVisibilityHint.UseHidden) != 0 ? Visibility.Hidden : Visibility.Collapsed;
#else
var notVisible = Visibility.Collapsed;
Expand Down
7 changes: 5 additions & 2 deletions src/ReactiveUI.Uwp/Common/ReactivePage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

using System;
using System.Diagnostics.CodeAnalysis;
#if NETFX_CORE || HAS_UNO
#if HAS_WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
#elif NETFX_CORE || HAS_UNO
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else
Expand Down Expand Up @@ -48,7 +51,7 @@ namespace ReactiveUI
/// </code>
/// </para>
/// <para>
/// Note that UWP projects do not support the <c>TypeArguments</c> attribute. The XAML designer window in WPF projects also does not
/// Note that UWP and WinUI projects do not support the <c>TypeArguments</c> attribute. The XAML designer window in WPF projects also does not
/// support generic types. To use <see cref="ReactivePage{TViewModel}"/> in XAML documents you need to create a base class
/// where you derive from <see cref="ReactivePage{TViewModel}"/> with the type argument filled in.
/// <code>
Expand Down
7 changes: 5 additions & 2 deletions src/ReactiveUI.Uwp/Common/ReactiveUserControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

using System;
using System.Diagnostics.CodeAnalysis;
#if NETFX_CORE || HAS_UNO
#if HAS_WINUI
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
#elif NETFX_CORE || HAS_UNO
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else
Expand Down Expand Up @@ -48,7 +51,7 @@ namespace ReactiveUI
/// </code>
/// </para>
/// <para>
/// Note that UWP projects do not support the <c>TypeArguments</c> attribute. The XAML designer window in WPF projects also does not
/// Note that UWP and WinUI projects do not support the <c>TypeArguments</c> attribute. The XAML designer window in WPF projects also does not
/// support generic types. To use <see cref="ReactiveUserControl{TViewModel}"/> in XAML documents you need to create a base class
/// where you derive from <see cref="ReactiveUserControl{TViewModel}"/> with the type argument filled in.
/// <code>
Expand Down
161 changes: 161 additions & 0 deletions src/ReactiveUI.WinUI/DependencyObjectObservableForProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// 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.Globalization;
using System.Linq.Expressions;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reflection;
using Microsoft.UI.Xaml;
using Splat;

#if HAS_UNO
namespace ReactiveUI.Uno
#else
namespace ReactiveUI
#endif
{
/// <summary>
/// Creates a observable for a property if available that is based on a DependencyProperty.
/// </summary>
public class DependencyObjectObservableForProperty : ICreatesObservableForProperty
{
/// <inheritdoc/>
public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false)
{
if (!typeof(DependencyObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
{
return 0;
}

if (GetDependencyPropertyFetcher(type, propertyName) == null)
{
return 0;
}

return 6;
}

/// <inheritdoc/>
public IObservable<IObservedChange<object, object?>> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false)
{
if (sender == null)
{
throw new ArgumentNullException(nameof(sender));
}

if (sender is not DependencyObject depSender)
{
throw new ArgumentException("The sender must be a DependencyObject", nameof(sender));
}

var type = sender.GetType();

if (beforeChanged)
{
this.Log().Warn(
CultureInfo.InvariantCulture,
"Tried to bind DO {0}.{1}, but DPs can't do beforeChanged. Binding as POCO object",
type.FullName,
propertyName);

var ret = new POCOObservableForProperty();
return ret.GetNotificationForProperty(sender, expression, propertyName, beforeChanged);
}

var dpFetcher = GetDependencyPropertyFetcher(type, propertyName);
if (dpFetcher == null)
{
this.Log().Warn(
CultureInfo.InvariantCulture,
"Tried to bind DO {0}.{1}, but DP doesn't exist. Binding as POCO object",
type.FullName,
propertyName);

var ret = new POCOObservableForProperty();
return ret.GetNotificationForProperty(sender, expression, propertyName, beforeChanged);
}

return Observable.Create<IObservedChange<object, object?>>(subj =>
{
var handler = new DependencyPropertyChangedCallback((_, _) =>
subj.OnNext(new ObservedChange<object, object?>(sender, expression, default)));
var dependencyProperty = dpFetcher();
var token = depSender.RegisterPropertyChangedCallback(dependencyProperty, handler);
return Disposable.Create(() => depSender.UnregisterPropertyChangedCallback(dependencyProperty, token));
});
}

private static PropertyInfo? ActuallyGetProperty(TypeInfo typeInfo, string propertyName)
{
var current = typeInfo;
while (current != null)
{
var ret = current.GetDeclaredProperty(propertyName);
if (ret?.IsStatic() == true)
{
return ret;
}

current = current.BaseType?.GetTypeInfo();
}

return null;
}

private static FieldInfo? ActuallyGetField(TypeInfo typeInfo, string propertyName)
{
var current = typeInfo;
while (current != null)
{
var ret = current.GetDeclaredField(propertyName);
if (ret?.IsStatic == true)
{
return ret;
}

current = current.BaseType?.GetTypeInfo();
}

return null;
}

private static Func<DependencyProperty>? GetDependencyPropertyFetcher(Type type, string propertyName)
{
var typeInfo = type.GetTypeInfo();

// Look for the DependencyProperty attached to this property name
var pi = ActuallyGetProperty(typeInfo, propertyName + "Property");
if (pi != null)
{
var value = pi.GetValue(null);

if (value is null)
{
return null;
}

return () => (DependencyProperty)value;
}

var fi = ActuallyGetField(typeInfo, propertyName + "Property");
if (fi != null)
{
var value = fi.GetValue(null);

if (value is null)
{
return null;
}

return () => (DependencyProperty)value;
}

return null;
}
}
}
49 changes: 24 additions & 25 deletions src/ReactiveUI.WinUI/DispatcherQueueScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
namespace System.Reactive.Concurrency
{
/// <summary>
/// Represents an object that schedules units of work on a <see cref="System.Windows.Threading.Dispatcher"/>.
/// Represents an object that schedules units of work on a <see cref="Microsoft.UI.Dispatching.DispatcherQueue"/>.
/// </summary>
/// <remarks>
/// This scheduler type is typically used indirectly through the <see cref="Linq.DispatcherObservable.ObserveOnDispatcher{TSource}(IObservable{TSource})"/> and <see cref="Linq.DispatcherObservable.SubscribeOnDispatcher{TSource}(IObservable{TSource})"/> methods that use the Dispatcher on the calling thread.
/// </remarks>
public class DispatcherQueueScheduler : LocalScheduler, ISchedulerPeriodic
{
/// <summary>
/// Gets the scheduler that schedules work on the <see cref="System.Windows.Threading.Dispatcher"/> for the current thread.
/// Gets the scheduler that schedules work on the <see cref="Microsoft.UI.Dispatching.DispatcherQueue"/> for the current thread.
/// </summary>
public static DispatcherQueueScheduler Current
{
Expand All @@ -36,33 +36,32 @@ public static DispatcherQueueScheduler Current
}

/// <summary>
/// Constructs a <see cref="DispatcherScheduler"/> that schedules units of work on the given <see cref="System.Windows.Threading.Dispatcher"/>.
/// Constructs a <see cref="DispatcherQueueScheduler"/> that schedules units of work on the given <see cref="Microsoft.UI.Dispatching.DispatcherQueue"/>.
/// </summary>
/// <param name="dispatcher"><see cref="DispatcherScheduler"/> to schedule work on.</param>
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
public DispatcherQueueScheduler(DispatcherQueue dispatcher)
/// <param name="dispatcherQueue"><see cref="Microsoft.UI.Dispatching.DispatcherQueue"/> to schedule work on.</param>
/// <exception cref="ArgumentNullException"><paramref name="dispatcherQueue"/> is <c>null</c>.</exception>
public DispatcherQueueScheduler(DispatcherQueue dispatcherQueue)
{
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
DispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue));
Priority = DispatcherQueuePriority.Normal;

}

/// <summary>
/// Constructs a <see cref="DispatcherScheduler"/> that schedules units of work on the given <see cref="System.Windows.Threading.Dispatcher"/> at the given priority.
/// Constructs a <see cref="DispatcherScheduler"/> that schedules units of work on the given <see cref="Microsoft.UI.Dispatching.DispatcherQueue"/> at the given priority.
/// </summary>
/// <param name="dispatcher"><see cref="DispatcherScheduler"/> to schedule work on.</param>
/// <param name="dispatcherQueue"><see cref="Microsoft.UI.Dispatching.DispatcherQueue"/> to schedule work on.</param>
/// <param name="priority">Priority at which units of work are scheduled.</param>
/// <exception cref="ArgumentNullException"><paramref name="dispatcher"/> is <c>null</c>.</exception>
public DispatcherQueueScheduler(DispatcherQueue dispatcher, DispatcherQueuePriority priority)
/// <exception cref="ArgumentNullException"><paramref name="dispatcherQueue"/> is <c>null</c>.</exception>
public DispatcherQueueScheduler(DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority)
{
Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
DispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue));
Priority = priority;
}

/// <summary>
/// Gets the <see cref="System.Windows.Threading.Dispatcher"/> associated with the <see cref="DispatcherScheduler"/>.
/// Gets the <see cref="Microsoft.UI.Dispatching.DispatcherQueue"/> associated with the <see cref="DispatcherQueueScheduler"/>.
/// </summary>
public DispatcherQueue Dispatcher { get; }
public DispatcherQueue DispatcherQueue { get; }

/// <summary>
/// Gets the priority at which work items will be dispatched.
Expand All @@ -86,20 +85,20 @@ public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TSta

var d = new SingleAssignmentDisposable();

Dispatcher.TryEnqueue(Priority,
DispatcherQueue.TryEnqueue(Priority,
() =>
{
if (!d.IsDisposed)
{
d.Disposable = action(this, state);
}
{
if (!d.IsDisposed)
{
d.Disposable = action(this, state);
}
});

return d;
}

/// <summary>
/// Schedules an action to be executed after <paramref name="dueTime"/> on the dispatcher, using a <see cref="System.Windows.Threading.DispatcherTimer"/> object.
/// Schedules an action to be executed after <paramref name="dueTime"/> on the dispatcherQueue, using a <see cref="Microsoft.UI.Dispatching.DispatcherQueueTimer"/> object.
/// </summary>
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
/// <param name="state">State passed to the action to be executed.</param>
Expand Down Expand Up @@ -127,7 +126,7 @@ private IDisposable ScheduleSlow<TState>(TState state, TimeSpan dueTime, Func<IS
{
var d = new MultipleAssignmentDisposable();

var timer = Dispatcher.CreateTimer();
var timer = DispatcherQueue.CreateTimer();

timer.Tick += (s, e) =>
{
Expand Down Expand Up @@ -163,7 +162,7 @@ private IDisposable ScheduleSlow<TState>(TState state, TimeSpan dueTime, Func<IS
}

/// <summary>
/// Schedules a periodic piece of work on the dispatcher, using a <see cref="System.Windows.Threading.DispatcherTimer"/> object.
/// Schedules a periodic piece of work on the dispatcherQueue, using a <see cref="Microsoft.UI.Dispatching.DispatcherQueueTimer"/> object.
/// </summary>
/// <typeparam name="TState">The type of the state passed to the scheduled action.</typeparam>
/// <param name="state">Initial state passed to the action upon the first iteration.</param>
Expand All @@ -184,7 +183,7 @@ public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<
throw new ArgumentNullException(nameof(action));
}

var timer = Dispatcher.CreateTimer();
var timer = DispatcherQueue.CreateTimer();

var state1 = state;

Expand Down
1 change: 0 additions & 1 deletion src/ReactiveUI.WinUI/ReactiveUI.WinUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<RuntimeIdentifiers>win10-x86;win10-x64</RuntimeIdentifiers>
<DefineConstants>HAS_WINUI</DefineConstants>
<PackageTags>mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;winui</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<UseWinUI>true</UseWinUI>
</PropertyGroup>

Expand Down
4 changes: 1 addition & 3 deletions src/ReactiveUI.WinUI/Registrations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ public void Register(Action<Func<object>, Type> registerFunction)
}

registerFunction(() => new PlatformOperations(), typeof(IPlatformOperations));

registerFunction(() => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher));

// registerFunction(() => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty));
registerFunction(() => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty));
registerFunction(() => new BooleanToVisibilityTypeConverter(), typeof(IBindingTypeConverter));
registerFunction(() => new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook));
registerFunction(() => new ComponentModelTypeConverter(), typeof(IBindingTypeConverter));
Expand Down

0 comments on commit b647a43

Please sign in to comment.