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.
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.
1 Answer 1
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.
-
\$\begingroup\$ Actually, it outputs two HTML strings. \$\endgroup\$Der Kommissar– Der Kommissar2016年01月05日 18:29:44 +00:00Commented Jan 5, 2016 at 18:29