Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading