diff --git a/Directory.Build.props b/Directory.Build.props index 3d8bfe70145..bf865da4779 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -40,6 +40,16 @@ + + + + + false + false + + + + diff --git a/GazeInputTest/GazeInputTest.csproj b/GazeInputTest/GazeInputTest.csproj index 481e440e2d7..7735a9bb1a5 100644 --- a/GazeInputTest/GazeInputTest.csproj +++ b/GazeInputTest/GazeInputTest.csproj @@ -150,7 +150,7 @@ - 6.2.9 + 6.2.10 1.0.2 diff --git a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/BluetoothLEHelper.cs b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/BluetoothLEHelper.cs index 30fd17ac770..97e20f13aec 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/BluetoothLEHelper.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/BluetoothLEHelper.cs @@ -8,12 +8,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Windows.ApplicationModel.Core; +using Microsoft.Toolkit.Uwp.Helpers; using Windows.Devices.Bluetooth; using Windows.Devices.Bluetooth.Advertisement; using Windows.Devices.Enumeration; using Windows.Foundation.Metadata; -using Windows.UI.Core; +using Windows.System; namespace Microsoft.Toolkit.Uwp.Connectivity { @@ -59,11 +59,19 @@ public class BluetoothLEHelper /// private BluetoothAdapter _adapter; + /// + /// Gets or sets which DispatcherQueue is used to dispatch UI updates. + /// + public DispatcherQueue DispatcherQueue { get; set; } + /// /// Prevents a default instance of the class from being created. /// - private BluetoothLEHelper() + /// The DispatcherQueue that should be used to dispatch UI updates, or null if this is being called from the UI thread. + private BluetoothLEHelper(DispatcherQueue dispatcherQueue = null) { + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); + #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Init(); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed @@ -201,8 +209,7 @@ private async Task Init() /// The advertisement. private async void AdvertisementWatcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args) { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - CoreDispatcherPriority.Normal, + await DispatcherQueue.ExecuteOnUIThreadAsync( () => { if (_readerWriterLockSlim.TryEnterReadLock(TimeSpan.FromSeconds(1))) @@ -217,7 +224,7 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( _readerWriterLockSlim.ExitReadLock(); } - }); + }, DispatcherQueuePriority.Normal); } /// @@ -286,19 +293,20 @@ private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformation // Protect against race condition if the task runs after the app stopped the deviceWatcher. if (sender == _deviceWatcher) { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - if (_readerWriterLockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(1))) + await DispatcherQueue.ExecuteOnUIThreadAsync( + () => { - var device = BluetoothLeDevices.FirstOrDefault(i => i.DeviceInfo.Id == deviceInfoUpdate.Id); - BluetoothLeDevices.Remove(device); + if (_readerWriterLockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(1))) + { + var device = BluetoothLeDevices.FirstOrDefault(i => i.DeviceInfo.Id == deviceInfoUpdate.Id); + BluetoothLeDevices.Remove(device); - var unusedDevice = _unusedDevices.FirstOrDefault(i => i.Id == deviceInfoUpdate.Id); - _unusedDevices?.Remove(unusedDevice); + var unusedDevice = _unusedDevices.FirstOrDefault(i => i.Id == deviceInfoUpdate.Id); + _unusedDevices?.Remove(unusedDevice); - _readerWriterLockSlim.ExitWriteLock(); - } - }); + _readerWriterLockSlim.ExitWriteLock(); + } + }, DispatcherQueuePriority.Normal); } } @@ -327,7 +335,7 @@ private async Task AddDeviceToList(DeviceInformation deviceInfo) // Make sure device name isn't blank or already present in the list. if (!string.IsNullOrEmpty(deviceInfo?.Name)) { - var device = new ObservableBluetoothLEDevice(deviceInfo); + var device = new ObservableBluetoothLEDevice(deviceInfo, DispatcherQueue); var connectable = (device.DeviceInfo.Properties.Keys.Contains("System.Devices.Aep.Bluetooth.Le.IsConnectable") && (bool)device.DeviceInfo.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"]) || (device.DeviceInfo.Properties.Keys.Contains("System.Devices.Aep.IsConnected") && @@ -335,8 +343,7 @@ private async Task AddDeviceToList(DeviceInformation deviceInfo) if (connectable) { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - CoreDispatcherPriority.Normal, + await DispatcherQueue.ExecuteOnUIThreadAsync( () => { if (_readerWriterLockSlim.TryEnterWriteLock(TimeSpan.FromSeconds(1))) @@ -348,7 +355,7 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( _readerWriterLockSlim.ExitWriteLock(); } - }); + }, DispatcherQueuePriority.Normal); return; } diff --git a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableBluetoothLEDevice.cs b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableBluetoothLEDevice.cs index b39312d417a..e54d84f4ef8 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableBluetoothLEDevice.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableBluetoothLEDevice.cs @@ -11,10 +11,11 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Windows.ApplicationModel.Core; +using Microsoft.Toolkit.Uwp.Helpers; using Windows.Devices.Bluetooth; using Windows.Devices.Bluetooth.GenericAttributeProfile; using Windows.Devices.Enumeration; +using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml.Media.Imaging; @@ -134,17 +135,25 @@ public int Compare(object x, object y) private ObservableCollection _services = new ObservableCollection(); + /// + /// Gets or sets which DispatcherQueue is used to dispatch UI updates. + /// + public DispatcherQueue DispatcherQueue { get; set; } + /// /// Initializes a new instance of the class. /// /// The device information. - public ObservableBluetoothLEDevice(DeviceInformation deviceInfo) + /// The DispatcherQueue that should be used to dispatch UI updates for this BluetoothLE Device, or null if this is being called from the UI thread. + public ObservableBluetoothLEDevice(DeviceInformation deviceInfo, DispatcherQueue dispatcherQueue = null) { DeviceInfo = deviceInfo; Name = DeviceInfo.Name; IsPaired = DeviceInfo.Pairing.IsPaired; + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); + LoadGlyph(); this.PropertyChanged += ObservableBluetoothLEDevice_PropertyChanged; @@ -395,7 +404,8 @@ private void ObservableBluetoothLEDevice_PropertyChanged(object sender, Property /// Thorws Exception when no permission to access device public async Task ConnectAsync() { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + await DispatcherQueue.ExecuteOnUIThreadAsync( + async () => { if (BluetoothLEDevice == null) { @@ -442,7 +452,7 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPrio throw new Exception(_result.ProtocolError.GetErrorString()); } } - }); + }, DispatcherQueuePriority.Normal); } /// @@ -468,8 +478,7 @@ public async Task DoInAppPairingAsync() /// The task of the update. public async Task UpdateAsync(DeviceInformationUpdate deviceUpdate) { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - CoreDispatcherPriority.Normal, + await DispatcherQueue.ExecuteOnUIThreadAsync( () => { DeviceInfo.Update(deviceUpdate); @@ -479,7 +488,7 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( LoadGlyph(); OnPropertyChanged("DeviceInfo"); - }); + }, DispatcherQueuePriority.Normal); } /// @@ -512,9 +521,7 @@ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName /// The arguments. private async void BluetoothLEDevice_NameChanged(BluetoothLEDevice sender, object args) { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - CoreDispatcherPriority.Normal, - () => { Name = BluetoothLEDevice.Name; }); + await DispatcherQueue.ExecuteOnUIThreadAsync(() => { Name = BluetoothLEDevice.Name; }, DispatcherQueuePriority.Normal); } /// @@ -524,13 +531,12 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( /// The arguments. private async void BluetoothLEDevice_ConnectionStatusChanged(BluetoothLEDevice sender, object args) { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - CoreDispatcherPriority.Normal, + await DispatcherQueue.ExecuteOnUIThreadAsync( () => { IsPaired = DeviceInfo.Pairing.IsPaired; IsConnected = BluetoothLEDevice.ConnectionStatus == BluetoothConnectionStatus.Connected; - }); + }, DispatcherQueuePriority.Normal); } /// @@ -538,15 +544,14 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( /// private async void LoadGlyph() { - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - CoreDispatcherPriority.Normal, + await DispatcherQueue.ExecuteOnUIThreadAsync( async () => { var deviceThumbnail = await DeviceInfo.GetGlyphThumbnailAsync(); var glyphBitmapImage = new BitmapImage(); await glyphBitmapImage.SetSourceAsync(deviceThumbnail); Glyph = glyphBitmapImage; - }); + }, DispatcherQueuePriority.Normal); } } } diff --git a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableGattCharacteristics.cs b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableGattCharacteristics.cs index 38f3d6331ab..c1ab661c904 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableGattCharacteristics.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/ObservableGattCharacteristics.cs @@ -7,10 +7,12 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.Helpers; using Windows.Devices.Bluetooth; using Windows.Devices.Bluetooth.GenericAttributeProfile; using Windows.Security.Cryptography; using Windows.Storage.Streams; +using Windows.System; namespace Microsoft.Toolkit.Uwp.Connectivity { @@ -110,13 +112,21 @@ public enum DisplayTypes /// private string _value; + /// + /// Gets or sets which DispatcherQueue is used to dispatch UI updates. + /// + public DispatcherQueue DispatcherQueue { get; set; } + /// /// Initializes a new instance of the class. /// /// The characteristic. /// The parent. - public ObservableGattCharacteristics(GattCharacteristic characteristic, ObservableGattDeviceService parent) + /// The DispatcherQueue that should be used to dispatch UI updates, or null if this is being called from the UI thread. + public ObservableGattCharacteristics(GattCharacteristic characteristic, ObservableGattDeviceService parent, DispatcherQueue dispatcherQueue = null) { + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); + Characteristic = characteristic; Parent = parent; Name = GattUuidsService.ConvertUuidToName(Characteristic.Uuid); @@ -459,9 +469,7 @@ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName /// The instance containing the event data. private async void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args) { - await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( - Windows.UI.Core.CoreDispatcherPriority.Normal, - () => { SetValue(args.CharacteristicValue); }); + await DispatcherQueue.ExecuteOnUIThreadAsync(() => { SetValue(args.CharacteristicValue); }, DispatcherQueuePriority.Normal); } /// diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Controls/CodeRenderer.cs b/Microsoft.Toolkit.Uwp.SampleApp/Controls/CodeRenderer.cs index d8ce3386a76..ea744557440 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Controls/CodeRenderer.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Controls/CodeRenderer.cs @@ -87,10 +87,14 @@ protected override void OnApplyTemplate() private void RenderDocument() { - _codeView?.Blocks?.Clear(); - _formatter = new RichTextBlockFormatter(_theme); - _formatter.FormatRichTextBlock(_displayedText, _language, _codeView); - _rendered = true; + if (_codeView != null) + { + _codeView.Blocks?.Clear(); + _formatter = new RichTextBlockFormatter(_theme); + + _formatter.FormatRichTextBlock(_displayedText, _language, _codeView); + _rendered = true; + } } private void CopyButton_Click(object sender, RoutedEventArgs e) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 77be6020d17..17b8faa23de 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -106,7 +106,7 @@ - 6.2.9 + 6.2.10 10.1901.28001 @@ -292,7 +292,7 @@ - + @@ -456,7 +456,7 @@ - + Designer @@ -726,8 +726,8 @@ BackgroundTaskHelperPage.xaml - - DispatcherHelperPage.xaml + + DispatcherQueueHelperPage.xaml DropShadowPanelPage.xaml @@ -1151,7 +1151,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatchHelper.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatchQueueHelper.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatchHelper.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatchQueueHelper.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatcherHelperCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatcherQueueHelperCode.bind similarity index 73% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatcherHelperCode.bind rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatcherQueueHelperCode.bind index 0641c40f79b..bda6b73042b 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatcherHelperCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatcherQueueHelperCode.bind @@ -1,8 +1,12 @@ +// From a UI thread, capture the DispatcherQueue once: +var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + +// The use it from any other thread int crossThreadReturnedValue = await Task.Run( async () => { // Task.Run() will guarantee the given piece of code be executed on a seperate thread pool. // This is used to simulate the scenario of updating the UI element from a different thread. - int returnedFromUIThread = await DispatcherHelper.ExecuteOnUIThreadAsync(() => + int returnedFromUIThread = await dispatcherQueue.ExecuteOnUIThreadAsync(() => { NormalTextBlock.Text = "Updated from a random thread!"; return 1; diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatcherHelperPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatcherQueueHelperPage.xaml similarity index 97% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatcherHelperPage.xaml rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatcherQueueHelperPage.xaml index 02543e03990..9001b65cb39 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherHelper/DispatcherHelperPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/DispatcherQueueHelper/DispatcherQueueHelperPage.xaml @@ -1,4 +1,4 @@ -(async () => { - int returnedFromUIThread = await DispatcherHelper.ExecuteOnUIThreadAsync(() => + int returnedFromUIThread = await dispatcherQueue.ExecuteOnUIThreadAsync(() => { NormalTextBlock.Text = "Updated from a random thread!"; return 1; diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs index 012b87981cb..bf7758af228 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs @@ -15,8 +15,6 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages /// public sealed partial class MasterDetailsViewPage : Page, IXamlRenderListener { - private double _previousWidth = Window.Current.Bounds.Width; - public MasterDetailsViewPage() { Emails = new List diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index e7f77241fb6..ba2268cb6cc 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -791,14 +791,14 @@ "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/PrintHelper.md" }, { - "Name": "DispatcherHelper", - "Type": "DispatcherHelperPage", + "Name": "DispatcherQueueHelper", + "Type": "DispatcherQueueHelperPage", "Subcategory": "Developer", - "About": "Allows easy interaction with Windows Runtime core message dispatcher for multi-threaded scenario (I.E: Run code on UI thread). ", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs", - "CodeFile": "DispatcherHelperCode.bind", + "About": "Allows easy interaction with Windows Runtime message dispatcher queue for multi-threaded scenario (I.E: Run code on UI thread).", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp/Helpers/DispatcherQueueHelper.cs", + "CodeFile": "DispatcherQueueHelperCode.bind", "Icon": "/Assets/Helpers.png", - "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/DispatcherHelper.md" + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/DispatcherQueueHelper.md" }, { "Name": "AdvancedCollectionView", diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs index 36875adcb58..8dfc02c5471 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs @@ -6,6 +6,7 @@ using Microsoft.Toolkit.Uwp.Helpers; using Microsoft.Toolkit.Uwp.SampleApp.Pages; using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.System; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Animation; @@ -127,7 +128,7 @@ private void SamplePickerGridView_Loaded(object sender, Windows.UI.Xaml.RoutedEv { if (s is UIElement samplePicker && samplePicker.Visibility == Visibility.Visible) { - DispatcherHelper.ExecuteOnUIThreadAsync(() => SamplePickerGridView.Focus(FocusState.Keyboard)); + DispatcherQueue.GetForCurrentThread().ExecuteOnUIThreadAsync(() => SamplePickerGridView.Focus(FocusState.Keyboard)); } }); } diff --git a/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks.csproj b/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks.csproj index b60b5c9d5cf..dae807b42d6 100644 --- a/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks.csproj +++ b/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks/Microsoft.Toolkit.Uwp.Samples.BackgroundTasks.csproj @@ -109,7 +109,7 @@ - 6.2.9 + 6.2.10 diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj b/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj index 2c5453812fe..a236696ac54 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj @@ -21,7 +21,7 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs index 26064167cdb..fee5a1b7b13 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs @@ -8,6 +8,7 @@ using Microsoft.Toolkit.Uwp.UI.Controls.DataGridInternals; using Microsoft.Toolkit.Uwp.UI.Controls.Primitives; using Microsoft.Toolkit.Uwp.UI.Data.Utilities; +using Microsoft.Toolkit.Uwp.Utilities; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; @@ -1059,7 +1060,16 @@ internal void ComputeLayoutRoundedWidth(double leftEdge) { if (this.OwningGrid != null && this.OwningGrid.UseLayoutRounding) { - double scale = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + double scale; + if (TypeHelper.IsXamlRootAvailable && OwningGrid.XamlRoot != null) + { + scale = OwningGrid.XamlRoot.RasterizationScale; + } + else + { + scale = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + } + double roundedLeftEdge = Math.Floor((scale * leftEdge) + 0.5) / scale; double roundedRightEdge = Math.Floor((scale * (leftEdge + this.ActualWidth)) + 0.5) / scale; this.LayoutRoundedWidth = roundedRightEdge - roundedLeftEdge; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/TypeHelper.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/TypeHelper.cs index 3792cb88f4c..672c2cea195 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/TypeHelper.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/TypeHelper.cs @@ -17,8 +17,9 @@ internal static class TypeHelper internal const char PropertyNameSeparator = '.'; internal const char RightIndexerToken = ']'; - private static bool isAPIContractAvailableInitialized = false; + private static bool isAPIsAvailableInitialized = false; private static bool isRS3OrHigher = false; + private static bool isXamlRootAvailable = false; // Methods private static Type FindGenericType(Type definition, Type type) @@ -412,19 +413,33 @@ internal static bool IsRS3OrHigher { get { - if (!isAPIContractAvailableInitialized) + if (!isAPIsAvailableInitialized) { - InitializeAPIContractAvailable(); + InitializeAPIsAvailable(); } return isRS3OrHigher; } } - internal static void InitializeAPIContractAvailable() + internal static bool IsXamlRootAvailable + { + get + { + if (!isAPIsAvailableInitialized) + { + InitializeAPIsAvailable(); + } + + return isXamlRootAvailable; + } + } + + internal static void InitializeAPIsAvailable() { isRS3OrHigher = Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 5); - isAPIContractAvailableInitialized = true; + isXamlRootAvailable = Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot"); + isAPIsAvailableInitialized = true; } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ControlHelpers.XamlHost.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ControlHelpers.XamlHost.cs new file mode 100644 index 00000000000..90514de7cd6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/ControlHelpers.XamlHost.cs @@ -0,0 +1,14 @@ +// 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. + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Internal class used to provide helpers for controls + /// + internal static partial class ControlHelpers + { + internal static bool IsXamlRootAvailable { get; } = Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot"); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel/DropShadowPanel.cs b/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel/DropShadowPanel.cs index dfa1609587c..9178516e57d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel/DropShadowPanel.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel/DropShadowPanel.cs @@ -86,6 +86,8 @@ protected override void OnContentChanged(object oldContent, object newContent) } } + UpdateShadowMask(); + base.OnContentChanged(oldContent, newContent); } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Logic.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Logic.cs index 7cebf555f61..c6c894fe5ea 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Logic.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Logic.cs @@ -36,7 +36,7 @@ private void UpdateEyedropper(Point position) UpdatePreview(x, y); } - private void UpadateWorkArea() + private void UpdateWorkArea() { if (_targetGrid == null) { @@ -50,9 +50,20 @@ private void UpadateWorkArea() else { var left = WorkArea.Left; - var right = Window.Current.Bounds.Width - WorkArea.Right; var top = WorkArea.Top; - var bottom = Window.Current.Bounds.Height - WorkArea.Bottom; + double right; + double bottom; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + right = XamlRoot.Size.Width - WorkArea.Right; + bottom = XamlRoot.Size.Height - WorkArea.Bottom; + } + else + { + right = Window.Current.Bounds.Width - WorkArea.Right; + bottom = Window.Current.Bounds.Height - WorkArea.Bottom; + } + _targetGrid.Margin = new Thickness(left, top, right, bottom); } } @@ -98,14 +109,41 @@ private void UpdatePreview(int centerX, int centerY) internal async Task UpdateAppScreenshotAsync() { - var renderTarget = new RenderTargetBitmap(); - var diaplayInfo = DisplayInformation.GetForCurrentView(); - var scale = diaplayInfo.RawPixelsPerViewPixel; - var scaleWidth = (int)Math.Ceiling(Window.Current.Bounds.Width / scale); - var scaleHeight = (int)Math.Ceiling(Window.Current.Bounds.Height / scale); - await renderTarget.RenderAsync(Window.Current.Content, scaleWidth, scaleHeight); - var pixels = await renderTarget.GetPixelsAsync(); - _appScreenshot = CanvasBitmap.CreateFromBytes(_device, pixels, renderTarget.PixelWidth, renderTarget.PixelHeight, DirectXPixelFormat.B8G8R8A8UIntNormalized); + double scale; + double width; + double height; + UIElement content; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + scale = XamlRoot.RasterizationScale; + width = XamlRoot.Size.Width; + height = XamlRoot.Size.Height; + content = XamlRoot.Content; + } + else + { + var displayInfo = DisplayInformation.GetForCurrentView(); + scale = displayInfo.RawPixelsPerViewPixel; + width = Window.Current.Bounds.Width; + height = Window.Current.Bounds.Height; + content = Window.Current.Content; + } + + try + { + var renderTarget = new RenderTargetBitmap(); + var scaleWidth = (int)Math.Ceiling(width / scale); + var scaleHeight = (int)Math.Ceiling(height / scale); + await renderTarget.RenderAsync(content, scaleWidth, scaleHeight); + var pixels = await renderTarget.GetPixelsAsync(); + _appScreenshot?.Dispose(); + _appScreenshot = null; + _appScreenshot = CanvasBitmap.CreateFromBytes(_device, pixels, renderTarget.PixelWidth, renderTarget.PixelHeight, DirectXPixelFormat.B8G8R8A8UIntNormalized); + } + catch (OutOfMemoryException ex) + { + System.Diagnostics.Debug.WriteLine(ex); + } } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Properties.cs index 915461e609f..785fc6b033f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.Properties.cs @@ -71,7 +71,7 @@ private static void OnWorkAreaChanged(DependencyObject d, DependencyPropertyChan { if (d is Eyedropper eyedropper) { - eyedropper.UpadateWorkArea(); + eyedropper.UpdateWorkArea(); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.cs index b84edb7239d..082bed75c66 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/Eyedropper.cs @@ -32,15 +32,16 @@ public partial class Eyedropper : Control private static readonly CoreCursor MoveCursor = new CoreCursor(CoreCursorType.Cross, 1); private readonly CanvasDevice _device = CanvasDevice.GetSharedDevice(); private readonly TranslateTransform _layoutTransform = new TranslateTransform(); - - private readonly Popup _popup; private readonly CanvasImageSource _previewImageSource; private readonly Grid _rootGrid; private readonly Grid _targetGrid; + + private Popup _popup; private CanvasBitmap _appScreenshot; private Action _lazyTask; private uint? _pointerId = null; private TaskCompletionSource _taskSource; + private double _currentDpi; /// /// Initializes a new instance of the class. @@ -53,10 +54,7 @@ public Eyedropper() { Background = new SolidColorBrush(Color.FromArgb(0x01, 0x00, 0x00, 0x00)) }; - _popup = new Popup - { - Child = _rootGrid - }; + RenderTransform = _layoutTransform; _previewImageSource = new CanvasImageSource(_device, PreviewPixelsPerRawPixel * PixelCountPerRow, PreviewPixelsPerRawPixel * PixelCountPerRow, 96f); Preview = _previewImageSource; @@ -100,12 +98,38 @@ public async Task Open(Point? startPoint = null) _rootGrid.Children.Add(_targetGrid); _rootGrid.Children.Add(this); - _rootGrid.Width = Window.Current.Bounds.Width; - _rootGrid.Height = Window.Current.Bounds.Height; - UpadateWorkArea(); + + if (_popup != null) + { + _popup.IsOpen = false; + } + + _popup = new Popup + { + Child = _rootGrid + }; + + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + _popup.XamlRoot = XamlRoot; + } + + if (ControlHelpers.IsXamlRootAvailable && _popup.XamlRoot != null) + { + _rootGrid.Width = _popup.XamlRoot.Size.Width; + _rootGrid.Height = _popup.XamlRoot.Size.Height; + } + else + { + _rootGrid.Width = Window.Current.Bounds.Width; + _rootGrid.Height = Window.Current.Bounds.Height; + } + + UpdateWorkArea(); _popup.IsOpen = true; var result = await _taskSource.Task; _taskSource = null; + _popup = null; _rootGrid.Children.Clear(); return result; } @@ -117,28 +141,72 @@ public void Close() { if (_taskSource != null && !_taskSource.Task.IsCanceled) { - _taskSource.SetCanceled(); + _taskSource.TrySetCanceled(); _rootGrid.Children.Clear(); } } private void HookUpEvents() { + Unloaded -= Eyedropper_Unloaded; Unloaded += Eyedropper_Unloaded; - Window.Current.SizeChanged += Window_SizeChanged; - DisplayInformation.GetForCurrentView().DpiChanged += Eyedropper_DpiChanged; + + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + XamlRoot.Changed -= XamlRoot_Changed; + XamlRoot.Changed += XamlRoot_Changed; + _currentDpi = XamlRoot.RasterizationScale; + } + else + { + var window = Window.Current; + window.SizeChanged -= Window_SizeChanged; + window.SizeChanged += Window_SizeChanged; + var displayInformation = DisplayInformation.GetForCurrentView(); + displayInformation.DpiChanged -= Eyedropper_DpiChanged; + displayInformation.DpiChanged += Eyedropper_DpiChanged; + _currentDpi = displayInformation.LogicalDpi; + } + + _targetGrid.PointerEntered -= TargetGrid_PointerEntered; _targetGrid.PointerEntered += TargetGrid_PointerEntered; + _targetGrid.PointerExited -= TargetGrid_PointerExited; _targetGrid.PointerExited += TargetGrid_PointerExited; + _targetGrid.PointerPressed -= TargetGrid_PointerPressed; _targetGrid.PointerPressed += TargetGrid_PointerPressed; + _targetGrid.PointerMoved -= TargetGrid_PointerMoved; _targetGrid.PointerMoved += TargetGrid_PointerMoved; + _targetGrid.PointerReleased -= TargetGrid_PointerReleased; _targetGrid.PointerReleased += TargetGrid_PointerReleased; } + private async void XamlRoot_Changed(XamlRoot sender, XamlRootChangedEventArgs args) + { + if (_rootGrid.Width != sender.Size.Width || _rootGrid.Height != sender.Size.Height) + { + UpdateRootGridSize(sender.Size.Width, sender.Size.Height); + } + + if (_currentDpi != sender.RasterizationScale) + { + _currentDpi = sender.RasterizationScale; + await UpdateAppScreenshotAsync(); + } + } + private void UnhookEvents() { Unloaded -= Eyedropper_Unloaded; - Window.Current.SizeChanged -= Window_SizeChanged; - DisplayInformation.GetForCurrentView().DpiChanged -= Eyedropper_DpiChanged; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + XamlRoot.Changed -= XamlRoot_Changed; + } + else + { + Window.Current.SizeChanged -= Window_SizeChanged; + DisplayInformation.GetForCurrentView().DpiChanged -= Eyedropper_DpiChanged; + } + if (_targetGrid != null) { _targetGrid.PointerEntered -= TargetGrid_PointerEntered; @@ -172,49 +240,68 @@ private void TargetGrid_PointerEntered(object sender, PointerRoutedEventArgs e) private async void Eyedropper_DpiChanged(DisplayInformation sender, object args) { + _currentDpi = sender.LogicalDpi; await UpdateAppScreenshotAsync(); } private async void TargetGrid_PointerReleased(object sender, PointerRoutedEventArgs e) { - var pointer = e.Pointer; - if (pointer.PointerId == _pointerId) + var point = e.GetCurrentPoint(_rootGrid); + await InternalPointerReleasedAsync(e.Pointer.PointerId, point.Position); + } + + // Internal abstraction is used by the Unit Tests + internal async Task InternalPointerReleasedAsync(uint pointerId, Point position) + { + if (pointerId == _pointerId) { - var point = e.GetCurrentPoint(_rootGrid); if (_appScreenshot == null) { await UpdateAppScreenshotAsync(); } - UpdateEyedropper(point.Position); - PickCompleted?.Invoke(this, EventArgs.Empty); + UpdateEyedropper(position); _pointerId = null; - if (!_taskSource.Task.IsCanceled) + if (_taskSource != null && !_taskSource.Task.IsCanceled) { - _taskSource.SetResult(Color); + _taskSource.TrySetResult(Color); } + + PickCompleted?.Invoke(this, EventArgs.Empty); } } private void TargetGrid_PointerMoved(object sender, PointerRoutedEventArgs e) { var pointer = e.Pointer; - if (pointer.PointerId == _pointerId) + var point = e.GetCurrentPoint(_rootGrid); + InternalPointerMoved(pointer.PointerId, point.Position); + } + + // Internal abstraction is used by the Unit Tests + internal void InternalPointerMoved(uint pointerId, Point position) + { + if (pointerId == _pointerId) { - var point = e.GetCurrentPoint(_rootGrid); - UpdateEyedropper(point.Position); + UpdateEyedropper(position); } } private async void TargetGrid_PointerPressed(object sender, PointerRoutedEventArgs e) { - _pointerId = e.Pointer.PointerId; var point = e.GetCurrentPoint(_rootGrid); + await InternalPointerPressedAsync(e.Pointer.PointerId, point.Position, e.Pointer.PointerDeviceType); + } + + // Internal abstraction is used by the Unit Tests + internal async Task InternalPointerPressedAsync(uint pointerId, Point position, Windows.Devices.Input.PointerDeviceType pointerDeviceType) + { + _pointerId = pointerId; PickStarted?.Invoke(this, EventArgs.Empty); await UpdateAppScreenshotAsync(); - UpdateEyedropper(point.Position); + UpdateEyedropper(position); - if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch) + if (pointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch) { VisualStateManager.GoToState(this, TouchState, false); } @@ -244,11 +331,16 @@ private void Eyedropper_Unloaded(object sender, RoutedEventArgs e) } private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs e) + { + UpdateRootGridSize(Window.Current.Bounds.Width, Window.Current.Bounds.Height); + } + + private void UpdateRootGridSize(double width, double height) { if (_rootGrid != null) { - _rootGrid.Width = Window.Current.Bounds.Width; - _rootGrid.Height = Window.Current.Bounds.Height; + _rootGrid.Width = width; + _rootGrid.Height = height; } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.Properties.cs index 15638ec6de8..59a29380918 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.Properties.cs @@ -80,6 +80,11 @@ private static void OnEyedropperEnabledChanged(DependencyObject d, DependencyPro if (eyedropperToolButton.EyedropperEnabled) { VisualStateManager.GoToState(eyedropperToolButton, eyedropperToolButton.IsPointerOver ? EyedropperEnabledPointerOverState : EyedropperEnabledState, true); + if (ControlHelpers.IsXamlRootAvailable && eyedropperToolButton.XamlRoot != null) + { + eyedropperToolButton._eyedropper.XamlRoot = eyedropperToolButton.XamlRoot; + } + eyedropperToolButton._eyedropper.Open().ConfigureAwait(false); } else @@ -125,14 +130,14 @@ private void HookUpTargetElementEvents(FrameworkElement target) } } - private void Target_PointerEntered(object sender, PointerRoutedEventArgs e) + private async void Target_PointerEntered(object sender, PointerRoutedEventArgs e) { - UpdateEyedropperWorkArea(); + await UpdateEyedropperWorkAreaAsync(); } - private void Target_SizeChanged(object sender, SizeChangedEventArgs e) + private async void Target_SizeChanged(object sender, SizeChangedEventArgs e) { - UpdateEyedropperWorkArea(); + await UpdateEyedropperWorkAreaAsync(); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.cs index cd2cd5c9828..3e67a6b1436 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Eyedropper/EyedropperToolButton.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Threading.Tasks; using Windows.Foundation; using Windows.UI.Core; using Windows.UI.Xaml; @@ -55,12 +56,29 @@ public EyedropperToolButton() private void HookUpEvents() { + Click -= EyedropperToolButton_Click; Click += EyedropperToolButton_Click; + Unloaded -= EyedropperToolButton_Unloaded; Unloaded += EyedropperToolButton_Unloaded; + ActualThemeChanged -= EyedropperToolButton_ActualThemeChanged; ActualThemeChanged += EyedropperToolButton_ActualThemeChanged; - Window.Current.SizeChanged += Window_SizeChanged; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + XamlRoot.Changed -= XamlRoot_Changed; + XamlRoot.Changed += XamlRoot_Changed; + _eyedropper.XamlRoot = XamlRoot; + } + else + { + Window.Current.SizeChanged -= Window_SizeChanged; + Window.Current.SizeChanged += Window_SizeChanged; + } + + _eyedropper.ColorChanged -= Eyedropper_ColorChanged; _eyedropper.ColorChanged += Eyedropper_ColorChanged; + _eyedropper.PickStarted -= Eyedropper_PickStarted; _eyedropper.PickStarted += Eyedropper_PickStarted; + _eyedropper.PickCompleted -= Eyedropper_PickCompleted; _eyedropper.PickCompleted += Eyedropper_PickCompleted; } @@ -69,7 +87,15 @@ private void UnhookEvents() Click -= EyedropperToolButton_Click; Unloaded -= EyedropperToolButton_Unloaded; ActualThemeChanged -= EyedropperToolButton_ActualThemeChanged; - Window.Current.SizeChanged -= Window_SizeChanged; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + XamlRoot.Changed -= XamlRoot_Changed; + } + else + { + Window.Current.SizeChanged -= Window_SizeChanged; + } + _eyedropper.ColorChanged -= Eyedropper_ColorChanged; _eyedropper.PickStarted -= Eyedropper_PickStarted; _eyedropper.PickCompleted -= Eyedropper_PickCompleted; @@ -165,18 +191,38 @@ private void EyedropperToolButton_Click(object sender, RoutedEventArgs e) EyedropperEnabled = !EyedropperEnabled; } - private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs e) + private async void Window_SizeChanged(object sender, WindowSizeChangedEventArgs e) + { + await UpdateEyedropperWorkAreaAsync(); + } + + private async void XamlRoot_Changed(XamlRoot sender, XamlRootChangedEventArgs args) { - UpdateEyedropperWorkArea(); + await UpdateEyedropperWorkAreaAsync(); } - private async void UpdateEyedropperWorkArea() + private async Task UpdateEyedropperWorkAreaAsync() { if (TargetElement != null) { - var transform = TargetElement.TransformToVisual(Window.Current.Content); + UIElement content; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + content = XamlRoot.Content; + } + else + { + content = Window.Current.Content; + } + + var transform = TargetElement.TransformToVisual(content); var position = transform.TransformPoint(default(Point)); _eyedropper.WorkArea = new Rect(position, new Size(TargetElement.ActualWidth, TargetElement.ActualHeight)); + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + _eyedropper.XamlRoot = XamlRoot; + } + await _eyedropper.UpdateAppScreenshotAsync(); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.cs index 1b59b69f842..199704bdcf5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.cs @@ -78,7 +78,15 @@ internal void ConfigureSpriteVisual(double width, double height, float zoomFacto internal void SetScale(float zoomFactor) { - _screenScale = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + _screenScale = XamlRoot.RasterizationScale; + } + else + { + _screenScale = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + } + var scale = _screenScale * zoomFactor; _surfaceBrush.Scale = new Vector2((float)(1 / scale)); _surfaceBrush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.NearestNeighbor; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs index c2c80be3b4f..23cb0a3ef3c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.cs @@ -181,13 +181,55 @@ public double MinZoomFactor private Rect ViewPort => new Rect(_infiniteCanvasScrollViewer.HorizontalOffset / _infiniteCanvasScrollViewer.ZoomFactor, _infiniteCanvasScrollViewer.VerticalOffset / _infiniteCanvasScrollViewer.ZoomFactor, ViewPortWidth, ViewPortHeight); - private double ViewPortHeight => (double.IsNaN(_infiniteCanvasScrollViewer.Height) - ? Window.Current.Bounds.Height - : _infiniteCanvasScrollViewer.ViewportHeight) / _infiniteCanvasScrollViewer.ZoomFactor; + private double ViewPortHeight + { + get + { + double height; + if (double.IsNaN(_infiniteCanvasScrollViewer.Height)) + { + if (ControlHelpers.IsXamlRootAvailable && _infiniteCanvasScrollViewer.XamlRoot != null) + { + height = _infiniteCanvasScrollViewer.XamlRoot.Size.Height; + } + else + { + height = Window.Current.Bounds.Height; + } + } + else + { + height = _infiniteCanvasScrollViewer.ViewportHeight; + } + + return height / _infiniteCanvasScrollViewer.ZoomFactor; + } + } - private double ViewPortWidth => (double.IsNaN(_infiniteCanvasScrollViewer.Width) - ? Window.Current.Bounds.Width - : _infiniteCanvasScrollViewer.ViewportWidth) / _infiniteCanvasScrollViewer.ZoomFactor; + private double ViewPortWidth + { + get + { + double width; + if (double.IsNaN(_infiniteCanvasScrollViewer.Width)) + { + if (ControlHelpers.IsXamlRootAvailable && _infiniteCanvasScrollViewer.XamlRoot != null) + { + width = _infiniteCanvasScrollViewer.XamlRoot.Size.Width; + } + else + { + width = Window.Current.Bounds.Width; + } + } + else + { + width = _infiniteCanvasScrollViewer.ViewportWidth; + } + + return width / _infiniteCanvasScrollViewer.ZoomFactor; + } + } /// /// Initializes a new instance of the class. @@ -227,12 +269,26 @@ protected override void OnApplyTemplate() if (double.IsNaN(_infiniteCanvasScrollViewer.Width)) { - _infiniteCanvasScrollViewer.Width = Window.Current.Bounds.Width; + if (ControlHelpers.IsXamlRootAvailable && _infiniteCanvasScrollViewer.XamlRoot != null) + { + _infiniteCanvasScrollViewer.Width = _infiniteCanvasScrollViewer.XamlRoot.Size.Width; + } + else + { + _infiniteCanvasScrollViewer.Width = Window.Current.Bounds.Width; + } } if (double.IsNaN(_infiniteCanvasScrollViewer.Height)) { - _infiniteCanvasScrollViewer.Height = Window.Current.Bounds.Height; + if (ControlHelpers.IsXamlRootAvailable && _infiniteCanvasScrollViewer.XamlRoot != null) + { + _infiniteCanvasScrollViewer.Height = _infiniteCanvasScrollViewer.XamlRoot.Size.Height; + } + else + { + _infiniteCanvasScrollViewer.Height = Window.Current.Bounds.Height; + } } base.OnApplyTemplate(); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs index 84fe733e1c4..12768168ffd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs @@ -190,14 +190,30 @@ internal bool UpdateMenuItemsFlyoutPlacement() internal FlyoutPlacementMode GetMenuFlyoutPlacementMode() { - var ttv = TransformToVisual(Window.Current.Content); + UIElement content; + double height; + double width; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + content = XamlRoot.Content; + height = XamlRoot.Size.Height; + width = XamlRoot.Size.Width; + } + else + { + content = Window.Current.Content; + height = Window.Current.Bounds.Height; + width = Window.Current.Bounds.Width; + } + + var ttv = TransformToVisual(content); var menuCoords = ttv.TransformPoint(new Point(0, 0)); if (Orientation == Orientation.Horizontal) { var menuCenter = menuCoords.Y + (ActualHeight / 2); - if (menuCenter <= Window.Current.Bounds.Height / 2) + if (menuCenter <= height / 2) { return FlyoutPlacementMode.Bottom; } @@ -210,7 +226,7 @@ internal FlyoutPlacementMode GetMenuFlyoutPlacementMode() { var menuCenter = menuCoords.X + (ActualWidth / 2); - if (menuCenter <= Window.Current.Bounds.Width / 2) + if (menuCenter <= width / 2) { return FlyoutPlacementMode.Right; } @@ -287,7 +303,7 @@ private void HideMenuItemsTooltips() internal void CalculateBounds() { - var ttv = TransformToVisual(Window.Current.Content); + var ttv = TransformToVisual(ControlHelpers.IsXamlRootAvailable && XamlRoot != null ? XamlRoot.Content : Window.Current.Content); Point screenCoords = ttv.TransformPoint(new Point(0, 0)); _bounds.X = screenCoords.X; _bounds.Y = screenCoords.Y; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Menu/MenuItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Menu/MenuItem.cs index 684fece2370..d12750d85b0 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Menu/MenuItem.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Menu/MenuItem.cs @@ -111,6 +111,11 @@ protected override void OnApplyTemplate() MenuFlyout.Closed -= MenuFlyout_Closed; } + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + MenuFlyout.XamlRoot = XamlRoot; + } + if (FlyoutButton != null) { FlyoutButton.PointerExited -= FlyoutButton_PointerExited; @@ -150,7 +155,17 @@ private void MenuItem_IsEnabledChanged(object sender, DependencyPropertyChangedE internal void CalculateBounds() { - var ttv = TransformToVisual(Window.Current.Content); + UIElement content; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + content = XamlRoot.Content; + } + else + { + content = Window.Current.Content; + } + + var ttv = TransformToVisual(content); Point screenCoords = ttv.TransformPoint(new Point(0, 0)); _bounds.X = screenCoords.X; _bounds.Y = screenCoords.Y; @@ -321,7 +336,26 @@ private void MenuFlyout_Opened(object sender, object e) if (!_menuFlyoutRepositioned) { - var popup = VisualTreeHelper.GetOpenPopups(Window.Current).FirstOrDefault(p => p.Child is MenuFlyoutPresenter); + IReadOnlyList popups; + UIElement content; + double outerContentWidth; + double outerContentHeight; + if (ControlHelpers.IsXamlRootAvailable && MenuFlyout.XamlRoot != null) + { + popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(MenuFlyout.XamlRoot); + content = MenuFlyout.XamlRoot.Content; + outerContentWidth = MenuFlyout.XamlRoot.Size.Width; + outerContentHeight = MenuFlyout.XamlRoot.Size.Height; + } + else + { + popups = VisualTreeHelper.GetOpenPopups(Window.Current); + content = Window.Current.Content; + outerContentWidth = Window.Current.Bounds.Width; + outerContentHeight = Window.Current.Bounds.Height; + } + + var popup = popups.FirstOrDefault(p => p.Child is MenuFlyoutPresenter); if (popup != null) { @@ -329,11 +363,11 @@ private void MenuFlyout_Opened(object sender, object e) var height = mfp.ActualHeight; var width = mfp.ActualWidth; - var flytoutButtonPoint = FlyoutButton.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0)); + var flytoutButtonPoint = FlyoutButton.TransformToVisual(content).TransformPoint(new Point(0, 0)); - if ((width > Window.Current.Bounds.Width - flytoutButtonPoint.X && + if ((width > outerContentWidth - flytoutButtonPoint.X && (MenuFlyout.Placement == FlyoutPlacementMode.Bottom)) || - (height > Window.Current.Bounds.Height - flytoutButtonPoint.Y && + (height > outerContentHeight - flytoutButtonPoint.Y && (MenuFlyout.Placement == FlyoutPlacementMode.Right))) { ShowMenuRepositioned(width, height); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewPanel.cs b/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewPanel.cs index 1c36bbf65fd..ee6e75568ba 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewPanel.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewPanel.cs @@ -64,12 +64,26 @@ protected override Size MeasureOverride(Size availableSize) if (double.IsInfinity(width)) { - width = Window.Current.Bounds.Width; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + width = XamlRoot.Size.Width; + } + else + { + width = Window.Current.Bounds.Width; + } } if (double.IsInfinity(height)) { - height = Window.Current.Bounds.Height; + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + height = XamlRoot.Size.Height; + } + else + { + height = Window.Current.Bounds.Height; + } } var finalSize = new Size(width, height); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Properties/AssemblyInfo.cs index 64a4e43ebc0..d2b81648fed 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Properties/AssemblyInfo.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Properties/AssemblyInfo.cs @@ -9,4 +9,5 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: InternalsVisibleTo("UnitTests")] +[assembly: InternalsVisibleTo("UnitTests.XamlIslands.UWPApp")] [assembly: NeutralResourcesLanguage("en-US")] \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs index 113fe7f77bf..a68de9da77a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs @@ -40,6 +40,11 @@ public void StyleHeader(ToolbarButton button) var list = new ListBox { Margin = new Thickness(0), Padding = new Thickness(0) }; headerFlyout = new Flyout { Content = list }; + if (ControlHelpers.IsXamlRootAvailable && button.XamlRoot != null) + { + headerFlyout.XamlRoot = button.XamlRoot; + } + string headerVal = "#"; for (int i = 1; i <= 5; i++) { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs index 1d3f7a59125..51ef091685f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs @@ -121,6 +121,11 @@ protected override void OnApplyTemplate() selectAllMenuItem.Click += (s, e) => SelectAll(); var menuFlyout = new MenuFlyout(); menuFlyout.Items.Add(selectAllMenuItem); + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + menuFlyout.XamlRoot = XamlRoot; + } + ContextFlyout = menuFlyout; } @@ -325,6 +330,11 @@ private async Task AddToken(object data) var menuFlyout = new MenuFlyout(); menuFlyout.Items.Add(removeMenuItem); + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + menuFlyout.XamlRoot = XamlRoot; + } + item.ContextFlyout = menuFlyout; var i = _wrapPanel.Children.Count - 1; diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj b/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj index c0e7bba86e7..56d5358d419 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj @@ -23,6 +23,6 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI/Cache/ImageCache.cs b/Microsoft.Toolkit.Uwp.UI/Cache/ImageCache.cs index 22191f7871e..ece261b30b7 100644 --- a/Microsoft.Toolkit.Uwp.UI/Cache/ImageCache.cs +++ b/Microsoft.Toolkit.Uwp.UI/Cache/ImageCache.cs @@ -10,6 +10,7 @@ using Microsoft.Toolkit.Uwp.Helpers; using Windows.Storage; using Windows.Storage.Streams; +using Windows.System; using Windows.UI.Xaml.Media.Imaging; namespace Microsoft.Toolkit.Uwp.UI @@ -34,11 +35,18 @@ public class ImageCache : CacheBase /// public static ImageCache Instance => _instance ?? (_instance = new ImageCache()); + /// + /// Gets or sets which DispatcherQueue is used to dispatch UI updates. + /// + public DispatcherQueue DispatcherQueue { get; set; } + /// /// Initializes a new instance of the class. /// - public ImageCache() + /// The DispatcherQueue that should be used to dispatch UI updates, or null if this is being called from the UI thread. + public ImageCache(DispatcherQueue dispatcherQueue = null) { + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); _extendedPropertyNames.Add(DateAccessedProperty); } @@ -55,7 +63,7 @@ protected override async Task InitializeTypeAsync(Stream stream, Li throw new FileNotFoundException(); } - return await DispatcherHelper.ExecuteOnUIThreadAsync(async () => + return await DispatcherQueue.ExecuteOnUIThreadAsync(async () => { BitmapImage image = new BitmapImage(); diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs index 112c178df0c..9b20f6f2e4e 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs @@ -4,8 +4,10 @@ using System; using System.Threading; +using Microsoft.Toolkit.Uwp.Helpers; using Windows.Devices.Input; using Windows.Foundation; +using Windows.System; using Windows.UI.Core; using Windows.UI.Input; using Windows.UI.Xaml; @@ -90,7 +92,7 @@ private static void UpdateChange(bool newValue) /// /// Function to set default value and subscribe to events /// - private static void SubscribeMiddleClickScrolling() + private static void SubscribeMiddleClickScrolling(DispatcherQueue dispatcherQueue) { _isPressed = true; _isMoved = false; @@ -101,7 +103,7 @@ private static void SubscribeMiddleClickScrolling() _isCursorAvailable = IsCursorResourceAvailable(); _timer?.Dispose(); - _timer = new Timer(Scroll, null, 5, 5); + _timer = new Timer(Scroll, dispatcherQueue, 5, 5); Window.Current.CoreWindow.PointerMoved -= CoreWindow_PointerMoved; Window.Current.CoreWindow.PointerReleased -= CoreWindow_PointerReleased; @@ -135,10 +137,16 @@ private static void UnsubscribeMiddleClickScrolling() /// Default param for . In this function it will be `null` private static void Scroll(object state) { + var dispatcherQueue = state as DispatcherQueue; + if (dispatcherQueue == null) + { + return; + } + var offsetX = _currentPosition.X - _startPosition.X; var offsetY = _currentPosition.Y - _startPosition.Y; - SetCursorType(offsetX, offsetY); + SetCursorType(dispatcherQueue, offsetX, offsetY); if (Math.Abs(offsetX) > _threshold || Math.Abs(offsetY) > _threshold) { @@ -154,7 +162,7 @@ private static void Scroll(object state) offsetX = offsetX > _maxSpeed ? _maxSpeed : offsetX; offsetY = offsetY > _maxSpeed ? _maxSpeed : offsetY; - RunInUIThread(() => + RunInUIThread(dispatcherQueue, () => { _scrollViewer?.ChangeView(_scrollViewer.HorizontalOffset + offsetX, _scrollViewer.VerticalOffset + offsetY, null, true); }); @@ -190,7 +198,7 @@ private static void ScrollViewer_PointerPressed(object sender, PointerRoutedEven // SubscribeMiddle if middle button is pressed if (pointerPoint.Properties.IsMiddleButtonPressed) { - SubscribeMiddleClickScrolling(); + SubscribeMiddleClickScrolling(DispatcherQueue.GetForCurrentThread()); _startPosition = Window.Current.CoreWindow.PointerPosition; _currentPosition = Window.Current.CoreWindow.PointerPosition; @@ -242,7 +250,7 @@ private static void CoreWindow_PointerReleased(CoreWindow sender, PointerEventAr Window.Current.CoreWindow.PointerPressed -= CoreWindow_PointerPressed; Window.Current.CoreWindow.PointerPressed += CoreWindow_PointerPressed; - SetCursorType(0, 0); + SetCursorType(DispatcherQueue.GetForCurrentThread(), 0, 0); } else { @@ -270,12 +278,7 @@ private static void CoreWindow_PointerExited(CoreWindow sender, PointerEventArgs UnsubscribeMiddleClickScrolling(); } - /// - /// Change cursor type depend upon offset from starting position - /// - /// Horizontal offset from starting position - /// Vertical offset from starting position - private static void SetCursorType(double offsetX, double offsetY) + private static void SetCursorType(DispatcherQueue dispatcherQueue, double offsetX, double offsetY) { if (!_isCursorAvailable) { @@ -323,7 +326,7 @@ private static void SetCursorType(double offsetX, double offsetY) if (_oldCursorID != cursorID) { - RunInUIThread(() => + RunInUIThread(dispatcherQueue, () => { Window.Current.CoreWindow.PointerCursor = new CoreCursor(CoreCursorType.Custom, cursorID); }); @@ -364,16 +367,9 @@ private static bool IsCursorResourceAvailable() return isCursorAvailable; } - /// - /// Run the give input action in UIThread - /// - /// Action to be run on UIThread - private static async void RunInUIThread(Action action) + private static async void RunInUIThread(DispatcherQueue dispatcherQueue, Action action) { - await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - action(); - }); + await dispatcherQueue.ExecuteOnUIThreadAsync(action, DispatcherQueuePriority.Normal); } } } diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/TitleBar/TitleBarExtensions.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/TitleBar/TitleBarExtensions.cs index 457614d405e..33353b8f485 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/TitleBar/TitleBarExtensions.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/TitleBar/TitleBarExtensions.cs @@ -4,7 +4,6 @@ using Windows.UI; using Windows.UI.ViewManagement; -using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace Microsoft.Toolkit.Uwp.UI.Extensions @@ -405,7 +404,7 @@ public static void SetInactiveForegroundColor(Page page, Color value) private static ApplicationViewTitleBar GetTitleBar() { - return IsTitleBarSupported ? Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().TitleBar : null; + return IsTitleBarSupported ? ApplicationView.GetForCurrentView()?.TitleBar : null; } } } diff --git a/Microsoft.Toolkit.Uwp.UI/Helpers/ThemeListener.cs b/Microsoft.Toolkit.Uwp.UI/Helpers/ThemeListener.cs index 42e75842550..9dc2559d506 100644 --- a/Microsoft.Toolkit.Uwp.UI/Helpers/ThemeListener.cs +++ b/Microsoft.Toolkit.Uwp.UI/Helpers/ThemeListener.cs @@ -4,11 +4,16 @@ using System; using System.Diagnostics; -using Windows.ApplicationModel.Core; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.Helpers; using Windows.Foundation.Metadata; +using Windows.System; using Windows.UI.ViewManagement; using Windows.UI.Xaml; +[assembly: InternalsVisibleTo("UnitTests.XamlIslands.UWPApp")] + namespace Microsoft.Toolkit.Uwp.UI.Helpers { /// @@ -42,6 +47,11 @@ public string CurrentThemeName /// public bool IsHighContrast { get; set; } + /// + /// Gets or sets which DispatcherQueue is used to dispatch UI updates. + /// + public DispatcherQueue DispatcherQueue { get; set; } + /// /// An event that fires if the Theme changes. /// @@ -53,16 +63,22 @@ public string CurrentThemeName /// /// Initializes a new instance of the class. /// - public ThemeListener() + /// The DispatcherQueue that should be used to dispatch UI updates, or null if this is being called from the UI thread. + public ThemeListener(DispatcherQueue dispatcherQueue = null) { CurrentTheme = Application.Current.RequestedTheme; IsHighContrast = _accessible.HighContrast; + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); + _accessible.HighContrastChanged += Accessible_HighContrastChanged; _settings.ColorValuesChanged += Settings_ColorValuesChanged; // Fallback in case either of the above fail, we'll check when we get activated next. - Window.Current.CoreWindow.Activated += CoreWindow_Activated; + if (Window.Current != null) + { + Window.Current.CoreWindow.Activated += CoreWindow_Activated; + } } private void Accessible_HighContrastChanged(AccessibilitySettings sender, object args) @@ -76,21 +92,28 @@ private void Accessible_HighContrastChanged(AccessibilitySettings sender, object // Note: This can get called multiple times during HighContrast switch, do we care? private async void Settings_ColorValuesChanged(UISettings sender, object args) + { + await OnColorValuesChanged(); + } + + // Internal abstraction is used by the Unit Tests + internal Task OnColorValuesChanged() { // Getting called off thread, so we need to dispatch to request value. - await CoreApplication.MainView?.CoreWindow?.Dispatcher?.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - // TODO: This doesn't stop the multiple calls if we're in our faked 'White' HighContrast Mode below. - if (CurrentTheme != Application.Current.RequestedTheme || - IsHighContrast != _accessible.HighContrast) + return DispatcherQueue.ExecuteOnUIThreadAsync( + () => { + // TODO: This doesn't stop the multiple calls if we're in our faked 'White' HighContrast Mode below. + if (CurrentTheme != Application.Current.RequestedTheme || + IsHighContrast != _accessible.HighContrast) + { #if DEBUG - Debug.WriteLine("Color Values Changed"); + Debug.WriteLine("Color Values Changed"); #endif - UpdateProperties(); - } - }); + UpdateProperties(); + } + }, DispatcherQueuePriority.Normal); } private void CoreWindow_Activated(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.WindowActivatedEventArgs args) @@ -133,7 +156,10 @@ public void Dispose() { _accessible.HighContrastChanged -= Accessible_HighContrastChanged; _settings.ColorValuesChanged -= Settings_ColorValuesChanged; - Window.Current.CoreWindow.Activated -= CoreWindow_Activated; + if (Window.Current != null) + { + Window.Current.CoreWindow.Activated -= CoreWindow_Activated; + } } } } diff --git a/Microsoft.Toolkit.Uwp/Extensions/StringExtensions.cs b/Microsoft.Toolkit.Uwp/Extensions/StringExtensions.cs index b70792c75a7..ac3fe240312 100644 --- a/Microsoft.Toolkit.Uwp/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit.Uwp/Extensions/StringExtensions.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using Windows.ApplicationModel.Resources; +using Windows.UI; +using Windows.UI.WindowManagement; +using Windows.UI.Xaml; namespace Microsoft.Toolkit.Uwp.Extensions { @@ -17,17 +20,41 @@ public static class StringExtensions /// Retrieves the provided resource for the current view context. /// /// Resource key to retrieve. + /// to be used to get the from. + /// You can retrieve this from a , (XamlIslands), or . /// string value for given resource or empty string if not found. - public static string GetViewLocalized(this string resourceKey) - => ResourceLoader.GetForCurrentView().GetString(resourceKey); + public static string GetViewLocalized(this string resourceKey, UIContext uiContext = null) + { + if (uiContext != null) + { + var resourceLoader = ResourceLoader.GetForUIContext(uiContext); + return resourceLoader.GetString(resourceKey); + } + else + { + return ResourceLoader.GetForCurrentView().GetString(resourceKey); + } + } /// /// Retrieves the provided resource for the given key for use independent of the UI thread. /// /// Resource key to retrieve. + /// to be used to get the from. + /// You can retrieve this from a , (XamlIslands), or . /// string value for given resource or empty string if not found. - public static string GetLocalized(this string resourceKey) - => IndependentLoader.GetString(resourceKey); + public static string GetLocalized(this string resourceKey, UIContext uiContext = null) + { + if (uiContext != null) + { + var resourceLoader = ResourceLoader.GetForUIContext(uiContext); + return resourceLoader.GetString(resourceKey); + } + else + { + return IndependentLoader.GetString(resourceKey); + } + } /// /// Retrieves the provided resource for the given key for use independent of the UI thread. diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs index e1389f5122e..f8e78311c55 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherHelper.cs @@ -12,6 +12,7 @@ namespace Microsoft.Toolkit.Uwp.Helpers /// /// This class provides static methods helper for executing code in UI thread of the main window. /// + [Obsolete] public static class DispatcherHelper { /// diff --git a/Microsoft.Toolkit.Uwp/Helpers/DispatcherQueueHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/DispatcherQueueHelper.cs new file mode 100644 index 00000000000..93f47ecc2f8 --- /dev/null +++ b/Microsoft.Toolkit.Uwp/Helpers/DispatcherQueueHelper.cs @@ -0,0 +1,239 @@ +// 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. + +using System; +using System.Threading.Tasks; +using Windows.Foundation.Metadata; +using Windows.System; + +namespace Microsoft.Toolkit.Uwp.Helpers +{ + /// + /// This class provides static methods helper for executing code in a DispatcherQueue. + /// + public static class DispatcherQueueHelper + { + /// + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. + /// + /// DispatcherQueue of a thread to run . + /// Function to be executed on the given dispatcher. + /// DispatcherQueue execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. + public static Task ExecuteOnUIThreadAsync(this DispatcherQueue dispatcher, Action function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) + { + if (function is null) + { + throw new ArgumentNullException(nameof(function)); + } + + /* Run the function directly when we have thread access. + * Also reuse Task.CompletedTask in case of success, + * to skip an unnecessary heap allocation for every invocation. */ + if (HasThreadAccess(dispatcher)) + { + try + { + function(); + + return Task.CompletedTask; + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + var taskCompletionSource = new TaskCompletionSource(); + + _ = dispatcher.TryEnqueue(priority, () => + { + try + { + function(); + + taskCompletionSource.SetResult(null); + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; + } + + /// + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. + /// + /// Returned data type of the function. + /// DispatcherQueue of a thread to run . + /// Function to be executed on the given dispatcher. + /// DispatcherQueue execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. + public static Task ExecuteOnUIThreadAsync(this DispatcherQueue dispatcher, Func function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) + { + if (function is null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (HasThreadAccess(dispatcher)) + { + try + { + return Task.FromResult(function()); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + var taskCompletionSource = new TaskCompletionSource(); + + _ = dispatcher.TryEnqueue(priority, () => + { + try + { + taskCompletionSource.SetResult(function()); + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; + } + + /// + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. + /// + /// DispatcherQueue of a thread to run . + /// Asynchrounous function to be executed on the given dispatcher. + /// DispatcherQueue execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. + public static Task ExecuteOnUIThreadAsync(this DispatcherQueue dispatcher, Func function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) + { + if (function is null) + { + throw new ArgumentNullException(nameof(function)); + } + + /* If we have thread access, we can retrieve the task directly. + * We don't use ConfigureAwait(false) in this case, in order + * to let the caller continue its execution on the same thread + * after awaiting the task returned by this function. */ + if (HasThreadAccess(dispatcher)) + { + try + { + if (function() is Task awaitableResult) + { + return awaitableResult; + } + + return Task.FromException(new InvalidOperationException("The Task returned by function cannot be null.")); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + var taskCompletionSource = new TaskCompletionSource(); + + _ = dispatcher.TryEnqueue(priority, async () => + { + try + { + if (function() is Task awaitableResult) + { + await awaitableResult.ConfigureAwait(false); + + taskCompletionSource.SetResult(null); + } + else + { + taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null.")); + } + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; + } + + /// + /// Extension method for . Offering an actual awaitable with optional result that will be executed on the given dispatcher. + /// + /// Returned data type of the function. + /// DispatcherQueue of a thread to run . + /// Asynchrounous function to be executed asynchrounously on the given dispatcher. + /// DispatcherQueue execution priority, default is normal. + /// An awaitable for the operation. + /// If the current thread has UI access, will be invoked directly. + public static Task ExecuteOnUIThreadAsync(this DispatcherQueue dispatcher, Func> function, DispatcherQueuePriority priority = DispatcherQueuePriority.Normal) + { + if (function is null) + { + throw new ArgumentNullException(nameof(function)); + } + + if (HasThreadAccess(dispatcher)) + { + try + { + if (function() is Task awaitableResult) + { + return awaitableResult; + } + + return Task.FromException(new InvalidOperationException("The Task returned by function cannot be null.")); + } + catch (Exception e) + { + return Task.FromException(e); + } + } + + var taskCompletionSource = new TaskCompletionSource(); + + _ = dispatcher.TryEnqueue(priority, async () => + { + try + { + if (function() is Task awaitableResult) + { + var result = await awaitableResult.ConfigureAwait(false); + + taskCompletionSource.SetResult(result); + } + else + { + taskCompletionSource.SetException(new InvalidOperationException("The Task returned by function cannot be null.")); + } + } + catch (Exception e) + { + taskCompletionSource.SetException(e); + } + }); + + return taskCompletionSource.Task; + } + + private static bool HasThreadAccess(DispatcherQueue dispatcher) + { + return ApiInformation.IsMethodPresent("Windows.System.DispatcherQueue", "HasThreadAccess") && dispatcher.HasThreadAccess; + } + } +} diff --git a/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelper.cs index 472951e2879..3e489433331 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelper.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Windows.ApplicationModel.Core; using Windows.Graphics.Printing; +using Windows.System; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Printing; @@ -96,13 +96,21 @@ public class PrintHelper : IDisposable /// private PrintHelperOptions _defaultPrintHelperOptions; + /// + /// Gets or sets which DispatcherQueue is used to dispatch UI updates. + /// + public DispatcherQueue DispatcherQueue { get; set; } + /// /// Initializes a new instance of the class. /// /// XAML panel used to attach printing canvas. Can be hidden in your UI with Opacity = 0 for instance /// Default settings for the print tasks - public PrintHelper(Panel canvasContainer, PrintHelperOptions defaultPrintHelperOptions = null) + /// The DispatcherQueue that should be used to dispatch UI updates, or null if this is being called from the UI thread. + public PrintHelper(Panel canvasContainer, PrintHelperOptions defaultPrintHelperOptions = null, DispatcherQueue dispatcherQueue = null) { + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); + if (canvasContainer == null) { throw new ArgumentNullException(); @@ -197,7 +205,7 @@ public void Dispose() } _printCanvas = null; - DispatcherHelper.ExecuteOnUIThreadAsync(() => + DispatcherQueue.ExecuteOnUIThreadAsync(() => { _printDocument.Paginate -= CreatePrintPreviewPages; _printDocument.GetPreviewPage -= GetPrintPreviewPage; @@ -221,7 +229,7 @@ private async Task DetachCanvas() { if (!_directPrint) { - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherQueue.ExecuteOnUIThreadAsync(() => { _canvasContainer.Children.Remove(_printCanvas); _printCanvas.Children.Clear(); @@ -254,32 +262,33 @@ private void PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs printTask.Completed += async (s, args) => { // Notify the user when the print operation fails. - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => - { - foreach (var element in _stateBags.Keys) - { - _stateBags[element].Restore(element); - } - - _stateBags.Clear(); - _canvasContainer.RequestedTheme = ElementTheme.Default; - await DetachCanvas(); - - switch (args.Completion) + await DispatcherQueue.ExecuteOnUIThreadAsync( + async () => { - case PrintTaskCompletion.Failed: - OnPrintFailed?.Invoke(); - break; - - case PrintTaskCompletion.Canceled: - OnPrintCanceled?.Invoke(); - break; - - case PrintTaskCompletion.Submitted: - OnPrintSucceeded?.Invoke(); - break; - } - }); + foreach (var element in _stateBags.Keys) + { + _stateBags[element].Restore(element); + } + + _stateBags.Clear(); + _canvasContainer.RequestedTheme = ElementTheme.Default; + await DetachCanvas(); + + switch (args.Completion) + { + case PrintTaskCompletion.Failed: + OnPrintFailed?.Invoke(); + break; + + case PrintTaskCompletion.Canceled: + OnPrintCanceled?.Invoke(); + break; + + case PrintTaskCompletion.Submitted: + OnPrintSucceeded?.Invoke(); + break; + } + }, DispatcherQueuePriority.Normal); }; sourceRequested.SetSource(_printDocumentSource); @@ -471,7 +480,7 @@ private Task AddOnePrintPreviewPage(FrameworkElement element, PrintPageDescripti // Save state if (!_stateBags.ContainsKey(element)) { - var stateBag = new PrintHelperStateBag(); + var stateBag = new PrintHelperStateBag(DispatcherQueue); stateBag.Capture(element); _stateBags.Add(element, stateBag); } @@ -506,7 +515,7 @@ private Task AddOnePrintPreviewPage(FrameworkElement element, PrintPageDescripti element.Margin = new Thickness(marginWidth / 2, marginHeight / 2, marginWidth / 2, marginHeight / 2); page.Content = element; - return DispatcherHelper.ExecuteOnUIThreadAsync( + return DispatcherQueue.ExecuteOnUIThreadAsync( () => { // Add the (newly created) page to the print canvas which is part of the visual tree and force it to go @@ -517,12 +526,12 @@ private Task AddOnePrintPreviewPage(FrameworkElement element, PrintPageDescripti // Add the page to the page preview collection _printPreviewPages.Add(page); - }, Windows.UI.Core.CoreDispatcherPriority.High); + }, DispatcherQueuePriority.High); } private Task ClearPageCache() { - return DispatcherHelper.ExecuteOnUIThreadAsync(() => + return DispatcherQueue.ExecuteOnUIThreadAsync(() => { if (!_directPrint) { diff --git a/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelperStateBag.cs b/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelperStateBag.cs index b98c3dd7984..7be4a651451 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelperStateBag.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/PrintHelper/PrintHelperStateBag.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Windows.System; using Windows.UI.Xaml; namespace Microsoft.Toolkit.Uwp.Helpers @@ -11,6 +12,13 @@ namespace Microsoft.Toolkit.Uwp.Helpers /// internal class PrintHelperStateBag { + private readonly DispatcherQueue _dispatcherQueue; + + internal PrintHelperStateBag(DispatcherQueue dispatcherQueue) + { + _dispatcherQueue = dispatcherQueue; + } + /// /// Gets or sets the stored horizontal alignment. /// @@ -55,7 +63,7 @@ public void Capture(FrameworkElement element) /// Element to restore state to public void Restore(FrameworkElement element) { - DispatcherHelper.ExecuteOnUIThreadAsync(() => + _dispatcherQueue.ExecuteOnUIThreadAsync(() => { element.HorizontalAlignment = HorizontalAlignment; element.VerticalAlignment = VerticalAlignment; diff --git a/Microsoft.Toolkit.Uwp/Helpers/RemoteDeviceHelper/RemoteDeviceHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/RemoteDeviceHelper/RemoteDeviceHelper.cs index ecfa44c2553..1601ef903c5 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/RemoteDeviceHelper/RemoteDeviceHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/RemoteDeviceHelper/RemoteDeviceHelper.cs @@ -7,6 +7,7 @@ using System.Collections.ObjectModel; using System.Linq; using Windows.Foundation.Metadata; +using Windows.System; using Windows.System.RemoteSystems; using Windows.System.Threading; @@ -24,11 +25,18 @@ public class RemoteDeviceHelper private RemoteSystemWatcher _remoteSystemWatcher; + /// + /// Gets or sets which DispatcherQueue is used to dispatch UI updates. + /// + public DispatcherQueue DispatcherQueue { get; set; } + /// /// Initializes a new instance of the class. /// - public RemoteDeviceHelper() + /// The DispatcherQueue that should be used to dispatch UI updates, or null if this is being called from the UI thread. + public RemoteDeviceHelper(DispatcherQueue dispatcherQueue = null) { + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); RemoteSystems = new ObservableCollection(); GenerateSystems(); } @@ -36,8 +44,11 @@ public RemoteDeviceHelper() /// /// Initializes a new instance of the class. /// - public RemoteDeviceHelper(List filter) + /// Initiate Enumeration with specific RemoteSysemKind with Filters + /// The DispatcherQueue that should be used to dispatch UI updates, or null if this is being called from the UI thread. + public RemoteDeviceHelper(List filter, DispatcherQueue dispatcherQueue = null) { + DispatcherQueue = dispatcherQueue ?? DispatcherQueue.GetForCurrentThread(); RemoteSystems = new ObservableCollection(); GenerateSystemsWithFilterAsync(filter); } @@ -86,7 +97,7 @@ private void RemoteSystemWatcher_EnumerationCompleted(RemoteSystemWatcher sender private async void RemoteSystemWatcher_RemoteSystemUpdated(RemoteSystemWatcher sender, RemoteSystemUpdatedEventArgs args) { - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherQueue.ExecuteOnUIThreadAsync(() => { RemoteSystems.Remove(RemoteSystems.First(a => a.Id == args.RemoteSystem.Id)); RemoteSystems.Add(args.RemoteSystem); @@ -95,7 +106,7 @@ await DispatcherHelper.ExecuteOnUIThreadAsync(() => private async void RemoteSystemWatcher_RemoteSystemRemoved(RemoteSystemWatcher sender, RemoteSystemRemovedEventArgs args) { - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherQueue.ExecuteOnUIThreadAsync(() => { RemoteSystems.Remove(RemoteSystems.First(a => a.Id == args.RemoteSystemId)); }); @@ -103,7 +114,7 @@ await DispatcherHelper.ExecuteOnUIThreadAsync(() => private async void RemoteSystemWatcher_RemoteSystemAdded(RemoteSystemWatcher sender, RemoteSystemAddedEventArgs args) { - await DispatcherHelper.ExecuteOnUIThreadAsync(() => + await DispatcherQueue.ExecuteOnUIThreadAsync(() => { RemoteSystems.Add(args.RemoteSystem); }); diff --git a/Microsoft.Toolkit.Uwp/Helpers/ScreenUnitHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/ScreenUnitHelper.cs index 0fb75aa4fd5..45b929272d6 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/ScreenUnitHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/ScreenUnitHelper.cs @@ -4,6 +4,7 @@ using System; using Windows.Graphics.Display; +using Windows.UI.Xaml; namespace Microsoft.Toolkit.Uwp.Helpers { @@ -22,8 +23,9 @@ public static class ScreenUnitHelper /// Start unit /// End unit /// The value to convert (using start unit) + /// The XamlRoot that will be used to get the screen scale. Required on Xaml Islands. /// The result of the conversion - public static float Convert(ScreenUnit from, ScreenUnit to, float value) + public static float Convert(ScreenUnit from, ScreenUnit to, float value, XamlRoot xamlRoot = null) { if (from == to) { @@ -45,7 +47,7 @@ public static float Convert(ScreenUnit from, ScreenUnit to, float value) if (to == ScreenUnit.EffectivePixel) { - return value / (float)DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + return value / GetScale(xamlRoot); } throw new ArgumentOutOfRangeException(nameof(to)); @@ -79,7 +81,7 @@ public static float Convert(ScreenUnit from, ScreenUnit to, float value) case ScreenUnit.EffectivePixel: if (to == ScreenUnit.Pixel) { - return value * (float)DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + return value * GetScale(xamlRoot); } throw new ArgumentOutOfRangeException(nameof(to)); @@ -88,5 +90,17 @@ public static float Convert(ScreenUnit from, ScreenUnit to, float value) throw new ArgumentOutOfRangeException(nameof(from)); } } + + private static float GetScale(XamlRoot xamlRoot) + { + if (xamlRoot != null) + { + return (float)xamlRoot.RasterizationScale; + } + else + { + return (float)DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel; + } + } } } diff --git a/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs b/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs index 978aa9a3ef1..9c5ffd40d32 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/SystemInformation.cs @@ -12,6 +12,7 @@ using Windows.System; using Windows.System.Profile; using Windows.System.UserProfile; +using Windows.UI.Xaml; namespace Microsoft.Toolkit.Uwp.Helpers { @@ -180,7 +181,8 @@ public TimeSpan AppUptime /// Tracks information about the app's launch. /// /// Details about the launch request and process. - public void TrackAppUse(LaunchActivatedEventArgs args) + /// The XamlRoot object from your visual tree. + public void TrackAppUse(IActivatedEventArgs args, XamlRoot xamlRoot = null) { if (args.PreviousExecutionState == ApplicationExecutionState.ClosedByUser || args.PreviousExecutionState == ApplicationExecutionState.NotRunning) @@ -213,24 +215,42 @@ public void TrackAppUse(LaunchActivatedEventArgs args) : DateTime.MinValue; } - void App_VisibilityChanged(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.VisibilityChangedEventArgs e) + if (xamlRoot != null) { - if (e.Visible) + void XamlRoot_Changed(XamlRoot sender, XamlRootChangedEventArgs e) { - _sessionStart = DateTime.UtcNow; + UpdateVisibility(sender.IsHostVisible); } - else + + xamlRoot.Changed -= XamlRoot_Changed; + xamlRoot.Changed += XamlRoot_Changed; + } + else + { + void App_VisibilityChanged(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.VisibilityChangedEventArgs e) { - var subsessionLength = DateTime.UtcNow.Subtract(_sessionStart).Ticks; + UpdateVisibility(e.Visible); + } - var uptimeSoFar = _localObjectStorageHelper.Read(nameof(AppUptime)); + Windows.UI.Core.CoreWindow.GetForCurrentThread().VisibilityChanged -= App_VisibilityChanged; + Windows.UI.Core.CoreWindow.GetForCurrentThread().VisibilityChanged += App_VisibilityChanged; + } + } - _localObjectStorageHelper.Save(nameof(AppUptime), uptimeSoFar + subsessionLength); - } + private void UpdateVisibility(bool visible) + { + if (visible) + { + _sessionStart = DateTime.UtcNow; } + else + { + var subsessionLength = DateTime.UtcNow.Subtract(_sessionStart).Ticks; - Windows.UI.Core.CoreWindow.GetForCurrentThread().VisibilityChanged -= App_VisibilityChanged; - Windows.UI.Core.CoreWindow.GetForCurrentThread().VisibilityChanged += App_VisibilityChanged; + var uptimeSoFar = _localObjectStorageHelper.Read(nameof(AppUptime)); + + _localObjectStorageHelper.Save(nameof(AppUptime), uptimeSoFar + subsessionLength); + } } /// diff --git a/UnitTests/Helpers/Test_DispatcherHelper.cs b/UnitTests/Helpers/Test_DispatcherHelper.cs index 3f6803e65fb..94e37242419 100644 --- a/UnitTests/Helpers/Test_DispatcherHelper.cs +++ b/UnitTests/Helpers/Test_DispatcherHelper.cs @@ -13,6 +13,7 @@ namespace UnitTests.Helpers { +#pragma warning disable CS0612 // Type or member is obsolete [TestClass] public class Test_DispatcherHelper { @@ -30,9 +31,9 @@ public void Test_DispatcherHelper_Action_Ok_UIThread() { DispatcherHelper.ExecuteOnUIThreadAsync(() => { - var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_Action_Ok_NonUIThread) }; + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_Action_Ok_UIThread) }; - Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_Action_Ok_NonUIThread)); + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_Action_Ok_UIThread)); }).Wait(); } @@ -77,10 +78,10 @@ public void Test_DispatcherHelper_FuncOfT_Ok_UIThread() { var textBlock = DispatcherHelper.ExecuteOnUIThreadAsync(() => { - return new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfT_Ok_NonUIThread) }; + return new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfT_Ok_UIThread) }; }).Result; - Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfT_Ok_NonUIThread)); + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfT_Ok_UIThread)); } [TestCategory("Helpers")] @@ -128,9 +129,9 @@ public void Test_DispatcherHelper_FuncOfTask_Ok_UIThread() { DispatcherHelper.ExecuteOnUIThreadAsync(() => { - var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTask_Ok_NonUIThread) }; + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTask_Ok_UIThread) }; - Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTask_Ok_NonUIThread)); + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTask_Ok_UIThread)); return Task.CompletedTask; }).Wait(); @@ -179,9 +180,9 @@ public void Test_DispatcherHelper_FuncOfTaskOfT_Ok_UIThread() { DispatcherHelper.ExecuteOnUIThreadAsync(() => { - var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_NonUIThread) }; + var textBlock = new TextBlock { Text = nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_UIThread) }; - Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_NonUIThread)); + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherHelper_FuncOfTaskOfT_Ok_UIThread)); return Task.FromResult(1); }).Wait(); @@ -218,4 +219,5 @@ public void Test_DispatcherHelper_FuncOfTaskOfT_Exception() Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); } } +#pragma warning restore CS0612 // Type or member is obsolete } diff --git a/UnitTests/Helpers/Test_DispatcherQueueHelper.cs b/UnitTests/Helpers/Test_DispatcherQueueHelper.cs new file mode 100644 index 00000000000..ba81bb1afc6 --- /dev/null +++ b/UnitTests/Helpers/Test_DispatcherQueueHelper.cs @@ -0,0 +1,293 @@ +// 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. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using System.Linq; +using System.Threading.Tasks; +using Windows.UI.Xaml.Controls; +using Microsoft.Toolkit.Uwp.Helpers; +using Windows.ApplicationModel.Core; +using Windows.UI.Core; +using Windows.System; + +namespace UnitTests.Helpers +{ + [TestClass] + [Ignore("Ignored until issue on .Net Native is fixed. These are working.")] + public class Test_DispatcherQueueHelper + { + private const int TIME_OUT = 5000; + + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_DispatcherQueueHelper_Action_Null() + { + DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Action)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_Action_Ok_UIThread() + { + DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_Action_Ok_UIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_Action_Ok_UIThread)); + }).Wait(); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherQueueHelper_Action_Ok_NonUIThread() + { + var taskSource = new TaskCompletionSource(); + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + CoreDispatcherPriority.Normal, async () => + { + try + { + var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + await Task.Run(async () => + { + await DispatcherQueueHelper.ExecuteOnUIThreadAsync(dispatcherQueue, () => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_Action_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_Action_Ok_NonUIThread)); + + taskSource.SetResult(null); + }); + }); + } + catch (Exception e) + { + taskSource.SetException(e); + } + }); + await taskSource.Task; + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_Action_Exception() + { + var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () => + { + throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_Action_Exception)); + }); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_DispatcherQueueHelper_FuncOfT_Null() + { + DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Func)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_FuncOfT_Ok_UIThread() + { + var textBlock = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () => + { + return new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_UIThread) }; + }).Result; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_UIThread)); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherQueueHelper_FuncOfT_Ok_NonUIThread() + { + var taskSource = new TaskCompletionSource(); + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + CoreDispatcherPriority.Normal, async () => + { + try + { + var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + await Task.Run(async () => + { + var textBlock = await DispatcherQueueHelper.ExecuteOnUIThreadAsync(dispatcherQueue, () => + { + return new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_NonUIThread) }; + }); + await DispatcherQueueHelper.ExecuteOnUIThreadAsync(dispatcherQueue, () => + { + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfT_Ok_NonUIThread)); + taskSource.SetResult(null); + }); + }); + } + catch (Exception e) + { + taskSource.SetException(e); + } + }); + await taskSource.Task; + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_FuncOfT_Exception() + { + var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), new Func(() => + { + throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_FuncOfT_Exception)); + })); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + [SuppressMessage("Style", "IDE0034", Justification = "Explicit overload for clarity")] + public void Test_DispatcherQueueHelper_FuncOfTask_Null() + { + DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Func)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_FuncOfTask_Ok_UIThread() + { + DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfTask_Ok_UIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfTask_Ok_UIThread)); + + return Task.CompletedTask; + }).Wait(); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherQueueHelper_FuncOfTask_Ok_NonUIThread() + { + var taskSource = new TaskCompletionSource(); + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + CoreDispatcherPriority.Normal, async () => + { + try + { + await DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), async () => + { + await Task.Yield(); + + var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfTask_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfTask_Ok_NonUIThread)); + + taskSource.SetResult(null); + }); + } + catch (Exception e) + { + taskSource.SetException(e); + } + }); + await taskSource.Task; + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_FuncOfTask_Exception() + { + var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), new Func(() => + { + throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_FuncOfTask_Exception)); + })); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + + [TestCategory("Helpers")] + [UITestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Test_DispatcherQueueHelper_FuncOfTaskOfT_Null() + { + DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), default(Func>)); + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_UIThread() + { + DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), () => + { + var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_UIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_UIThread)); + + return Task.FromResult(1); + }).Wait(); + } + + [TestCategory("Helpers")] + [TestMethod] + public async Task Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_NonUIThread() + { + var taskSource = new TaskCompletionSource(); + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + CoreDispatcherPriority.Normal, async () => + { + try + { + await DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), async () => + { + await Task.Yield(); + + var textBlock = new TextBlock { Text = nameof(Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_NonUIThread) }; + + Assert.AreEqual(textBlock.Text, nameof(Test_DispatcherQueueHelper_FuncOfTaskOfT_Ok_NonUIThread)); + + taskSource.SetResult(null); + + return textBlock; + }); + } + catch (Exception e) + { + taskSource.SetException(e); + } + }); + await taskSource.Task; + } + + [TestCategory("Helpers")] + [UITestMethod] + public void Test_DispatcherQueueHelper_FuncOfTaskOfT_Exception() + { + var task = DispatcherQueueHelper.ExecuteOnUIThreadAsync(DispatcherQueue.GetForCurrentThread(), new Func>(() => + { + throw new ArgumentException(nameof(this.Test_DispatcherQueueHelper_FuncOfTaskOfT_Exception)); + })); + + Assert.IsNotNull(task); + Assert.AreEqual(task.Status, TaskStatus.Faulted); + Assert.IsNotNull(task.Exception); + Assert.IsInstanceOfType(task.Exception.InnerExceptions.FirstOrDefault(), typeof(ArgumentException)); + } + } +} diff --git a/UnitTests/UnitTests.Notifications.Shared/.editorconfig b/UnitTests/UnitTests.Notifications.Shared/.editorconfig new file mode 100644 index 00000000000..68caad1b9dc --- /dev/null +++ b/UnitTests/UnitTests.Notifications.Shared/.editorconfig @@ -0,0 +1,7 @@ +[*.{cs,vb}] + +# SA1601: Partial elements should be documented +dotnet_diagnostic.SA1601.severity = none +dotnet_diagnostic.CS1573.severity = none +dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.CS1712.severity = none \ No newline at end of file diff --git a/UnitTests/UnitTests.Notifications.UWP/UnitTests.Notifications.UWP.csproj b/UnitTests/UnitTests.Notifications.UWP/UnitTests.Notifications.UWP.csproj index f1df22649e9..9de862b14da 100644 --- a/UnitTests/UnitTests.Notifications.UWP/UnitTests.Notifications.UWP.csproj +++ b/UnitTests/UnitTests.Notifications.UWP/UnitTests.Notifications.UWP.csproj @@ -124,7 +124,7 @@ - 6.2.9 + 6.2.10 1.2.0 diff --git a/UnitTests/UnitTests.Notifications.WinRT/UnitTests.Notifications.WinRT.csproj b/UnitTests/UnitTests.Notifications.WinRT/UnitTests.Notifications.WinRT.csproj index 3d76f6342fd..4c48bc07fed 100644 --- a/UnitTests/UnitTests.Notifications.WinRT/UnitTests.Notifications.WinRT.csproj +++ b/UnitTests/UnitTests.Notifications.WinRT/UnitTests.Notifications.WinRT.csproj @@ -124,7 +124,7 @@ - 6.2.9 + 6.2.10 2.1.0 diff --git a/UnitTests/UnitTests.XamlIslands.Package/Images/LockScreenLogo.scale-200.png b/UnitTests/UnitTests.XamlIslands.Package/Images/LockScreenLogo.scale-200.png new file mode 100644 index 00000000000..735f57adb5d Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.Package/Images/LockScreenLogo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.Package/Images/SplashScreen.scale-200.png b/UnitTests/UnitTests.XamlIslands.Package/Images/SplashScreen.scale-200.png new file mode 100644 index 00000000000..023e7f1feda Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.Package/Images/SplashScreen.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.Package/Images/Square150x150Logo.scale-200.png b/UnitTests/UnitTests.XamlIslands.Package/Images/Square150x150Logo.scale-200.png new file mode 100644 index 00000000000..af49fec1a54 Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.Package/Images/Square150x150Logo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.Package/Images/Square44x44Logo.scale-200.png b/UnitTests/UnitTests.XamlIslands.Package/Images/Square44x44Logo.scale-200.png new file mode 100644 index 00000000000..ce342a2ec8a Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.Package/Images/Square44x44Logo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.Package/Images/Square44x44Logo.targetsize-24_altform-unplated.png b/UnitTests/UnitTests.XamlIslands.Package/Images/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 00000000000..f6c02ce97e0 Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.Package/Images/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/UnitTests/UnitTests.XamlIslands.Package/Images/StoreLogo.png b/UnitTests/UnitTests.XamlIslands.Package/Images/StoreLogo.png new file mode 100644 index 00000000000..7385b56c0e4 Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.Package/Images/StoreLogo.png differ diff --git a/UnitTests/UnitTests.XamlIslands.Package/Images/Wide310x150Logo.scale-200.png b/UnitTests/UnitTests.XamlIslands.Package/Images/Wide310x150Logo.scale-200.png new file mode 100644 index 00000000000..288995b397f Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.Package/Images/Wide310x150Logo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.Package/Package.appxmanifest b/UnitTests/UnitTests.XamlIslands.Package/Package.appxmanifest new file mode 100644 index 00000000000..1d1aaf29eda --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.Package/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + UnitTests.XamlIslands.Package + Microsoft + Images\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UnitTests/UnitTests.XamlIslands.Package/UnitTests.XamlIslands.Package.wapproj b/UnitTests/UnitTests.XamlIslands.Package/UnitTests.XamlIslands.Package.wapproj new file mode 100644 index 00000000000..a6903f454c6 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.Package/UnitTests.XamlIslands.Package.wapproj @@ -0,0 +1,98 @@ + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + AnyCPU + + + Release + AnyCPU + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + dcfbf9f1-2bbe-498d-b6c9-8ade50c06cdc + 10.0.18362.0 + 10.0.18362.0 + en-US + false + ..\UnitTests.XamlIslands\UnitTests.XamlIslands.csproj + + + + Designer + + + + + + + + + + + + + + True + + + + + Retail + Debug + $(PlatformShortName) + true + + + + + + <_TemporaryFilteredWapProjOutput Include="@(_FilteredNonWapProjProjectOutput)" /> + <_FilteredNonWapProjProjectOutput Remove="@(_TemporaryFilteredWapProjOutput)" /> + <_FilteredNonWapProjProjectOutput Include="@(_TemporaryFilteredWapProjOutput)"> + + + + + + \ No newline at end of file diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/App.xaml b/UnitTests/UnitTests.XamlIslands.UWPApp/App.xaml new file mode 100644 index 00000000000..0ff39e7d274 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/App.xaml @@ -0,0 +1,7 @@ + + diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/App.xaml.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/App.xaml.cs new file mode 100644 index 00000000000..b62796cd941 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/App.xaml.cs @@ -0,0 +1,23 @@ +// 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. + +using Microsoft.Toolkit.Win32.UI.XamlHost; +using Windows.System; +using Windows.UI.Xaml; + +namespace UnitTests.XamlIslands.UWPApp +{ + public sealed partial class App : XamlApplication + { + internal static DispatcherQueue Dispatcher { get; set; } + + internal static XamlRoot XamlRoot { get; set; } + + public App() + { + Initialize(); + InitializeComponent(); + } + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/LockScreenLogo.scale-200.png b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 00000000000..735f57adb5d Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/LockScreenLogo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/SplashScreen.scale-200.png b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/SplashScreen.scale-200.png new file mode 100644 index 00000000000..023e7f1feda Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/SplashScreen.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square150x150Logo.scale-200.png b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 00000000000..af49fec1a54 Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square150x150Logo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square44x44Logo.scale-200.png b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 00000000000..ce342a2ec8a Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square44x44Logo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 00000000000..f6c02ce97e0 Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/StoreLogo.png b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/StoreLogo.png new file mode 100644 index 00000000000..7385b56c0e4 Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/StoreLogo.png differ diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Wide310x150Logo.scale-200.png b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 00000000000..288995b397f Binary files /dev/null and b/UnitTests/UnitTests.XamlIslands.UWPApp/Assets/Wide310x150Logo.scale-200.png differ diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/Assert.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/Assert.cs new file mode 100644 index 00000000000..c192ef9bfdc --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/Assert.cs @@ -0,0 +1,19 @@ +// 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. + +using System; + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + public static class Assert + { + internal static void AreEqual(object expected, object actual) + { + if (!expected.Equals(actual)) + { + throw new Exception($"Assert.AreEqual failed. Expected:<{expected}>. Actual:<{actual}>."); + } + } + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/IgnoreAttribute.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/IgnoreAttribute.cs new file mode 100644 index 00000000000..c3ea2a6ce70 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/IgnoreAttribute.cs @@ -0,0 +1,12 @@ +// 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. + +using System; + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + public class IgnoreAttribute : Attribute + { + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestClassAttribute.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestClassAttribute.cs new file mode 100644 index 00000000000..1370afd2190 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestClassAttribute.cs @@ -0,0 +1,12 @@ +// 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. + +using System; + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + public class TestClassAttribute : Attribute + { + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestCleanupAttribute.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestCleanupAttribute.cs new file mode 100644 index 00000000000..5892b5c26d6 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestCleanupAttribute.cs @@ -0,0 +1,12 @@ +// 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. + +using System; + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + public class TestCleanupAttribute : Attribute + { + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestInitializeAttribute.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestInitializeAttribute.cs new file mode 100644 index 00000000000..69e7f95b589 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestInitializeAttribute.cs @@ -0,0 +1,12 @@ +// 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. + +using System; + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + public class TestInitializeAttribute : Attribute + { + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestMethodAttribute.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestMethodAttribute.cs new file mode 100644 index 00000000000..db9ff5407de --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/FakeTestClasses/TestMethodAttribute.cs @@ -0,0 +1,12 @@ +// 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. + +using System; + +namespace Microsoft.VisualStudio.TestTools.UnitTesting +{ + public class TestMethodAttribute : Attribute + { + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Package.appxmanifest b/UnitTests/UnitTests.XamlIslands.UWPApp/Package.appxmanifest new file mode 100644 index 00000000000..481c2c4426a --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + + + UnitTests.XamlIslands.UWPApp + Microsoft + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Properties/AssemblyInfo.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..37c4bfdef0f --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +// 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. + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UnitTests.XamlIslands.UWPApp")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UnitTests.XamlIslands.UWPApp")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Properties/Default.rd.xml b/UnitTests/UnitTests.XamlIslands.UWPApp/Properties/Default.rd.xml new file mode 100644 index 00000000000..af00722cdf9 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/Properties/Default.rd.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/STATestClassAttribute.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/STATestClassAttribute.cs new file mode 100644 index 00000000000..2874de0183f --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/STATestClassAttribute.cs @@ -0,0 +1,23 @@ +// 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. + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.XamlIslands.UWPApp +{ + public class STATestClassAttribute : TestClassAttribute + { + /* + public override TestMethodAttribute GetTestMethodAttribute(TestMethodAttribute testMethodAttribute) + { + if (testMethodAttribute is STATestMethodAttribute) + { + return testMethodAttribute; + } + + return new STATestMethodAttribute(base.GetTestMethodAttribute(testMethodAttribute)); + } + */ + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/STATestMethodAttribute.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/STATestMethodAttribute.cs new file mode 100644 index 00000000000..9114d709cdc --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/STATestMethodAttribute.cs @@ -0,0 +1,52 @@ +// 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. + +// using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.XamlIslands.UWPApp +{ + public class STATestMethodAttribute : TestMethodAttribute + { + /* + private readonly TestMethodAttribute _testMethodAttribute; + + public STATestMethodAttribute() + { + } + + public STATestMethodAttribute(TestMethodAttribute testMethodAttribute) + { + _testMethodAttribute = testMethodAttribute; + } + + public override TestResult[] Execute(ITestMethod testMethod) + { + if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) + { + return Invoke(testMethod); + } + + TestResult[] result = null; + + var thread = new Thread(() => result = Invoke(testMethod)); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + + return result; + } + + private TestResult[] Invoke(ITestMethod testMethod) + { + if (_testMethodAttribute != null) + { + return _testMethodAttribute.Execute(testMethod); + } + + return new[] { testMethod.Invoke(null) }; + } + */ + } +} diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/Strings/en-us/Resources.resw b/UnitTests/UnitTests.XamlIslands.UWPApp/Strings/en-us/Resources.resw new file mode 100644 index 00000000000..291d768851a --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/Strings/en-us/Resources.resw @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ABCDEF + + \ No newline at end of file diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/TestsPage.xaml b/UnitTests/UnitTests.XamlIslands.UWPApp/TestsPage.xaml new file mode 100644 index 00000000000..1af0c478ab1 --- /dev/null +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/TestsPage.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + +