At its simplest, the problem is to run some code when CTRL-ALT-A is pressed while editing a Microsoft Word 2003 file.
The code that runs wants to be written in C# and it’s purpose is to modify the content and styles of the current document.
In my first solution, I created a Word Add-In (using Visual Studio Tools for Office) and Visual Studio 2003. The Add-In has to register its DLL under the right Office keys to be found by Word. When Word starts, the assembly’s Connect method gets called. It proceeds to install a new toolbar into Word containing a few buttons. It’s easy to connect the Click event of the buttons with the C# code I want to run. Getting the code to run in response to a CTRL-ALT-A is considerably harder.
The first trick was to discover that a macro of the form below will invoke the C# event handler assigned to a toolbar button:
Sub TonesNotesNew()
Dim cb As CommandBar
Set cb = Application.CommandBars("TonesNotes")
Dim cbc As CommandBarButton
Set cbc = cb.FindControl(Tag:="New")
cbc.Execute
cbc.State = msoButtonUp
End Sub
The last piece of plumbing is to assign CTRL-ALT-A to invoke the macro “TonesNotesNew”.
The second time around I implemented the same functionality as a Word template file which is one of the new Visual Studio 2005 beta 2 project templates. The template file now holds the macros, the styles I use for this functionality, and the C# assembly that does the actual work.
What’s New:
· You can now update posted notes by reposting the archived copy of a post. The archived file now maintains a posting history to remind you of when you posted the note to which blogs.
· The Confirmation Dialog now comes up only once if a note is posted to at least one blog that requires confirmation. The dialog has been reworked to let you change any part of the note (title, date, categories, body) and shows you an updated preview of any edits made to the html.
· Improved error checking. Undoubtedly more to go.
TonesNotes version 1.0.1512 source code zip.
Just finished the first round of implementation and testing on closing the loop to allow updating blog posts when an archived note is updated.
Archived notes now will contain a brief history of where and when they might have been posted. If the archived note is posted again, after making some changes for example, the existing blog posts will be updated with the new content and the archived note will be updated in place.
Also reorganized the confirmation dialog to confirm only once for all posts and before archiving. The dialog’s preview tab now updates correctly if you edit the HTML.
Under some circumstances Microsoft Word wasn’t making a style defined by the add-in in the main document available to temporary documents created for archiving.
The resulting exception caused an early termination of all posting actions.
Download the latest Tones Notes release
What’s New:
Download the latest release of TonesNotes from here.
The latest release of TonesNotes is available here http://workspaces.gotdotnet.com/tonesnotes. It lets you post to a .Text blog from any open Word document and now supports posting images in the body of the note.
The README.htm file installed with the release contains a code change for the Dottext blog server to support posting images.
The latest version of TonesNotes lets you post blog entries from Word that contain images:
Here is the change required of 0.95 vintage Dottext codebase.
Add the following method to the SimpleBlogService.asmx.cs file:
/// <summary>
/// Add an image to a specified gallery and return the url by which it will be accessible.
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="gallery">Title of the target gallery, gallery will be created if it doesn't exist.</param>
/// <param name="postdate">Date to associate with the image. Not currently supported by the data model.</param>
/// <param name="title">Title for the image.</param>
/// <param name="description">Description for the image.</param>
/// <param name="categories">Array of categories to associate with the image. Not currently supported by the data model.</param>
/// <param name="filename">The non-path image filename including extension. Note that currently filenames must be unique within a gallery.</param>
/// <param name="data">Image file data in on-disk format matching the filename's extension.</param>
/// <returns>A url in the form http://blogs.mydomain.com/myblog/8/o_filename.jpg, where 8 is the integer CategoryID assigned to the gallery.
/// The returned url is an absolute url with a "o_" prefix on the filename.
/// Caller can make the prefix "t_" for thumbnail and "r_" for standard size.</returns>
[WebMethod(MessageName="AddImageToGallery",Description="Insert an image into a gallery with Categories",EnableSession=false)]
public string AddImageToGallery
( string username
, string password
, string gallery
, DateTime postdate
, string title
, string description
, string[] categories
, string filename
, byte[] data
) {
BlogConfig config = Config.CurrentBlog(Context);
CheckUser(username,password);
int categoryID = 0;
// Lookup the categoryID of gallery (gallery is the title).
LinkCategoryCollection lcc = Links.GetCategories(CategoryType.ImageCollection, false);
foreach (LinkCategory lc in lcc) if (string.Compare(lc.Title, gallery, true) == 0) { categoryID = lc.CategoryID; break; }
// If gallery doesn't exist, try creating it.
if (categoryID == 0) {
LinkCategory category = new LinkCategory();
category.CategoryType = CategoryType.ImageCollection;
category.Title = gallery;
category.IsActive = false;
category.Description = "Images uploaded with new posts.";
categoryID = Links.CreateLinkCategory(category);
if (categoryID == 0) throw new Exception("CategoryID is zero");
}
// Insert the new image into the selected gallery.
// This puts three copies (thumbnail, standard size, original size) of the image on disk under the "images" folder
// and adds a record to the blog_Images table.
Image image = new Image();
image.CategoryID = categoryID;
image.Title = title;
image.IsActive = true;
image.File = Images.GetFileName(filename);
image.LocalFilePath = Images.LocalGalleryFilePath(Context, categoryID);
int imageID = Images.InsertImage(image, data);
if (imageID == 0) throw new Exception("ImageID is zero");
string baseImagePath = Images.HttpGalleryFilePath(Context, image.CategoryID);
return baseImagePath + image.OriginalFile;
}
TonesNotes is a free add-in for Microsoft Word available here http://workspaces.gotdotnet.com/tonesnotes. It lets you post to a .Text blog from any open Word document.
Other places to look for (or leave) information about TonesNotes are the .Text Forum, the .Text Wiki, or my own blog Tone's Notes
The intended mode of operation is to use a single Word document in which the ideas you’re working on take shape. When an idea is baked, place the insertion point within its content, click the “Post” button on the TonesNotes toolbar, and the content is simultaneously posted to one or more blogs and archived as a separate Word document on your local file system.
The command bar has a “New” button which starts a new entry at the bottom of the active Word document. An entry is demarked by a paragraph with the style “Heading 1” by default. The “New” command inserts a paragraph with a date stamp which will become the post creation time. Complete the note heading with a comma separated list of categories and a title. Anything in the note heading paragraph not recognized as a date or category name becomes part of the title.
Since not all ideas are suitable for public posting, the behavior of the Post command is driven by assigning a category name to each configured blog. A note is posted to each blog whose category name is listed in the note heading, or simply archived if it isn't meant to be blogged.
In addition to the “New” and “Post” functions, the toolbar has a “Config” button which brings up the configuration dialog.
The installation was successful if you see a new toolbar with three buttons on it ("Post", "New", and "Config") when you launch Microsoft Word.
Drag the toolbar to place it next to your existing toolbars at a convenient location.
To get in the swing of things, it might be helpful to start by creating a dummy note which you will post and archive if you follow along with the examples below.
With a Word document open (doesn't matter which), click the "New" button. You should see something like this appear at the end of the document:
2004-02-12 12:56:32 Thu,
The paragraph style has been set to Heading 1 (this is configurable) and the insertion point should be blinking just after the comma. If you don't like the date or time format you can change them later. Complete the note heading so it looks like this:
2004-02-12 12:56:32 Thu, myblog, My first note using TonesNotes
The commas are important. "myblog" is the category name assigned to your blog by default. By sticking it in the note heading like this, you're saying that when the note gets posted, it should go to your blog. The rest of the heading will become the title of your blog posting.
Every note you post has to have a body, so type a return and some text like the following:
2004-02-12 12:56:32 Thu, myblog, My first note using TonesNotes
This is the body of my note. It can contain most of the formatting Word has to offer.
That's it, your first note is ready to Post, but before you click the "Post" button, it iss essential that you customize TonesNotes' configuration.
Click the "Config" button to set things up for your particular situation.
The "Archive" tab controls how TonesNotes saves a copy of each note you post to your local file system.
If you just installed TonesNotes, you're initially configured to save each note under a folder called "Notebook" in your "My Documents" folder.
Here is the purpose of each item on the "Archive" tab:
The "Blog" tab controls how TonesNotes posts each note to one or more blog servers. In this version of TonesNotes, you must be using a .Text (Dottext) blog server.
If you just installed TonesNotes, you will have to make changes to this tab's settings before you can post to your blog.
Here is the purpose of each item on the "Blog" tab:
The "Advanced" tab controls a number of important settings. You don't need to make changes here but you probably will eventually.
Here is the purpose of each item on the "Advanced" tab:
Add the following function to the SimpleBlogService.asmx.cs file:
/// <summary>
/// Add an image to a specified gallery and return the url by which it will be accessible.
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="gallery">Title of the target gallery, gallery will be created if it doesn't exist.</param>
/// <param name="postdate">Date to associate with the image. Not currently supported by the data model.</param>
/// <param name="title">Title for the image.</param>
/// <param name="description">Description for the image.</param>
/// <param name="categories">Array of categories to associate with the image. Not currently supported by the data model.</param>
/// <param name="filename">The non-path image filename including extension. Note that currently filenames must be unique within a gallery.</param>
/// <param name="data">Image file data in on-disk format matching the filename's extension.</param>
/// <returns>A url in the form http://blogs.mydomain.com/myblog/8/o_filename.jpg, where 8 is the integer CategoryID assigned to the gallery.
/// The returned url is an absolute url with a "o_" prefix on the filename.
/// Caller can make the prefix "t_" for thumbnail and "r_" for standard size.</returns>
[WebMethod(MessageName="AddImageToGallery",Description="Insert an image into a gallery with Categories",EnableSession=false)]
public string AddImageToGallery
( string username
, string password
, string gallery
, DateTime postdate
, string title
, string description
, string[] categories
, string filename
, byte[] data
) {
BlogConfig config = Config.CurrentBlog(Context);
CheckUser(username,password);
int categoryID = 0;
// Lookup the categoryID of gallery (gallery is the title).
LinkCategoryCollection lcc = Links.GetCategories(CategoryType.ImageCollection, false);
foreach (LinkCategory lc in lcc) if (string.Compare(lc.Title, gallery, true) == 0) { categoryID = lc.CategoryID; break; }
// If gallery doesn't exist, try creating it.
if (categoryID == 0) {
LinkCategory category = new LinkCategory();
category.CategoryType = CategoryType.ImageCollection;
category.Title = gallery;
category.IsActive = false;
category.Description = "Images uploaded with new posts.";
categoryID = Links.CreateLinkCategory(category);
if (categoryID == 0) throw new Exception("CategoryID is zero");
}
// Insert the new image into the selected gallery.
// This puts three copies (thumbnail, standard size, original size) of the image on disk under the "images" folder
// and adds a record to the blog_Images table.
Image image = new Image();
image.CategoryID = categoryID;
image.Title = title;
image.IsActive = true;
image.File = Images.GetFileName(filename);
image.LocalFilePath = Images.LocalGalleryFilePath(Context, categoryID);
int imageID = Images.InsertImage(image, data);
if (imageID == 0) throw new Exception("ImageID is zero");
string baseImagePath = Images.HttpGalleryFilePath(Context, image.CategoryID);
return baseImagePath + image.OriginalFile;
}
I released the alpha version of my blog posting add-in for Microsoft Word 2003 or XP as a GotDotNet workspace: http://workspaces.gotdotnet.com/TonesNotes
TonesNotes is a free and lets you post to a .Text blog from any open Word document.
The intended mode of operation is to use a single Word document in which the ideas you’re working on take shape. When an idea is baked, place the insertion point within its content, click the “Post” button on the TonesNotes toolbar, and the content is simultaneously posted to one or more blogs and archived as a separate Word document on your local file system.
The command bar has a “New” button which starts a new entry at the bottom of the active Word document. An entry is demarked by a paragraph with the style “Heading 1” by default. The “New” command inserts a paragraph with a date stamp which will become the post creation time. Complete the note heading with a comma separated list of categories and a title. Anything in the note heading paragraph not recognized as a date or category name becomes part of the title.
Since not all ideas are suitable for public posting, the behavior of the Post command is driven by assigning a category name to each configured blog. A note is posted to each blog whose category name is listed in the entry heading, or simply archived if it isn't meant to be blogged.
In addition to the “New” and “Post” functions, the toolbar has a “Config” button which brings up the configuration dialog.
Here’s a topic page in the .Text wiki that lists pros, cons, and configuration for various authoring options including TonesNotes: http://dottextwiki.scottwater.com/default.aspx/Dottext.AuthoringOptions