From 8e4bd7a213210feff42ade340a8be6624e9f5c64 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 11 Dec 2023 21:02:42 -0600 Subject: [PATCH 1/3] Fixed compositor nullreference in WinUI 3 --- .../src/Brushes/BackdropGammaTransferBrush.cs | 11 +++++++++-- .../Media/src/Brushes/CanvasBrushBase.cs | 10 ++++++++-- .../Media/src/Brushes/ImageBlendBrush.cs | 13 ++++++++++--- .../src/Helpers/SurfaceLoader.Instance.cs | 7 ++++++- components/Media/src/Helpers/SurfaceLoader.cs | 4 ++++ .../PipelineBuilder.Initialization.cs | 7 ++++++- .../Media/src/Pipelines/PipelineBuilder.cs | 18 +++++++++++++++--- 7 files changed, 58 insertions(+), 12 deletions(-) diff --git a/components/Media/src/Brushes/BackdropGammaTransferBrush.cs b/components/Media/src/Brushes/BackdropGammaTransferBrush.cs index 1b7de60b..c0129f64 100644 --- a/components/Media/src/Brushes/BackdropGammaTransferBrush.cs +++ b/components/Media/src/Brushes/BackdropGammaTransferBrush.cs @@ -345,7 +345,14 @@ protected override void OnConnected() return; } - var backdrop = Window.Current.Compositor.CreateBackdropBrush(); + +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + + var backdrop = compositor.CreateBackdropBrush(); // Use a Win2D blur affect applied to a CompositionBackdropBrush. var graphicsEffect = new GammaTransferEffect @@ -370,7 +377,7 @@ protected override void OnConnected() Source = new CompositionEffectSourceParameter("backdrop") }; - var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect, new[] + var effectFactory = compositor.CreateEffectFactory(graphicsEffect, new[] { "GammaTransfer.AlphaAmplitude", "GammaTransfer.AlphaExponent", diff --git a/components/Media/src/Brushes/CanvasBrushBase.cs b/components/Media/src/Brushes/CanvasBrushBase.cs index 55cd1be0..b841dd95 100644 --- a/components/Media/src/Brushes/CanvasBrushBase.cs +++ b/components/Media/src/Brushes/CanvasBrushBase.cs @@ -66,7 +66,13 @@ protected override void OnConnected() _graphics.RenderingDeviceReplaced -= CanvasDevice_RenderingDeviceReplaced; } - _graphics = CanvasComposition.CreateCompositionGraphicsDevice(Window.Current.Compositor, _device); +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + + _graphics = CanvasComposition.CreateCompositionGraphicsDevice(compositor, _device); _graphics.RenderingDeviceReplaced += CanvasDevice_RenderingDeviceReplaced; // Delay creating composition resources until they're required. @@ -95,7 +101,7 @@ protected override void OnConnected() } } - _surfaceBrush = Window.Current.Compositor.CreateSurfaceBrush(surface); + _surfaceBrush = compositor.CreateSurfaceBrush(surface); _surfaceBrush.Stretch = CompositionStretch.Fill; CompositionBrush = _surfaceBrush; diff --git a/components/Media/src/Brushes/ImageBlendBrush.cs b/components/Media/src/Brushes/ImageBlendBrush.cs index 4f5123ba..8f541a46 100644 --- a/components/Media/src/Brushes/ImageBlendBrush.cs +++ b/components/Media/src/Brushes/ImageBlendBrush.cs @@ -126,6 +126,12 @@ private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedE /// protected override void OnConnected() { +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + // Delay creating composition resources until they're required. if (CompositionBrush == null && Source != null && Source is BitmapImage bitmap) { @@ -133,8 +139,9 @@ protected override void OnConnected() // If UriSource is invalid, StartLoadFromUri will return a blank texture. _surface = LoadedImageSurface.StartLoadFromUri(bitmap.UriSource); + // Load Surface onto SurfaceBrush - _surfaceBrush = Window.Current.Compositor.CreateSurfaceBrush(_surface); + _surfaceBrush = compositor.CreateSurfaceBrush(_surface); _surfaceBrush.Stretch = CompositionStretchFromStretch(Stretch); #if WINUI2 @@ -150,7 +157,7 @@ protected override void OnConnected() return; } - var backdrop = Window.Current.Compositor.CreateBackdropBrush(); + var backdrop = compositor.CreateBackdropBrush(); // Use a Win2D invert affect applied to a CompositionBackdropBrush. var graphicsEffect = new CanvasBlendEffect @@ -161,7 +168,7 @@ protected override void OnConnected() Foreground = new CompositionEffectSourceParameter("image") }; - var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect); + var effectFactory = compositor.CreateEffectFactory(graphicsEffect); var effectBrush = effectFactory.CreateBrush(); effectBrush.SetSourceParameter("backdrop", backdrop); diff --git a/components/Media/src/Helpers/SurfaceLoader.Instance.cs b/components/Media/src/Helpers/SurfaceLoader.Instance.cs index 013e4aad..97245ece 100644 --- a/components/Media/src/Helpers/SurfaceLoader.Instance.cs +++ b/components/Media/src/Helpers/SurfaceLoader.Instance.cs @@ -43,7 +43,12 @@ public sealed partial class SurfaceLoader : IDisposable /// A instance to use in the current window public static SurfaceLoader GetInstance() { - return GetInstance(Window.Current.Compositor); +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + return GetInstance(compositor); } /// diff --git a/components/Media/src/Helpers/SurfaceLoader.cs b/components/Media/src/Helpers/SurfaceLoader.cs index ea40b913..813e65b8 100644 --- a/components/Media/src/Helpers/SurfaceLoader.cs +++ b/components/Media/src/Helpers/SurfaceLoader.cs @@ -44,7 +44,11 @@ public sealed partial class SurfaceLoader /// A that returns the loaded instance public static async Task LoadImageAsync(Uri uri, DpiMode dpiMode, CacheMode cacheMode = CacheMode.Default) { +#if WINUI2 var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif // Lock and check the cache first using (await Win2DMutex.LockAsync()) diff --git a/components/Media/src/Pipelines/PipelineBuilder.Initialization.cs b/components/Media/src/Pipelines/PipelineBuilder.Initialization.cs index 8997d397..12aa971a 100644 --- a/components/Media/src/Pipelines/PipelineBuilder.Initialization.cs +++ b/components/Media/src/Pipelines/PipelineBuilder.Initialization.cs @@ -46,7 +46,12 @@ public static PipelineBuilder FromBackdrop() { ValueTask Factory() { - var brush = BackdropBrushCache.GetValue(Window.Current.Compositor, c => c.CreateBackdropBrush()); +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + var brush = BackdropBrushCache.GetValue(compositor, c => c.CreateBackdropBrush()); return new ValueTask(brush); } diff --git a/components/Media/src/Pipelines/PipelineBuilder.cs b/components/Media/src/Pipelines/PipelineBuilder.cs index 4057c7ea..ff5db887 100644 --- a/components/Media/src/Pipelines/PipelineBuilder.cs +++ b/components/Media/src/Pipelines/PipelineBuilder.cs @@ -160,6 +160,12 @@ private PipelineBuilder( [Pure] public async Task BuildAsync() { +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + var effect = await this.sourceProducer() as IGraphicsEffect; // Validate the pipeline @@ -170,8 +176,8 @@ public async Task BuildAsync() // Build the effects factory var factory = this.animationProperties.Count > 0 - ? Window.Current.Compositor.CreateEffectFactory(effect, this.animationProperties) - : Window.Current.Compositor.CreateEffectFactory(effect); + ? compositor.CreateEffectFactory(effect, this.animationProperties) + : compositor.CreateEffectFactory(effect); // Create the effect factory and apply the final effect var effectBrush = factory.CreateBrush(); @@ -191,7 +197,13 @@ public async Task BuildAsync() /// A that returns the final instance to use public async Task AttachAsync(UIElement target, UIElement? reference = null) { - SpriteVisual visual = Window.Current.Compositor.CreateSpriteVisual(); +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + + SpriteVisual visual = compositor.CreateSpriteVisual(); visual.Brush = await BuildAsync(); From 6a4b8639e122a07d1a9cd0960df8c9a788f4bb91 Mon Sep 17 00:00:00 2001 From: Dub1shu Date: Fri, 5 Jan 2024 20:34:52 +0900 Subject: [PATCH 2/3] Fix crash on setting string.Empty in SettingsCard --- .../src/SettingsCard/SettingsCard.cs | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/components/SettingsControls/src/SettingsCard/SettingsCard.cs b/components/SettingsControls/src/SettingsCard/SettingsCard.cs index ba645331..cd691bb4 100644 --- a/components/SettingsControls/src/SettingsCard/SettingsCard.cs +++ b/components/SettingsControls/src/SettingsCard/SettingsCard.cs @@ -247,20 +247,20 @@ private void OnDescriptionChanged() { if (GetTemplateChild(DescriptionPresenter) is FrameworkElement descriptionPresenter) { - descriptionPresenter.Visibility = Description != null - ? Visibility.Visible - : Visibility.Collapsed; + descriptionPresenter.Visibility = IsNullOrEmptyString(Description) + ? Visibility.Collapsed + : Visibility.Visible; } - + } private void OnHeaderChanged() { if (GetTemplateChild(HeaderPresenter) is FrameworkElement headerPresenter) { - headerPresenter.Visibility = Header != null - ? Visibility.Visible - : Visibility.Collapsed; + headerPresenter.Visibility = IsNullOrEmptyString(Header) + ? Visibility.Collapsed + : Visibility.Visible; } } @@ -274,7 +274,7 @@ private void CheckVerticalSpacingState(VisualState s) { // On state change, checking if the Content should be wrapped (e.g. when the card is made smaller or the ContentAlignment is set to Vertical). If the Content and the Header or Description are not null, we add spacing between the Content and the Header/Description. - if (s != null && (s.Name == RightWrappedState || s.Name == RightWrappedNoIconState || s.Name == VerticalState) && (Content != null) && (Header != null || Description != null)) + if (s != null && (s.Name == RightWrappedState || s.Name == RightWrappedNoIconState || s.Name == VerticalState) && (Content != null) && (!IsNullOrEmptyString(Header) || !IsNullOrEmptyString(Description))) { VisualStateManager.GoToState(this, ContentSpacingState, true); } @@ -295,4 +295,19 @@ private void CheckVerticalSpacingState(VisualState s) return FocusManager.GetFocusedElement() as FrameworkElement; } } + + private static bool IsNullOrEmptyString(object obj) + { + if (obj == null) + { + return true; + } + + if (obj is string objString && objString == string.Empty) + { + return true; + } + + return false; + } } From eb98b48266df91635fe7d3ee3fed5fa17b8cf928 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 8 Jan 2024 20:02:21 -0600 Subject: [PATCH 3/3] Added tests for #310 --- .../SettingsControls/tests/Test_SettingsCard.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/components/SettingsControls/tests/Test_SettingsCard.cs b/components/SettingsControls/tests/Test_SettingsCard.cs index f4928c53..3ba535b6 100644 --- a/components/SettingsControls/tests/Test_SettingsCard.cs +++ b/components/SettingsControls/tests/Test_SettingsCard.cs @@ -11,6 +11,20 @@ namespace SettingsControlsExperiment.Tests; [TestClass] public partial class SettingsCardTestClass : VisualUITestBase { + [UIThreadTestMethod] + public void EmptyNameTest(SettingsCard card) + { + // See https://github.com/CommunityToolkit/Windows/issues/310#issue-2066181868 + card.Name = string.Empty; + } + + [UIThreadTestMethod] + public void EmptyDescriptionTest(SettingsCard card) + { + // See https://github.com/CommunityToolkit/Windows/issues/310#issue-2066181868 + card.Description = string.Empty; + } + // If you don't need access to UI objects directly or async code, use this pattern. [TestMethod] public void SimpleSynchronousExampleTest()