Skip to content

Commit b47f214

Browse files
Remove M.T/CT.Uwp/WinUI.UI dependency, internalize helpers
Need to remove the dependency as that can't build the new version of it due to namespace conflicts with the global usings. Once we have a package version of the new unified version, we can investigate a better solution for building in both modes.
1 parent 66ab9fa commit b47f214

7 files changed

Lines changed: 216 additions & 8 deletions

File tree

CommunityToolkit.Tests.Shared/CommunityToolkit.Tests.Shared.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
<Compile Include="$(MSBuildThisFileDirectory)App.xaml.cs">
1919
<DependentUpon>App.xaml</DependentUpon>
2020
</Compile>
21+
<Compile Include="$(MSBuildThisFileDirectory)Internal\CompositionTargetHelper.cs" />
22+
<Compile Include="$(MSBuildThisFileDirectory)Internal\DispatcherQueueExtensions.cs" />
2123
<Compile Include="$(MSBuildThisFileDirectory)Log.cs" />
2224
<Compile Include="$(MSBuildThisFileDirectory)VisualUITestBase.cs" />
2325
</ItemGroup>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.Tests.Internal;
6+
7+
/// <summary>
8+
/// Provides helpers for the <see cref="CompositionTarget"/> class.
9+
/// </summary>
10+
public static class CompositionTargetHelper
11+
{
12+
/// <summary>
13+
/// Provides a method to execute code after the rendering pass is completed.
14+
/// <seealso href="https://github.com/microsoft/microsoft-ui-xaml/blob/c045cde57c5c754683d674634a0baccda34d58c4/dev/dll/SharedHelpers.cpp#L399"/>
15+
/// <seealso href="https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/"/>
16+
/// </summary>
17+
/// <param name="action">Action to be executed after render pass</param>
18+
/// <param name="options"><see cref="TaskCreationOptions"/> for how to handle async calls with <see cref="TaskCompletionSource{TResult}"/>.</param>
19+
/// <returns>Awaitable Task</returns>
20+
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action, TaskCreationOptions? options = null)
21+
{
22+
if (action is null)
23+
{
24+
ThrowArgumentNullException();
25+
}
26+
27+
TaskCompletionSource<bool> taskCompletionSource = options.HasValue ? new(options.Value)
28+
: new();
29+
30+
try
31+
{
32+
void Callback(object? sender, object args)
33+
{
34+
CompositionTarget.Rendering -= Callback;
35+
36+
action!();
37+
38+
taskCompletionSource.SetResult(true);
39+
}
40+
41+
CompositionTarget.Rendering += Callback;
42+
}
43+
catch (Exception e)
44+
{
45+
taskCompletionSource.SetException(e); // Note this can just sometimes be a wrong thread exception, see WinUI function notes.
46+
}
47+
48+
return taskCompletionSource.Task;
49+
50+
static void ThrowArgumentNullException() => throw new ArgumentNullException("The parameter \"action\" must not be null.");
51+
}
52+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Runtime.CompilerServices;
6+
7+
#if WINAPPSDK
8+
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
9+
using DispatcherQueuePriority = Microsoft.UI.Dispatching.DispatcherQueuePriority;
10+
#else
11+
using Windows.Foundation.Metadata;
12+
using DispatcherQueue = Windows.System.DispatcherQueue;
13+
using DispatcherQueuePriority = Windows.System.DispatcherQueuePriority;
14+
#endif
15+
16+
namespace CommunityToolkit.Tests.Internal;
17+
18+
//// Local internal copy of our queue extension for the VisualUITestBase class until we figure out how to swap between package and source.
19+
20+
/// <summary>
21+
/// Helpers for executing code in a <see cref="DispatcherQueue"/>.
22+
/// </summary>
23+
public static class DispatcherQueueExtensions
24+
{
25+
/// <summary>
26+
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
27+
/// <see cref="Task"/> that completes when the invocation of the function is completed.
28+
/// </summary>
29+
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
30+
/// <param name="function">The <see cref="Action"/> to invoke.</param>
31+
/// <param name="priority">The priority level for the function to invoke.</param>
32+
/// <returns>A <see cref="Task"/> that completes when the invocation of <paramref name="function"/> is over.</returns>
33+
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
34+
public static Task EnqueueAsync(this DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
35+
{
36+
// Run the function directly when we have thread access.
37+
// Also reuse Task.CompletedTask in case of success,
38+
// to skip an unnecessary heap allocation for every invocation.
39+
if (dispatcher.HasThreadAccess)
40+
{
41+
try
42+
{
43+
function();
44+
45+
return Task.CompletedTask;
46+
}
47+
catch (Exception e)
48+
{
49+
return Task.FromException(e);
50+
}
51+
}
52+
53+
static Task TryEnqueueAsync(DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority)
54+
{
55+
var taskCompletionSource = new TaskCompletionSource<object?>();
56+
57+
if (!dispatcher.TryEnqueue(priority, () =>
58+
{
59+
try
60+
{
61+
function();
62+
63+
taskCompletionSource.SetResult(null);
64+
}
65+
catch (Exception e)
66+
{
67+
taskCompletionSource.SetException(e);
68+
}
69+
}))
70+
{
71+
taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation"));
72+
}
73+
74+
return taskCompletionSource.Task;
75+
}
76+
77+
return TryEnqueueAsync(dispatcher, function, priority);
78+
}
79+
80+
/// <summary>
81+
/// Invokes a given function on the target <see cref="DispatcherQueue"/> and returns a
82+
/// <see cref="Task"/> that acts as a proxy for the one returned by the given function.
83+
/// </summary>
84+
/// <param name="dispatcher">The target <see cref="DispatcherQueue"/> to invoke the code on.</param>
85+
/// <param name="function">The <see cref="Func{TResult}"/> to invoke.</param>
86+
/// <param name="priority">The priority level for the function to invoke.</param>
87+
/// <returns>A <see cref="Task"/> that acts as a proxy for the one returned by <paramref name="function"/>.</returns>
88+
/// <remarks>If the current thread has access to <paramref name="dispatcher"/>, <paramref name="function"/> will be invoked directly.</remarks>
89+
public static Task EnqueueAsync(this DispatcherQueue dispatcher, Func<Task> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal)
90+
{
91+
// If we have thread access, we can retrieve the task directly.
92+
// We don't use ConfigureAwait(false) in this case, in order
93+
// to let the caller continue its execution on the same thread
94+
// after awaiting the task returned by this function.
95+
if (dispatcher.HasThreadAccess)
96+
{
97+
try
98+
{
99+
if (function() is Task awaitableResult)
100+
{
101+
return awaitableResult;
102+
}
103+
104+
return Task.FromException(GetEnqueueException("The Task returned by function cannot be null."));
105+
}
106+
catch (Exception e)
107+
{
108+
return Task.FromException(e);
109+
}
110+
}
111+
112+
static Task TryEnqueueAsync(DispatcherQueue dispatcher, Func<Task> function, DispatcherQueuePriority priority)
113+
{
114+
var taskCompletionSource = new TaskCompletionSource<object?>();
115+
116+
if (!dispatcher.TryEnqueue(priority, async () =>
117+
{
118+
try
119+
{
120+
if (function() is Task awaitableResult)
121+
{
122+
await awaitableResult.ConfigureAwait(false);
123+
124+
taskCompletionSource.SetResult(null);
125+
}
126+
else
127+
{
128+
taskCompletionSource.SetException(GetEnqueueException("The Task returned by function cannot be null."));
129+
}
130+
}
131+
catch (Exception e)
132+
{
133+
taskCompletionSource.SetException(e);
134+
}
135+
}))
136+
{
137+
taskCompletionSource.SetException(GetEnqueueException("Failed to enqueue the operation"));
138+
}
139+
140+
return taskCompletionSource.Task;
141+
}
142+
143+
return TryEnqueueAsync(dispatcher, function, priority);
144+
}
145+
146+
/// <summary>
147+
/// Creates an <see cref="InvalidOperationException"/> to return when an enqueue operation fails.
148+
/// </summary>
149+
/// <param name="message">The message of the exception.</param>
150+
/// <returns>An <see cref="InvalidOperationException"/> with a specified message.</returns>
151+
[MethodImpl(MethodImplOptions.NoInlining)]
152+
private static InvalidOperationException GetEnqueueException(string message)
153+
{
154+
return new InvalidOperationException(message);
155+
}
156+
}

CommunityToolkit.Tests.Shared/VisualUITestBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using CommunityToolkit.Tests.Internal;
6+
57
namespace CommunityToolkit.Tests;
68

79
/// <summary>

GlobalUsings_Tests.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,9 @@
66
// Learn more global using directives at https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-directive#global-modifier
77

88
#if !WINAPPSDK
9-
global using Microsoft.Toolkit.Uwp;
10-
global using Microsoft.Toolkit.Uwp.UI;
11-
global using Microsoft.Toolkit.Uwp.UI.Helpers;
129
global using Windows.UI;
1310
global using Windows.UI.Core;
1411
#else
15-
global using CommunityToolkit.WinUI;
16-
global using CommunityToolkit.WinUI.UI;
17-
global using CommunityToolkit.WinUI.UI.Helpers;
1812
global using Microsoft.UI;
1913
#endif
2014

ProjectHeads/Tests.Head.Uwp.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<SDKReference Include="TestPlatform.Universal, Version=$(UnitTestPlatformVersion)" />
12-
<PackageReference Include="Microsoft.Toolkit.Uwp.UI" Version="7.1.2"/>
12+
<!-- TODO: We need a way to toggle this between Package and Source of the Extension module if we want to set it up 'globally' -->
13+
<!--<PackageReference Include="Microsoft.Toolkit.Uwp.UI" Version="7.1.2"/>-->
1314
</ItemGroup>
1415
</Project>

ProjectHeads/Tests.Head.WinAppSdk.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
<Version>17.1.0</Version>
88
<ExcludeAssets>build</ExcludeAssets>
99
</PackageReference>
10-
<PackageReference Include="CommunityToolkit.WinUI.UI" Version="7.1.2"/>
10+
<!-- TODO: See comment in LINK:Tests.Head.Uwp.props file -->
11+
<!-- <PackageReference Include="CommunityToolkit.WinUI.UI" Version="7.1.2"/> -->
1112
</ItemGroup>
1213

1314
<ItemGroup>

0 commit comments

Comments
 (0)