MarkdownTextBlock: ThemeResource support, DP migration & performance fixes#785
Open
MarkdownTextBlock: ThemeResource support, DP migration & performance fixes#785
Conversation
11 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.
MarkdownTextBlock: ThemeResource support, DP migration & performance fixes
Summary
This PR modernizes the
MarkdownTextBlockcontrol's theming architecture and fixes several performance/memory issues. The control now supports proper{ThemeResource}brushes that update automatically on Light/Dark/HighContrast theme switches — previously, theme changes had no effect on markdown styling.Addresses: #611
What changed
Theming & DependencyProperty migration
Previously, all styling lived in plain C# classes (
MarkdownThemeswith auto-properties, wrapped byMarkdownConfig). This meant{ThemeResource}couldn't target them, and theme switches (Light ↔ Dark) had no effect on markdown brush colors.New architecture: All 67+ styling properties are now DependencyProperties directly on
MarkdownTextBlock. The intermediaryMarkdownThemesandMarkdownConfigclasses are deleted entirely. TextElement renderers (MyHeading,MyCodeBlock,MyQuote,MyTable, etc.) receive theMarkdownTextBlockcontrol reference and read DPs directly from it at render time.DPs added to
MarkdownTextBlock.Properties.csH1–H6×FontSize,Foreground,FontWeight,MarginInlineCodeBackground,InlineCodeForeground,InlineCodeBorderBrush,InlineCodeBorderThickness,InlineCodeCornerRadius,InlineCodePadding,InlineCodeFontSize,InlineCodeFontWeightCodeBlockBackground,CodeBlockForeground,CodeBlockBorderBrush,CodeBlockBorderThickness,CodeBlockPadding,CodeBlockMargin,CodeBlockFontFamily,CodeBlockCornerRadiusQuoteBackground,QuoteForeground,QuoteBorderBrush,QuoteBorderThickness,QuoteMargin,QuotePadding,QuoteBarMargin,QuoteCornerRadiusTableHeadingBackground,TableBorderBrush,TableBorderThickness,TableCellPadding,TableMargin,TableCornerRadiusHorizontalRuleBrush,HorizontalRuleThickness,HorizontalRuleMarginLinkForegroundImageMaxWidth,ImageMaxHeight,ImageStretchParagraphMargin,ParagraphLineHeight,ListBulletSpacing,ListGutterWidthBoldFontWeightBaseUrl,ImageProvider,SVGRenderer(previously onMarkdownConfig)Default style (
MarkdownTextBlock.xaml)ThemeDictionarieswithDefault,Light, andHighContrastdictionaries define all brush resources (e.g.,MarkdownTextBlockH1Foreground→TextFillColorPrimaryBrush). HighContrast usesSystemColorButtonTextColorBrush,SystemColorHotlightColorBrush, etc.SettingsControlsin this repo.Rendering architecture changes
WinUIRenderer— RemovedConfigproperty. Constructor now takes(MyFlowDocument, MarkdownTextBlock). Already had aMarkdownTextBlockproperty.MarkdownThemes/MarkdownConfigparameter toMarkdownTextBlock. Each reads DPs directly (e.g.,_control.H1FontSize,_control.CodeBlockBackground).renderer.MarkdownTextBlockinstead ofrenderer.Config.Themes.OnThemePropertyChangedcallback that usesDispatcherQueue.TryEnqueuewith a boolean guard to coalesce multiple simultaneous DP changes (e.g., 20+ brush updates on theme switch) into one re-render.ActualThemeChangedhandler as belt-and-suspenders for theme switching, using the same batching guard.Performance & memory fixes
Fixed
HttpClientsocket leak (MyImage.cs)Each image load created
new HttpClient(), never disposed.HttpClientmaintains connection pools internally, so per-instance creation leaks socket handles. Replaced with astatic readonly HttpClientshared across all image loads.Fixed
Image.Loadedevent handler leak (MyImage.cs)_image.Loaded += LoadImagewas never unsubscribed. On every theme-change re-render, the oldMyImageobjects remained rooted via the event handler delegate, preventing garbage collection. Now unsubscribes immediately at the top of the handler.Fixed
ActualThemeChangedevent leak (MarkdownTextBlock.xaml.cs)The event was subscribed in the constructor but never unsubscribed. Moved to
Loaded/Unloadedlifecycle handlers, matching the pattern used by the old CommunityToolkit MarkdownTextBlock control.Eliminated per-image
DefaultSVGRendererallocation (MyImage.cs)Every image without a custom SVG renderer created
new DefaultSVGRenderer(). Since it's stateless, replaced with astatic readonlysingleton.Fixed double-scan on
HtmlNodeCollection(HtmlWriter.cs)node.ChildNodes.Remove(node.ChildNodes.FirstOrDefault(...))scanned the collection twice — once to find the node, once to remove it. Refactored to a singleFirstOrDefault+ null-check +Remove.Improved text buffer growth strategy (
WinUIRenderer.cs)When text exceeded the 1024-char buffer, the entire buffer was replaced with an exact-fit
ToCharArray()— likely too small for the next call, causing repeated reallocations. Now uses a doubling strategy (Math.Max(length, buffer.Length * 2)).Sample updates
Configbinding; defaults come from the XAML style automatically.x:Bindfunction bindings.Breaking changes
MarkdownConfigandMarkdownThemesclasses are deleted. Users who were settingConfigshould set properties directly on the control:BaseUrl,ImageProvider,SVGRendererare now DPs onMarkdownTextBlock(previously onMarkdownConfig):Testing
ImageProviderConstraintTestupdated to use new DP API