[BUG] Icons of shortcuts not displayed correctly (#209, #218, #155), version 1.0.21.2

This commit is contained in:
Markus Hofknecht 2021-10-13 17:13:11 +02:00
parent a16a3687fa
commit 1e330d40a8
11 changed files with 234 additions and 105 deletions

View file

@ -545,7 +545,6 @@ namespace SystemTrayMenu.Business
foreach (DataGridViewRow row in dgv.Rows)
{
RowData rowData = (RowData)row.Cells[2].Value;
rowData?.Dispose();
DisposeMenu(rowData.SubMenu);
}
}
@ -759,13 +758,13 @@ namespace SystemTrayMenu.Business
if (!AsEnumerable.Any(m => m.Visible))
{
openCloseState = OpenCloseState.Default;
if (IconReader.ClearIfCacheTooBig())
{
GC.Collect();
MainPreload();
}
openCloseState = OpenCloseState.Default;
}
}

View file

@ -463,8 +463,8 @@ namespace SystemTrayMenu
private static void UpgradeIfNotUpgraded()
{
string path = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoaming).FilePath;
path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
// string path = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoaming).FilePath;
// path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (!Settings.Default.IsUpgraded)
{
Settings.Default.Upgrade();

View file

@ -17,7 +17,7 @@ namespace SystemTrayMenu.DataClasses
using TAFactory.IconPack;
using Menu = SystemTrayMenu.UserInterface.Menu;
internal class RowData : IDisposable
internal class RowData
{
private static readonly Icon White50PercentageIcon = Properties.Resources.White50Percentage;
private static readonly Icon NotFoundIcon = Properties.Resources.NotFound;
@ -26,8 +26,6 @@ namespace SystemTrayMenu.DataClasses
private string arguments;
private string text;
private Icon icon;
private bool diposeIcon = true;
private bool isDisposed;
internal RowData()
{
@ -59,13 +57,7 @@ namespace SystemTrayMenu.DataClasses
internal bool IconLoading { get; set; }
public void Dispose()
{
Dispose(true);
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
internal string FilePathIcon { get; set; }
internal void SetText(string text)
{
@ -79,9 +71,7 @@ namespace SystemTrayMenu.DataClasses
if (HiddenEntry)
{
row[0] = IconReader.AddIconOverlay(
data.icon,
White50PercentageIcon);
row[0] = IconReader.AddIconOverlay(data.icon, White50PercentageIcon);
}
else
{
@ -111,10 +101,8 @@ namespace SystemTrayMenu.DataClasses
}
else if (isDirectory)
{
icon = IconReader.GetFolderIconSTA(
TargetFilePath,
IconReader.FolderType.Closed,
false);
icon = IconReader.GetFolderIconWithCache(TargetFilePathOrig, IconReader.FolderType.Closed, false, true, out bool loading);
IconLoading = loading;
}
else
{
@ -138,18 +126,18 @@ namespace SystemTrayMenu.DataClasses
{
handled = SetSln();
}
else if (fileExtension == ".appref-ms")
{
showOverlay = true;
}
if (!handled)
{
try
{
icon = IconReader.GetFileIconWithCache(
TargetFilePathOrig,
showOverlay,
true,
out bool loading);
FilePathIcon = TargetFilePathOrig;
icon = IconReader.GetFileIconWithCache(FilePathIcon, showOverlay, true, out bool loading);
IconLoading = loading;
diposeIcon = false;
}
catch (Exception ex)
{
@ -215,8 +203,7 @@ namespace SystemTrayMenu.DataClasses
OpenItem(e, ref toCloseByDoubleClick);
}
if (ContainsMenu &&
(e == null || e.Button == MouseButtons.Left))
if (ContainsMenu && (e == null || e.Button == MouseButtons.Left))
{
Log.ProcessStart(TargetFilePath);
if (!Properties.Settings.Default.StaysOpenWhenItemClicked)
@ -228,21 +215,31 @@ namespace SystemTrayMenu.DataClasses
internal Icon ReadLoadedIcon()
{
bool showOverlay = false;
string fileExtension = Path.GetExtension(TargetFilePath);
if (fileExtension == ".lnk" || fileExtension == ".url")
if (ContainsMenu)
{
showOverlay = true;
icon = IconReader.GetFolderIconWithCache(TargetFilePathOrig, IconReader.FolderType.Closed, false, false, out bool loading);
IconLoading = loading;
}
else
{
bool showOverlay = false;
string fileExtension = Path.GetExtension(TargetFilePath);
if (fileExtension == ".lnk" || fileExtension == ".url" || fileExtension == ".appref-ms")
{
showOverlay = true;
}
string filePath = FilePathIcon;
if (string.IsNullOrEmpty(filePath))
{
filePath = TargetFilePathOrig;
}
icon = IconReader.GetFileIconWithCache(filePath, showOverlay, false, out bool loading);
IconLoading = loading;
}
icon = IconReader.GetFileIconWithCache(
TargetFilePathOrig,
showOverlay,
false,
out bool loading);
IconLoading = loading;
if (!loading && icon == null)
if (!IconLoading && icon == null)
{
icon = NotFoundIcon;
}
@ -250,19 +247,6 @@ namespace SystemTrayMenu.DataClasses
return icon;
}
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
if (diposeIcon)
{
icon?.Dispose();
}
}
isDisposed = true;
}
private void OpenItem(MouseEventArgs e, ref bool toCloseByOpenItem)
{
if (!ContainsMenu &&
@ -285,7 +269,8 @@ namespace SystemTrayMenu.DataClasses
if (string.IsNullOrEmpty(Path.GetExtension(resolvedLnkPath)))
{
icon = IconReader.GetFolderIconSTA(TargetFilePath, IconReader.FolderType.Open, true);
icon = IconReader.GetFolderIconWithCache(TargetFilePathOrig, IconReader.FolderType.Open, true, true, out bool loading);
IconLoading = loading;
handled = true;
isLnkDirectory = true;
}
@ -300,8 +285,7 @@ namespace SystemTrayMenu.DataClasses
else
{
IWshShell shell = new WshShell();
IWshShortcut lnk = shell.CreateShortcut(TargetFilePath)
as IWshShortcut;
IWshShortcut lnk = shell.CreateShortcut(TargetFilePath) as IWshShortcut;
arguments = lnk.Arguments;
workingDirectory = lnk.WorkingDirectory;
TargetFilePath = resolvedLnkPath;
@ -324,15 +308,17 @@ namespace SystemTrayMenu.DataClasses
{
if (FileUrl.GetDefaultBrowserPath(out string browserPath))
{
icon = IconReader.GetFileIconWithCache(browserPath, true, true, out bool loading);
FilePathIcon = browserPath;
icon = IconReader.GetFileIconWithCache(FilePathIcon, true, true, out bool loading);
IconLoading = loading;
diposeIcon = false;
handled = true;
}
}
else if (System.IO.File.Exists(iconFile))
{
icon = Icon.ExtractAssociatedIcon(iconFile);
FilePathIcon = iconFile;
icon = IconReader.GetFileIconWithCache(FilePathIcon, true, true, out bool loading);
IconLoading = loading;
handled = true;
}
else
@ -342,10 +328,7 @@ namespace SystemTrayMenu.DataClasses
}
catch (Exception ex)
{
Log.Warn(
$"path:'{TargetFilePath}', " +
$"iconFile:'{iconFile}'",
ex);
Log.Warn($"path:'{TargetFilePath}', iconFile:'{iconFile}'", ex);
}
SetText($"{FileInfo.Name[0..^4]}");
@ -356,24 +339,15 @@ namespace SystemTrayMenu.DataClasses
private bool SetSln()
{
bool handled = false;
StringBuilder executable = new StringBuilder(1024);
try
{
DllImports.NativeMethods.Shell32FindExecutable(TargetFilePath, string.Empty, executable);
// icon = IconReader.GetFileIcon(executable, false);
// e.g. VS 2019 icon, need another icom in imagelist
List<Icon> extractedIcons = IconHelper.ExtractAllIcons(
executable.ToString());
icon = extractedIcons.Last();
icon = IconReader.GetExtractAllIconsLastWithCache(TargetFilePathOrig, true, out bool loading);
IconLoading = loading;
handled = true;
}
catch (Exception ex)
{
Log.Warn(
$"path:'{TargetFilePath}', " +
$"executable:'{executable}'",
ex);
Log.Warn($"path:'{TargetFilePath}'", ex);
}
return handled;

View file

@ -31,9 +31,7 @@ namespace SystemTrayMenu.Helper
public void Dispose()
{
Dispose(true);
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
internal void CellMouseEnter(object sender, DataGridViewCellEventArgs newEventArgs)

View file

@ -85,7 +85,7 @@ namespace SystemTrayMenu.Helpers
}
title = Truncate(title, 128); // max 255
string Truncate(string value, int maxLength)
static string Truncate(string value, int maxLength)
{
if (!string.IsNullOrEmpty(value) &&
value.Length > maxLength)

View file

@ -54,9 +54,7 @@ namespace SystemTrayMenu.UserInterface
public void Dispose()
{
Dispose(true);
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
internal void Fade(FadingState state)

View file

@ -18,7 +18,6 @@ namespace SystemTrayMenu.DllImports
[Flags]
internal enum TPM : uint
{
#pragma warning disable SA1602 // Enumeration items should be documented
LEFTBUTTON = 0x0000, // LEFTALIGN = 0x0000, // TOPALIGN = 0x0000, // HORIZONTAL = 0x0000,
RIGHTBUTTON = 0x0002,
CENTERALIGN = 0x0004,
@ -35,7 +34,6 @@ namespace SystemTrayMenu.DllImports
VERNEGANIMATION = 0x2000,
NOANIMATION = 0x4000,
LAYOUTRTL = 0x8000,
#pragma warning restore SA1602 // Enumeration items should be documented
}
/// <summary>

View file

@ -39,5 +39,5 @@ using System.Runtime.InteropServices;
// 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: AssemblyVersion("1.0.21.1")]
[assembly: AssemblyFileVersion("1.0.21.1")]
[assembly: AssemblyVersion("1.0.21.2")]
[assembly: AssemblyFileVersion("1.0.21.2")]

View file

@ -295,7 +295,7 @@
//
// timerUpdateIcons
//
this.timerUpdateIcons.Interval = 300;
this.timerUpdateIcons.Interval = 100;
this.timerUpdateIcons.Tick += new System.EventHandler(this.TimerUpdateIcons_Tick);
//
// Menu

View file

@ -1063,9 +1063,7 @@ namespace SystemTrayMenu.Utilities
int nResult = DllImports.NativeMethods.Shell32SHGetDesktopFolder(out IntPtr pUnkownDesktopFolder);
if (nResult != ResultOK)
{
#pragma warning disable CA1303 // Do not pass literals as localized parameters
throw new ShellContextMenuException("Failed to get the desktop shell folder");
#pragma warning restore CA1303 //=> Exceptions not translated in logfile => OK
}
oDesktopFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(pUnkownDesktopFolder, typeof(IShellFolder));

View file

@ -10,9 +10,11 @@ namespace SystemTrayMenu.Utilities
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TAFactory.IconPack;
// from https://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using
// added ImageList_GetIcon, IconCache, AddIconOverlay
@ -62,7 +64,81 @@ namespace SystemTrayMenu.Utilities
return cleared;
}
public static Icon GetFileIconWithCache(string filePath, bool linkOverlay, bool updateIconInBackground, out bool loading)
public static Icon GetExtractAllIconsLastWithCache(string filePath, bool updateIconInBackground, out bool loading)
{
bool linkOverlay = false;
loading = false;
string key = filePath;
string extension = Path.GetExtension(filePath);
if (IsExtensionWithSameIcon(extension))
{
key = extension + linkOverlay;
}
ClearBadIcons();
if (!DictIconCache.TryGetValue(key, out Icon icon))
{
icon = LoadingIcon;
loading = true;
if (updateIconInBackground)
{
new Thread(UpdateIconInBackground).Start();
void UpdateIconInBackground()
{
DictIconCache.GetOrAdd(key, GetExtractAllIconsLast);
}
}
}
Icon GetExtractAllIconsLast(string keyExtension)
{
return GetExtractAllIconsLastSTA(filePath);
}
return icon;
}
public static Icon GetExtractAllIconsLastSTA(string filePath)
{
Icon icon = null;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
icon = GetExtractAllIconsLast(filePath);
}
else
{
Thread staThread = new Thread(new ParameterizedThreadStart(StaThreadMethod));
void StaThreadMethod(object obj)
{
icon = GetExtractAllIconsLast(filePath);
}
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(icon);
staThread.Join();
}
static Icon GetExtractAllIconsLast(string filePath)
{
StringBuilder executable = new StringBuilder(1024);
DllImports.NativeMethods.Shell32FindExecutable(filePath, string.Empty, executable);
// icon = IconReader.GetFileIcon(executable, false);
// e.g. VS 2019 icon, need another icom in imagelist
List<Icon> extractedIcons = IconHelper.ExtractAllIcons(
executable.ToString());
return extractedIcons.Last();
}
return icon;
}
public static Icon GetFileIconWithCache(string filePath, bool linkOverlay, bool updateIconInBackground, out bool loading, string keyPath = "")
{
loading = false;
string extension = Path.GetExtension(filePath);
@ -73,11 +149,18 @@ namespace SystemTrayMenu.Utilities
}
string key = filePath;
if (!string.IsNullOrEmpty(keyPath))
{
key = keyPath;
}
if (IsExtensionWithSameIcon(extension))
{
key = extension + linkOverlay;
}
ClearBadIcons();
if (!DictIconCache.TryGetValue(key, out Icon icon))
{
icon = LoadingIcon;
@ -101,6 +184,48 @@ namespace SystemTrayMenu.Utilities
return icon;
}
public static Icon GetFolderIconWithCache(string path, FolderType folderType, bool linkOverlay, bool updateIconInBackground, out bool loading)
{
loading = false;
IconSize size = IconSize.Small;
// with this we get another folder icon than windows explorer
// if (Scaling.Factor > 1)
// {
// size = IconSize.Large;
// }
string key = path;
// Maybe we can reduce object when checking if a standard folder
// if (IsStandardFolderIcon(key))
// {
// key = "folder" + linkOverlay;
// }
ClearBadIcons();
if (!DictIconCache.TryGetValue(key, out Icon icon))
{
icon = LoadingIcon;
loading = true;
if (updateIconInBackground)
{
new Thread(UpdateIconInBackground).Start();
void UpdateIconInBackground()
{
DictIconCache.GetOrAdd(key, GetFolder);
}
}
}
Icon GetFolder(string keyExtension)
{
return GetFolderIconSTA(path, folderType, linkOverlay, size);
}
return icon;
}
public static Icon GetFolderIconSTA(
string directoryPath,
FolderType folderType,
@ -108,13 +233,30 @@ namespace SystemTrayMenu.Utilities
IconSize size = IconSize.Small)
{
Icon icon = null;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
icon = GetFolderIcon(
directoryPath,
folderType,
linkOverlay,
size);
}
else
{
Thread staThread = new Thread(new ParameterizedThreadStart(StaThreadMethod));
void StaThreadMethod(object obj)
{
icon = GetFolderIcon(
directoryPath,
folderType,
linkOverlay,
size);
}
Task<Icon> task = Task.Factory.StartNew(() => GetFolderIcon(
directoryPath,
folderType,
linkOverlay,
size));
icon = task.Result;
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(icon);
staThread.Join();
}
return icon;
}
@ -182,20 +324,28 @@ namespace SystemTrayMenu.Utilities
Icon icon = null;
if (originalIcon != null)
{
using Bitmap target = new Bitmap(
originalIcon.Width,
originalIcon.Height,
PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(target);
using Bitmap target = new Bitmap(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb);
using Graphics graphics = Graphics.FromImage(target);
graphics.DrawIcon(originalIcon, 0, 0);
graphics.DrawIcon(overlay, 0, 0);
target.MakeTransparent(target.GetPixel(1, 1));
icon = Icon.FromHandle(target.GetHicon());
IntPtr hIcon = target.GetHicon();
icon = (Icon)Icon.FromHandle(hIcon).Clone();
DllImports.NativeMethods.User32DestroyIcon(hIcon);
}
return icon;
}
private static void ClearBadIcons()
{
IEnumerable<string> badKeysList = DictIconCache.Where(x => x.Value == null).Select(x => x.Key);
foreach (string badKey in badKeysList)
{
DictIconCache.Remove(badKey, out _);
}
}
private static bool IsExtensionWithSameIcon(string fileExtension)
{
bool isExtensionWithSameIcon = true;
@ -213,8 +363,22 @@ namespace SystemTrayMenu.Utilities
{
Icon icon = null;
Task<Icon> task = Task.Factory.StartNew(() => GetFileIcon(filePath, linkOverlay, size));
icon = task.Result;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
icon = GetFileIcon(filePath, linkOverlay, size);
}
else
{
Thread staThread = new Thread(new ParameterizedThreadStart(StaThreadMethod));
void StaThreadMethod(object obj)
{
icon = GetFileIcon(filePath, linkOverlay, size);
}
staThread.SetApartmentState(ApartmentState.STA);
staThread.Start(icon);
staThread.Join();
}
return icon;
}