Conversation
The header image sits inside the ControlExample, and the toggle theme button sets RequestedTheme on the example content and its container rather than on the page, so none of the page level theme notifications fired and the light/dark image never swapped. The page worked around that with a 200ms DispatcherTimer comparing the page's theme against the ControlExample's on every tick, which also meant UpdateTypographyImage ran five times a second, re-decoding the PNG each time, for as long as a toggled theme stayed in effect. ThemeManager keeps ActualTheme in sync on every element of the tree, so a single ActualThemeChanged handler on the image itself covers the toggle and application level theme changes alike. It replaces the timer, the multi-level theme detection, the singleton subscription, the RequestedTheme property override and the deferred one-shot refresh.
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #438.
PropertyDescriptor.AddValueChanged(component, handler)stores component -> handler in a dictionary that lives on the descriptor, and the descriptor behindDependencyPropertyDescriptor.FromPropertyis cached per (property, type) for the lifetime of the process. EveryAddValueChangedwithout a matchingRemoveValueChangedtherefore roots the component forever, and when the component is aFrameworkElement, its parent chain and window go with it.The reported repro (a window with
WindowHelper.UseModernWindowStyle="True", closed, then GC'd) had two independent roots, only one of which was in the report:TitleBarControl.descriptor_WindowStyle/descriptor_ResizeModeWindowHelper.WindowResizeModeDescriptorSetWindowStyleSetWindowStyle(window)call, which never comes: neverFour more sites had the same pattern, on themselves or on an ancestor. Each one transitively roots the window it lives in:
InfoBar: ownForegroundSettingsCard: ownIsPressed/IsMouseOverMessageBox: ownWindowHelper.SystemBackdropTypeAnimatedIcon: an ancestor'sAnimatedIcon.State, re-subscribed on every load with a lambda that was impossible to removePopupPositioneralready pairs add/remove and is untouched.Change
Two shapes, depending on who owns the property:
Watching own properties (
InfoBar,SettingsCard,MessageBox): drop the descriptor entirely and overrideOnPropertyChanged, comparinge.Property. Same notifications, nothing to unsubscribe, less code.Watching another object (
TitleBarControl,WindowHelper,AnimatedIcon): keep the descriptor, but release the handler once the subscription is no longer needed,Window.Closedfor the two window-level roots,Unloaded(and before re-subscribing) forAnimatedIcon.AnimatedIcon's handler is now stored in a field so it can actually be removed.Two adjacent bugs in
WindowHelper.SetWindowStylecame along:ActualThemeChangedhandler and the constructed fresh on every call, soRemoveActualThemeChangedHandler/Loaded -=never matched anything and each call piled on another subscription. Both are now static methods that take the window from sender.TitleBarControl.UpdateButtonActualAvailabilities()is public and dereferenced_parentWindowunconditionally; itnow returns early when there is no parent wi
Gallery: TypographyPage
The same audit turned up two non-descriptor roots on this page, both created in the constructor and never undone: a subscription to the
ThemeManager.Current.ActualApplicationThemeChangedsingleton, and a 200msDispatcherTimerthat was started and never stopped.The timer existed because the header image sits inside the
ControlExample, and the toggle theme button setsRequestedThemeon the example content and its container rather than on the page, so no page-level theme notification fired and the light/dark image never swapped (hence the//FAILED TRIALScomment). The workaround compared the page's theme against theControlExample's on every tick, which also meant the PNG was re-decoded five times a second for as long as a toggled theme stayed in effect.ThemeManagerkeepsActualThemein sync on every element of the tree, so oneActualThemeChangedhandler on the image itself covers the toggle and application-level theme changes alike. That replaces the timer, the multi-level theme detection, the singleton subscription, theRequestedThemeproperty override and a deferred one-shot refresh: -157 lines.MessageBox.SystemBackdropTypeProperty_Descriptor(public static field) is removed. It existed only to host the leaking subscription this PR deletes, and callingAddValueChangedon it is exactly what keeps aMessageBoxalive forever.