fix(ui): obwódka okna nie błyska na kobalt przy starcie i aktywacji#183
Conversation
Przyczyna: wybraną obwódkę (DWMWA_BORDER_COLOR — „brak"/kolor) nakładaliśmy reaktywnie i z odroczeniem (Loaded + ApplicationIdle), a WPF-UI/DWM maluje krawędź akcentem (kobalt) wcześniej — przy pierwszym pokazaniu okna, przy każdej (de)aktywacji i po zmianie motywu — stąd błysk. - MainWindow: hook WndProc + WindowBorder.Apply przeniesione z Loaded do OnSourceInitialized (hwnd już istnieje, okno jeszcze niepokazane) → wybrana obwódka obowiązuje od pierwszej klatki. - WindowBorder.Keep: zamiast odroczonego ApplicationIdle instaluje hook WM_NCACTIVATE na każdym oknie — korekta krawędzi jest synchroniczna, w tym samym cyklu repaint (dotyczy też dialogów). Zmiana wizualna (DWM/Win11) — do weryfikacji na Windows 11. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXXgwUeSkZsXYVKRzyMvV9
There was a problem hiding this comment.
AI Code Review by LlamaPReview
🎯 TL;DR & Recommendation
Recommendation: Approve with suggestions
This PR eliminates the cobalt border flash at startup and activation by moving border application from Loaded/ApplicationIdle to OnSourceInitialized and using a WM_NCACTIVATE hook for synchronous reapplies. The logic is sound and the risk is low, though there is a minor maintainability concern with duplicate hook registration.
📄 Documentation Diagram
This diagram documents the refactored window border application flow, ensuring the border is set before the first frame and reapplied synchronously on activation.
sequenceDiagram
participant Window as MainWindow
participant WB as WindowBorder
participant DWM as DWM (Windows)
Note over Window,WB: OnSourceInitialized (early)
Window->>WB: Apply(this)
WB->>DWM: DwmSetWindowAttribute (DWMWA_COLOR_NONE)
Note over DWM: Border set before first paint
Note over Window,WB: Activation occurs
DWM-->>Window: WM_NCACTIVATE
Window->>WB: BorderHook (via HwndSource hook)
WB->>DWM: DwmSetWindowAttribute (synchronous)
Note over DWM: Border reapplied in same repaint cycle
Note over Window,WB: Safety net at ApplicationIdle
Window->>WB: Apply (dispatched)
WB->>DWM: DwmSetWindowAttribute
🌟 Strengths
- Clear, well-documented fix that addresses the root cause of the flash.
- Low regression risk: changes are limited to timing of existing functionality.
💡 Suggestions (P2)
- src/RdpManager/WindowBorder.cs: Each call to
Keepregisters a duplicateBorderHookviaHwndSource.AddHook, causing redundantDwmSetWindowAttributecalls on everyWM_NCACTIVATEand slight performance overhead. Consider guarding against duplicates with an attached property or similar mechanism.
💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.
| @@ -62,19 +62,30 @@ public static void Keep(Window window) | |||
| { | |||
There was a problem hiding this comment.
[Contextual Comment]
This comment refers to code near real line 61. Anchored to nearest_changed(62) line 62.
P2 | Confidence: High
Each invocation of Keep adds a new BorderHook to the window's HwndSource hook chain without removing any previous instance of the same static method. If Keep is called more than once for the same window (e.g., the class handler for Loaded fires multiple times, or window lifecycle is unusual), the BorderHook handler will be registered multiple times. This causes redundant DwmSetWindowAttribute calls on every WM_NCACTIVATE message. While the functional effect is still correct (the same value is set repeatedly), the extra P/Invoke calls are unnecessary and can degrade performance during rapid activation/deactivation sequences. The old code suffered from a similar problem with multiple Activated event subscriptions, so this is not a regression, but migrating to HwndSource hooks provides a clean moment to guard against duplicates.
Code Suggestion:
// Attached property to track whether hook is already installed for this window.
private static readonly DependencyProperty HookInstalledProperty =
DependencyProperty.RegisterAttached("HookInstalled", typeof(bool), typeof(WindowBorder),
new PropertyMetadata(false));
public static void Keep(Window window)
{
if (window == null) return;
if ((bool)window.GetValue(HookInstalledProperty)) return;
window.SetValue(HookInstalledProperty, true);
Apply(window);
var source = HwndSource.FromHwnd(new WindowInteropHelper(window).Handle);
if (source == null) return;
source.AddHook(BorderHook);
source.Disposed += (_, __) => window.ClearValue(HookInstalledProperty);
window.Dispatcher.BeginInvoke(new Action(() => Apply(window)),
System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}Evidence: method:Keep, symbol:BorderHook
Problem
Obwódka okna błyska na kobalt (
#2657D6) przy uruchomieniu i przy niektórych akcjach, mimo ustawienia obwódki na „brak"/„systemowa".Przyczyna
Krawędź na Win11 to
DWMWA_BORDER_COLOR. WPF-UI/DWM maluje ją akcentem (kobalt) przy pierwszym pokazaniu okna, przy każdej (de)aktywacji i po zmianie motywu. Wybraną obwódkę („brak" =DWMWA_COLOR_NONE) nakładaliśmy reaktywnie i z odroczeniem:WindowBorder.SetSpecna starcie leci, zanim istnieje jakiekolwiek okno (App.xaml.cs).Loaded(class-handler →WindowBorder.Keep) + odroczonyApplicationIdle— czyli po pierwszym malowaniu.WM_NCACTIVATEwMainWindowbył instalowany dopiero wLoaded.Efekt: okno/dialog na moment pokazuje kobaltową krawędź, którą gasimy ułamek sekundy później.
Poprawka
WindowBorder.Apply(this)przeniesione zLoadeddoOnSourceInitialized(hwnd już jest, okno jeszcze niepokazane) → wybrana obwódka obowiązuje od pierwszej klatki.ApplicationIdleinstaluje hookWM_NCACTIVATEna każdym oknie — korekta krawędzi jest synchroniczna, w tym samym cyklu repaint (obejmuje też dialogi przy otwieraniu/zamykaniu).Weryfikacja
Jeśli na dialogach nadal mignie pierwsza klatka, dołożę nałożenie obwódki w ich
OnSourceInitialized(wymaga wspólnego punktu bazowego) — celowo zacząłem od pewnych, niskiego ryzyka zmian.🤖 Generated with Claude Code
https://claude.ai/code/session_01KXXgwUeSkZsXYVKRzyMvV9
Generated by Claude Code