Merge branch 'master' into deprecate-radial

This commit is contained in:
Michael Hawker MSFT (XAML Llama) 2021-02-17 15:55:46 -08:00 коммит произвёл GitHub
Родитель c80a78bc64 189e91aa13
Коммит 72bcb6b884
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 153 добавлений и 201 удалений

Просмотреть файл

@ -11,7 +11,7 @@
<Grid Margin="40"
Background="White">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Height="3000">
<Grid Height="6000">
<Border Width="200"
Height="200"
HorizontalAlignment="Center"
@ -35,7 +35,8 @@
VerticalAlignment="Top"
Background="Transparent"
BorderThickness="0"
Click="CloseButton_Click">
Click="CloseButton_Click"
Foreground="Black">
<SymbolIcon Symbol="Cancel" />
</Button>
</Grid>

Просмотреть файл

@ -82,16 +82,19 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages
if (lazyLoadingControlHost != null)
{
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
// Allow this to act as a toggle.
if (lazyLoadingControlHost.Child == null)
{
lazyLoadingControlHost.Child = imageExLazyLoadingControl;
}
else
{
lazyLoadingControlHost.Child = null;
}
}
});
SampleController.Current.RegisterNewCommand("Clear image cache", async (sender, args) =>
{
container?.Children?.Clear();
GC.Collect(); // Force GC to free file locks
await ImageCache.Instance.ClearAsync();
});
SampleController.Current.RegisterNewCommand("Remove images", (sender, args) => container?.Children?.Clear());
await LoadDataAsync();
}

Просмотреть файл

@ -1,23 +0,0 @@
// 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
{
/// <summary>
/// The type of caching to be applied to <see cref="ImageEx"/>.
/// Default is <see cref="Custom"/>
/// </summary>
public enum ImageExCachingStrategy
{
/// <summary>
/// Caching is handled by <see cref="ImageEx"/>'s custom caching system.
/// </summary>
Custom,
/// <summary>
/// Caching is handled internally by UWP.
/// </summary>
Internal
}
}

Просмотреть файл

@ -6,6 +6,7 @@ using Windows.Media.Casting;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace Microsoft.Toolkit.Uwp.UI.Controls
{
@ -33,7 +34,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <inheritdoc/>
public override CompositionBrush GetAlphaMask()
{
return IsInitialized ? (Image as Image).GetAlphaMask() : null;
if (IsInitialized && Image is Image image)
{
return image.GetAlphaMask();
}
return null;
}
/// <summary>
@ -42,7 +48,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <returns>The image as a <see cref="CastingSource"/>.</returns>
public CastingSource GetAsCastingSource()
{
return IsInitialized ? (Image as Image).GetAsCastingSource() : null;
if (IsInitialized && Image is Image image)
{
return image.GetAsCastingSource();
}
return null;
}
}
}

Просмотреть файл

@ -40,11 +40,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// </summary>
public static readonly DependencyProperty IsCacheEnabledProperty = DependencyProperty.Register(nameof(IsCacheEnabled), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="CachingStrategy"/> dependency property.
/// </summary>
public static readonly DependencyProperty CachingStrategyProperty = DependencyProperty.Register(nameof(CachingStrategy), typeof(ImageExCachingStrategy), typeof(ImageExBase), new PropertyMetadata(ImageExCachingStrategy.Custom));
/// <summary>
/// Identifies the <see cref="EnableLazyLoading"/> dependency property.
/// </summary>
@ -126,15 +121,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
set { SetValue(IsCacheEnabledProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating how the <see cref="ImageEx"/> will be cached.
/// </summary>
public ImageExCachingStrategy CachingStrategy
{
get { return (ImageExCachingStrategy)GetValue(CachingStrategyProperty); }
set { SetValue(CachingStrategyProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether gets or sets is lazy loading enable. (17763 or higher supported)
/// </summary>

Просмотреть файл

@ -24,9 +24,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// </summary>
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(object), typeof(ImageExBase), new PropertyMetadata(null, SourceChanged));
private Uri _uri;
private bool _isHttpSource;
private CancellationTokenSource _tokenSource = null;
//// Used to track if we get a new request, so we can cancel any potential custom cache loading.
private CancellationTokenSource _tokenSource;
private object _lazyLoadingSource;
/// <summary>
@ -66,19 +66,28 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
return uri.IsAbsoluteUri && (uri.Scheme == "http" || uri.Scheme == "https");
}
/// <summary>
/// Method to call to assign an <see cref="ImageSource"/> value to the underlying <see cref="Image"/> powering <see cref="ImageExBase"/>.
/// </summary>
/// <param name="source"><see cref="ImageSource"/> to assign to the image.</param>
private void AttachSource(ImageSource source)
{
var image = Image as Image;
var brush = Image as ImageBrush;
if (image != null)
// Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
// as we register to both the ImageOpened/ImageFailed events of the underlying control.
// We only need to call those methods if we fail in other cases before we get here.
if (Image is Image image)
{
image.Source = source;
}
else if (brush != null)
else if (Image is ImageBrush brush)
{
brush.ImageSource = source;
}
if (source == null)
{
VisualStateManager.GoToState(this, UnloadedState, true);
}
}
private async void SetSource(object source)
@ -88,15 +97,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
return;
}
this._tokenSource?.Cancel();
_tokenSource?.Cancel();
this._tokenSource = new CancellationTokenSource();
_tokenSource = new CancellationTokenSource();
AttachSource(null);
if (source == null)
{
VisualStateManager.GoToState(this, UnloadedState, true);
return;
}
@ -107,106 +115,29 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
{
AttachSource(imageSource);
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
VisualStateManager.GoToState(this, LoadedState, true);
return;
}
_uri = source as Uri;
if (_uri == null)
var uri = source as Uri;
if (uri == null)
{
var url = source as string ?? source.ToString();
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out _uri))
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
{
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new UriFormatException("Invalid uri specified.")));
VisualStateManager.GoToState(this, FailedState, true);
return;
}
}
_isHttpSource = IsHttpUri(_uri);
if (!_isHttpSource && !_uri.IsAbsoluteUri)
if (!IsHttpUri(uri) && !uri.IsAbsoluteUri)
{
_uri = new Uri("ms-appx:///" + _uri.OriginalString.TrimStart('/'));
uri = new Uri("ms-appx:///" + uri.OriginalString.TrimStart('/'));
}
await LoadImageAsync(_uri);
}
private async Task LoadImageAsync(Uri imageUri)
{
if (_uri != null)
{
if (IsCacheEnabled)
{
switch (CachingStrategy)
{
case ImageExCachingStrategy.Custom when _isHttpSource:
await SetHttpSourceCustomCached(imageUri);
break;
case ImageExCachingStrategy.Custom:
case ImageExCachingStrategy.Internal:
default:
AttachSource(new BitmapImage(imageUri));
break;
}
}
else if (string.Equals(_uri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
{
var source = _uri.OriginalString;
const string base64Head = "base64,";
var index = source.IndexOf(base64Head);
if (index >= 0)
{
var bytes = Convert.FromBase64String(source.Substring(index + base64Head.Length));
var bitmap = new BitmapImage();
AttachSource(bitmap);
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
}
}
else
{
AttachSource(new BitmapImage(_uri)
{
CreateOptions = BitmapCreateOptions.IgnoreImageCache
});
}
}
}
private async Task SetHttpSourceCustomCached(Uri imageUri)
{
try
{
var propValues = new List<KeyValuePair<string, object>>();
if (DecodePixelHeight > 0)
{
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelHeight), DecodePixelHeight));
}
if (DecodePixelWidth > 0)
{
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelWidth), DecodePixelWidth));
}
if (propValues.Count > 0)
{
propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelType), DecodePixelType));
}
var img = await ImageCache.Instance.GetFromCacheAsync(imageUri, true, _tokenSource.Token, propValues);
lock (LockObj)
{
// If you have many imageEx in a virtualized ListView for instance
// controls will be recycled and the uri will change while waiting for the previous one to load
if (_uri == imageUri)
{
AttachSource(img);
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
VisualStateManager.GoToState(this, LoadedState, true);
}
}
await LoadImageAsync(uri, _tokenSource.Token);
}
catch (OperationCanceledException)
{
@ -214,15 +145,91 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
catch (Exception e)
{
lock (LockObj)
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
VisualStateManager.GoToState(this, FailedState, true);
}
}
private async Task LoadImageAsync(Uri imageUri, CancellationToken token)
{
if (imageUri != null)
{
if (IsCacheEnabled)
{
if (_uri == imageUri)
var img = await ProvideCachedResourceAsync(imageUri, token);
if (!_tokenSource.IsCancellationRequested)
{
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
VisualStateManager.GoToState(this, FailedState, true);
// Only attach our image if we still have a valid request.
AttachSource(img);
}
}
else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
{
var source = imageUri.OriginalString;
const string base64Head = "base64,";
var index = source.IndexOf(base64Head);
if (index >= 0)
{
var bytes = Convert.FromBase64String(source.Substring(index + base64Head.Length));
var bitmap = new BitmapImage();
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
if (!_tokenSource.IsCancellationRequested)
{
AttachSource(bitmap);
}
}
}
else
{
AttachSource(new BitmapImage(imageUri)
{
CreateOptions = BitmapCreateOptions.IgnoreImageCache
});
}
}
}
/// <summary>
/// This method is provided in case a developer would like their own custom caching strategy for <see cref="ImageExBase"/>.
/// By default it uses the built-in UWP cache provided by <see cref="BitmapImage"/> and
/// the <see cref="Image"/> control itself. This method should return an <see cref="ImageSource"/>
/// value of the image specified by the provided uri parameter.
/// A <see cref="CancellationToken"/> is provided in case the current request is invalidated
/// (e.g. the container is recycled before the original image is loaded).
/// The Toolkit also has an image cache helper which can be used as well:
/// <see cref="CacheBase{T}.GetFromCacheAsync(Uri, bool, CancellationToken, List{KeyValuePair{string, object}})"/> in <see cref="ImageCache"/>.
/// </summary>
/// <example>
/// <code>
/// var propValues = new List&lt;KeyValuePair&lt;string, object>>();
///
/// if (DecodePixelHeight > 0)
/// {
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelHeight), DecodePixelHeight));
/// }
/// if (DecodePixelWidth > 0)
/// {
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelWidth), DecodePixelWidth));
/// }
/// if (propValues.Count > 0)
/// {
/// propValues.Add(new KeyValuePair&lt;string, object>(nameof(DecodePixelType), DecodePixelType));
/// }
///
/// // A token is provided here as well to cancel the request to the cache,
/// // if a new image is requested.
/// return await ImageCache.Instance.GetFromCacheAsync(imageUri, true, token, propValues);
/// </code>
/// </example>
/// <param name="imageUri"><see cref="Uri"/> of the image to load from the cache.</param>
/// <param name="token">A <see cref="CancellationToken"/> which is used to signal when the current request is outdated.</param>
/// <returns><see cref="Task"/></returns>
protected virtual Task<ImageSource> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
{
// By default we just use the built-in UWP image cache provided within the Image control.
return Task.FromResult((ImageSource)new BitmapImage(imageUri));
}
}
}

Просмотреть файл

@ -21,7 +21,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
[TemplateVisualState(Name = UnloadedState, GroupName = CommonGroup)]
[TemplateVisualState(Name = FailedState, GroupName = CommonGroup)]
[TemplatePart(Name = PartImage, Type = typeof(object))]
[TemplatePart(Name = PartProgress, Type = typeof(ProgressRing))]
public abstract partial class ImageExBase : Control
{
private bool _isInViewport;
@ -31,11 +30,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// </summary>
protected const string PartImage = "Image";
/// <summary>
/// ProgressRing name in template
/// </summary>
protected const string PartProgress = "Progress";
/// <summary>
/// VisualStates name in template
/// </summary>
@ -66,22 +60,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// </summary>
protected object Image { get; private set; }
/// <summary>
/// Gets backing object for the ProgressRing
/// </summary>
protected ProgressRing Progress { get; private set; }
/// <summary>
/// Gets object used for lock
/// </summary>
protected object LockObj { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="ImageExBase"/> class.
/// </summary>
public ImageExBase()
{
LockObj = new object();
}
/// <summary>
@ -90,14 +73,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <param name="handler">Routed Event Handler</param>
protected void AttachImageOpened(RoutedEventHandler handler)
{
var image = Image as Image;
var brush = Image as ImageBrush;
if (image != null)
if (Image is Image image)
{
image.ImageOpened += handler;
}
else if (brush != null)
else if (Image is ImageBrush brush)
{
brush.ImageOpened += handler;
}
@ -109,14 +89,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <param name="handler">RoutedEventHandler</param>
protected void RemoveImageOpened(RoutedEventHandler handler)
{
var image = Image as Image;
var brush = Image as ImageBrush;
if (image != null)
if (Image is Image image)
{
image.ImageOpened -= handler;
}
else if (brush != null)
else if (Image is ImageBrush brush)
{
brush.ImageOpened -= handler;
}
@ -128,14 +105,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <param name="handler">Exception Routed Event Handler</param>
protected void AttachImageFailed(ExceptionRoutedEventHandler handler)
{
var image = Image as Image;
var brush = Image as ImageBrush;
if (image != null)
if (Image is Image image)
{
image.ImageFailed += handler;
}
else if (brush != null)
else if (Image is ImageBrush brush)
{
brush.ImageFailed += handler;
}
@ -147,14 +121,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// <param name="handler">Exception Routed Event Handler</param>
protected void RemoveImageFailed(ExceptionRoutedEventHandler handler)
{
var image = Image as Image;
var brush = Image as ImageBrush;
if (image != null)
if (Image is Image image)
{
image.ImageFailed -= handler;
}
else if (brush != null)
else if (Image is ImageBrush brush)
{
brush.ImageFailed -= handler;
}
@ -169,7 +140,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
RemoveImageFailed(OnImageFailed);
Image = GetTemplateChild(PartImage) as object;
Progress = GetTemplateChild(PartProgress) as ProgressRing;
IsInitialized = true;
@ -191,26 +161,23 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
base.OnApplyTemplate();
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var newSquareSize = Math.Min(finalSize.Width, finalSize.Height) / 8.0;
if (Progress?.Width == newSquareSize)
{
Progress.Height = newSquareSize;
}
return base.ArrangeOverride(finalSize);
}
private void OnImageOpened(object sender, RoutedEventArgs e)
/// <summary>
/// Underlying <see cref="Image.ImageOpened"/> event handler.
/// </summary>
/// <param name="sender">Image</param>
/// <param name="e">Event Arguments</param>
protected virtual void OnImageOpened(object sender, RoutedEventArgs e)
{
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
VisualStateManager.GoToState(this, LoadedState, true);
}
private void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
/// <summary>
/// Underlying <see cref="Image.ImageFailed"/> event handler.
/// </summary>
/// <param name="sender">Image</param>
/// <param name="e">Event Arguments</param>
protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
{
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new Exception(e.ErrorMessage)));
VisualStateManager.GoToState(this, FailedState, true);

Просмотреть файл

@ -20,7 +20,7 @@
<PackageTags>UWP Toolkit Windows Controls XAML Markdown CameraPreview Camera DropShadow ImageEx InAppNotification InfiniteCanvas Radial RadialProgressBar Scroll ScrollHeader Tile</PackageTags>
<!-- ARM64 builds for managed apps use .NET Native. We can't use the Reflection Provider for that. -->
<EnableTypeInfoReflection Condition="'$(Configuration)' == 'Debug'">false</EnableTypeInfoReflection>
<LangVersion>8.0</LangVersion>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>