6
\$\begingroup\$

I have a blog that I occasionally post to, but it's a PITA because of the fact that it has it's own WYSIWYG editor and doesn't support Markdown. So I have to switch the WYSIWYG editor to the HTML editor, and of course the HTML is garbled from the WYSIWYG editor.

So, I wrote a programme that I can feed Markdown into, and it will let me see what the HTML would be, and a preview of how it would look. It's all very simple.

Screenshot in Action

The XAML:

<Window x:Class="Markdown_Markup.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:Markdown_Markup"
        mc:Ignorable="d"
        Title="MainWindow" Height="297" Width="474"
        x:Name="_this">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StatusBar Height="24" VerticalAlignment="Bottom" Grid.ColumnSpan="3" Grid.Row="2"/>
        <Menu x:Name="menu" Height="24" VerticalAlignment="Top" Grid.ColumnSpan="3"/>
        <TextBox x:Name="markdownTextBox" Margin="5,50,5,29" TextWrapping="Wrap" Grid.RowSpan="3" TextChanged="textBox_TextChanged" AcceptsReturn="True" AcceptsTab="True"/>
        <TextBox Margin="5,28,5,5" TextWrapping="Wrap" Grid.Column="1" Grid.Row="1" IsReadOnly="True" DataContext="{Binding ElementName=_this}" Text="{Binding Path=MarkdownHtml}"/>
        <TextBox Margin="5,26,5,29" TextWrapping="Wrap" Grid.Column="1" Grid.Row="2" IsReadOnly="True" DataContext="{Binding ElementName=_this}" Text="{Binding Path=RenderHtml}"/>
        <TextBox x:Name="styleTextBox" Margin="5,50,5,0" TextWrapping="Wrap" Grid.Column="1" TextChanged="textBox_TextChanged" AcceptsReturn="True" AcceptsTab="True"/>
        <WebBrowser x:Name="renderPreviewBrowser" Grid.Column="2" Margin="5,50,5,29" Grid.RowSpan="3" Navigating="renderPreviewBrowser_Navigating" />
        <Label Content="Markdown Content:" HorizontalAlignment="Left" Margin="5,24,0,0" VerticalAlignment="Top"/>
        <Label Content="Additional CSS:" Grid.Column="1" HorizontalAlignment="Left" Margin="5,24,0,0" VerticalAlignment="Top"/>
        <Label Content="Markdown HTML:" Grid.Column="1" HorizontalAlignment="Left" Margin="5,2,0,0" Grid.Row="1" VerticalAlignment="Top"/>
        <Label Content="Render HTML:" Grid.Column="1" HorizontalAlignment="Left" Margin="5,0,0,0" Grid.Row="2" VerticalAlignment="Top"/>
        <Label Content="HTML Preview:" Grid.Column="2" HorizontalAlignment="Left" Margin="5,24,0,0" VerticalAlignment="Top"/>
    </Grid>
</Window>

The Code Behind:

using MarkdownSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Markdown_Markup
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public static readonly DependencyProperty MarkdownHtmlProperty = DependencyProperty.Register("MarkdownHtml", typeof(string), typeof(MainWindow), new UIPropertyMetadata(string.Empty));

        public string MarkdownHtml
        {
            get
            {
                return (string)GetValue(MarkdownHtmlProperty);
            }
            set
            {
                SetValue(MarkdownHtmlProperty, value);
            }
        }

        public static readonly DependencyProperty RenderHtmlProperty = DependencyProperty.Register("RenderHtml", typeof(string), typeof(MainWindow), new UIPropertyMetadata(string.Empty));

        public string RenderHtml
        {
            get
            {
                return (string)GetValue(RenderHtmlProperty);
            }
            set
            {
                SetValue(RenderHtmlProperty, value);
            }
        }

        public MainWindow()
        {
            InitializeComponent();
        }

        private void textBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            var markdown = new Markdown();
            var html = markdown.Transform(markdownTextBox.Text);

            MarkdownHtml = html;

            html = $"<html>\r\n\t<head>\r\n\t\t<style>\r\n\t\t\t{styleTextBox.Text}\r\n\t\t</style>\r\n\t</head>\r\n\t<body>\r\n\t\t{html}\r\n\t</body>\r\n</html>";
            RenderHtml = html;

            renderPreviewBrowser?.NavigateToString(html);
        }

        private void renderPreviewBrowser_Navigating(object sender, NavigatingCancelEventArgs e)
        {
            // This prevents links in the page from navigating, this also means we cannot call WebBrowser.Navigate for any browsers with this event.
            if (e.Uri != null)
            {
                e.Cancel = true;
            }
        }
    }
}

If you're going to run it, you need to install MarkdownSharp from NuGet.

\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

You want to unleash the almighty power of WPF, and bind to a ViewModel; WPF and the Model-View-ViewModel design go completely hand-in-hand, especially if you like your code testable.

As it stands the only way to test the application logic is to actually run it and see what happens - that's good for a prototype app, but for an actual real-world app you'll want something a bit more robust.

Start with a class, and identify what your view needs - as always, input and output:

  • Input

    • Some Markdown content
    • Some CSS content
  • Output

    • The resulting HTML

Easy as pie. Your ViewModel could start like this:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private string _markdownContent;
    public string MarkdownContent
    {
        get { return _markdownContent; }
        set
        {
            _markdownContent = value;
            OnPropertyChanged();
        }
    }

    private string _cssContent;
    public string CssContent
    {
        get { return _cssContent; }
        set
        {
            _cssContent = value;
            OnPropertyChanged();
        }
    }

    private string _htmlContent;
    public string HtmlContent
    {
        get { return _htmlContent; }
        set
        {
            _htmlContent = value;
            OnPropertyChanged();
        }
    }
    // todo: INotifyPropertyChanged implementation
}

How is that used? I'll cheat a little and do this to illustrate:

public MainWindow()
{
    InitializeComponent();
    DataContext = new MainWindowViewModel();
}

private MainWindowViewModel ViewModel
{
    get { return DataContext As MainWindowViewModel; }
}

Now your View knows about a ViewModel, and its DataContext is set to an instance of it. You'll probably want a better way to do this though, but that's just to get the ball rolling.

The MainWindow's DataContext will now be inherited by everything in the XAML that doesn't override it - get rid of all that DataContext={foobar} markup, you don't need it anymore.

Now you can do this (removed fluff for clarity):

<TextBox Content="{Binding MarkdownContent}"/>
<TextBox Content="{Binding CssContent}"/>

The WebBrowser is a little more fun (involves creating a behavior and an attached property - see the linked SO post), but at the end of the day boils down to this:

<WebBrowser local:BrowserBehavior.Html="{Binding HtmlContent}"/>

What does that entail? Whenever the markdown/css changes inside the textboxes, the ViewModel knows, because its setters are running - you have a handle to run your application logic, without writing a single line of code in the View's code-behind! No need to handle TextChanged, and no need to even name any of the controls!

Now, you can implement the application logic directly inside the ViewModel class, or better, you can constructor-inject the ViewModel with an object that's solely responsible for that.

\$\endgroup\$
1
  • \$\begingroup\$ Actually, it outputs two HTML strings. \$\endgroup\$ Jan 5, 2016 at 18:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.