diff --git a/README.md b/README.md index af3ac54..e7a495b 100644 --- a/README.md +++ b/README.md @@ -72,19 +72,19 @@ See [Contribution Guidelines](https://www.reactiveui.net/contribute/) for furthe - +
Glenn Watson

Melbourne, Australia

- +
Rodney Littles II

Texas, USA

- +
Artyom Gorchakov

Moscow, Russia

@@ -92,13 +92,13 @@ See [Contribution Guidelines](https://www.reactiveui.net/contribute/) for furthe - +
Colt Bauman

South Korea

- +
Chris Pulman

United Kingdom

diff --git a/src/Directory.build.props b/src/Directory.build.props index d22018a..4e3fddd 100644 --- a/src/Directory.build.props +++ b/src/Directory.build.props @@ -28,15 +28,20 @@ preview True latest + $(NoWarn);SA1648;CA1816;CA1001;CS0108;CS0114;CS3021;CS1574;CA1303;NETSDK1206 CS8600;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8623;CS8624;CS8625;CS8626;CS8627;CS8628;CS8629;CS8630;CS8634;CS8766;CS8767 true true - true - + true + mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;net;unoplatform + net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst;net7.0-macos;net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst;net8.0-macos + + 14.2 + 14.0 + 30.0 + 10.0.19041.0 + 10.0.19041.0 - - - portable @@ -44,10 +49,10 @@ - - + + - + @@ -57,7 +62,7 @@ - + @@ -72,8 +77,8 @@ - - + + diff --git a/src/Directory.build.targets b/src/Directory.build.targets index d3b2d43..0871acf 100644 --- a/src/Directory.build.targets +++ b/src/Directory.build.targets @@ -7,49 +7,19 @@ false - - $(DefineConstants);NETSTANDARD;PORTABLE + + $(DefineConstants);IOS - - $(DefineConstants);NET_461;XAML + + $(DefineConstants);MAC - - 10.0.16299.0 - $(DefineConstants);NETFX_CORE;XAML;WINDOWS_UWP + + $(DefineConstants);TVOS - - $(DefineConstants);MONO;UIKIT;COCOA;IOS + + $(DefineConstants);ANDROID - - $(DefineConstants);MONO;UIKIT;COCOA;IOS + + $(DefineConstants);MACCATALYST - - $(DefineConstants);MONO;COCOA;MAC - - - $(DefineConstants);MONO;COCOA;MAC - - - $(DefineConstants);MONO;UIKIT;COCOA;TVOS - - - $(DefineConstants);MONO;UIKIT;COCOA;TVOS - - - $(DefineConstants);MONO;UIKIT;COCOA;WATCHOS - - - $(DefineConstants);MONO;ANDROID - false - - - $(DefineConstants);MONO;ANDROID - - - $(DefineConstants);TIZEN - - - diff --git a/src/ReactiveUI.Uno.WinUI/DispatcherQueueScheduler.cs b/src/ReactiveUI.Uno.WinUI/DispatcherQueueScheduler.cs new file mode 100644 index 0000000..5a42a46 --- /dev/null +++ b/src/ReactiveUI.Uno.WinUI/DispatcherQueueScheduler.cs @@ -0,0 +1,206 @@ +// 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 more information. +// +#if WINDOWS10_0_19041_0 + +using System.Reactive.Disposables; +using System.Threading; +using Microsoft.UI.Dispatching; + +namespace System.Reactive.Concurrency; + +/// +/// Represents an object that schedules units of work on a . +/// +public class DispatcherQueueScheduler : LocalScheduler, ISchedulerPeriodic +{ + /// + /// Gets the scheduler that schedules work on the for the current thread. + /// + public static DispatcherQueueScheduler Current + { + get + { + var dispatcher = DispatcherQueue.GetForCurrentThread(); + if (dispatcher == null) + { + throw new InvalidOperationException("There is no current dispatcher thread"); + } + + return new DispatcherQueueScheduler(dispatcher); + } + } + + /// + /// Constructs a that schedules units of work on the given . + /// + /// to schedule work on. + /// is null. + public DispatcherQueueScheduler(DispatcherQueue dispatcherQueue) + { + DispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue)); + Priority = DispatcherQueuePriority.Normal; + } + + /// + /// Constructs a DispatcherScheduler that schedules units of work on the given at the given priority. + /// + /// to schedule work on. + /// Priority at which units of work are scheduled. + /// is null. + public DispatcherQueueScheduler(DispatcherQueue dispatcherQueue, DispatcherQueuePriority priority) + { + DispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue)); + Priority = priority; + } + + /// + /// Gets the associated with the . + /// + public DispatcherQueue DispatcherQueue { get; } + + /// + /// Gets the priority at which work items will be dispatched. + /// + public DispatcherQueuePriority Priority { get; } + + /// + /// Schedules an action to be executed on the dispatcher. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var d = new SingleAssignmentDisposable(); + + DispatcherQueue.TryEnqueue(Priority, + () => + { + if (!d.IsDisposed) + { + d.Disposable = action(this, state); + } + }); + + return d; + } + + /// + /// Schedules an action to be executed after on the dispatcherQueue, using a object. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var dt = Scheduler.Normalize(dueTime); + if (dt.Ticks == 0) + { + return Schedule(state, action); + } + + return ScheduleSlow(state, dt, action); + } + + private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action) + { + var d = new MultipleAssignmentDisposable(); + + var timer = DispatcherQueue.CreateTimer(); + + timer.Tick += (s, e) => + { + var t = Interlocked.Exchange(ref timer, null); + if (t != null) + { + try + { + d.Disposable = action(this, state); + } + finally + { + t.Stop(); + action = static (s, t) => Disposable.Empty; + } + } + }; + + timer.Interval = dueTime; + timer.Start(); + + d.Disposable = Disposable.Create(() => + { + var t = Interlocked.Exchange(ref timer, null); + if (t != null) + { + t.Stop(); + action = static (s, t) => Disposable.Empty; + } + }); + + return d; + } + + /// + /// Schedules a periodic piece of work on the dispatcherQueue, using a object. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is null. + /// is less than . + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var timer = DispatcherQueue.CreateTimer(); + + var state1 = state; + + timer.Tick += (s, e) => + { + state1 = action(state1); + }; + + timer.Interval = period; + timer.Start(); + + return Disposable.Create(() => + { + var t = Interlocked.Exchange(ref timer, null); + if (t != null) + { + t.Stop(); + action = static _ => _; + } + }); + } +} +#endif diff --git a/src/ReactiveUI.Uno.WinUI/ReactiveUI.Uno.WinUI.csproj b/src/ReactiveUI.Uno.WinUI/ReactiveUI.Uno.WinUI.csproj index 4f5c3c8..95295c2 100644 --- a/src/ReactiveUI.Uno.WinUI/ReactiveUI.Uno.WinUI.csproj +++ b/src/ReactiveUI.Uno.WinUI/ReactiveUI.Uno.WinUI.csproj @@ -1,50 +1,26 @@ - + - netstandard2.0;MonoAndroid12.0;MonoAndroid12.1;MonoAndroid13.0;Xamarin.iOS10;Xamarin.Mac20;net6.0-android;net6.0-ios;net6.0-macos;net6.0-maccatalyst;net6.0-windows10.0.17763.0;net7.0-android;net7.0-ios;net7.0-macos;net7.0-maccatalyst;net7.0-windows10.0.17763.0 + $(UnoTargetFrameworks);net7.0-windows10.0.19041.0;net8.0-windows10.0.19041.0 ReactiveUI.Uno.WinUI Contains the ReactiveUI platform specific extensions for Uno WinUI - $(DefineConstants);HAS_UNO;HAS_WINUI - $(NoWarn);SA1648;CA1816;CA1001;CS0108;CS0114;CS3021;CS1574;CA1303 - mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;net;unoplatform;winui + $(DefineConstants);HAS_WINUI + $(PackageTags);winui - - $(DefineConstants);WASM - - + $(DefineConstants);HAS_UNO_WINUI + win-x64;win-x86;win-arm64 - - - - - + - - - - + - + - - - - - - - - - - - - - - - - + + diff --git a/src/ReactiveUI.Uno.sln b/src/ReactiveUI.Uno.sln index f267b09..903f92d 100644 --- a/src/ReactiveUI.Uno.sln +++ b/src/ReactiveUI.Uno.sln @@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 16.0.31613.86 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BD9762CF-E104-481C-96A6-26E624B86283}" ProjectSection(SolutionItems) = preProject ..\.editorconfig = ..\.editorconfig + ..\.gitignore = ..\.gitignore Directory.build.props = Directory.build.props Directory.build.targets = Directory.build.targets global.json = global.json diff --git a/src/ReactiveUI.Uno/ActivationForViewFetcher.cs b/src/ReactiveUI.Uno/ActivationForViewFetcher.cs index 4793483..de95136 100644 --- a/src/ReactiveUI.Uno/ActivationForViewFetcher.cs +++ b/src/ReactiveUI.Uno/ActivationForViewFetcher.cs @@ -7,56 +7,54 @@ using System.Linq; using System.Reactive.Linq; using System.Reflection; +using Windows.Foundation; #if HAS_UNO_WINUI using Microsoft.UI.Xaml; -using Windows.Foundation; #else -using Windows.Foundation; using Windows.UI.Xaml; #endif -namespace ReactiveUI.Uno +namespace ReactiveUI.Uno; + +/// +/// ActivationForViewFetcher is how ReactiveUI determine when a +/// View is activated or deactivated. This is usually only used when porting +/// ReactiveUI to a new UI framework. +/// +public class ActivationForViewFetcher : IActivationForViewFetcher { - /// - /// ActivationForViewFetcher is how ReactiveUI determine when a - /// View is activated or deactivated. This is usually only used when porting - /// ReactiveUI to a new UI framework. - /// - public class ActivationForViewFetcher : IActivationForViewFetcher - { - /// - public int GetAffinityForView(Type view) => typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0; + /// + public int GetAffinityForView(Type view) => typeof(FrameworkElement).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0; - /// - public IObservable GetActivationForView(IActivatableView view) + /// + public IObservable GetActivationForView(IActivatableView view) + { + if (view is not FrameworkElement fe) { - if (view is not FrameworkElement fe) - { - return Observable.Empty; - } + return Observable.Empty; + } #pragma warning disable SA1114 // Parameter list after. - var viewLoaded = Observable.FromEvent, bool>( - - eventHandler => (_, _) => eventHandler(true), - x => fe.Loading += x, - x => fe.Loading -= x); - - var viewUnloaded = Observable.FromEvent( - handler => - { - void EventHandler(object sender, RoutedEventArgs e) => handler(false); - return EventHandler; - }, - x => fe.Unloaded += x, - x => fe.Unloaded -= x); - - return viewLoaded - .Merge(viewUnloaded) - .Select(b => b ? fe.WhenAnyValue(x => x.IsHitTestVisible).SkipWhile(x => !x) : Observables.False) - .Switch() - .DistinctUntilChanged(); - } + var viewLoaded = Observable.FromEvent, bool>( + + eventHandler => (_, _) => eventHandler(true), + x => fe.Loading += x, + x => fe.Loading -= x); + + var viewUnloaded = Observable.FromEvent( + handler => + { + void EventHandler(object sender, RoutedEventArgs e) => handler(false); + return EventHandler; + }, + x => fe.Unloaded += x, + x => fe.Unloaded -= x); + + return viewLoaded + .Merge(viewUnloaded) + .Select(b => b ? fe.WhenAnyValue(x => x.IsHitTestVisible).SkipWhile(x => !x) : Observables.False) + .Switch() + .DistinctUntilChanged(); } } diff --git a/src/ReactiveUI.Uno/ActivationHelper.cs b/src/ReactiveUI.Uno/ActivationHelper.cs index 02db312..482d0f2 100644 --- a/src/ReactiveUI.Uno/ActivationHelper.cs +++ b/src/ReactiveUI.Uno/ActivationHelper.cs @@ -5,30 +5,28 @@ using Splat; -namespace ReactiveUI.Uno +namespace ReactiveUI.Uno; + +internal static class ActivationHelper { - internal static class ActivationHelper + static ActivationHelper() { + if (UnoActivated) + { + return; + } - static ActivationHelper() + UnoActivated = true; + Locator.RegisterResolverCallbackChanged(() => { - if (UnoActivated) + if (Locator.CurrentMutable is null) { return; } - UnoActivated = true; - Locator.RegisterResolverCallbackChanged(() => - { - if (Locator.CurrentMutable is null) - { - return; - } - - new Registrations().Register((f, t) => Locator.CurrentMutable.RegisterConstant(f(), t)); - }); - } - - internal static bool UnoActivated { get; } + new Registrations().Register((f, t) => Locator.CurrentMutable.RegisterConstant(f(), t)); + }); } + + internal static bool UnoActivated { get; } } diff --git a/src/ReactiveUI.Uno/Common/AutoDataTemplateBindingHook.cs b/src/ReactiveUI.Uno/Common/AutoDataTemplateBindingHook.cs index 60f01f0..30331be 100644 --- a/src/ReactiveUI.Uno/Common/AutoDataTemplateBindingHook.cs +++ b/src/ReactiveUI.Uno/Common/AutoDataTemplateBindingHook.cs @@ -4,103 +4,73 @@ // See the LICENSE file in the project root for full license information. using System; -using System.Diagnostics.CodeAnalysis; using System.Linq; #if HAS_WINUI using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Markup; -#elif NETFX_CORE || HAS_UNO +#else using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Markup; -#else -using System.Windows; -using System.Windows.Controls; -using System.Windows.Markup; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI -#endif +namespace ReactiveUI.Uno; + +/// +/// AutoDataTemplateBindingHook is a binding hook that checks ItemsControls +/// that don't have DataTemplates, and assigns a default DataTemplate that +/// loads the View associated with each ViewModel. +/// +public class AutoDataTemplateBindingHook : IPropertyBindingHook { /// - /// AutoDataTemplateBindingHook is a binding hook that checks ItemsControls - /// that don't have DataTemplates, and assigns a default DataTemplate that - /// loads the View associated with each ViewModel. + /// Gets the default item template. /// - public class AutoDataTemplateBindingHook : IPropertyBindingHook + public static Lazy DefaultItemTemplate { get; } = new(() => { - /// - /// Gets the default item template. - /// - [SuppressMessage("Design", "CA1307: Use the currency locale settings", Justification = "Not available on all platforms.")] - public static Lazy DefaultItemTemplate { get; } = new(() => - { -#if NETFX_CORE || HAS_UNO - const string template = + const string template = @" "; - return (DataTemplate)XamlReader.Load(template); -#else - const string template = " " + - "" + - ""; + return (DataTemplate)XamlReader.Load(template); + }); - var assemblyName = typeof(AutoDataTemplateBindingHook).Assembly.FullName; - assemblyName = assemblyName?.Substring(0, assemblyName.IndexOf(',')); + /// + public bool ExecuteHook(object? source, object target, Func[]> getCurrentViewModelProperties, Func[]> getCurrentViewProperties, BindingDirection direction) + { + ArgumentNullException.ThrowIfNull(getCurrentViewProperties); -#if HAS_WINUI - return (DataTemplate)XamlReader.Load(template.Replace("__ASSEMBLYNAME__", assemblyName)); -#else - return (DataTemplate)XamlReader.Parse(template.Replace("__ASSEMBLYNAME__", assemblyName)); -#endif -#endif - }); + var viewProperties = getCurrentViewProperties(); + var lastViewProperty = viewProperties.LastOrDefault(); - /// - public bool ExecuteHook(object? source, object target, Func[]> getCurrentViewModelProperties, Func[]> getCurrentViewProperties, BindingDirection direction) + if (lastViewProperty?.Sender is not ItemsControl itemsControl) { - if (getCurrentViewProperties is null) - { - throw new ArgumentNullException(nameof(getCurrentViewProperties)); - } - - var viewProperties = getCurrentViewProperties(); - var lastViewProperty = viewProperties.LastOrDefault(); - - if (lastViewProperty?.Sender is not ItemsControl itemsControl) - { - return true; - } - - if (!string.IsNullOrEmpty(itemsControl.DisplayMemberPath)) - { - return true; - } + return true; + } - if (viewProperties.Last().GetPropertyName() != "ItemsSource") - { - return true; - } + if (!string.IsNullOrEmpty(itemsControl.DisplayMemberPath)) + { + return true; + } - if (itemsControl.ItemTemplate is not null) - { - return true; - } + if (viewProperties.Last().GetPropertyName() != "ItemsSource") + { + return true; + } - if (itemsControl.ItemTemplateSelector is not null) - { - return true; - } + if (itemsControl.ItemTemplate is not null) + { + return true; + } - itemsControl.ItemTemplate = DefaultItemTemplate.Value; + if (itemsControl.ItemTemplateSelector is not null) + { return true; } + + itemsControl.ItemTemplate = DefaultItemTemplate.Value; + return true; } } diff --git a/src/ReactiveUI.Uno/Common/BooleanToVisibilityHint.cs b/src/ReactiveUI.Uno/Common/BooleanToVisibilityHint.cs index 772378e..e85f467 100644 --- a/src/ReactiveUI.Uno/Common/BooleanToVisibilityHint.cs +++ b/src/ReactiveUI.Uno/Common/BooleanToVisibilityHint.cs @@ -4,43 +4,28 @@ // See the LICENSE file in the project root for full license information. using System; -using System.Diagnostics.CodeAnalysis; + #if HAS_WINUI using Microsoft.UI.Xaml; -#elif NETFX_CORE || HAS_UNO -using Windows.UI.Xaml; #else -using System.Windows; +using Windows.UI.Xaml; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI -#endif +namespace ReactiveUI.Uno; + +/// +/// Enum that hints at the visibility of a ui element. +/// +[Flags] +public enum BooleanToVisibilityHint { /// - /// Enum that hints at the visibility of a ui element. + /// Do not modify the boolean type conversion from it's default action of using the Visibility.Collapsed. /// - [SuppressMessage("Name", "CA1714: Flags enums should have plural names", Justification = "For legacy support")] - [Flags] - public enum BooleanToVisibilityHint - { - /// - /// Do not modify the boolean type conversion from it's default action of using the Visibility.Collapsed. - /// - None = 0, - - /// - /// Inverse the action of the boolean type conversion, when it's true collapse the visibility. - /// - Inverse = 1 << 1, + None = 0, -#if !NETFX_CORE && !HAS_UNO - /// - /// Use the hidden version rather than the Collapsed. - /// - UseHidden = 1 << 2, -#endif - } + /// + /// Inverse the action of the boolean type conversion, when it's true collapse the visibility. + /// + Inverse = 1 << 1, } diff --git a/src/ReactiveUI.Uno/Common/BooleanToVisibilityTypeConverter.cs b/src/ReactiveUI.Uno/Common/BooleanToVisibilityTypeConverter.cs index c945fa3..ec220d8 100644 --- a/src/ReactiveUI.Uno/Common/BooleanToVisibilityTypeConverter.cs +++ b/src/ReactiveUI.Uno/Common/BooleanToVisibilityTypeConverter.cs @@ -4,72 +4,60 @@ // See the LICENSE file in the project root for full license information. using System; + #if HAS_WINUI using Microsoft.UI.Xaml; -#elif NETFX_CORE || HAS_UNO -using Windows.UI.Xaml; #else -using System.Windows; +using Windows.UI.Xaml; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI -#endif +namespace ReactiveUI.Uno; + +/// +/// This type convert converts between Boolean and XAML Visibility - the +/// conversionHint is a BooleanToVisibilityHint. +/// +public class BooleanToVisibilityTypeConverter : IBindingTypeConverter { - /// - /// This type convert converts between Boolean and XAML Visibility - the - /// conversionHint is a BooleanToVisibilityHint. - /// - public class BooleanToVisibilityTypeConverter : IBindingTypeConverter + /// + public int GetAffinityForObjects(Type fromType, Type toType) { - /// - public int GetAffinityForObjects(Type fromType, Type toType) + if (fromType == typeof(bool) && toType == typeof(Visibility)) { - if (fromType == typeof(bool) && toType == typeof(Visibility)) - { - return 10; - } - - if (fromType == typeof(Visibility) && toType == typeof(bool)) - { - return 10; - } - - return 0; + return 10; } - /// - public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + if (fromType == typeof(Visibility) && toType == typeof(bool)) { - var hint = conversionHint is BooleanToVisibilityHint visibilityHint ? - visibilityHint : - BooleanToVisibilityHint.None; - - if (toType == typeof(Visibility) && from is bool fromBool) - { - var fromAsBool = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !fromBool : fromBool; + return 10; + } -#if !NETFX_CORE && !HAS_UNO && !HAS_WINUI - var notVisible = (hint & BooleanToVisibilityHint.UseHidden) != 0 ? Visibility.Hidden : Visibility.Collapsed; -#else - var notVisible = Visibility.Collapsed; -#endif - result = fromAsBool ? Visibility.Visible : notVisible; - return true; - } + return 0; + } - if (from is Visibility fromAsVis) - { - result = fromAsVis == Visibility.Visible ^ (hint & BooleanToVisibilityHint.Inverse) == 0; - } - else - { - result = Visibility.Visible; - } + /// + public bool TryConvert(object? from, Type toType, object? conversionHint, out object result) + { + var hint = conversionHint is BooleanToVisibilityHint visibilityHint ? + visibilityHint : + BooleanToVisibilityHint.None; + if (toType == typeof(Visibility) && from is bool fromBool) + { + var fromAsBool = (hint & BooleanToVisibilityHint.Inverse) != 0 ? !fromBool : fromBool; + result = fromAsBool ? Visibility.Visible : Visibility.Collapsed; return true; } + + if (from is Visibility fromAsVis) + { + result = fromAsVis == Visibility.Visible ^ (hint & BooleanToVisibilityHint.Inverse) == 0; + } + else + { + result = Visibility.Visible; + } + + return true; } } diff --git a/src/ReactiveUI.Uno/Common/PlatformOperations.cs b/src/ReactiveUI.Uno/Common/PlatformOperations.cs index 0d8850a..192b177 100644 --- a/src/ReactiveUI.Uno/Common/PlatformOperations.cs +++ b/src/ReactiveUI.Uno/Common/PlatformOperations.cs @@ -5,32 +5,23 @@ using System; -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI -#endif +namespace ReactiveUI.Uno; + +/// +/// Returns the current orientation of the device on Windows. +/// +public class PlatformOperations : IPlatformOperations { - /// - /// Returns the current orientation of the device on Windows. - /// - public class PlatformOperations : IPlatformOperations + /// + public string? GetOrientation() { - /// - public string? GetOrientation() + try + { + return Windows.Graphics.Display.DisplayInformation.GetForCurrentView().CurrentOrientation.ToString(); + } + catch (Exception) { -#if NETFX_CORE || HAS_UNO - try - { - return Windows.Graphics.Display.DisplayInformation.GetForCurrentView().CurrentOrientation.ToString(); - } - catch (Exception) - { - return null; - } -#else return null; -#endif } } } diff --git a/src/ReactiveUI.Uno/Common/ReactivePage.cs b/src/ReactiveUI.Uno/Common/ReactivePage.cs index 4b2f5d2..1cef2ae 100644 --- a/src/ReactiveUI.Uno/Common/ReactivePage.cs +++ b/src/ReactiveUI.Uno/Common/ReactivePage.cs @@ -4,171 +4,155 @@ // See the LICENSE file in the project root for full license information. using System; + #if HAS_WINUI using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -#elif NETFX_CORE || HAS_UNO +#else using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -#else -using System.Windows; -using System.Windows.Controls; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI +namespace ReactiveUI.Uno; + +/// +/// A that is reactive. +/// +/// +/// +/// This class is a that is also reactive. That is, it implements . +/// You can extend this class to get an implementation of rather than writing one yourself. +/// +/// +/// Note that the XAML for your control must specify the same base class, including the generic argument you provide for your view +/// model. To do this, use the TypeArguments attribute as follows: +/// +/// +/// +/// +/// ]]> +/// +/// +/// +/// Note that UWP and WinUI projects do not support the TypeArguments attribute. The XAML designer window in WPF projects also does not +/// support generic types. To use in XAML documents you need to create a base class +/// where you derive from with the type argument filled in. +/// +/// { /* No code needed here */ } +/// +/// public partial class YourView : YourViewBase +/// { +/// /* Your code */ +/// } +/// ]]> +/// +/// Then you can use this base class as root in your XAML document. +/// +/// +/// +/// +/// ]]> +/// +/// +/// +/// +/// The type of the view model backing the view. +/// +#if IOS +[global::Foundation.Register] #endif +public partial class ReactivePage : + Page, IViewFor + where TViewModel : class { /// - /// A that is reactive. + /// The view model dependency property. /// - /// - /// - /// This class is a that is also reactive. That is, it implements . - /// You can extend this class to get an implementation of rather than writing one yourself. - /// - /// - /// Note that the XAML for your control must specify the same base class, including the generic argument you provide for your view - /// model. To do this, use the TypeArguments attribute as follows: - /// - /// - /// - /// - /// ]]> - /// - /// - /// - /// Note that UWP and WinUI projects do not support the TypeArguments attribute. The XAML designer window in WPF projects also does not - /// support generic types. To use in XAML documents you need to create a base class - /// where you derive from with the type argument filled in. - /// - /// { /* No code needed here */ } - /// - /// public partial class YourView : YourViewBase - /// { - /// /* Your code */ - /// } - /// ]]> - /// - /// Then you can use this base class as root in your XAML document. - /// - /// - /// - /// - /// ]]> - /// - /// - /// - /// - /// The type of the view model backing the view. - /// -#if HAS_UNO && IOS - [global::Foundation.Register] -#endif - public -#if HAS_UNO - partial -#endif - class ReactivePage : - Page, IViewFor - where TViewModel : class - { - /// - /// The view model dependency property. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register( - "ViewModel", - typeof(TViewModel), - typeof(ReactivePage), - new PropertyMetadata(null)); + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register( + "ViewModel", + typeof(TViewModel), + typeof(ReactivePage), + new PropertyMetadata(null)); - static ReactivePage() - { - var a = ActivationHelper.UnoActivated; - } + static ReactivePage() => _ = ActivationHelper.UnoActivated; -#if HAS_UNO - /// - /// Initializes a new instance of the class. - /// - protected ReactivePage() - { - // needed so the others are optional. - } + /// + /// Initializes a new instance of the class. + /// + protected ReactivePage() + { + // needed so the others are optional. + } #if ANDROID - /// - /// Initializes a new instance of the class. - /// Native constructor, do not use explicitly. - /// - /// - /// Used by the Xamarin Runtime to materialize native - /// objects that may have been collected in the managed world. - /// - /// A containing a Java Native Interface (JNI) object reference. - /// A indicating how to handle. - protected ReactivePage(IntPtr javaReference, global::Android.Runtime.JniHandleOwnership transfer) - : base(javaReference, transfer) - { - } + /// + /// Initializes a new instance of the class. + /// Native constructor, do not use explicitly. + /// + /// + /// Used by the Xamarin Runtime to materialize native + /// objects that may have been collected in the managed world. + /// + /// A containing a Java Native Interface (JNI) object reference. + /// A indicating how to handle. + protected ReactivePage(IntPtr javaReference, global::Android.Runtime.JniHandleOwnership transfer) + : base(javaReference, transfer) + { + } #endif #if IOS - /// - /// Initializes a new instance of the class. - /// Native constructor, do not use explicitly. - /// - /// Handle to the native control. - /// - /// Used by the Xamarin Runtime to materialize native. - /// objects that may have been collected in the managed world. - /// - protected ReactivePage(IntPtr handle) - : base(handle) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// Native constructor, do not use explicitly. + /// + /// Handle to the native control. + /// + /// Used by the Xamarin Runtime to materialize native. + /// objects that may have been collected in the managed world. + /// + protected ReactivePage(IntPtr handle) + : base(handle) + { + } #endif - /// - /// Gets the binding root view model. - /// - public TViewModel? BindingRoot => ViewModel; + /// + /// Gets the binding root view model. + /// + public TViewModel? BindingRoot => ViewModel; - /// - public TViewModel? ViewModel - { - get => (TViewModel)GetValue(ViewModelProperty); - set => SetValue(ViewModelProperty, value); - } + /// + public TViewModel? ViewModel + { + get => (TViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (TViewModel?)value; - } + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TViewModel?)value; } } diff --git a/src/ReactiveUI.Uno/Common/ReactiveUserControl.cs b/src/ReactiveUI.Uno/Common/ReactiveUserControl.cs index aab9bca..1b8596e 100644 --- a/src/ReactiveUI.Uno/Common/ReactiveUserControl.cs +++ b/src/ReactiveUI.Uno/Common/ReactiveUserControl.cs @@ -4,173 +4,155 @@ // See the LICENSE file in the project root for full license information. using System; -using System.Diagnostics.CodeAnalysis; + #if HAS_WINUI using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -#elif NETFX_CORE || HAS_UNO +#else using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -#else -using System.Windows; -using System.Windows.Controls; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI +namespace ReactiveUI.Uno; + +/// +/// A that is reactive. +/// +/// +/// +/// This class is a that is also reactive. That is, it implements . +/// You can extend this class to get an implementation of rather than writing one yourself. +/// +/// +/// Note that the XAML for your control must specify the same base class, including the generic argument you provide for your view +/// model. To do this, use the TypeArguments attribute as follows: +/// +/// +/// +/// +/// ]]> +/// +/// +/// +/// Note that UWP and WinUI projects do not support the TypeArguments attribute. The XAML designer window in WPF projects also does not +/// support generic types. To use in XAML documents you need to create a base class +/// where you derive from with the type argument filled in. +/// +/// { /* No code needed here */ } +/// +/// public partial class YourView : YourViewBase +/// { +/// /* Your code */ +/// } +/// ]]> +/// +/// Then you can use this base class as root in your XAML document. +/// +/// +/// +/// +/// ]]> +/// +/// +/// +/// +/// The type of the view model backing the view. +/// +#if IOS +[global::Foundation.Register] #endif +public partial class ReactiveUserControl : + UserControl, IViewFor + where TViewModel : class { /// - /// A that is reactive. + /// The view model dependency property. /// - /// - /// - /// This class is a that is also reactive. That is, it implements . - /// You can extend this class to get an implementation of rather than writing one yourself. - /// - /// - /// Note that the XAML for your control must specify the same base class, including the generic argument you provide for your view - /// model. To do this, use the TypeArguments attribute as follows: - /// - /// - /// - /// - /// ]]> - /// - /// - /// - /// Note that UWP and WinUI projects do not support the TypeArguments attribute. The XAML designer window in WPF projects also does not - /// support generic types. To use in XAML documents you need to create a base class - /// where you derive from with the type argument filled in. - /// - /// { /* No code needed here */ } - /// - /// public partial class YourView : YourViewBase - /// { - /// /* Your code */ - /// } - /// ]]> - /// - /// Then you can use this base class as root in your XAML document. - /// - /// - /// - /// - /// ]]> - /// - /// - /// - /// - /// The type of the view model backing the view. - /// -#if HAS_UNO && IOS - [global::Foundation.Register] -#endif - [SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "Deliberate usage")] - public -#if HAS_UNO - partial -#endif - class ReactiveUserControl : - UserControl, IViewFor - where TViewModel : class - { - /// - /// The view model dependency property. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register( - "ViewModel", - typeof(TViewModel), - typeof(ReactiveUserControl), - new PropertyMetadata(null)); + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register( + "ViewModel", + typeof(TViewModel), + typeof(ReactiveUserControl), + new PropertyMetadata(null)); - static ReactiveUserControl() - { - var a = ActivationHelper.UnoActivated; - } + static ReactiveUserControl() => _ = ActivationHelper.UnoActivated; -#if HAS_UNO - /// - /// Initializes a new instance of the class. - /// - protected ReactiveUserControl() - { - // needed so the others are optional. - } + /// + /// Initializes a new instance of the class. + /// + protected ReactiveUserControl() + { + // needed so the others are optional. + } #if ANDROID - /// - /// Initializes a new instance of the class. - /// Native constructor, do not use explicitly. - /// - /// - /// Used by the Xamarin Runtime to materialize native - /// objects that may have been collected in the managed world. - /// - /// A containing a Java Native Interface (JNI) object reference. - /// A indicating how to handle handle. - protected ReactiveUserControl(IntPtr javaReference, global::Android.Runtime.JniHandleOwnership transfer) - : base(javaReference, transfer) - { - } + /// + /// Initializes a new instance of the class. + /// Native constructor, do not use explicitly. + /// + /// + /// Used by the Xamarin Runtime to materialize native + /// objects that may have been collected in the managed world. + /// + /// A containing a Java Native Interface (JNI) object reference. + /// A indicating how to handle handle. + protected ReactiveUserControl(IntPtr javaReference, global::Android.Runtime.JniHandleOwnership transfer) + : base(javaReference, transfer) + { + } #endif #if IOS - /// - /// Initializes a new instance of the class. - /// Native constructor, do not use explicitly. - /// - /// Handle to the native control. - /// - /// Used by the Xamarin Runtime to materialize native. - /// objects that may have been collected in the managed world. - /// - protected ReactiveUserControl(IntPtr handle) - : base(handle) - { - } -#endif + /// + /// Initializes a new instance of the class. + /// Native constructor, do not use explicitly. + /// + /// Handle to the native control. + /// + /// Used by the Xamarin Runtime to materialize native. + /// objects that may have been collected in the managed world. + /// + protected ReactiveUserControl(IntPtr handle) + : base(handle) + { + } #endif - /// - /// Gets the binding root view model. - /// - public TViewModel? BindingRoot => ViewModel; + /// + /// Gets the binding root view model. + /// + public TViewModel? BindingRoot => ViewModel; - /// - public TViewModel? ViewModel - { - get => (TViewModel)GetValue(ViewModelProperty); - set => SetValue(ViewModelProperty, value); - } + /// + public TViewModel? ViewModel + { + get => (TViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } - /// - object? IViewFor.ViewModel - { - get => ViewModel; - set => ViewModel = (TViewModel?)value; - } + /// + object? IViewFor.ViewModel + { + get => ViewModel; + set => ViewModel = (TViewModel?)value; } } diff --git a/src/ReactiveUI.Uno/Common/RoutedViewHost.cs b/src/ReactiveUI.Uno/Common/RoutedViewHost.cs index f4d12dd..4147571 100644 --- a/src/ReactiveUI.Uno/Common/RoutedViewHost.cs +++ b/src/ReactiveUI.Uno/Common/RoutedViewHost.cs @@ -10,181 +10,151 @@ #if HAS_WINUI using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -#elif NETFX_CORE || HAS_UNO -using System.Windows; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; #else - -using System.Windows; -using System.Windows.Controls; - +using Windows.UI.Xaml; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else +namespace ReactiveUI.Uno; -namespace ReactiveUI -#endif +/// +/// This control hosts the View associated with a Router, and will display +/// the View and wire up the ViewModel whenever a new ViewModel is +/// navigated to. Put this control as the only control in your Window. +/// +public partial class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger { /// - /// This control hosts the View associated with a Router, and will display - /// the View and wire up the ViewModel whenever a new ViewModel is - /// navigated to. Put this control as the only control in your Window. + /// The router dependency property. /// - public -#if HAS_UNO - partial -#endif - class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger + public static readonly DependencyProperty RouterProperty = + DependencyProperty.Register("Router", typeof(RoutingState), typeof(RoutedViewHost), new PropertyMetadata(null)); + + /// + /// The default content property. + /// + public static readonly DependencyProperty DefaultContentProperty = + DependencyProperty.Register("DefaultContent", typeof(object), typeof(RoutedViewHost), new PropertyMetadata(null)); + + /// + /// The view contract observable property. + /// + public static readonly DependencyProperty ViewContractObservableProperty = + DependencyProperty.Register("ViewContractObservable", typeof(IObservable), typeof(RoutedViewHost), new PropertyMetadata(Observable.Default)); + + private string? _viewContract; + + static RoutedViewHost() => _ = ActivationHelper.UnoActivated; + + /// + /// Initializes a new instance of the class. + /// + public RoutedViewHost() { - /// - /// The router dependency property. - /// - public static readonly DependencyProperty RouterProperty = - DependencyProperty.Register("Router", typeof(RoutingState), typeof(RoutedViewHost), new PropertyMetadata(null)); - - /// - /// The default content property. - /// - public static readonly DependencyProperty DefaultContentProperty = - DependencyProperty.Register("DefaultContent", typeof(object), typeof(RoutedViewHost), new PropertyMetadata(null)); - - /// - /// The view contract observable property. - /// - public static readonly DependencyProperty ViewContractObservableProperty = - DependencyProperty.Register("ViewContractObservable", typeof(IObservable), typeof(RoutedViewHost), new PropertyMetadata(Observable.Default)); - - private string? _viewContract; - - static RoutedViewHost() - { - var a = ActivationHelper.UnoActivated; - } + HorizontalContentAlignment = HorizontalAlignment.Stretch; + VerticalContentAlignment = VerticalAlignment.Stretch; - /// - /// Initializes a new instance of the class. - /// - public RoutedViewHost() - { -#if NETFX_CORE - DefaultStyleKey = typeof(RoutedViewHost); -#endif - HorizontalContentAlignment = HorizontalAlignment.Stretch; - VerticalContentAlignment = VerticalAlignment.Stretch; - - var platform = Locator.Current.GetService(); - Func platformGetter = () => default; - - if (platform is null) - { - // NB: This used to be an error but WPF design mode can't read - // good or do other stuff good. - this.Log().Error("Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); - } - else - { - platformGetter = () => platform.GetOrientation(); - } - - ViewContractObservable = ModeDetector.InUnitTestRunner() - ? Observable.Never - : Observable.FromEvent( - eventHandler => - { - void Handler(object sender, SizeChangedEventArgs e) => eventHandler(platformGetter()); - return Handler; - }, - x => SizeChanged += x, - x => SizeChanged -= x) - .StartWith(platformGetter()) - .DistinctUntilChanged(); - - IRoutableViewModel? currentViewModel = null; - var vmAndContract = this.WhenAnyObservable(x => x.Router.CurrentViewModel).Do(x => currentViewModel = x).StartWith(currentViewModel).CombineLatest( - this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract), - (viewModel, contract) => (viewModel, contract)); - - this.WhenActivated(d => - { - // NB: The DistinctUntilChanged is useful because most views in - // WinRT will end up getting here twice - once for configuring - // the RoutedViewHost's ViewModel, and once on load via SizeChanged - d(vmAndContract.DistinctUntilChanged<(IRoutableViewModel? viewModel, string? contract)>().Subscribe( - ResolveViewForViewModel, - ex => RxApp.DefaultExceptionHandler.OnNext(ex))); - }); - } + var platform = Locator.Current.GetService(); + Func platformGetter = () => default; - /// - /// Gets or sets the of the view model stack. - /// - public RoutingState Router + if (platform is null) { - get => (RoutingState)GetValue(RouterProperty); - set => SetValue(RouterProperty, value); + // NB: This used to be an error but WPF design mode can't read + // good or do other stuff good. + this.Log().Error("Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); } - - /// - /// Gets or sets the content displayed whenever there is no page currently - /// routed. - /// - public object DefaultContent + else { - get => GetValue(DefaultContentProperty); - set => SetValue(DefaultContentProperty, value); + platformGetter = () => platform.GetOrientation(); } - /// - /// Gets or sets the view contract observable. - /// - /// - /// The view contract observable. - /// - public IObservable ViewContractObservable + ViewContractObservable = ModeDetector.InUnitTestRunner() + ? Observable.Never + : Observable.FromEvent( + eventHandler => + { + void Handler(object sender, SizeChangedEventArgs e) => eventHandler(platformGetter()); + return Handler; + }, + x => SizeChanged += x, + x => SizeChanged -= x) + .StartWith(platformGetter()) + .DistinctUntilChanged(); + + IRoutableViewModel? currentViewModel = null; + var vmAndContract = this.WhenAnyObservable(x => x.Router.CurrentViewModel).Do(x => currentViewModel = x).StartWith(currentViewModel).CombineLatest( + this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract), + (viewModel, contract) => (viewModel, contract)); + + this.WhenActivated(d => { - get => (IObservable)GetValue(ViewContractObservableProperty); - set => SetValue(ViewContractObservableProperty, value); - } + // NB: The DistinctUntilChanged is useful because most views in + // WinRT will end up getting here twice - once for configuring + // the RoutedViewHost's ViewModel, and once on load via SizeChanged + d(vmAndContract.DistinctUntilChanged<(IRoutableViewModel? viewModel, string? contract)>().Subscribe( + ResolveViewForViewModel, + ex => RxApp.DefaultExceptionHandler.OnNext(ex))); + }); + } - /// - /// Gets or sets the view contract. - /// - public string? ViewContract - { - get => _viewContract; - set => ViewContractObservable = Observable.Return(value); - } + /// + /// Gets or sets the of the view model stack. + /// + public RoutingState Router + { + get => (RoutingState)GetValue(RouterProperty); + set => SetValue(RouterProperty, value); + } - /// - /// Gets or sets the view locator. - /// - /// - /// The view locator. - /// - public IViewLocator? ViewLocator { get; set; } + /// + /// Gets or sets the content displayed whenever there is no page currently + /// routed. + /// + public object DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } - private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) + /// + /// Gets or sets the view contract observable. + /// + /// + /// The view contract observable. + /// + public IObservable ViewContractObservable + { + get => (IObservable)GetValue(ViewContractObservableProperty); + set => SetValue(ViewContractObservableProperty, value); + } + + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => _viewContract; + set => ViewContractObservable = Observable.Return(value); + } + + /// + /// Gets or sets the view locator. + /// + /// + /// The view locator. + /// + public IViewLocator? ViewLocator { get; set; } + + private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) + { + if (x.viewModel is null) { - if (x.viewModel is null) - { - Content = DefaultContent; - return; - } - - var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var view = viewLocator.ResolveView(x.viewModel, x.contract) ?? viewLocator.ResolveView(x.viewModel); - - if (view is null) - { - throw new Exception($"Couldn't find view for '{x.viewModel}'."); - } - - view.ViewModel = x.viewModel; - Content = view; + Content = DefaultContent; + return; } + + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + var view = (viewLocator.ResolveView(x.viewModel, x.contract) ?? viewLocator.ResolveView(x.viewModel)) ?? throw new Exception($"Couldn't find view for '{x.viewModel}'."); + view.ViewModel = x.viewModel; + Content = view; } } diff --git a/src/ReactiveUI.Uno/Common/ViewModelViewHost.cs b/src/ReactiveUI.Uno/Common/ViewModelViewHost.cs index cc14f38..6da5f87 100644 --- a/src/ReactiveUI.Uno/Common/ViewModelViewHost.cs +++ b/src/ReactiveUI.Uno/Common/ViewModelViewHost.cs @@ -8,180 +8,150 @@ using Splat; #if HAS_WINUI - using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; - -#elif NETFX_CORE || HAS_UNO - -using System.Windows; +#else using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; +#endif -#else +namespace ReactiveUI.Uno; + +/// +/// This content control will automatically load the View associated with +/// the ViewModel property and display it. This control is very useful +/// inside a DataTemplate to display the View associated with a ViewModel. +/// +public partial class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger +{ + /// + /// The default content dependency property. + /// + public static readonly DependencyProperty DefaultContentProperty = + DependencyProperty.Register(nameof(DefaultContent), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null)); -using System.Windows; -using System.Windows.Controls; + /// + /// The view model dependency property. + /// + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null)); -#endif + /// + /// The view contract observable dependency property. + /// + public static readonly DependencyProperty ViewContractObservableProperty = + DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Default)); -#if HAS_UNO + private string? _viewContract; -namespace ReactiveUI.Uno -#else + static ViewModelViewHost() => _ = ActivationHelper.UnoActivated; -namespace ReactiveUI -#endif -{ /// - /// This content control will automatically load the View associated with - /// the ViewModel property and display it. This control is very useful - /// inside a DataTemplate to display the View associated with a ViewModel. + /// Initializes a new instance of the class. /// - public -#if HAS_UNO - partial -#endif - class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger + public ViewModelViewHost() { - /// - /// The default content dependency property. - /// - public static readonly DependencyProperty DefaultContentProperty = - DependencyProperty.Register(nameof(DefaultContent), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null)); - - /// - /// The view model dependency property. - /// - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null)); - - /// - /// The view contract observable dependency property. - /// - public static readonly DependencyProperty ViewContractObservableProperty = - DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Default)); - - private string? _viewContract; - - static ViewModelViewHost() - { - var a = ActivationHelper.UnoActivated; - } + var platform = Locator.Current.GetService(); + Func platformGetter = () => default; - /// - /// Initializes a new instance of the class. - /// - public ViewModelViewHost() + if (platform is null) { -#if NETFX_CORE - DefaultStyleKey = typeof(ViewModelViewHost); -#endif - - var platform = Locator.Current.GetService(); - Func platformGetter = () => default; - - if (platform is null) - { - // NB: This used to be an error but WPF design mode can't read - // good or do other stuff good. - this.Log().Error("Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); - } - else - { - platformGetter = () => platform.GetOrientation(); - } - - ViewContractObservable = ModeDetector.InUnitTestRunner() - ? Observable.Never - : Observable.FromEvent( - eventHandler => - { - void Handler(object? sender, SizeChangedEventArgs e) => eventHandler(platformGetter()!); - return Handler; - }, - x => SizeChanged += x, - x => SizeChanged -= x) - .StartWith(platformGetter()) - .DistinctUntilChanged(); - - var contractChanged = this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract); - var viewModelChanged = this.WhenAnyValue(x => x.ViewModel).StartWith(ViewModel); - var vmAndContract = contractChanged - .CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)); - - this.WhenActivated(d => - { - d(contractChanged - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(x => _viewContract = x ?? string.Empty)); - - d(vmAndContract.DistinctUntilChanged().Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract))); - }); + // NB: This used to be an error but WPF design mode can't read + // good or do other stuff good. + this.Log().Error("Couldn't find an IPlatformOperations implementation. Please make sure you have installed the latest version of the ReactiveUI packages for your platform. See https://reactiveui.net/docs/getting-started/installation for guidance."); } - - /// - /// Gets or sets the view contract observable. - /// - public IObservable ViewContractObservable + else { - get => (IObservable)GetValue(ViewContractObservableProperty); - set => SetValue(ViewContractObservableProperty, value); + platformGetter = () => platform.GetOrientation(); } - /// - /// Gets or sets the content displayed by default when no content is set. - /// - public object DefaultContent + ViewContractObservable = ModeDetector.InUnitTestRunner() + ? Observable.Never + : Observable.FromEvent( + eventHandler => + { + void Handler(object? sender, SizeChangedEventArgs e) => eventHandler(platformGetter()); + return Handler; + }, + x => SizeChanged += x, + x => SizeChanged -= x) + .StartWith(platformGetter()) + .DistinctUntilChanged(); + + var contractChanged = this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract); + var viewModelChanged = this.WhenAnyValue(x => x.ViewModel).StartWith(ViewModel); + var vmAndContract = contractChanged + .CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)); + + this.WhenActivated(d => { - get => GetValue(DefaultContentProperty); - set => SetValue(DefaultContentProperty, value); - } + d(contractChanged + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(x => _viewContract = x ?? string.Empty)); - /// - /// Gets or sets the ViewModel to display. - /// - public object? ViewModel - { - get => GetValue(ViewModelProperty); - set => SetValue(ViewModelProperty, value); - } + d(vmAndContract.DistinctUntilChanged().Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract))); + }); + } - /// - /// Gets or sets the view contract. - /// - public string? ViewContract - { - get => _viewContract; - set => ViewContractObservable = Observable.Return(value); - } + /// + /// Gets or sets the view contract observable. + /// + public IObservable ViewContractObservable + { + get => (IObservable)GetValue(ViewContractObservableProperty); + set => SetValue(ViewContractObservableProperty, value); + } - /// - /// Gets or sets the view locator. - /// - public IViewLocator? ViewLocator { get; set; } + /// + /// Gets or sets the content displayed by default when no content is set. + /// + public object DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } - private void ResolveViewForViewModel(object? viewModel, string? contract) - { - if (viewModel is null) - { - Content = DefaultContent; - return; - } + /// + /// Gets or sets the ViewModel to display. + /// + public object? ViewModel + { + get => GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } - var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var viewInstance = viewLocator.ResolveView(viewModel, contract) ?? viewLocator.ResolveView(viewModel); + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => _viewContract; + set => ViewContractObservable = Observable.Return(value); + } + + /// + /// Gets or sets the view locator. + /// + public IViewLocator? ViewLocator { get; set; } - if (viewInstance is null) - { - Content = DefaultContent; - this.Log().Warn($"The {nameof(ViewModelViewHost)} could not find a valid view for the view model of type {viewModel.GetType()} and value {viewModel}."); - return; - } + private void ResolveViewForViewModel(object? viewModel, string? contract) + { + if (viewModel is null) + { + Content = DefaultContent; + return; + } - viewInstance.ViewModel = viewModel; + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + var viewInstance = viewLocator.ResolveView(viewModel, contract) ?? viewLocator.ResolveView(viewModel); - Content = viewInstance; + if (viewInstance is null) + { + Content = DefaultContent; + this.Log().Warn($"The {nameof(ViewModelViewHost)} could not find a valid view for the view model of type {viewModel.GetType()} and value {viewModel}."); + return; } + + viewInstance.ViewModel = viewModel; + + Content = viewInstance; } } diff --git a/src/ReactiveUI.Uno/CoreDispatcherScheduler.cs b/src/ReactiveUI.Uno/CoreDispatcherScheduler.cs index f82b710..2117170 100644 --- a/src/ReactiveUI.Uno/CoreDispatcherScheduler.cs +++ b/src/ReactiveUI.Uno/CoreDispatcherScheduler.cs @@ -4,252 +4,251 @@ // See the LICENSE file in the project root for full license information. // - +#if !(WINDOWS10_0_19041_0) using System.Reactive.Disposables; using System.Runtime.ExceptionServices; using System.Threading; +using Windows.UI.Core; #if HAS_UNO_WINUI using Microsoft.UI.Xaml; -using Windows.UI.Core; #else -using Windows.UI.Core; using Windows.UI.Xaml; #endif -namespace System.Reactive.Concurrency +namespace System.Reactive.Concurrency; + +/// +/// Represents an object that schedules units of work on a . +/// +/// +/// This scheduler type is typically used indirectly through the and methods that use the current Dispatcher. +/// +[CLSCompliant(false)] +public sealed class CoreDispatcherScheduler : LocalScheduler, ISchedulerPeriodic { /// - /// Represents an object that schedules units of work on a . + /// Constructs a that schedules units of work on the given . /// - /// - /// This scheduler type is typically used indirectly through the and methods that use the current Dispatcher. - /// - [CLSCompliant(false)] - public sealed class CoreDispatcherScheduler : LocalScheduler, ISchedulerPeriodic + /// Dispatcher to schedule work on. + /// is null. + public CoreDispatcherScheduler(CoreDispatcher dispatcher) { - /// - /// Constructs a that schedules units of work on the given . - /// - /// Dispatcher to schedule work on. - /// is null. - public CoreDispatcherScheduler(CoreDispatcher dispatcher) - { - Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - Priority = CoreDispatcherPriority.Normal; - } + Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + Priority = CoreDispatcherPriority.Normal; + } - /// - /// Constructs a that schedules units of work on the given with the given priority. - /// - /// Dispatcher to schedule work on. - /// Priority for scheduled units of work. - /// is null. - public CoreDispatcherScheduler(CoreDispatcher dispatcher, CoreDispatcherPriority priority) - { - Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - Priority = priority; - } + /// + /// Constructs a that schedules units of work on the given with the given priority. + /// + /// Dispatcher to schedule work on. + /// Priority for scheduled units of work. + /// is null. + public CoreDispatcherScheduler(CoreDispatcher dispatcher, CoreDispatcherPriority priority) + { + Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + Priority = priority; + } - /// - /// Gets the scheduler that schedules work on the associated with the current Window. - /// - public static CoreDispatcherScheduler Current + /// + /// Gets the scheduler that schedules work on the associated with the current Window. + /// + public static CoreDispatcherScheduler Current + { + get { - get + var window = Window.Current; + if (window == null) { - var window = Window.Current; - if (window == null) - { - throw new InvalidOperationException("There is no current window that has been created."); - } - - return new CoreDispatcherScheduler(window.Dispatcher); + throw new InvalidOperationException("There is no current window that has been created."); } + + return new CoreDispatcherScheduler(window.Dispatcher); } + } - /// - /// Gets the associated with the . - /// - public CoreDispatcher Dispatcher { get; } - - /// - /// Gets the priority at which work is scheduled. - /// - public CoreDispatcherPriority Priority { get; } - - /// - /// Schedules an action to be executed on the dispatcher. - /// - /// The type of the state passed to the scheduled action. - /// State passed to the action to be executed. - /// Action to be executed. - /// The disposable object used to cancel the scheduled action (best effort). - /// is null. - public override IDisposable Schedule(TState state, Func action) + /// + /// Gets the associated with the . + /// + public CoreDispatcher Dispatcher { get; } + + /// + /// Gets the priority at which work is scheduled. + /// + public CoreDispatcherPriority Priority { get; } + + /// + /// Schedules an action to be executed on the dispatcher. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, Func action) + { + if (action == null) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } + throw new ArgumentNullException(nameof(action)); + } - var d = new SingleAssignmentDisposable(); + var d = new SingleAssignmentDisposable(); - var res = Dispatcher.RunAsync(Priority, () => + var res = Dispatcher.RunAsync(Priority, () => + { + if (!d.IsDisposed) { - if (!d.IsDisposed) + try + { + d.Disposable = action(this, state); + } + catch (Exception ex) { - try + // + // Work-around for the behavior of throwing from RunAsync not propagating + // the exception to the Application.UnhandledException event (as of W8RP) + // as our users have come to expect from previous XAML stacks using Rx. + // + // If we wouldn't do this, there be an observable behavioral difference + // between scheduling with TimeSpan.Zero or using this overload. + // + // For scheduler implementation guidance rules, see TaskPoolScheduler.cs + // in System.Reactive.PlatformServices\Reactive\Concurrency. + // + var timer = new DispatcherTimer { - d.Disposable = action(this, state); - } - catch (Exception ex) + Interval = TimeSpan.Zero + }; + timer.Tick += (_, _) => { - // - // Work-around for the behavior of throwing from RunAsync not propagating - // the exception to the Application.UnhandledException event (as of W8RP) - // as our users have come to expect from previous XAML stacks using Rx. - // - // If we wouldn't do this, there be an observable behavioral difference - // between scheduling with TimeSpan.Zero or using this overload. - // - // For scheduler implementation guidance rules, see TaskPoolScheduler.cs - // in System.Reactive.PlatformServices\Reactive\Concurrency. - // - var timer = new DispatcherTimer - { - Interval = TimeSpan.Zero - }; - timer.Tick += (_, _) => - { - timer.Stop(); - ExceptionDispatchInfo.Capture(ex).Throw(); - }; - - timer.Start(); - } - } - }); + timer.Stop(); + ExceptionDispatchInfo.Capture(ex).Throw(); + }; - return StableCompositeDisposable.Create( - d, - Disposable.Create(res, _ => _.Cancel()) - ); - } - - /// - /// Schedules an action to be executed after on the dispatcher, using a object. - /// - /// The type of the state passed to the scheduled action. - /// State passed to the action to be executed. - /// Action to be executed. - /// Relative time after which to execute the action. - /// The disposable object used to cancel the scheduled action (best effort). - /// is null. - public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); + timer.Start(); + } } + }); - var dt = Scheduler.Normalize(dueTime); - if (dt.Ticks == 0) - { - return Schedule(state, action); - } + return StableCompositeDisposable.Create( + d, + Disposable.Create(res, _ => _.Cancel()) + ); + } - return ScheduleSlow(state, dt, action); + /// + /// Schedules an action to be executed after on the dispatcher, using a object. + /// + /// The type of the state passed to the scheduled action. + /// State passed to the action to be executed. + /// Action to be executed. + /// Relative time after which to execute the action. + /// The disposable object used to cancel the scheduled action (best effort). + /// is null. + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); } - private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action) + var dt = Scheduler.Normalize(dueTime); + if (dt.Ticks == 0) { - var d = new MultipleAssignmentDisposable(); + return Schedule(state, action); + } - var timer = new DispatcherTimer(); + return ScheduleSlow(state, dt, action); + } - timer.Tick += (_, _) => - { - var t = Interlocked.Exchange(ref timer, null); - if (t != null) - { - try - { - d.Disposable = action(this, state); - } - finally - { - t.Stop(); -#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - action = null; -#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. - } - } - }; + private IDisposable ScheduleSlow(TState state, TimeSpan dueTime, Func action) + { + var d = new MultipleAssignmentDisposable(); - timer.Interval = dueTime; - timer.Start(); + var timer = new DispatcherTimer(); - d.Disposable = Disposable.Create(() => + timer.Tick += (_, _) => + { + var t = Interlocked.Exchange(ref timer, null); + if (t != null) { - var t = Interlocked.Exchange(ref timer, null); - if (t != null) + try + { + d.Disposable = action(this, state); + } + finally { t.Stop(); - action = (_, _) => Disposable.Empty; +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + action = null; +#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. } - }); + } + }; - return d; - } + timer.Interval = dueTime; + timer.Start(); - /// - /// Schedules a periodic piece of work on the dispatcher, using a object. - /// - /// The type of the state passed to the scheduled action. - /// Initial state passed to the action upon the first iteration. - /// Period for running the work periodically. - /// Action to be executed, potentially updating the state. - /// The disposable object used to cancel the scheduled recurring action (best effort). - /// is null. - /// is less than . - public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + d.Disposable = Disposable.Create(() => { - // - // According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid. - // Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there. - // - if (period < TimeSpan.Zero) + var t = Interlocked.Exchange(ref timer, null); + if (t != null) { - throw new ArgumentOutOfRangeException(nameof(period)); + t.Stop(); + action = (_, _) => Disposable.Empty; } + }); - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } + return d; + } + + /// + /// Schedules a periodic piece of work on the dispatcher, using a object. + /// + /// The type of the state passed to the scheduled action. + /// Initial state passed to the action upon the first iteration. + /// Period for running the work periodically. + /// Action to be executed, potentially updating the state. + /// The disposable object used to cancel the scheduled recurring action (best effort). + /// is null. + /// is less than . + public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) + { + // + // According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid. + // Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there. + // + if (period < TimeSpan.Zero) + { + throw new ArgumentOutOfRangeException(nameof(period)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } - var timer = new DispatcherTimer(); + var timer = new DispatcherTimer(); - var state1 = state; + var state1 = state; - timer.Tick += (_, _) => - { - state1 = action(state1); - }; + timer.Tick += (_, _) => + { + state1 = action(state1); + }; - timer.Interval = period; - timer.Start(); + timer.Interval = period; + timer.Start(); - return Disposable.Create(() => + return Disposable.Create(() => + { + var t = Interlocked.Exchange(ref timer, null); + if (t != null) { - var t = Interlocked.Exchange(ref timer, null); - if (t != null) - { - t.Stop(); - action = _ => _; - } - }); - } + t.Stop(); + action = _ => _; + } + }); } } +#endif diff --git a/src/ReactiveUI.Uno/DependencyObjectObservableForProperty.cs b/src/ReactiveUI.Uno/DependencyObjectObservableForProperty.cs index a106e56..c92a3f8 100644 --- a/src/ReactiveUI.Uno/DependencyObjectObservableForProperty.cs +++ b/src/ReactiveUI.Uno/DependencyObjectObservableForProperty.cs @@ -10,156 +10,149 @@ using System.Reactive.Linq; using System.Reflection; using Splat; + #if HAS_WINUI using Microsoft.UI.Xaml; #else using Windows.UI.Xaml; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI -#endif +namespace ReactiveUI.Uno; + +/// +/// Creates a observable for a property if available that is based on a DependencyProperty. +/// +public class DependencyObjectObservableForProperty : ICreatesObservableForProperty { - /// - /// Creates a observable for a property if available that is based on a DependencyProperty. - /// - public class DependencyObjectObservableForProperty : ICreatesObservableForProperty + /// + public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) { - /// - public int GetAffinityForObject(Type type, string propertyName, bool beforeChanged = false) + if (!typeof(DependencyObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { - if (!typeof(DependencyObject).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - { - return 0; - } - - if (GetDependencyPropertyFetcher(type, propertyName) is null) - { - return 0; - } - - return 6; + return 0; } - /// - public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) + if (GetDependencyPropertyFetcher(type, propertyName) is null) { - if (sender is null) - { - throw new ArgumentNullException(nameof(sender)); - } + return 0; + } - if (sender is not DependencyObject depSender) - { - throw new ArgumentException("The sender must be a DependencyObject", nameof(sender)); - } + return 6; + } - var type = sender.GetType(); + /// + public IObservable> GetNotificationForProperty(object sender, Expression expression, string propertyName, bool beforeChanged = false, bool suppressWarnings = false) + { + ArgumentNullException.ThrowIfNull(sender); - 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, suppressWarnings); - } + if (sender is not DependencyObject depSender) + { + throw new ArgumentException("The sender must be a DependencyObject", nameof(sender)); + } - var dpFetcher = GetDependencyPropertyFetcher(type, propertyName); - if (dpFetcher is 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, suppressWarnings); - } + var type = sender.GetType(); - return Observable.Create>(subj => - { - var handler = new DependencyPropertyChangedCallback((_, _) => - subj.OnNext(new ObservedChange(sender, expression, default))); + 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, suppressWarnings); + } - var dependencyProperty = dpFetcher(); - var token = depSender.RegisterPropertyChangedCallback(dependencyProperty, handler); - return Disposable.Create(() => depSender.UnregisterPropertyChangedCallback(dependencyProperty, token)); - }); + var dpFetcher = GetDependencyPropertyFetcher(type, propertyName); + if (dpFetcher is 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, suppressWarnings); } - private static PropertyInfo? ActuallyGetProperty(TypeInfo typeInfo, string propertyName) + return Observable.Create>(subj => { - var current = typeInfo; - while (current is not null) - { - var ret = current.GetDeclaredProperty(propertyName); - if (ret?.IsStatic() == true) - { - return ret; - } + var handler = new DependencyPropertyChangedCallback((_, _) => + subj.OnNext(new ObservedChange(sender, expression, default))); + + var dependencyProperty = dpFetcher(); + var token = depSender.RegisterPropertyChangedCallback(dependencyProperty, handler); + return Disposable.Create(() => depSender.UnregisterPropertyChangedCallback(dependencyProperty, token)); + }); + } - current = current.BaseType?.GetTypeInfo(); + private static PropertyInfo? ActuallyGetProperty(TypeInfo typeInfo, string propertyName) + { + var current = typeInfo; + while (current is not null) + { + var ret = current.GetDeclaredProperty(propertyName); + if (ret?.IsStatic() == true) + { + return ret; } - return null; + current = current.BaseType?.GetTypeInfo(); } - private static FieldInfo? ActuallyGetField(TypeInfo typeInfo, string propertyName) + return null; + } + + private static FieldInfo? ActuallyGetField(TypeInfo typeInfo, string propertyName) + { + var current = typeInfo; + while (current is not null) { - var current = typeInfo; - while (current is not null) + var ret = current.GetDeclaredField(propertyName); + if (ret?.IsStatic == true) { - var ret = current.GetDeclaredField(propertyName); - if (ret?.IsStatic == true) - { - return ret; - } - - current = current.BaseType?.GetTypeInfo(); + return ret; } - return null; + current = current.BaseType?.GetTypeInfo(); } - private static Func? GetDependencyPropertyFetcher(Type type, string propertyName) - { - var typeInfo = type.GetTypeInfo(); + return null; + } - // Look for the DependencyProperty attached to this property name - var pi = ActuallyGetProperty(typeInfo, propertyName + "Property"); - if (pi is not null) - { - var value = pi.GetValue(null); + private static Func? GetDependencyPropertyFetcher(Type type, string propertyName) + { + var typeInfo = type.GetTypeInfo(); - if (value is null) - { - return null; - } + // Look for the DependencyProperty attached to this property name + var pi = ActuallyGetProperty(typeInfo, propertyName + "Property"); + if (pi is not null) + { + var value = pi.GetValue(null); - return () => (DependencyProperty)value; + if (value is null) + { + return null; } - var fi = ActuallyGetField(typeInfo, propertyName + "Property"); - if (fi is not null) - { - var value = fi.GetValue(null); + return () => (DependencyProperty)value; + } - if (value is null) - { - return null; - } + var fi = ActuallyGetField(typeInfo, propertyName + "Property"); + if (fi is not null) + { + var value = fi.GetValue(null); - return () => (DependencyProperty)value; + if (value is null) + { + return null; } - return null; + return () => (DependencyProperty)value; } + + return null; } } diff --git a/src/ReactiveUI.Uno/ReactiveUI.Uno.csproj b/src/ReactiveUI.Uno/ReactiveUI.Uno.csproj index 140c7b9..87d734f 100644 --- a/src/ReactiveUI.Uno/ReactiveUI.Uno.csproj +++ b/src/ReactiveUI.Uno/ReactiveUI.Uno.csproj @@ -1,32 +1,12 @@  - netstandard2.0;MonoAndroid12.0;MonoAndroid12.1;MonoAndroid13.0;Xamarin.iOS10;Xamarin.Mac20;net6.0-android;net6.0-ios;net6.0-macos;net6.0-maccatalyst;net7.0-android;net7.0-ios;net7.0-macos;net7.0-maccatalyst + $(UnoTargetFrameworks) ReactiveUI.Uno Contains the ReactiveUI platform specific extensions for Uno - $(DefineConstants);HAS_UNO - $(NoWarn);SA1648;CA1816;CA1001;CS0108;CS0114;CS3021;CS1574;CA1303 - mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;net;unoplatform - - $(DefineConstants);WASM - - - - - c:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Xamarin.VisualStudio\Xamarin.Mac.dll - c:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\Extensions\Xamarin.VisualStudio\Xamarin.Mac.dll - c:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Xamarin.VisualStudio\Xamarin.Mac.dll - c:\Program Files\Microsoft Visual Studio\2022\Preview\Common7\IDE\Extensions\Xamarin.VisualStudio\Xamarin.Mac.dll - - - - - - - - + diff --git a/src/ReactiveUI.Uno/Registrations.cs b/src/ReactiveUI.Uno/Registrations.cs index 64d4fe6..76c72f7 100644 --- a/src/ReactiveUI.Uno/Registrations.cs +++ b/src/ReactiveUI.Uno/Registrations.cs @@ -5,58 +5,47 @@ using System; using System.Reactive.Concurrency; -using System.Reactive.PlatformServices; -namespace ReactiveUI.Uno +namespace ReactiveUI.Uno; + +/// +/// UWP platform registrations. +/// +/// +public class Registrations : IWantsToRegisterStuff { - /// - /// UWP platform registrations. - /// - /// - public class Registrations : IWantsToRegisterStuff + /// + public void Register(Action, Type> registerFunction) { - /// - public void Register(Action, Type> registerFunction) - { - if (registerFunction is null) - { - throw new ArgumentNullException(nameof(registerFunction)); - } + ArgumentNullException.ThrowIfNull(registerFunction); - registerFunction(() => new PlatformOperations(), typeof(IPlatformOperations)); - registerFunction(() => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher)); - registerFunction(() => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty)); - registerFunction(() => new StringConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new ByteToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new NullableByteToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new ShortToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new NullableShortToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new IntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new NullableIntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new LongToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new NullableLongToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new SingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new NullableSingleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new DoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new NullableDoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new DecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new NullableDecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new BooleanToVisibilityTypeConverter(), typeof(IBindingTypeConverter)); - registerFunction(() => new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); - registerFunction(() => new WinRTAppDataDriver(), typeof(ISuspensionDriver)); + registerFunction(() => new PlatformOperations(), typeof(IPlatformOperations)); + registerFunction(() => new ActivationForViewFetcher(), typeof(IActivationForViewFetcher)); + registerFunction(() => new DependencyObjectObservableForProperty(), typeof(ICreatesObservableForProperty)); + registerFunction(() => new StringConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new ByteToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new NullableByteToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new ShortToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new NullableShortToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new IntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new NullableIntegerToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new LongToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new NullableLongToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new SingleToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new NullableSingleToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new DoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new NullableDoubleToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new DecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new NullableDecimalToStringTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new BooleanToVisibilityTypeConverter(), typeof(IBindingTypeConverter)); + registerFunction(() => new AutoDataTemplateBindingHook(), typeof(IPropertyBindingHook)); + registerFunction(() => new WinRTAppDataDriver(), typeof(ISuspensionDriver)); -#if NETSTANDARD - if (WasmPlatformEnlightenmentProvider.IsWasm) - { - RxApp.TaskpoolScheduler = WasmScheduler.Default; - RxApp.MainThreadScheduler = WasmScheduler.Default; - } - else + RxApp.TaskpoolScheduler = TaskPoolScheduler.Default; +#if WINDOWS10_0_19041_0 + RxApp.MainThreadScheduler = new WaitForDispatcherScheduler(() => DispatcherQueueScheduler.Current); +#else + RxApp.MainThreadScheduler = new WaitForDispatcherScheduler(() => CoreDispatcherScheduler.Current); #endif - { - RxApp.TaskpoolScheduler = TaskPoolScheduler.Default; - RxApp.MainThreadScheduler = new WaitForDispatcherScheduler(() => CoreDispatcherScheduler.Current); - } - } } } diff --git a/src/ReactiveUI.Uno/TransitioningContentControl.Empty.cs b/src/ReactiveUI.Uno/TransitioningContentControl.Empty.cs index d5f0ab1..69db1bb 100644 --- a/src/ReactiveUI.Uno/TransitioningContentControl.Empty.cs +++ b/src/ReactiveUI.Uno/TransitioningContentControl.Empty.cs @@ -3,28 +3,17 @@ // 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.Diagnostics.CodeAnalysis; #if HAS_WINUI using Microsoft.UI.Xaml.Controls; #else using Windows.UI.Xaml.Controls; #endif -#if HAS_UNO -namespace ReactiveUI.Uno -#else -namespace ReactiveUI -#endif +namespace ReactiveUI.Uno; + +/// +/// A control with a single transition. +/// +public partial class TransitioningContentControl : ContentControl { - /// - /// A control with a single transition. - /// - [SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "Deliberate usage")] - public -#if HAS_UNO - partial -#endif - class TransitioningContentControl : ContentControl - { - } } diff --git a/src/ReactiveUI.Uno/WinRTAppDataDriver.cs b/src/ReactiveUI.Uno/WinRTAppDataDriver.cs index 563cbc9..fc6f62c 100644 --- a/src/ReactiveUI.Uno/WinRTAppDataDriver.cs +++ b/src/ReactiveUI.Uno/WinRTAppDataDriver.cs @@ -14,7 +14,7 @@ using Windows.Storage; using UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding; -namespace ReactiveUI; +namespace ReactiveUI.Uno; /// /// Loads and saves state to persistent storage. @@ -39,10 +39,7 @@ public IObservable LoadState() => /// public IObservable SaveState(object state) { - if (state is null) - { - throw new ArgumentNullException(nameof(state)); - } + ArgumentNullException.ThrowIfNull(state); try { diff --git a/src/global.json b/src/global.json index 781c465..5832575 100644 --- a/src/global.json +++ b/src/global.json @@ -1,8 +1,7 @@ { "sdk": { - "version": "7.0", - "rollForward": "latestMinor", - "allowPrerelease": true + "version": "8.0.10", + "rollForward": "latestFeature" }, "msbuild-sdks": { "MSBuild.Sdk.Extras": "3.0.44"