CodeBlog: Writing a Blogging Extension for Visual Studio 2010

CodeBlog is a simple Visual Studio extension that publishes sections of code to Twitter and to WordPress blogs from inside the IDE. We walk through the construction of CodeBlog to explore some common Visual Studio extension tasks.
By Staff
CodeBlog is a handy little extension that lets you clip code sections from the editor in Microsoft® Visual Studio® and send them out to Twitter or your blog, all from the convenience of a right-click menu option. Fun as it is, its real purpose is as a simple example of a Visual Studio integration package that we can use to illustrate customizations such as adding a menu item, hooking into the editor, and creating and managing a tool window. I built CodeBlog using the Visual Studio 2010 Release Candidate and the Visual Studio 2010 SDK RC.

Once the extension is installed, CodeBlog adds an entry to the code editor context menu labeled “Blog and Tweet.” When you select the menu item, CodeBlog finds the text of the current selection and then does the following:

  • Sends the text to Twitter, using the Twitter API
  • Posts the text to a WordPress blog as a draft, using the MetaWeblog API
  • Adds a visual to the editor (called an adornment) making the selection that notes the URL of the blog entry
  • Adds the date, URL, and text of the blog entry to a list that you can view later in a tool window
  • Brings up the draft blog entry in a browser for editing
The extension defines the Blog and Tweet Entries tool window that lists code that you've blogged and tweeted, and the menu entry in the View/Other Windows menu that opens the tool window.

Creating the Package

We're actually using two Visual Studio extension models in the creation of CodeBlog. CodeBlog is an extension that includes both a managed VSPackage and a MEF (Managed Extensibility Framework) component part. The VSPackage part of CodeBlog has access to UI elements in the Visual Studio environment such as menu entries and tool windows. The MEF component part we'll use to add adornments to text in the editor.

The SDK installs a Visual Studio Package project template that you can use to kick start the development of a VSPackage. With this template, the project creation wizard lets you optionally include a menu command, tool window, and custom editor as part of your new project. For CodeBlog, we'll select the menu command and tool window options, providing a command name (“Blog and Tweet”) for the menu command, a window name for the tool window (“Blog and Tweet Entries”), and command IDs for the menu command and for the menu entry that brings up the tool window.

The template-generated project includes the skeleton package source file and several other files worth a brief mention as we examine the structure of a VSPackage. The main file is the CodeBlogPackage class, derived from the MPF (Managed Package Framework) Package class, which implements the IVsPackage interface and handles registration with the shell. Because we asked for a menu command and a tool window, the package class also includes initialization code to set up the menu commands and a method to show the tool window.

The tool window class inherits from MPF ToolWindowPane and includes a WPF user control as its content. When we set up the layout for the Blog and Tweet Entries window, we'll do most of the customizations to the user control. The generated project also includes a XAML file and a class file for the user control.

Commands for the package are defined in a command table (VSCT) file. The VSCT file specifies the layout of the commands within the shell menus, using the IDs we specified. It provides generic bitmaps for each menu entry that you can replace with your own bitmaps, although I didn't take that step for CodeBlog. The wizard places the tool window menu entry under View/Other Windows, and the menu command in the Tools window. As a first customization, we'll want to move the menu command from the Tools window to the editor context menu so that it will come up on a right-click.

Hooking Up the Menu Entry

The single menu command for CodeBlog is contained in a menu group. In the VSCT file, the group definition looks like this as generated:


<Group guid="guidCodeBlogCmdSet" id="MyMenuGroup" priority="0x0600">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
      
</Group>

To move the “Blog and Tweet” menu entry to the context menu, all we need to do is specify the code window context editor as the parent for the menu group, using the identifier IDM_VS_CTX_CODEWIN:

    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>

That change brings the menu entry up on a right-click, just as we wanted:

When the menu entry is clicked, we'll need to see if any text is selected, and take action on the selected text if it is. To find the selection, we first need to find the active view. As a VSPackage, CodeBlog finds the active view in the IDE using Visual Studio services. We get the active view using the VSTextManager service:


IVsTextManager txtMgr =
(IvsTextManager)GetService(typeof(SVsTextManager));
int mustHaveFocus = 1;
txtMgr.GetActiveView(mustHaveFocus, null, out vTextView);

Once we have the active view, we can use it to get the IWPFTextView which we'll need to extract the selection, and, later, to add the adornments. The technique involves a few other calls and is explained in this MSDN® walkthrough. (The walkthrough is also the source of much of the code CodeBlog uses to manage text adornments.)

We get the selected text from the IWPFTextView like this:


string selectedText = view.Selection.SelectedSpans[0].GetText();

And then we have the text that we'll need for the tweet and for the blog entry.

The Blog and Tweet menu command is only applicable when there is selected text in a text editor, so it makes sense to disable the menu entry when that condition is not met. CodeBlog accomplishes this, for the most part, by handling the BeforeQueryStatus event on the menu entry and enabling it only if there is a text selection. However, this method will not work if the user brings up the context menu before the CodeBlog package is loaded. In that case, the menu entry will be enabled even without a selection (although the handler will just ignore the click).

This behavior is actually by design in Visual Studio, which loads package menu entries at startup but which doesn't load the package class (which is expensive) until it is actually needed. Visual Studio provides a mechanism for packages to set visibility and enable states declaratively, in the VSCT, by certain pre-defined conditions (e.g., UICONTEXT_Debugging). CodeBlog can't use this mechanism because an active text selection is not one of the defined conditions, so it has to deal with the single stray enable case.

Text Adornments

In Visual Studio, graphical effects in the editor are called adornments. Adornments are WPF UIElements that are placed on specific z-ordered layers in the editor display. For CodeBlog, we'll add a custom adornment that draws a gray-blue box around the code that we've blogged, and a text-box label that shows the blog entry URL.

These adornments are managed and drawn by a separate project in the CodeBlog extension implemented as a MEF component part. MEF is a general mechanism for hosting plugins in which individual components export and import functionality from other components based on contracts. Visual Studio uses MEF as a framework for handling editor extensions, such as the one in CodeBlog that draws the text adornment.

The MEF project includes a Connector class that has several functions. First, Connector implements IWpfTextViewCreationListener. Implementing and exporting this interface allows Connector to be notified when new text views are created. It exports this interface like this:


[Export(typeof(IWpfTextViewCreationListener))]
[ContentType("text")]

[TextViewRole(PredefinedTextViewRoles.Document)]
public sealed class Connector : IWpfTextViewCreationListener

The ContentType attribute notes the types of content we're interested in (any kind of text). TextViewRole notes the kinds of text views we're interested in (documents-- loosely, all file-based views). Whenever a new text view with matching attributes is created, the framework calls the Connector's TextViewCreated method.

When TextViewCreated is called, the Connector creates a Manager class for the new view. The Manager, along with the associated Provider class, is responsible for tracking the collection of Adornment objects that represent the actual spans of text from which blog entries have been created. Each Adornment object also includes the URL of the entry.

The Connector's second function is to export a layer definition:


[Export(typeof(AdornmentLayerDefinition))]
[Name("PostAdornmentLayer")]
[Order(After = PredefinedAdornmentLayers.Selection, 
       Before =  PredefinedAdornmentLayers.Text)]
public AdornmentLayerDefinition postLayerDefinition;

This defines where the adornment layer for blog posts will fit in the layer order, and assigns it a name (“PostAdornmentLayer”). When a Manager is created for a text view, it finds this layer in the view by name. When a new Adornment is added to the collection maintained by the Manager, the Manager adds the visual representation of the adornment to the specified layer. The visual representation is a WPF canvas, created from the Adornment object, on which the highlighted block and the URL text block are drawn.

Finally, the Connector provides an Execute method that is called directly by the CodeBlog package when the menu entry is selected (the package has a reference to the MEF component). The Execute method looks up the adornment Provider for the view specified by the package and uses the Provider to add a new adornment to the view with the selected text and with the blog entry URL.

Delivering the Tweet

Posting the selected text to Twitter and to the blog is the core functionality of CodeBlog, and it’s easy to accomplish. We're just consuming web services, and running as a managed VSPackage we have the full power of C# and .NET at our disposal. Plus, we get to stand on the shoulders of giants here. The Twitter client in CodeBlog is a stripped-down version of the client in Demo Dashboard, a full-featured Visual Studio extension example that you can get from CodePlex, while CodeBlog's MetaWebLog client is built on top of Charles Cook's XML-RPC.NET class library.

CodeBlog's clients are minimal implementations. They do only what they need for Code Blog, and that is to post an entry, although the APIs themselves are much more capable. CodeBlog's Twitter client implements just one call, UpdateStatus, where message is the code entry to post:


internal void UpdateStatus(string message)
{
    string uri = "http://www.twitter.com/statuses/update.xml";
    PostToTwitter(uri, string.Concat("status=", Uri.EscapeDataString(message)));
}

PostToTwitter sets up an HTTP POST request and sends it off. Authentication is handled ahead of time by a call to Twitter's verify_credentials API when the client is created. The call returns a session cookie that CodeBlog retains and sends with future POST requests.

For the blog, CodeBlog communicates with WordPress through the MetaWebLog XML-RPC API. Again, CodeBlog implements just one call, NewPost:


public string NewPost(Post content, bool publish)

{

    // first paramter, blogId, is ignored by wordpress.
     
     return protocol.newPost("", userName, password, content, publish);

}

The protocol object is a proxy for the XML-RPC call on the server, provided by XML-RPC.NET. The content parameter is a Post structure that includes the code to post plus a title, date, and category information. Publish is a flag that determines whether the entry is published as it is posted or not. Since we want to create a draft version on WordPress, we submit the publish parameter as false.

This is how the call to the WordPress client is set up in the main package class:


Post post = new Post();
post.categories = new string[] {"Code"};

post.title = "Code I'm Working On";
 
post.description = "[sourcecode]" +  selectedText +
 "[/sourcecode]";
                        post.dateCreated = DateTime.Now;

                        string postId = blogClient.NewPost(post, false);

We wrap the selected text in the [sourcecode] shortcode, which tells WordPress that the entry is source code and should be formatted that way. Since WordPress supports language-specific formatting, including formatting for C# and Visual Basic, once nice enhancement here (still a TTD) would be to determine the type of code in the editor so we could set the language attribute with the sourcecode shortcode.

We use the postId returned from WordPress to format the draft and permalink URLs. The draft URL is used to bring up the draft entry for editing in the browser, which we accomplish with the following line of code:


System.Diagnostics.Process.Start(draftUrl);
 
The permalink URL is used for display, both in the note on the text adornment and in the Blog and Tweet Entries tool window.

Tracking Entries in the Tool Window

The final component of CodeBlog we'll discuss is the tool window that maintains a list of previous blog entries. As mentioned above, the wizard already created a starter tool window that is shown when the user selects the Blog and Tweet Entries window from the Other Windows menu.

Our tool window will manage just the contents of the Blog and Tweet entries window. The frame, provided by the Visual Studio shell, will handle all the docking behaviors for our window automatically.

We set the content of the tool window through the user control. For CodeBlog, we create a listbox that will display the blog entries in the user control XAML. We'll bind the listbox to the list of blog entries through the ItemsSource property of the listbox:


myControl.listBox1.ItemsSource = BlogEntries;
 
BlogEntries is implemented as an ObservableCollection of BlogEntry object, so when we make changes to the collection the listbox will be notified of those changes and will update its display. BlogEntry is a simple class that includes DateCreated, Description, and BlogUrl properties. So now, all we have to do to display the blog entries nicely in the tool window listbox is to create an item template.

The item template consists of three text boxes, one for the date, one for the entry contents, and one for the hyperlink. The main section of the template XAML looks like this:


<TextBlock Text="{Binding Path=DateCreated,StringFormat=Blogged: {0:f}}"

                 Padding="8,0,8,0"
                               
                  FontSize="14"

                 FontWeight="Bold"          
                  Background="DarkBlue"

                 Foreground="White"

                 DockPanel.Dock="Top"/>
                        <TextBlock Text="{Binding Path=Description}"

                 Margin="16,10,16,10"

                  Background="Gainsboro"
                              
                  FontFamily="Courier New"

                 DockPanel.Dock="Top"/>
<TextBlock Padding="4,0,0,0"

          DockPanel.Dock="Top">
                              
    <Hyperlink NavigateUri="{Binding Path=BlogUrl}"

              RequestNavigate="Hyperlink_RequestNavigate">

       <TextBlock Text="{Binding Path=BlogUrl}"/>
                       
     </Hyperlink>
</TextBlock>

 
There are a couple of interesting things here. The binding for the date property uses a StringFormat to create a title line for the blog entry that contains a formatted date. The hyperlink is clickable and the control responds to the RequestNavigate event to show the link in the browser. Although we didn't get too creative here with things like blends or images, it's clear that building the tool window content as a WPF element gives you tremendous flexibility in customizing the UI.

There's a lot more that could be done with CodeBlog. There's the language detection discussed above, more feedback from the web services, and persistence for the blog entries (although it's not clear how long you'd actually want the text adornments to persist for old blog entries). But while CodeBlog may not be feature-rich, working through it has demonstrated some common steps in Visual Studio extension development. The next step is to apply that experience to some new innovative extensions.

* This article was commissioned by and prepared for Microsoft Corporation. This document is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, IN THIS SUMMARY.

Add to My MSN