Skip to content
This repository was archived by the owner on Feb 25, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ff33aa2
Merge pull request #16 from windows-toolkit/master
h82258652 Apr 26, 2020
48a3bff
Merge pull request #17 from windows-toolkit/master
h82258652 May 6, 2020
9fc4599
Merge pull request #18 from windows-toolkit/master
h82258652 Sep 18, 2020
8dfc8e3
Add lazy loading threshold
h82258652 Sep 18, 2020
01349a6
Merge branch 'master' into lazyLoadingThreshold
h82258652 Sep 19, 2020
07c4e85
use FindAscendants instead
h82258652 Oct 25, 2020
cf34c64
Merge branch 'lazyLoadingThreshold' of https://github.com/h82258652/U…
h82258652 Oct 25, 2020
c94aaa0
Merge branch 'master' into lazyLoadingThreshold
h82258652 Oct 25, 2020
7ae5a0c
remove IsLazyLoadingSupported property
h82258652 Oct 25, 2020
fb40d85
Merge branch 'master' into lazyLoadingThreshold
h82258652 Nov 6, 2020
ad1c678
Merge branch 'master' into lazyLoadingThreshold
h82258652 Nov 10, 2020
74502e4
Add RectExtensions
h82258652 Nov 10, 2020
a2cc630
Use extension method to detect is intersect
h82258652 Nov 10, 2020
1600c78
Merge branch 'master' into lazyLoadingThreshold
h82258652 Nov 13, 2020
63dcc3e
Merge branch 'master' into lazyLoadingThreshold
h82258652 Nov 21, 2020
46e4bbb
Add more test cases
h82258652 Nov 21, 2020
21c4fb5
Merge branch 'master' into lazyLoadingThreshold
michael-hawker Jan 14, 2021
cef75d1
Merge branch 'master' into lazyLoadingThreshold
h82258652 Jan 20, 2021
4eef5ab
improve performance while lazy loading is not enabled
h82258652 Jan 20, 2021
731f70f
Merge branch 'master' into lazyLoadingThreshold
michael-hawker Jan 21, 2021
de190e4
Merge branch 'master' into lazyLoadingThreshold
michael-hawker Jan 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Top"
Foreground="OrangeRed"
IsHitTestVisible="False"
Text="Please scroll down to see the effect." />
IsHitTestVisible="False"><Run Text="Please scroll down to see the effect." /><LineBreak /><Run Text="The default threshold for triggering lazy loading is 300 px." /></TextBlock>
<Button Width="48"
Height="48"
HorizontalAlignment="Right"
Expand Down
1 change: 1 addition & 0 deletions Microsoft.Toolkit.Uwp.UI.Controls/ImageEx/ImageEx.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="LazyLoadingThreshold" Value="300" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you picked 300?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michael-hawker About this I think a lot. I searched google and it tells me Chromium will calculate this by the screen size and network type. And then I searched the most widely used JavaScript image lazy loading library, got this. This lib use 300px as default. I think UWP is mostly used on a PC, not a mobile device. The network should be not bad, 300px should be suitable for most situations.

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ImageEx">
Expand Down
42 changes: 41 additions & 1 deletion Microsoft.Toolkit.Uwp.UI.Controls/ImageEx/ImageExBase.Members.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ public partial class ImageExBase
/// <summary>
/// Identifies the <see cref="EnableLazyLoading"/> dependency property.
/// </summary>
public static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
public static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false, EnableLazyLoadingChanged));

/// <summary>
/// Identifies the <see cref="LazyLoadingThreshold"/> dependency property.
/// </summary>
public static readonly DependencyProperty LazyLoadingThresholdProperty = DependencyProperty.Register(nameof(LazyLoadingThreshold), typeof(double), typeof(ImageExBase), new PropertyMetadata(default(double), LazyLoadingThresholdChanged));

/// <summary>
/// Returns a mask that represents the alpha channel of an image as a <see cref="CompositionBrush"/>
Expand Down Expand Up @@ -139,5 +144,40 @@ public bool EnableLazyLoading
get { return (bool)GetValue(EnableLazyLoadingProperty); }
set { SetValue(EnableLazyLoadingProperty, value); }
}

/// <summary>
/// Gets or sets a value indicating the threshold for triggering lazy loading.
/// </summary>
public double LazyLoadingThreshold
{
get { return (double)GetValue(LazyLoadingThresholdProperty); }
set { SetValue(LazyLoadingThresholdProperty, value); }
}

private static void EnableLazyLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control)
{
var value = (bool)e.NewValue;
if (value)
{
control.LayoutUpdated += control.ImageExBase_LayoutUpdated;

control.InvalidateLazyLoading();
}
else
{
control.LayoutUpdated -= control.ImageExBase_LayoutUpdated;
}
}
}

private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ImageExBase control && control.EnableLazyLoading)
{
control.InvalidateLazyLoading();
}
}
}
}
49 changes: 41 additions & 8 deletions Microsoft.Toolkit.Uwp.UI.Controls/ImageEx/ImageExBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Linq;
using Microsoft.Toolkit.Uwp.Extensions;
using Microsoft.Toolkit.Uwp.UI.Extensions;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
Expand Down Expand Up @@ -79,8 +82,6 @@ public abstract partial class ImageExBase : Control
public ImageExBase()
{
LockObj = new object();

EffectiveViewportChanged += ImageExBase_EffectiveViewportChanged;
}

/// <summary>
Expand Down Expand Up @@ -215,15 +216,47 @@ private void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
VisualStateManager.GoToState(this, FailedState, true);
}

private void ImageExBase_EffectiveViewportChanged(FrameworkElement sender, EffectiveViewportChangedEventArgs args)
private void ImageExBase_LayoutUpdated(object sender, object e)
{
InvalidateLazyLoading();
Comment thread
h82258652 marked this conversation as resolved.
}

private void InvalidateLazyLoading()
{
var bringIntoViewDistanceX = args.BringIntoViewDistanceX;
var bringIntoViewDistanceY = args.BringIntoViewDistanceY;
if (!IsLoaded)
{
_isInViewport = false;
return;
}

// Find the first ascendant ScrollViewer, if not found, use the root element.
FrameworkElement hostElement = null;
var ascendants = this.FindAscendants().OfType<FrameworkElement>();
foreach (var ascendant in ascendants)
{
hostElement = ascendant;
if (hostElement is ScrollViewer)
{
break;
}
}

if (hostElement == null)
{
_isInViewport = false;
return;
}

var width = ActualWidth;
var height = ActualHeight;
var controlRect = TransformToVisual(hostElement)
.TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
var lazyLoadingThreshold = LazyLoadingThreshold;
var hostRect = new Rect(
0 - lazyLoadingThreshold,
0 - lazyLoadingThreshold,
hostElement.ActualWidth + (2 * lazyLoadingThreshold),
hostElement.ActualHeight + (2 * lazyLoadingThreshold));

if (bringIntoViewDistanceX <= width && bringIntoViewDistanceY <= height)
if (controlRect.IntersectsWith(hostRect))
{
_isInViewport = true;

Expand Down
37 changes: 37 additions & 0 deletions Microsoft.Toolkit.Uwp/Extensions/RectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using Rect = Windows.Foundation.Rect;

namespace Microsoft.Toolkit.Uwp.Extensions
{
/// <summary>
/// Extensions for the <see cref="Rect"/> type.
/// </summary>
public static class RectExtensions
{
/// <summary>
/// Determines if a rectangle intersects with another rectangle.
/// </summary>
/// <param name="rect1">The first rectangle to test.</param>
/// <param name="rect2">The second rectangle to test.</param>
/// <returns>This method returns <see langword="true"/> if there is any intersection, otherwise <see langword="false"/>.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IntersectsWith(this Rect rect1, Rect rect2)
{
if (rect1.IsEmpty || rect2.IsEmpty)
{
return false;
}

return (rect1.Left <= rect2.Right) &&
(rect1.Right >= rect2.Left) &&
(rect1.Top <= rect2.Bottom) &&
(rect1.Bottom >= rect2.Top);
}
}
}
40 changes: 40 additions & 0 deletions UnitTests/UnitTests.UWP/Extensions/Test_RectExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.Toolkit.Uwp.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Foundation;

namespace UnitTests.Extensions
{
[TestClass]
public class Test_RectExtensions
{
[TestCategory("RectExtensions")]
[TestMethod]
[DataRow(0, 0, 2, 2, 0, 0, 2, 2, true)]// Full intersection.
[DataRow(0, 0, 2, 2, 1, 1, 2, 2, true)]// Partial intersection.
[DataRow(0, 0, 2, 2, -2, 0, 2, 2, true)]// Left edge intersection.
[DataRow(0, 0, 2, 2, 0, -2, 2, 2, true)]// Top edge intersection.
[DataRow(0, 0, 2, 2, 2, 0, 2, 2, true)]// Right edge intersection.
[DataRow(0, 0, 2, 2, 0, 2, 2, 2, true)]// Bottom edge intersection.
[DataRow(0, 0, 2, 2, -2, -2, 2, 2, true)]// Left top corner(0, 0) intersection.
[DataRow(0, 0, 2, 2, 2, -2, 2, 2, true)]// Right top corner(2, 0) intersection.
[DataRow(0, 0, 2, 2, 2, 2, 2, 2, true)]// Right bottom corner(2, 2) intersection.
[DataRow(0, 0, 2, 2, -2, 2, 2, 2, true)]// Left bottom corner(0, 2) intersection.
[DataRow(0, 0, 2, 2, 3, 0, 2, 2, false)]// No intersection.
Comment thread
michael-hawker marked this conversation as resolved.
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1117:Parameters should be on same line or separate lines", Justification = "Put the parameters of the same rectangle on the same line is clearer.")]
public static void Test_RectExtensions_IntersectsWith(
double rect1X, double rect1Y, double rect1Width, double rect1Height,
double rect2X, double rect2Y, double rect2Width, double rect2Height,
bool shouldIntersectsWith)
{
var rect1 = new Rect(rect1X, rect1Y, rect1Width, rect1Height);
var rect2 = new Rect(rect2X, rect2Y, rect2Width, rect2Height);
var isIntersectsWith = rect1.IntersectsWith(rect2);
Assert.IsTrue(isIntersectsWith == shouldIntersectsWith);
}
}
}
1 change: 1 addition & 0 deletions UnitTests/UnitTests.UWP/UnitTests.UWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
<Compile Include="Converters\Test_TypeToObjectConverter.cs" />
<Compile Include="Extensions\Helpers\ObjectWithNullableBoolProperty.cs" />
<Compile Include="Extensions\Test_PointExtensions.cs" />
<Compile Include="Extensions\Test_RectExtensions.cs" />
<Compile Include="Extensions\Test_SizeExtensions.cs" />
<Compile Include="Extensions\Test_BitmapIconExtensionMarkupExtension.cs" />
<Compile Include="Extensions\Test_FontIconSourceExtensionMarkupExtension.cs" />
Expand Down