MarkdownTextBlock
The MarkdownTextBlock control provides full markdown parsing and rendering for Universal Windows Apps. Originally created for the open source reddit app Baconit, the control was engineered to be simple to use and very efficient. One of the main design considerations for the control was it needed to be performant enough to provide a great user experience in virtualized lists. With the custom markdown parser and efficient XAML rendering, we were able to achieve excellent performance; providing a smooth UI experience even with complex Markdown on low end hardware.
Under the hood, the control uses XAML sub-elements to build the visual rendering tree for the Markdown input. We chose to use full XAML elements overusing the RichEditTextBlock control because the RichEditTextBlock isn't flexible enough to correctly render all of the standard Markdown styles.
Syntax
<controls:MarkdownTextBlock
Text="**This is *Markdown*!**"
MarkdownRendered="MarkdownText_MarkdownRendered"
LinkClicked="MarkdownText_LinkClicked"
Margin="6">
</controls:MarkdownTextBlock>
Limitations
Here are some limitations you may encounter:
- All images are stretched with the same stretch value (defined by ImageStretch property)
- Relative Links & Relative Images needs to be handled manually using
LinkClicked
event.
Sample Output
Note: scrolling is smooth, the gif below is not.
Properties
The MarkdownTextBlock control is highly customizable to blend with any theme. Customizable properties include:
Property | Type | Description |
---|---|---|
CodeBackground | Brush | Gets or sets the brush used to fill the background of a code block |
CodeBorderBrush | Brush | Gets or sets the brush used to render the border fill of a code block |
CodeBorderThickness | Thickness | Gets or sets the thickness of the border around code blocks |
CodeFontFamily | FontFamily | Gets or sets the font used to display code. If this is null , then Windows.UI.Xaml.Media.FontFamily is used |
CodeForeground | Brush | Gets or sets the brush used to render the text inside a code block. If this is null , then Foreground is used |
CodeMargin | Thickness | Gets or sets the space between the code border and the text |
CodePadding | Thickness | Gets or sets space between the code border and the text |
CodeStyling | StyleDictionary | Gets or sets the Default Code Styling for Code Blocks |
EmojiFontFamily | FontFamily | Gets or sets the font used to display emojis. If this is null , then Segoe UI Emoji font is used |
Header1FontSize | double | Gets or sets the font size for level 1 headers |
Header1FontWeight | FontWeight | Gets or sets the font weight to use for level 1 headers |
Header1Foreground | Brush | Gets or sets the foreground brush for level 1 headers |
Header1Margin | Thickness | Gets or sets the margin for level 1 headers |
Header2FontSize | double | Gets or sets the font size for level 2 headers |
Header2FontWeight | FontWeight | Gets or sets the font weight to use for level 2 headers |
Header2Foreground | Brush | Gets or sets the foreground brush for level 2 headers |
Header2Margin | Thickness | Gets or sets the margin for level 2 headers |
Header3FontSize | double | Gets or sets the font size for level 3 headers |
Header3FontWeight | FontWeight | Gets or sets the font weight to use for level 3 headers |
Header3Foreground | Brush | Gets or sets the foreground brush for level 3 headers |
Header3Margin | Thickness | Gets or sets the margin for level 3 headers |
Header4FontSize | double | Gets or sets the font size for level 4 headers |
Header4FontWeight | FontWeight | Gets or sets the font weight to use for level 4 headers |
Header4Foreground | Brush | Gets or sets the foreground brush for level 4 headers |
Header4Margin | Thickness | Gets or sets the margin for level 4 headers |
Header5FontSize | double | Gets or sets the font size for level 5 headers |
Header5FontWeight | FontWeight | Gets or sets the font weight to use for level 5 headers |
Header5Foreground | Brush | Gets or sets the foreground brush for level 5 headers |
Header5Margin | Thickness | Gets or sets the margin for level 5 headers |
Header6FontSize | double | Gets or sets the font size for level 6 headers |
Header6FontWeight | FontWeight | Gets or sets the font weight to use for level 6 headers |
Header6Foreground | Brush | Gets or sets the foreground brush for level 6 headers |
Header6Margin | Thickness | Gets or sets the margin for level 6 headers |
HorizontalRuleBrush | Brush | Gets or sets the brush used to render a horizontal rule. If this is null , then HorizontalRuleBrush is used |
HorizontalRuleMargin | Thickness | Gets or sets the margin used for horizontal rules |
HorizontalRuleThickness | double | Gets or sets the vertical thickness of the horizontal rule |
ImageMaxHeight | double | Gets or sets the MaxHeight for images |
ImageMaxWidth | double | Gets or sets the MaxWidth for images |
ImageStretch | Stretch | Gets or sets the stretch used for images |
InlineCodeBackground | Brush | Gets or sets the foreground brush for inline code. |
InlineCodeBorderBrush | Brush | Gets or sets the border brush for inline code |
InlineCodeBorderThickness | Thickness | Gets or sets the thickness of the border for inline code |
InlineCodeFontFamily | FontFamily | Gets or sets the font used to display code. If this is null , then Windows.UI.Xaml.Media.FontFamily is used |
InlineCodePadding | Thickness | Gets or sets the padding for inline code |
InlineCodeMargin | Thickness | Gets or sets the margin for inline code |
IsTextSelectionEnabled | bool | Gets or sets a value indicating whether text selection is enabled |
LinkForeground | Brush | Gets or sets the brush used to render links. If this is null , then Foreground is used |
ListBulletSpacing | double | Gets or sets the space between the list item bullets/numbers and the list item content |
ListGutterWidth | double | Gets or sets the width of the space used by list item bullets/numbers |
ListMargin | Thickness | Gets or sets the margin used by lists |
ParagraphMargin | Thickness | Gets or sets the margin used for paragraphs |
ParagraphLineHeight | int | Gets or sets the line hegiht used for paragraphs. |
QuoteBackground | Brush | Gets or sets the brush used to fill the background of a quote block |
QuoteBorderBrush | Brush | Gets or sets the brush used to render a quote border. If this is null, then QuoteBorderBrush is used |
QuoteBorderThickness | Thickness | Gets or sets the thickness of quote borders. |
QuoteForeground | Brush | Gets or sets the brush used to render the text inside a quote block. If this is null , then Foreground is used |
QuoteMargin | Thickness | Gets or sets the space outside of quote borders |
QuotePadding | Thickness | Gets or sets the space between the quote border and the text |
SchemeList | string(separated by comma) | Gets or sets the custom SchemeList to render a URL. |
TableBorderBrush | Brush | Gets or sets the brush used to render table borders. If this is null, then TableBorderBrush is used |
TableBorderThickness | double | Gets or sets the thickness of any table borders |
TableCellPadding | Thickness | Gets or sets the padding inside each cell |
TableMargin | Thickness | Gets or sets the margin used by tables |
YamlBorderThickness | Thickness | Gets or sets the thickness of any yaml header borders. |
YamlBorderBrush | Brush | Gets or sets the brush used to render yaml header borders. If this is null, then TableBorderBrush is used |
Text | string | Gets or sets the markdown text to display |
TextWrapping | TextWrapping | Gets or sets the word wrapping behavior |
UriPrefix | string | Gets or sets the Prefix of Uri |
UseSyntaxHighlighting | bool | Gets or sets a value indicating whether to use Syntax Highlighting on Code |
WrapCodeBlock | bool | Gets or sets a value indicating whether to Wrap the Code Block or use a Horizontal Scroll |
Events
Events | Description |
---|---|
CodeBlockResolving | Fired when a Code Block is being Rendered. The default implementation is to output the CodeBlock as Plain Text. You must set Handled to true in order to process your changes |
ImageClicked | Fired when an image element in the markdown was tapped |
ImageResolving | Fired when an image from the markdown document needs to be resolved. The default implementation is basically. You must set Handled to true in order to process your changes |
LinkClicked | Fired when a link element in the markdown was tapped |
MarkdownRendered | Fired when the text is done parsing and formatting. Fires each time the markdown is rendered |
LinkClicked
Use this event to handle clicking on links for Markdown, by default the MarkdownTextBlock does not handle Clicking on Links.
private async void MarkdownText_LinkClicked(object sender, LinkClickedEventArgs e)
{
if (Uri.TryCreate(e.Link, UriKind.Absolute, out Uri link))
{
await Launcher.LaunchUriAsync(link);
}
}
ImageClicked
Use this event to handle clicking on images for Markdown, by default the MarkdownTextBlock does not handle Clicking on Images.
private async void MarkdownText_ImageClicked(object sender, LinkClickedEventArgs e)
{
if (Uri.TryCreate(e.Link, UriKind.Absolute, out Uri link))
{
await Launcher.LaunchUriAsync(link);
}
}
ImageResolving
Use this event to customize how images in the markdown document are resolved.
Set the ImageResolvingEventArgs.Image property to the image that should be shown in the rendered markdown document.
Also don't forget to set the ImageResolvingEventArgs.Handled flag to true, otherwise your custom image will not be used.
private void MarkdownText_OnImageResolving(object sender, ImageResolvingEventArgs e)
{
// This is basically the default implementation
e.Image = new BitmapImage(new Uri(e.Url));
e.Handled = true;
}
This event also supports loading the image in an asynchronous way.
Just request a Deferral which you complete when you're done.
private async void MarkdownText_OnImageResolving(object sender, ImageResolvingEventArgs e)
{
var deferral = e.GetDeferral();
e.Image = await GetImageFromDatabaseAsync(e.Url);
e.Handled = true;
deferral.Complete();
}
CodeBlockResolving
Use this event to customise how Code Block text is rendered, this is useful for providing Cusom Syntax Highlighting. Built in Syntax Highlighting is already provided with UseSyntaxHighlighting
.
Manipulate the Inline Collection, and then set e.Handled to true, otherwise the changes won't be processed.
private void MarkdownText_CodeBlockResolving(object sender, CodeBlockResolvingEventArgs e)
{
if (e.CodeLanguage == "CUSTOM")
{
e.Handled = true;
e.InlineCollection.Add(new Run { Foreground = new SolidColorBrush(Colors.Red), Text = e.Text, FontWeight = FontWeights.Bold });
}
}
Rendering
You can customise the rendering of the MarkdownTextBlock, by inheriting from MarkdownRenderer
and setting it as the renderer:
var block = new MarkdownTextBlock();
block.SetRenderer<InheritedMarkdownRenderer>();
This will likely require intimate knowledge of the implementation of the MarkdownRenderer
, take a look at the following:
Sample Project
MarkdownTextBlock Sample Page Source. You can see this in action in the Windows Community Toolkit Sample App.
Default Template
MarkdownTextBlock XAML File is the XAML template used in the toolkit for the default styling.
Requirements
Device family | Universal, 10.0.16299.0 or higher |
---|---|
Namespace | Microsoft.Toolkit.Uwp.UI.Controls |
NuGet package | Microsoft.Toolkit.Uwp.UI.Controls |