Click image for full screenshot - 306 KB
Table of Contents
{ Automatically generated Table of Contents }
This is a short article about an HTML editor I assembled with special support for writing articles at The Code Project.
Office 2007 doesn't contain a version of Frontpage, which I used to use to write articles. I didn't want to install Frontpage 2003 over Office 2007 "just in case!" So, I had a look around for an HTML editor that can show a preview side-by-side with the raw HTML. Visual Studio was the best one I found, but the preview has to be below the HTML, which is not what I want on my widescreen monitors. So I decided to write one myself. Here it is and I hope you find it useful.
Note: As Derek Bartram pointed out in the message board below, Visual Studio 2008 can split the source and design views vertically. The setting is:
Tools | Options | HTML Designer | General | Split views vertically
It's a basic Windows Forms application with Weifen Luo's DockPanel Suite [^], so you can arrange the panes just how you want them. The editor is part of #develop
from the ic#code [^] people. I also used their #ziplib
. The preview pane is a plain old WebBrowser control.
There are two types of windows in BobBuilder: editor and preview. You must have exactly one editor window open for each document, but you can have any number of preview windows open.
The TextEditor
control from #develop
has lots of nice features, like undo/redo and syntax highlighting. It also has loads of options that I have surfaced in the Tools | Options dialog. I have added shortcuts for some HTML tags like <code>
and <pre>
, which are often found in articles at The Code Project. Also, when you are in an opening tag, pressing TAB
will close the tag for you and put the caret in the middle.
To help prevent illegal HTML being rendered, there are a few "autocompleted" characters. Firstly, if you type a <
, you will get <>
. Then if you are inside a tag, the quote, "
, and single quote, '
, characters will double up.
The preview windows update continuously while you use the editor. If you have a system sound set for the "Navigation Started" event, you might like to disable it! The preview windows attempt to remember their scroll positions at each update, but this is not possible if illegal HTML is rendered.
There are buttons for the standard formattings like bold and italic. There is also an Action
to insert a clickety.
There are a menu item and a toolstrip button to insert a Table of Contents. This command parses the HTML using Regex
s for all <hN>
tags and inserts anchors around them. It then adds unordered lists of links to make the table. If a table already exists, then it will be replaced. Otherwise, the new table is inserted at the current caret position.
Any relative hyperlinks, for example to anchors, do not work in the preview windows. This is because the HTML is passed to the WebBrowser controls using their DocumentText
property. When this happens, the WebBrowser sets the URI to about:blank
and things stop working...
However, before the HTML is passed to the WebBrowser control, it is modified a bit. Some JavaScript is added to help maintain the scroll position and a <base>
tag is added to make any inline references work, such as images. You can always save the document and use Tools | External Browser to quickly check that your relative links are working.
Note: when you upload your article, the HTML will be put in one of the main directories, but all other files (images, ZIPs, ... ) will be put in a subdirectory named the same as the basename of the article. For example, BobBuilder.aspx is in /KB/cs/ and all other files are in /KB/cs/BobBuilder/. You might like to replicate this structure locally when you are writing your article.
There are a couple of points to highlight:
I implemented the MVC pattern, but with a twist: I used the Observer pattern with the views subscribing to the (Singleton
) controller as publisher. When an action is initiated, a command is sent to the controller using a normal method call. The controller does its work, firing events to communicate with any participating views.
A neat feature of the .NET event
implementation of Observer is that the subscribers can alter the EventArgs
derived parameter, so passing information back to the publisher. This is demonstrated in the implementation of the CurrentDocument
property:
partial class Controller
{
public static Document CurrentDocument
{
get
{
return Instance
.ExecuteCore( null, new Command.GetCurrentDocument() )
.Document;
}
}
}
Instance
gets the Singleton
instance and ExecuteCore
is a member method which returns its Command
parameter. For the GetCurrentDocument
command, all ExecuteCore
does is raise the static Execute
event. All the views have subscribed to this event and, when the currently active view handles it, it fills in the Document
property of the command with its own (the current) document. So, when the event
returns after calling all the views in its invocation list, the Document
property has been set by the current view and is returned to whoever needs it.
This pattern made life easier than having the controller maintain a collection of some view interface. Now this is all taken care of by the built-in event
handling, while reducing the coupling between the controller and the views.
I could not find a .NET way to determine the right member of this enumeration for a given char
. In the end, I wrote this snippet:
[System.Runtime.InteropServices.DllImport( "user32.dll" )]
static extern short VkKeyScan( char ch );
static Keys ConvertToKeys( char c )
{
short vk = VkKeyScan( c );
int key = vk & 0x00FF;
int mod = vk & 0xFF00;
int iKeys = ( mod << 8 ) | key;
Keys eKeys = ( Keys ) iKeys;
return eKeys;
}
This is a short list of possible future enhancements. Please leave a comment below if you have any opinions or ideas.
- Spelling checker
- More common formattings
- Configurable shortcuts
- Macro compiler
- Remember toolstrip positions
- Handle exceptions better
|
2008 Feb 01: |
Initial release
|
|
2008 Feb 01: |
Removed smileys |
|
2008 Feb 03: |
Added auto-generation of Table of Contents |
|
2008 Feb 07: |
Fixed auto-generation of Table of Contents |
|
2009 Aug 05: |
Fixed preview placement with new DOCTYPE in template |