Merge pull request #4149 from icsharpcode/fix/4143-taskbar-icon

Re-send the window icon after a theme flip or resume
This commit is contained in:
Christoph Wille
2026-10-09 09:31:34 +02:00
committed by GitHub
2 changed files with 62 additions and 0 deletions
+20
View File
@@ -60,6 +60,26 @@ public class MainWindowTests
window.DataContext.Should().BeOfType<MainWindowViewModel>();
}
[AvaloniaTest]
public void ReapplyWindowIcon_Hands_The_Platform_A_New_Icon_Instance()
{
// The platform window ignores an Icon assignment of the instance it already holds, so
// re-sending the icon to the OS taskbar only works with a fresh WindowIcon.
// Arrange
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var before = window.Icon;
before.Should().NotBeNull();
// Act
window.ReapplyWindowIcon();
// Assert
window.Icon.Should().NotBeNull();
window.Icon.Should().NotBeSameAs(before);
}
[AvaloniaTest]
public async Task Assembly_Tree_Pane_Is_Visible_In_Layout()
{
+42
View File
@@ -16,13 +16,17 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel;
using System.Composition;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.Threading;
using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.ViewModels;
@@ -80,6 +84,8 @@ namespace ICSharpCode.ILSpy.Views
AddHandler(PointerReleasedEvent, OnBrowserNavigationPointerReleased,
RoutingStrategies.Bubble, handledEventsToo: true);
ApplySessionSettings(settingsService.SessionSettings);
if (OperatingSystem.IsWindows())
Win32Properties.AddWndProcHookCallback(this, TaskbarIconWndProcHook);
Opened += async (_, _) => {
AppLog.Mark("MainWindow.Opened fired");
using (AppLog.Phase("AssemblyTreeModel.Initialize"))
@@ -140,6 +146,42 @@ namespace ICSharpCode.ILSpy.Views
}
}
const uint WM_SETTINGCHANGE = 0x001A;
const uint WM_POWERBROADCAST = 0x0218;
const int PBT_APMRESUMEAUTOMATIC = 0x12;
bool windowIconRefreshPending;
// The Windows taskbar re-queries window icons when the system theme flips. A query that
// arrives while the UI thread is not pumping messages (typically right after resume from
// hibernate, which is also when a scheduled light/dark switch fires) goes unanswered, and
// the taskbar button stays blank because nothing prompts the taskbar to ask again.
// These messages are only dispatched once the thread pumps again, so re-sending the icon
// shortly afterwards repairs the button. The delay coalesces the burst of
// ImmersiveColorSet broadcasts a single theme flip produces.
IntPtr TaskbarIconWndProcHook(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
bool iconMayBeLost = (msg == WM_POWERBROADCAST && wParam == PBT_APMRESUMEAUTOMATIC)
|| (msg == WM_SETTINGCHANGE && Marshal.PtrToStringUni(lParam) == "ImmersiveColorSet");
if (iconMayBeLost && !windowIconRefreshPending)
{
windowIconRefreshPending = true;
DispatcherTimer.RunOnce(() => {
windowIconRefreshPending = false;
ReapplyWindowIcon();
}, TimeSpan.FromSeconds(2));
}
return IntPtr.Zero;
}
// The platform window ignores an assignment of the WindowIcon instance it already holds,
// so re-sending the icon to the OS requires a new instance.
internal void ReapplyWindowIcon()
{
using var stream = AssetLoader.Open(new Uri("avares://ILSpy/Assets/ILSpy.ico"));
Icon = new WindowIcon(stream);
}
void ApplySessionSettings(SessionSettings session)
{
Position = session.WindowPosition;