From 18e6a4cf418781e4fb51ca27de5fcb6be358fdd9 Mon Sep 17 00:00:00 2001
From: Josh <51800232+JD-Howard@users.noreply.github.com>
Date: Thu, 12 Jan 2023 23:11:41 -0700
Subject: [PATCH] Expanded the SampleAppCustomized project
---
.../Customized/ColumnScalingTableRenderer.cs | 142 ++++++++++++++++++
.../Customized/MarkdownViewer.cs | 25 ++-
.../Customized/WpfRenderer.cs | 21 ++-
.../MainWindow.xaml | 100 +++++++++++-
.../MainWindow.xaml.cs | 30 +++-
5 files changed, 299 insertions(+), 19 deletions(-)
create mode 100644 src/Markdig.Wpf.SampleAppCustomized/Customized/ColumnScalingTableRenderer.cs
diff --git a/src/Markdig.Wpf.SampleAppCustomized/Customized/ColumnScalingTableRenderer.cs b/src/Markdig.Wpf.SampleAppCustomized/Customized/ColumnScalingTableRenderer.cs
new file mode 100644
index 0000000..afa336d
--- /dev/null
+++ b/src/Markdig.Wpf.SampleAppCustomized/Customized/ColumnScalingTableRenderer.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Linq;
+using System.Windows;
+using Markdig.Renderers.Wpf;
+using TableColumnAlign = Markdig.Extensions.Tables.TableColumnAlign;
+using Table = Markdig.Extensions.Tables.Table;
+using TableCell = Markdig.Extensions.Tables.TableCell;
+using TableRow = Markdig.Extensions.Tables.TableRow;
+using WpfDocs = System.Windows.Documents;
+
+
+namespace Markdig.Wpf.SampleAppCustomized.Customized
+{
+ public class ColumnScalingTableRenderer : WpfObjectRenderer
+ {
+ protected override void Write(Renderers.WpfRenderer renderer, Table table)
+ {
+ if (renderer == null) throw new ArgumentNullException(nameof(renderer));
+ if (table == null) throw new ArgumentNullException(nameof(table));
+
+ var wpfTable = new WpfDocs.Table();
+
+ wpfTable.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.TableStyleKey);
+
+ #region Customization
+
+ var columnValueList = new System.Collections.Generic.List?[table.ColumnDefinitions.Count];
+
+ #endregion
+
+ foreach (var tableColumnDefinition in table.ColumnDefinitions)
+ {
+ wpfTable.Columns.Add(new WpfDocs.TableColumn
+ {
+ Width = (tableColumnDefinition?.Width ?? 0) != 0 ?
+ new GridLength(tableColumnDefinition!.Width, GridUnitType.Star) :
+ GridLength.Auto,
+ });
+ }
+
+ var wpfRowGroup = new WpfDocs.TableRowGroup();
+
+ renderer.Push(wpfTable);
+ renderer.Push(wpfRowGroup);
+
+ foreach (var rowObj in table)
+ {
+ var row = (TableRow)rowObj;
+ var wpfRow = new WpfDocs.TableRow();
+
+ renderer.Push(wpfRow);
+
+ if (row.IsHeader)
+ {
+ wpfRow.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.TableHeaderStyleKey);
+ }
+
+ for (var i = 0; i < row.Count; i++)
+ {
+ #region Customization
+
+ if (i < columnValueList.Length - 1 && columnValueList[i] is null)
+ columnValueList[i] = new(row.Count);
+
+ #endregion
+
+ var cellObj = row[i];
+ var cell = (TableCell)cellObj;
+ var wpfCell = new WpfDocs.TableCell
+ {
+ ColumnSpan = cell.ColumnSpan,
+ RowSpan = cell.RowSpan,
+ };
+
+ wpfCell.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.TableCellStyleKey);
+
+ renderer.Push(wpfCell);
+ renderer.Write(cell);
+ renderer.Pop();
+
+ var txt = new WpfDocs.TextRange(wpfCell.ContentStart, wpfCell.ContentEnd).Text;
+ columnValueList[i]?.Add(txt);
+
+ if (table.ColumnDefinitions.Count > 0)
+ {
+ var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
+ ? i
+ : cell.ColumnIndex;
+ columnIndex = columnIndex >= table.ColumnDefinitions.Count ? table.ColumnDefinitions.Count - 1 : columnIndex;
+ var alignment = table.ColumnDefinitions[columnIndex].Alignment;
+
+
+ #region Customization
+
+ if (row.IsHeader)
+ alignment = TableColumnAlign.Center;
+
+ #endregion
+
+ if (alignment.HasValue)
+ {
+ switch (alignment)
+ {
+ case TableColumnAlign.Center:
+ wpfCell.TextAlignment = TextAlignment.Center;
+ break;
+ case TableColumnAlign.Right:
+ wpfCell.TextAlignment = TextAlignment.Right;
+ break;
+ case TableColumnAlign.Left:
+ wpfCell.TextAlignment = TextAlignment.Left;
+ break;
+ }
+ }
+ }
+ }
+
+ renderer.Pop();
+ }
+
+ #region Customization
+
+ var weights = columnValueList.Select(x => x?.Max(y => y.Length) ?? 0.0).ToArray();
+ var sum = weights.Sum();
+ for (int i = 0; i < weights.Length; i++)
+ {
+ var col = wpfTable.Columns[i];
+ var weight = weights[i];
+ col.Width = weight == 0
+ ? new GridLength(0, GridUnitType.Pixel)
+ : new GridLength(weight / sum * 100.0, GridUnitType.Star);
+ }
+
+ #endregion
+
+ renderer.Pop();
+ renderer.Pop();
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/Markdig.Wpf.SampleAppCustomized/Customized/MarkdownViewer.cs b/src/Markdig.Wpf.SampleAppCustomized/Customized/MarkdownViewer.cs
index 8d4888c..b131a4f 100644
--- a/src/Markdig.Wpf.SampleAppCustomized/Customized/MarkdownViewer.cs
+++ b/src/Markdig.Wpf.SampleAppCustomized/Customized/MarkdownViewer.cs
@@ -5,7 +5,7 @@ namespace Markdig.Wpf.SampleAppCustomized.Customized
{
///
/// Usually the image paths in the Markdown must be relative to the application or absolute.
- /// This classes make it possible to change the root path.
+ /// This class utilizes a custom renderer and facilitates changing the root path.
///
public class MarkdownViewer : Markdig.Wpf.MarkdownViewer
{
@@ -27,13 +27,30 @@ public string UCRootPath
protected override void RefreshDocument()
{
- // In some cases the path is not updated fast enough, so we force it
+ Document = Markdown is null
+ ? null
+ : Markdig.Wpf.Markdown.ToFlowDocument(Markdown, Pipeline ?? DefaultPipeline, _renderer);
+ }
+
+
+ private WpfRenderer? _renderer = null;
+ public void SetCustomRenderer(bool? state)
+ {
+ // For obvious contrast, we only provide a custom renderer in 1 of the 3 presentation states
+ if (state != true)
+ {
+ _renderer = null;
+ return;
+ }
+
+ // In some cases the UCRootPath is not updated fast enough, so we force it
this.GetBindingExpression(UCRootPathProperty)?.UpdateTarget();
-
+
var path = UCRootPath;
if (!string.IsNullOrEmpty(path) && !path.EndsWith("/", StringComparison.Ordinal))
path = path.Remove(path.LastIndexOf('/') + 1);
- Document = Markdown != null ? Markdig.Wpf.Markdown.ToFlowDocument(Markdown, Pipeline ?? DefaultPipeline, new Customized.WpfRenderer(path)) : null;
+
+ _renderer = new WpfRenderer(new LinkInlineRenderer(path), new ColumnScalingTableRenderer());
}
}
}
diff --git a/src/Markdig.Wpf.SampleAppCustomized/Customized/WpfRenderer.cs b/src/Markdig.Wpf.SampleAppCustomized/Customized/WpfRenderer.cs
index c3a68f2..33aef26 100644
--- a/src/Markdig.Wpf.SampleAppCustomized/Customized/WpfRenderer.cs
+++ b/src/Markdig.Wpf.SampleAppCustomized/Customized/WpfRenderer.cs
@@ -1,24 +1,33 @@
namespace Markdig.Wpf.SampleAppCustomized.Customized
{
+ ///
+ /// Create a custom Renderer instead of using the one in the Markdig.Wpf package
+ ///
public class WpfRenderer : Markdig.Renderers.WpfRenderer
{
- private string _linkpath;
+ private readonly Renderers.IMarkdownObjectRenderer[] _injections;
///
/// Initializes the WPF renderer
///
- /// image path for the custom LinkInlineRenderer
- public WpfRenderer(string linkpath) : base()
+ /// custom renderer injection list
+ public WpfRenderer(params Renderers.IMarkdownObjectRenderer[] injectRenderers) : base()
{
- _linkpath = linkpath;
+ _injections = injectRenderers;
}
///
- /// Load first the custom renderer's
+ /// Load our custom renderer's before the base versions. By doing this, our renderers can
+ /// handle/continue on that specific Markdig type before ever reaching the default renderer
///
protected override void LoadRenderers()
{
- ObjectRenderers.Add(new LinkInlineRenderer(_linkpath));
+ if (_injections.Length > 0)
+ {
+ foreach (var renderer in _injections)
+ ObjectRenderers.Add(renderer);
+ }
+
base.LoadRenderers();
}
}
diff --git a/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml b/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml
index ebc379e..17babc9 100644
--- a/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml
+++ b/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml
@@ -6,15 +6,107 @@
xmlns:local="clr-namespace:Markdig.Wpf.SampleAppCustomized.Customized"
xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf"
mc:Ignorable="d"
+ DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow">
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml.cs b/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml.cs
index a4526e0..77fde04 100644
--- a/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml.cs
+++ b/src/Markdig.Wpf.SampleAppCustomized/MainWindow.xaml.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.IO;
using System.Windows;
+using System.Windows.Controls.Primitives;
namespace Markdig.Wpf.SampleAppCustomized
{
@@ -9,7 +10,16 @@ namespace Markdig.Wpf.SampleAppCustomized
///
public partial class MainWindow : Window
{
- private bool useExtensions = true;
+ private bool? _presentationState = false;
+
+ ///
+ /// Represents the IsChecked 3-State ToggleButton that changes the presentation
+ ///
+ public bool? PresentationState
+ {
+ get => _presentationState;
+ set => ToggleExtensions(value);
+ }
public MainWindow()
{
@@ -34,12 +44,22 @@ private void ClickOnImage(object sender, System.Windows.Input.ExecutedRoutedEven
MessageBox.Show($"URL: {e.Parameter}");
}
- private void ToggleExtensionsButton_OnClick(object sender, RoutedEventArgs e)
+ private void ToggleExtensions(bool? newValue)
{
- useExtensions = !useExtensions;
- Viewer.Pipeline = useExtensions ? new MarkdownPipelineBuilder().UseSupportedExtensions().Build() : new MarkdownPipelineBuilder().Build();
+ _presentationState = newValue;
+
+ // Setting the custom renderer first since the pipeline property will trigger refresh
+ Viewer.SetCustomRenderer(newValue);
+ Viewer.Pipeline = newValue switch
+ {
+ // Even if we include a custom renderer, we still have to enable the applicable
+ // Pipeline extensions that handle that Markdig type.
+ // In this case, UsePipeTables() enables the use of ColumnScalingTableRenderer
+ true => new MarkdownPipelineBuilder().UsePipeTables().Build(),
+ false => new MarkdownPipelineBuilder().UseSupportedExtensions().Build(),
+ _ => new MarkdownPipelineBuilder().Build()
+ };
}
-
}
}