Improve async icon loading

Response time for an loaded icon should be faster as update no longer depends on timer
Loading icon will now share image source prevents creation of lost of loading icon copies
This commit is contained in:
Peter Kirmeier 2023-05-19 15:27:35 +02:00
parent f7fff25ba7
commit 13e7cedaa6
5 changed files with 105 additions and 101 deletions

View file

@ -206,33 +206,22 @@ namespace SystemTrayMenu.DataClasses
internal void ReadIcon(bool updateIconInBackground)
{
bool loading;
Icon? icon;
bool cacheHit;
if (IsPointingToFolder)
{
icon = IconReader.GetFolderIconWithCache(Path, ShowOverlay, updateIconInBackground, Level == 0, out loading);
cacheHit = IconReader.GetFolderIconWithCache(Path, ShowOverlay, updateIconInBackground, Level == 0, UpdateFinalIcon);
}
else
{
icon = IconReader.GetFileIconWithCache(Path, ResolvedPath, ShowOverlay, updateIconInBackground, Level == 0, out loading);
cacheHit = IconReader.GetFileIconWithCache(Path, ResolvedPath, ShowOverlay, updateIconInBackground, Level == 0, UpdateFinalIcon);
}
IconLoading = loading;
if (!IconLoading)
if (!cacheHit)
{
if (icon == null)
{
icon = Resources.NotFound;
}
else if (HiddenEntry)
{
icon = IconReader.AddIconOverlay(icon, Resources.White50Percentage);
}
IconLoading = true;
ColumnIcon = SystemTrayMenu.Resources.StaticResources.LoadingImgSrc; // TODO: Maybe add rotation animation like for the loading Menu icon? (See: pictureBoxLoading, LoadingRotation)
}
ColumnIcon = icon?.ToImageSource();
ColumnIcon?.Freeze();
}
internal void OpenItem(int clickCount)
@ -350,5 +339,23 @@ namespace SystemTrayMenu.DataClasses
BackgroundBrush = Brushes.White;
}
}
private void UpdateFinalIcon(Icon? icon)
{
if (icon == null)
{
icon = Resources.NotFound;
}
else if (HiddenEntry)
{
icon = IconReader.AddIconOverlay(icon, Resources.White50Percentage);
}
ImageSource? imgsrc = icon?.ToImageSource();
imgsrc?.Freeze();
IconLoading = false;
ColumnIcon = imgsrc;
}
}
}

View file

@ -1,13 +1,39 @@
// <copyright file="StaticResources.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace SystemTrayMenu.Resources
{
using System.Drawing;
public class StaticResources
{
public static readonly Icon LoadingIcon = Properties.Resources.Loading;
}
}
// <copyright file="StaticResources.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace SystemTrayMenu.Resources
{
using System.Drawing;
using System.Windows.Media;
using SystemTrayMenu.Utilities;
internal class StaticResources
{
internal static readonly Icon LoadingIcon = Properties.Resources.Loading;
private static readonly object LoadingImgSrcLock = new ();
private static ImageSource? loadingImgSrc;
internal static ImageSource LoadingImgSrc
{
get
{
if (loadingImgSrc == null)
{
lock (LoadingImgSrcLock)
{
if (loadingImgSrc == null)
{
loadingImgSrc = Properties.Resources.Loading.ToImageSource();
loadingImgSrc.Freeze(); // Make it accessible by any thread
}
}
}
return loadingImgSrc;
}
}
}
}

View file

@ -8,6 +8,7 @@ namespace SystemTrayMenu.UserInterface.HotkeyTextboxControl
public class EventDelay
{
private readonly object checkLock = new();
private readonly long waitTime;
private long lastCheck;
@ -18,9 +19,7 @@ namespace SystemTrayMenu.UserInterface.HotkeyTextboxControl
public bool Check()
{
#pragma warning disable CA2002
lock (this)
#pragma warning restore CA2002
lock (checkLock)
{
long now = DateTime.Now.Ticks;
bool isPassed = now - lastCheck > waitTime;

View file

@ -37,7 +37,6 @@ namespace SystemTrayMenu.UserInterface
private static readonly RoutedEvent FadeOutEvent = EventManager.RegisterRoutedEvent(
nameof(FadeOut), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Menu));
private readonly DispatcherTimer timerUpdateIcons = new (DispatcherPriority.Background, Dispatcher.CurrentDispatcher);
private readonly string folderPath;
#if TODO // SEARCH
public const string RowFilterShowAll = "[SortIndex] LIKE '%0%'";
@ -116,7 +115,7 @@ namespace SystemTrayMenu.UserInterface
labelStatus.Content = Translator.GetText("loading");
// Todo: use embedded resources that we can assign image in XAML already
pictureBoxLoading.Source = SystemTrayMenu.Resources.StaticResources.LoadingIcon.ToImageSource();
pictureBoxLoading.Source = SystemTrayMenu.Resources.StaticResources.LoadingImgSrc;
pictureBoxLoading.Visibility = Visibility.Visible;
}
@ -201,8 +200,6 @@ namespace SystemTrayMenu.UserInterface
Closed += (_, _) =>
{
timerUpdateIcons.Stop();
if (RowDataParent?.SubMenu == this)
{
RowDataParent.SubMenu = null;
@ -213,8 +210,6 @@ namespace SystemTrayMenu.UserInterface
item.SubMenu?.Close();
}
};
timerUpdateIcons.Tick += TimerUpdateIcons_Tick;
}
internal event Action<RowData>? StartLoadSubMenu;
@ -455,11 +450,6 @@ namespace SystemTrayMenu.UserInterface
{
SetSubMenuState(state.Value);
}
if (startIconLoading)
{
timerUpdateIcons.Start();
}
}
internal void ActivateWithFade(bool recursive)
@ -484,8 +474,6 @@ namespace SystemTrayMenu.UserInterface
internal void ShowWithFade(bool transparency, bool recursive)
{
timerUpdateIcons.Start();
if (recursive)
{
SubMenu?.ShowWithFade(transparency, true);
@ -1129,27 +1117,6 @@ namespace SystemTrayMenu.UserInterface
AppRestart.ByMenuButton();
}
private void TimerUpdateIcons_Tick(object? sender, EventArgs e)
{
int iconsToUpdate = 0;
foreach (RowData rowData in dgv.Items)
{
rowData.RowIndex = dgv.Items.IndexOf(rowData);
if (rowData.IconLoading)
{
iconsToUpdate++;
rowData.ReadIcon(false);
}
}
if (iconsToUpdate < 1)
{
timerUpdateIcons.Stop();
}
}
private void MainMenu_MoveStart(object sender, MouseButtonEventArgs e)
{
// Hide all sub menus to clear the view for repositioning of the main menu

View file

@ -54,17 +54,16 @@ namespace SystemTrayMenu.Utilities
}
}
public static Icon? GetFileIconWithCache(
public static bool GetFileIconWithCache(
string path,
string resolvedPath,
bool linkOverlay,
bool updateIconInBackground,
bool checkPersistentFirst,
out bool loading)
Action<Icon?> onIconLoaded)
{
loading = false;
Icon? icon = null;
bool cacheHit;
Icon? icon;
string key;
string extension = Path.GetExtension(path);
if (IsExtensionWithSameIcon(extension))
@ -79,8 +78,7 @@ namespace SystemTrayMenu.Utilities
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) &&
!DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon))
{
icon = Resources.StaticResources.LoadingIcon;
loading = true;
cacheHit = false;
if (updateIconInBackground)
{
IconSize size = IconSize.Small;
@ -94,30 +92,34 @@ namespace SystemTrayMenu.Utilities
new Thread(UpdateIconInBackground).Start();
void UpdateIconInBackground()
{
DictIconCache(checkPersistentFirst).GetOrAdd(key, GetIconSTA(path, resolvedPath, linkOverlay, size, false));
Icon? icon = DictIconCache(checkPersistentFirst).GetOrAdd(key, GetIconSTA(path, resolvedPath, linkOverlay, size, false));
onIconLoaded(icon);
}
}
}
else
{
cacheHit = true;
onIconLoaded(icon);
}
return icon;
return cacheHit;
}
public static Icon? GetFolderIconWithCache(
public static bool GetFolderIconWithCache(
string path,
bool linkOverlay,
bool updateIconInBackground,
bool checkPersistentFirst,
out bool loading)
Action<Icon?> onIconLoaded)
{
loading = false;
Icon? icon = null;
bool cacheHit;
Icon? icon;
string key = path;
if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) &&
!DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon))
{
icon = Resources.StaticResources.LoadingIcon;
loading = true;
cacheHit = false;
if (updateIconInBackground)
{
@ -132,14 +134,17 @@ namespace SystemTrayMenu.Utilities
if (IsPreloading)
{
DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder);
cacheHit = true;
icon = DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder);
onIconLoaded(icon);
}
else
{
new Thread(UpdateIconInBackground).Start();
void UpdateIconInBackground()
{
DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder);
Icon? icon = DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder);
onIconLoaded(icon);
}
}
@ -149,8 +154,13 @@ namespace SystemTrayMenu.Utilities
}
}
}
else
{
cacheHit = true;
onIconLoaded(icon);
}
return icon;
return cacheHit;
}
public static Icon? GetIconSTA(string path, string resolvedPath, bool linkOverlay, IconSize size, bool isFolder)
@ -176,21 +186,16 @@ namespace SystemTrayMenu.Utilities
return icon;
}
public static Icon? AddIconOverlay(Icon? originalIcon, Icon overlay)
public static Icon AddIconOverlay(Icon originalIcon, Icon overlay)
{
Icon? icon = originalIcon;
if (originalIcon != null)
{
using Bitmap target = new(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb);
using Graphics graphics = Graphics.FromImage(target);
graphics.DrawIcon(originalIcon, 0, 0);
graphics.DrawIcon(overlay, new(0, 0, originalIcon.Width + 2, originalIcon.Height + 2));
target.MakeTransparent(target.GetPixel(1, 1));
IntPtr hIcon = target.GetHicon();
icon = (Icon)Icon.FromHandle(hIcon).Clone();
NativeMethods.User32DestroyIcon(hIcon);
}
using Bitmap target = new(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb);
using Graphics graphics = Graphics.FromImage(target);
graphics.DrawIcon(originalIcon, 0, 0);
graphics.DrawIcon(overlay, new(0, 0, originalIcon.Width + 2, originalIcon.Height + 2));
target.MakeTransparent(target.GetPixel(1, 1));
IntPtr hIcon = target.GetHicon();
Icon icon = (Icon)Icon.FromHandle(hIcon).Clone();
NativeMethods.User32DestroyIcon(hIcon);
return icon;
}
@ -220,7 +225,7 @@ namespace SystemTrayMenu.Utilities
Path.GetExtension(resolvedPath).Equals(".ico", StringComparison.InvariantCultureIgnoreCase))
{
icon = Icon.ExtractAssociatedIcon(resolvedPath);
if (linkOverlay)
if (linkOverlay && icon != null)
{
icon = AddIconOverlay(icon, Properties.Resources.LinkArrow);
}