From f371fc83adb6cf212517cf6c3b20014f9e24d88e Mon Sep 17 00:00:00 2001 From: PointStar Date: Mon, 11 May 2026 18:43:53 +0300 Subject: [PATCH] fix(ribbon): switch icons from loadImage to per-button getImage callbacks After the previous commit the ribbon rendered with NO icons at all. Cause: loadImage on customUI is the Office-2007 pattern; Office 2010+ silently falls back to no image when the callback signature or timing isn't exactly what it expects. Some hosts also cache a null result. Switch to the more reliable per-button pattern: - Each button declares getImage=OnGetImage (no image= attribute). - OnGetImage(IRibbonControl control) maps control.Id -> embedded PNG name and returns a fresh Bitmap. - If a resource is missing we log it AND the full list of GetManifestResourceNames() so we can diagnose any future mismatch from the addin log alone. Builds clean. PNGs unchanged; csproj entries are the same as before. The previous OnLoadImage callback is removed. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/OutlookAddin/OutlookAddin.csproj | 14 +---- src/OutlookAddin/Ribbon/ExplorerRibbon.cs | 63 +++++++++++++++++---- src/OutlookAddin/Ribbon/ExplorerRibbon.xml | 8 +-- src/OutlookAddin/Ribbon/InspectorRibbon.xml | 4 +- 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/OutlookAddin/OutlookAddin.csproj b/src/OutlookAddin/OutlookAddin.csproj index fd16f18..4567767 100644 --- a/src/OutlookAddin/OutlookAddin.csproj +++ b/src/OutlookAddin/OutlookAddin.csproj @@ -36,7 +36,7 @@ C:\Users\Chaim\source\repos\OutlookAddin\Publish\ \\192.168.10.97\Projects\LegalCRM\Add-in\ en - 1.0.0.1 + 1.0.0.2 true true 7 @@ -258,18 +258,6 @@ OutlookAddin.Ribbon.InspectorRibbon.xml - - OutlookAddin.Images.klear-file.png - - - OutlookAddin.Images.klear-sidebar.png - - - OutlookAddin.Images.klear-settings.png - - - OutlookAddin.Images.klear-compose.png - Code diff --git a/src/OutlookAddin/Ribbon/ExplorerRibbon.cs b/src/OutlookAddin/Ribbon/ExplorerRibbon.cs index f158d81..2b5b1a2 100644 --- a/src/OutlookAddin/Ribbon/ExplorerRibbon.cs +++ b/src/OutlookAddin/Ribbon/ExplorerRibbon.cs @@ -136,27 +136,68 @@ namespace OutlookAddin.Ribbon } /// - /// VSTO ribbon image callback. The XML uses image="klear-file.png" - /// (etc.) and Office calls back here once per image to resolve the - /// resource into an actual Bitmap. The PNGs are shipped as - /// EmbeddedResource under the OutlookAddin.Images.* logical name. + /// Per-button image callback. Office calls this once per ribbon + /// button that has getImage="OnGetImage" and we map control.Id to + /// the embedded PNG resource. Returning null falls back to + /// "no icon" (text-only) rather than throwing. /// - public Bitmap? OnLoadImage(string imageName) + public Bitmap? OnGetImage(IRibbonControl control) { try { - if (string.IsNullOrWhiteSpace(imageName)) return null; - var resourceName = "OutlookAddin.Images." + imageName; - using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) + if (control == null) return null; + string fileName; + switch (control.Id) { - if (stream == null) return null; - return new Bitmap(stream); + case "FileToEspoCrm": fileName = "klear-file.png"; break; + case "ShowSidebar": fileName = "klear-sidebar.png"; break; + case "OpenEspoCrmSettings": fileName = "klear-settings.png"; break; + case "ComposeFromCase": fileName = "klear-compose.png"; break; + default: return null; } + return LoadEmbeddedBitmap(fileName, control.Id); } - catch + catch (Exception ex) { + LogIconWarning(ex, "OnGetImage exception for {0}", control?.Id); return null; } } + + private static Bitmap? LoadEmbeddedBitmap(string fileName, string ribbonId) + { + var asm = Assembly.GetExecutingAssembly(); + var resourceName = "OutlookAddin.Images." + fileName; + var stream = asm.GetManifestResourceStream(resourceName); + if (stream == null) + { + // Help debug by listing every resource we DO have. + var available = string.Join(", ", asm.GetManifestResourceNames()); + LogIconWarning(null, + "Icon resource not found: {0} (ribbon button {1}). Available resources: {2}", + resourceName, ribbonId, available); + return null; + } + try + { + return new Bitmap(stream); + } + finally + { + stream.Dispose(); + } + } + + private static void LogIconWarning(Exception? ex, string template, params object?[] args) + { + try + { + var logger = Globals.ThisAddIn?.AddInHost?.Logger; + if (logger == null) return; + if (ex != null) logger.Warning(ex, template, args); + else logger.Warning(template, args); + } + catch { /* ignore — logging must never crash the ribbon */ } + } } } diff --git a/src/OutlookAddin/Ribbon/ExplorerRibbon.xml b/src/OutlookAddin/Ribbon/ExplorerRibbon.xml index 99f6ae5..59dc97c 100644 --- a/src/OutlookAddin/Ribbon/ExplorerRibbon.xml +++ b/src/OutlookAddin/Ribbon/ExplorerRibbon.xml @@ -1,5 +1,5 @@ - + @@ -9,7 +9,7 @@ screentip="תייק את המייל הנבחר ל-Klear" supertip="פותח חלון חיפוש לבחירת תיק, לקוח או איש קשר ב-Klear, ומתייק את המייל הנבחר אליו." size="large" - image="klear-file.png" + getImage="OnGetImage" onAction="OnFileToEspoCrm" getEnabled="OnGetFileToEspoCrmEnabled" />