From 1e330d40a845bfebb94d75255cde84a4e47a6418 Mon Sep 17 00:00:00 2001 From: Markus Hofknecht Date: Wed, 13 Oct 2021 17:13:11 +0200 Subject: [PATCH] [BUG] Icons of shortcuts not displayed correctly (#209, #218, #155), version 1.0.21.2 --- Business/Menus.cs | 5 +- Config/Config.cs | 4 +- DataClasses/RowData.cs | 118 ++++------- Helpers/DgvMouseRow.cs | 2 - Helpers/DragDropHelper.cs | 2 +- Helpers/Fading.cs | 2 - NativeDllImport/TrackPopupMenuEx.cs | 2 - Properties/AssemblyInfo.cs | 4 +- UserInterface/Menu.Designer.cs | 2 +- .../ShellContextMenu/ShellContextMenu.cs | 2 - Utilities/File/IconReader.cs | 196 ++++++++++++++++-- 11 files changed, 234 insertions(+), 105 deletions(-) diff --git a/Business/Menus.cs b/Business/Menus.cs index a397727..9c95d8f 100644 --- a/Business/Menus.cs +++ b/Business/Menus.cs @@ -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; } } diff --git a/Config/Config.cs b/Config/Config.cs index 640b47b..9edeebb 100644 --- a/Config/Config.cs +++ b/Config/Config.cs @@ -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(); diff --git a/DataClasses/RowData.cs b/DataClasses/RowData.cs index b923cc3..b61ceb7 100644 --- a/DataClasses/RowData.cs +++ b/DataClasses/RowData.cs @@ -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 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; diff --git a/Helpers/DgvMouseRow.cs b/Helpers/DgvMouseRow.cs index aeee7af..f7f3041 100644 --- a/Helpers/DgvMouseRow.cs +++ b/Helpers/DgvMouseRow.cs @@ -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) diff --git a/Helpers/DragDropHelper.cs b/Helpers/DragDropHelper.cs index 8bc47a6..fe08cf0 100644 --- a/Helpers/DragDropHelper.cs +++ b/Helpers/DragDropHelper.cs @@ -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) diff --git a/Helpers/Fading.cs b/Helpers/Fading.cs index c468969..6a81580 100644 --- a/Helpers/Fading.cs +++ b/Helpers/Fading.cs @@ -54,9 +54,7 @@ namespace SystemTrayMenu.UserInterface public void Dispose() { Dispose(true); -#if DEBUG GC.SuppressFinalize(this); -#endif } internal void Fade(FadingState state) diff --git a/NativeDllImport/TrackPopupMenuEx.cs b/NativeDllImport/TrackPopupMenuEx.cs index 85ad99b..59a0649 100644 --- a/NativeDllImport/TrackPopupMenuEx.cs +++ b/NativeDllImport/TrackPopupMenuEx.cs @@ -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 } /// diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index e51384e..a9a23e3 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -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")] diff --git a/UserInterface/Menu.Designer.cs b/UserInterface/Menu.Designer.cs index ec782e8..174f4ad 100644 --- a/UserInterface/Menu.Designer.cs +++ b/UserInterface/Menu.Designer.cs @@ -295,7 +295,7 @@ // // timerUpdateIcons // - this.timerUpdateIcons.Interval = 300; + this.timerUpdateIcons.Interval = 100; this.timerUpdateIcons.Tick += new System.EventHandler(this.TimerUpdateIcons_Tick); // // Menu diff --git a/UserInterface/ShellContextMenu/ShellContextMenu.cs b/UserInterface/ShellContextMenu/ShellContextMenu.cs index eada81b..49cf4f5 100644 --- a/UserInterface/ShellContextMenu/ShellContextMenu.cs +++ b/UserInterface/ShellContextMenu/ShellContextMenu.cs @@ -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)); diff --git a/Utilities/File/IconReader.cs b/Utilities/File/IconReader.cs index 4ccb93e..f12999d 100644 --- a/Utilities/File/IconReader.cs +++ b/Utilities/File/IconReader.cs @@ -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 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 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 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 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; }