BigKid
BigKid

Reputation: 9

How can I render HTML or markdown in a WPF datagrid?

Is there a way to render/show HTML (or Markdown) in a .NET Core WPF Datagrid column / cell ?

I have some text in a database which I would like to add some formatting options to. I was thinking to add the possibility to format it using markdown.

This text then is displayed in a datagrid using itemsource = List<myobject>, and I would like to have it displayed formatted in the datagrid column/cell.

So I used Markdig to generate HTML from the text in the database. This gives me something like:

<p>This is a text with some <em>emphasis</em></p>

And this should be displayed like:

This is a text with some emphasis

But then I get stuck. It seems like HTML is something alien to WPF.

Upvotes: 0

Views: 3575

Answers (1)

Rich N
Rich N

Reputation: 9475

It's possible to do this with WPF's WebBrowser control in a DataGridTemplateColumn. Some sample code is below.

To get the WebBrowser to display an HTML string you have to call NavigateToString. I've done this using an attached property as described in another Stack Overflow answer.

I've also done some work to remove the disabled scrollbars that the control will put in a cell by default, even if the content fits. I've used CSS styling to do this, but it's a little hacky.

Note that we are warned that the WebBrowser control is quite heavyweight in terms of memory usage, so you probably wouldn't want to do it this way in a grid with a lot of rows.

enter image description here

C#:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp14
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.CreateProducts();
            this.MyDataGrid.ItemsSource = Products;
        }

        public ObservableCollection<Product> Products { get; set; }

        private void CreateProducts()
        {
            Products = new ObservableCollection<Product>
            {
                new Product{ID = 1, Html = "HTML with no formatting"},
                new Product{ID = 2, Html= "<h1>This is a <i>test</i> header</h1>"},
                new Product{ID = 3, Html="<p>This is a text with some <em>emphasis</em></p>"},
                new Product{ID = 4, Html="<ul><li>Product 1</li><li>Product 2</li><li>Product 3</li></ul>"}
            };
        }
    }

    public class Product : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private int id;
        public int ID
        {
            get { return id; }
            set
            {
                id = value;
                PropertyChanged?.Invoke(this,
                   new PropertyChangedEventArgs(nameof(ID)));
            }
        }

        private string html;
        public string Html
        {
            get { return html; }
            set
            {
                html = RemoveScrollbars(value);
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(Html)));
            }
        }

        private string RemoveScrollbars(string html)
        {
            string before = "<head><style>body{overflow:hidden;}</style></head><body>";
            string after = "</body>";
            return before + html + after;
        }
    }

    public static class BrowserBehavior
    {
        public static readonly DependencyProperty HtmlProperty = DependencyProperty.RegisterAttached(
            "Html",
            typeof(string),
            typeof(BrowserBehavior),
            new FrameworkPropertyMetadata(OnHtmlChanged));

        [AttachedPropertyBrowsableForType(typeof(WebBrowser))]
        public static string GetHtml(WebBrowser d)
        {
            return (string)d.GetValue(HtmlProperty);
        }

        public static void SetHtml(WebBrowser d, string value)
        {
            d.SetValue(HtmlProperty, value);
        }

        static void OnHtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            WebBrowser wb = d as WebBrowser;
            if (wb != null)
                wb.NavigateToString(e.NewValue as string);
        }
    }
}

XAML:

<Window x:Class="WpfApp14.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp14"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <DataGrid x:Name="MyDataGrid" AutoGenerateColumns="False" CanUserAddRows="False">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding ID}"/>
            <DataGridTemplateColumn Width="300">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <WebBrowser local:BrowserBehavior.Html="{Binding Html}" Height="90"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Window>

Upvotes: 3

Related Questions