In a recent blog post on my website redesign, I remarked that I had been using Markdown to write my blog posts. I have come to quite like Markdown as a format to use to write sine it keeps my hands on the keyboard and enables an easy way to format my posts without the need to use a full-featured work processing application.
I have been using MarkdownPad as my markdown editor as it appeared to be the only good editor available on Windows (side note: why is it that all of the interesting markdown development is happening on macOS?). The problem with MarkdownPad is that it hasn't been updated in some time (it seems new development has stopped) and on Windows 10 it requires some very specific developer library to be installed in order to get the live preview functionality to work. I haven't seen any good UWP markdown editors in the Windows Store, so I thought: why don't I make one myself?
Enter Furthermark. I created Furthermark as a simple UWP markdown editor that uses some of my favorite features of MarkdownPad. Namely:
I think I came pretty close. There's still a long road ahead, but I think I have a pretty good version 1.0.
UWP is an interesting platform to develop for; the sandboxing and the built-in controls don't have the flexibility that Win32 or traditional .NET development allow for. As an application that will only target desktop platforms, Furthermark had to sacrifice some "traditional" application paradigms in order to get the results I wanted.
The biggest example is the resize slider for the preview window. Most windows control libraries (WinForms, WPF, MFC) have a control for a resize panel or a slider. UWP does not. I could (and would like to in a future version) implement my own control that behaves similarly to a resize panel, but it would take a lot of extra effort. So for now, I implemented a slider control to change the size. Not ideal, but it works.
The other example is the status bar. I wanted a status bar to show
These are things that a lot of people expect from a text editor. Common editors from Notepad to Microsoft Word have some variation of these features. Should be pretty easy, right? As it turns out the status bar became the hardest part to implement. Getting the bar with no content was easy. Furthermark uses a single frame with a UWP Grid control, so my "status bar" is just the last row in my grid. Done.
The hard part were the items inside the status bar. In my first out of (hopefully) many posts, I want to outline how I got the status bar working the way I wanted it to. I plan to opensource Furthermark once I get it working the way I want and clean up some of the code.
Also, I want to warn you all, I wanted to brush up on some of my modern .NET Syntax, and I was lacking in the VB.NET arena, so as a challenge to myself, I wrote Furthermark in VB.NET. If you don't like it, you certainly don't have to keep reading.
This was probably one of the easier ones. I made a read-only property for wordcount that I raise a property changed notification for when the text changes. This property calls a RegEx match to split out on certain types of whitespace. It's fast and fairly accurate.
Public ReadOnly Property WordCount As String
Get
Dim EditorString As String, Count As Integer = 0
Editor.Document.GetText(Windows.UI.Text.TextGetOptions.None, EditorString)
If Not String.IsNullOrWhiteSpace(EditorString) Then
Dim col As MatchCollection = Regex.Matches(EditorString, "[\S]+")
Count = col.Count
End If
Return Count.ToString("N0")
End Get
End Property
These two were the easiest. The RichEditBox control I'm using for the editor has methods to get the line count (and strings have a length property). All I do is set a private Integer
variable to the line count and length and have read-only public properties to call ToString("N0")
on these in order to give myself the comma separator for when we go over one thousand lines or characters.
When you save the document or copy the HTML of the Markdown, you get a notification that fades in and out temporarily from the status bar. I have a method to raise a notification:
Private Async Sub ShowStatusNotification(ByVal Text As String)
StatusText = Text
RaisePropertyChanged(NameOf(StatusText))
RaiseStatusText.Begin()
Await Task.Delay(4000)
ExitStatusText.Begin()
End Sub
I have a storyboard in the XAML to fade in and out over a duration of 1000ms (one second). This method sets the status text, brings in the text, waits for 4000ms (four seconds) and then fades it back out.
<StackPanel.Resources>
<Storyboard x:Name="RaiseStatusText">
<FadeInThemeAnimation TargetName="StatusTextTextBlock" Duration="1000"/>
</Storyboard>
<Storyboard x:Name="ExitStatusText">
<FadeOutThemeAnimation TargetName="StatusTextTextBlock" Duration="1000"/>
</Storyboard>
</StackPanel.Resources>
This one wasn't a complete necessary, but I really wanted to have this implemented. The most simple text editor on Windows (Notepad) has this feature (event if it wasn't as simple as it seemed) and I wanted it too. My problem was overthinking the math behind trying to figure this out, and the inconsistent nature of pulling the text from the RichEditBox. The most important thing I learned was getting the text and replacing the line endings to use Lf
rather than the Windows default of CrLf
.
The next step was splitting the text and getting the cursor position to determine where in the line the cursor was.
Dim Col As Integer, LastNewLine As Integer, Text As String
ThisEditor.Document.GetText(TextGetOptions.UseLf, Text)
Col = ThisEditor.Document.Selection.EndPosition
While Col > Text.Length And Col > 0
Col -= 1
End While
Text = Text.Substring(0, Col)
LastNewLine = Text.LastIndexOf(vbLf)
Me.Col = Col - LastNewLine
RaisePropertyChanged(NameOf(Col))
_Line = Text.Count(Function(a) a = CChar(vbLf)) + 1
RaisePropertyChanged(NameOf(Line))
What I do here is:
This is the one item I actually really didn't want to have, but unfortunaly the RichEditBox control supports using the Insert key to change the text typing mode from insert to overwrite, and I wanted a way to show the user how the editor was going to behave. This wasn't super hard to implement in the end. It turns out the RichEditBox control will show what the type mode is as a enumeration flag when looking at the Document's Options
property. So I have an event handler on the PreviewKeyDown
event of the edit box (regular KeyDown
doesn't register the "Insert" key) to set a private variable.
_IsOverwrite = Not ThisTextBox.Document.Selection.Options.HasFlag(SelectionOptions.Overtype)
RaisePropertyChanged(NameOf(InsOvr))
Then a read-only string property will get the text result of the status.
Public ReadOnly Property InsOvr As String
Get
Dim Result As String = "INS"
If _IsOverwrite Then
Result = "OVR"
End If
Return Result
End Get
End Property
Like I mentioned above, I'd like to make the application opensource at some point, but I'm not quite ready for the world to see my mess of code. I will also post an update once the application is available on the Windows store, it's currently going through the certification process. I will post updates here on my blog as well as on the official website Furthermark.com.