Blog Item Editor Sample

This sample implements an editor for blog item files having the .blit extension with XML format.

Points of Interest:

  • VSXtra supports creating custom editors. With the Managed Package Framework you have to write about 1200 lines of code for a custom editor. VSXtra provides the EditorFactoryBase<TPackage, TEditorPane> and EditorPaneBase<TPackage, TEditorFactory, TUIControl> classes to dramatically reduce the number of code lines to be written down by the developers.
Package source code:

using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using VSXtra;

namespace DeepDiver.BlogItemEditor
{
  [PackageRegistration(UseManagedResourcesOnly = true)]
  [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]
  [InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]
  [ProvideLoadKey("Standard", "1.0", "BlogItemEditor", "DeepDiver", 1)]
  [ProvideEditorFactory(typeof(BlogItemEditorFactory), 200, TrustLevel = __VSEDITORTRUSTLEVEL.ETL_AlwaysTrusted)]
  [ProvideEditorExtension(typeof(BlogItemEditorFactory),
    BlogItemEditorPackage.BlogFileExtension,
    32,
    ProjectGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}",
    TemplateDir = @"..\..\Templates",
    NameResourceID = 200)]
  [ProvideEditorLogicalView(typeof(BlogItemEditorFactory), "{7651a703-06e5-11d1-8ebd-00a0c90f26ea}")]
  [Guid(GuidList.guidBlogItemEditorPkgString)]
  public sealed class BlogItemEditorPackage : PackageBase
  {
    public const string BlogFileExtension = ".blit";
  }
}
Editor Factory source code:

using System.Runtime.InteropServices;
using DeepDiver.BlogItemEditor;
using VSXtra;

namespace DeepDiver.BlogItemEditor
{
  [Guid(GuidList.GuidBlogEditorFactoryString)]
  public sealed class BlogItemEditorFactory: 
    EditorFactoryBase<BlogItemEditorPackage,BlogItemEditorPane>
  {
  }
}
Editor Pane source code:

using System;
using DeepDiver.BlogItemEditor;
using VSXtra;

namespace DeepDiver.BlogItemEditor
{
  public sealed class BlogItemEditorPane: 
    EditorPaneBase<BlogItemEditorPackage, BlogItemEditorFactory, BlogItemEditorControl>
  {
    private readonly BlogItemEditorData _EditorData = new BlogItemEditorData();

    public BlogItemEditorPane()
    {
      UIControl.ContentChanged += DataChangedInView;
    }

    protected override string GetFileExtension()
    {
      return BlogItemEditorPackage.BlogFileExtension;
    }

    protected override void LoadFile(string fileName)
    {
      _EditorData.ReadFrom(fileName);
      UIControl.RefreshView(_EditorData);
    }

    protected override void SaveFile(string fileName)
    {
      UIControl.RefreshData(_EditorData);
      _EditorData.SaveTo(fileName);
    }

    void DataChangedInView(object sender, EventArgs e)
    {
      OnContentChanged();
    }
  }
}
Editor UI source code:

using System;
using System.Windows.Forms;
using DeepDiver.BlogItemEditor;
using VSXtra;

namespace DeepDiver.BlogItemEditor
{
  public partial class BlogItemEditorControl : 
    UserControl,
    ICommonEditorCommand
  {
    public BlogItemEditorControl()
    {
      InitializeComponent();
    }

    public void RefreshView(BlogItemEditorData data)
    {
      TitleEdit.Text = data.Title ?? string.Empty;
      CategoriesEdit.Text = data.Categories ?? String.Empty;
      BodyEdit.Text = data.Body ?? String.Empty;
    }

    public void RefreshData(BlogItemEditorData data)
    {
      data.Title = TitleEdit.Text;
      data.Categories = CategoriesEdit.Text;
      data.Body = BodyEdit.Text;
    }

    bool ICommonEditorCommand.SupportsSelectAll
    {
      get { return false; }
    }

    bool ICommonEditorCommand.SupportsCopy
    {
      get { return ActiveControlHasSelection; }
    }

    bool ICommonEditorCommand.SupportsCut
    {
      get { return ActiveControlHasSelection; }
    }

    bool ICommonEditorCommand.SupportsPaste
    {
      get { return ActiveCanPasteFromClipboard; }
    }

    bool ICommonEditorCommand.SupportsRedo
    {
      get { return false; }
    }

    bool ICommonEditorCommand.SupportsUndo
    {
      get { return false; }
    }

    void ICommonEditorCommand.DoSelectAll()
    {
      throw new NotImplementedException();
    }

    void ICommonEditorCommand.DoCopy()
    {
      var active = ActiveControl as TextBox;
      if (active != null) active.Copy();
    }

    void ICommonEditorCommand.DoCut()
    {
      var active = ActiveControl as TextBox;
      if (active != null) active.Cut();
    }

    void ICommonEditorCommand.DoPaste()
    {
      var active = ActiveControl as TextBox;
      if (active != null) active.Paste();
    }

    void ICommonEditorCommand.DoRedo()
    {
      throw new NotImplementedException();
    }

    void ICommonEditorCommand.DoUndo()
    {
      throw new NotImplementedException();
    }

    public event EventHandler ContentChanged;

    private void RaiseContentChanged(object sender, EventArgs e)
    {
      if (ContentChanged != null) ContentChanged.Invoke(sender, e);
    }

    private void ControlContentChanged(object sender, EventArgs e)
    {
      RaiseContentChanged(sender, e);
    }

    private bool ActiveControlHasSelection
    {
      get
      {
        var active = ActiveControl as TextBox;
        return active == null
                 ? false
                 : active.SelectionLength > 0;
      }
    }

    private bool ActiveCanPasteFromClipboard
    {
      get
      {
        var active = ActiveControl as TextBox;
        return (active != null && Clipboard.ContainsText());
      }
    }
  }
}
Document Data source code:

using System;
using System.IO;
using System.Xml.Linq;

namespace DeepDiver.BlogItemEditor
{
  public sealed class BlogItemEditorData : IXmlPersistable
  {
    public const string BlogItemNamespace = "http://www.codeplex.com/VSXtra/BlogItemv1.0";
    public const string BlogItemLiteral = "BlogItem";
    public const string TitleLiteral = "Title";
    public const string CategoriesLiteral = "Categories";
    public const string CategoryLiteral = "Category";
    public const string BodyLiteral = "Body";

    private readonly XName BlogItemXName = XName.Get(BlogItemLiteral, BlogItemNamespace);
    private readonly XName TitleXName = XName.Get(TitleLiteral, BlogItemNamespace);
    private readonly XName CategoriesXName = XName.Get(CategoriesLiteral, BlogItemNamespace);
    private readonly XName CategoryXName = XName.Get(CategoryLiteral, BlogItemNamespace);
    private readonly XName BodyXName = XName.Get(BodyLiteral, BlogItemNamespace);

    public BlogItemEditorData()
    {
    } 
    
    public BlogItemEditorData(string title, string categories, string body)
    {
      Title = title;
      Categories = categories;
      Body = body;
    }

    public string Title { get; set; }
    public string Categories { get; set; }
    public string Body { get; set; }

    public void SaveTo(string fileName)
    {
      var root = new XElement(BlogItemXName);
      var objectDoc = new XDocument(root);
      SaveTo(root);
      objectDoc.Save(fileName, SaveOptions.DisableFormatting);
    }

    public void ReadFrom(string fileName)
    {
      string fileContent = File.ReadAllText(fileName);
      XDocument objectDoc = XDocument.Parse(fileContent, LoadOptions.PreserveWhitespace);
      XElement root = objectDoc.Element(BlogItemXName);
      if (root == null)
        throw new InvalidOperationException(
          "Root '" + BlogItemLiteral + "' element cannot be found.");
      ReadFrom(root);
    }

    public void SaveTo(XElement targetElement)
    {
      targetElement.Add(new XElement(TitleXName, Title));
      var categories = new XElement(CategoriesXName);
      targetElement.Add(categories);
      string[] categoryList = Categories.Split(';');
      foreach (string category in categoryList)
      {
        string trimmed = category.Trim();
        if (trimmed.Length > 0)
        {
          categories.Add(new XElement(CategoryXName, trimmed));
        }
      }
      targetElement.Add(
        new XElement(BodyXName,
                     new XCData(Body))
        );
    }

    public void ReadFrom(XElement sourceElement)
    {
      XElement titleElement = sourceElement.Element(TitleXName);
      if (titleElement == null)
        throw new InvalidOperationException("'Title' element is missing");
      Title = titleElement.Value;
      Categories = string.Empty;
      XElement categoriesElement = sourceElement.Element(CategoriesXName);
      if (categoriesElement != null)
      {
        foreach(XElement categoryElement in categoriesElement.Elements(CategoryXName))
        {
          if (Categories.Length > 0) Categories += "; ";
          Categories += categoryElement.Value;
        }
      }
      XElement bodyElement = sourceElement.Element(BodyXName);
      if (bodyElement == null)
        throw new InvalidOperationException("'Body' element is missing");
      Body = bodyElement.Value;
      Body = Body.Replace("\n", "\r\n");
    }
  }
}
Screenshots:

BlogItemEditor1.png

BlogItemEditor2.png

Last edited Aug 31, 2008 at 9:00 AM by INovak, version 5

Comments

No comments yet.