Do you Wiki? Word2MediaWiki.NET first release is live!


I just wanted to let my faithful readers know that I have finally completed a beta version of the Word2MediaWiki.NET add-in for Microsoft Word 2003.

This’ll be useful to anyone that regularly contributes to Wikipedia, or those that have a bunch of Word documents that they’d like to share on Wikipedia.

It’s just as useful for any of the other hundreds of wikis that use the same server software as Wikipedia (known as MediaWiki), and it’ll be of interest to those folks who’ve used Word2MediaWikiPlus (from whose VBA source code I converted much of the basis of Word2MediaWiki.NET).

Grab a copy here:

And please, let me know about any issues or experiences you have with it by posting a Discussion item here:

Office add-ins: VSTO or Shared? Why should I care?

I’ve noticed an interesting pattern in the VSTO Forum on MSDN.  Cindy Meister (the queen of VSTO & Word programing) will very often ask a poster whether they’re developing a Shared Add-in or a VSTO Add-in.  Nearly all the responses to such questions indicate that the poster has no idea, and I can’t blame them.  It seems that even if someone’s used the VSTO solution templates to create a new project targeted at Office applications, they could still end up inadvertently creating a Shared (or COM) add-in.

I finally got frustrated enough at trying to judge whether a question I’d ask would be “worthy” of the VSTO Forum, or would just be punted back to me to find an appropriate newsgroup — so I decided to figure out what’s the point of this once and for all.

I quickly discovered that Cindy has gotten this question in the past, and has summarized some of the differences here:

However, that still doesn’t answer the question of “Why should it matter whether my VSTO project actually turns out to be merely a Shared Add-in?”

  • Does it change the way that you’d use certain .NET classes — or does it limit the kinds of classes that can be used by a Shared Add-in?
  • Does it create incompatibilities on the deployed client — or does it require a different deployment approach for a Shared Add-in?
  • Do certain data types, methods for creating Event Handlers, or Office PIA-encapsulated APIs not work in a Shared Add-in that would work in a VSTO Add-in?

How can I tell whether the project I’m developing is a VSTO Add-in or a Shared Add-in?

  • Is there some set of Imports/Using statements that only work in one or the other (e.g. Imports Microsoft.Office.Core vs. Imports Microsoft.Office.Interop.Word)?
  • Do certain Visual Studio Templates lead to either Shared Add-ins or VSTO Add-ins (e.g. Visual C# > Office > 2003 Add-ins > Word Add-in vs. Visual C# > Office > 2007 Add-ins > Word Add-in vs Visual C# > Office > Word Document)?  Or is it only relevant if the template used is Other Project Types > Extensibility > Shared Add-in?
  • Once I’ve created the project/solution, and if I don’t remember 100%, is there some filename, configuration setting, unique Event or other property of the project that would definitively indicate that it’s VSTO vs. Shared (e.g. ThisAddIn.vb, ThisAddIn_WindowActivate())?

For me, the specific issue that I have been having issues with, and which I’m beginning to suspect has a different solution depending on “Shared” or “VSTO” (despite the lack of clarity or obviousness in any of the sample code you’ll find on the ‘net), is the specific means by which a custom Event Handler can be created for CommandBarButtons in a managed add-in for Word 2003.

I’ve personally seen both code like this (C#):

uiButton.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(uiButton_Click);

and like this (VB):

    Private WithEvents uiButton As Microsoft.Office.Core.CommandBarButton

It’s entirely UNclear whether these are exactly equivalent, and if not, under what environmental conditions/constraints one is better than the other.  It’s also completely beyond me whether this would be considered part of a VSTO add-in or a Shared add-in.  Finally, I wouldn’t have a clue what I should do differently were I to know which type of add-in this was – would it affect which of these two handler-setup approaches I used?  What types of objects I should use?  Something else?

Porting Word2MediaWikiPlus to VB.NET: Part 14 (Mysteries Abound)

[Previous articles in this series: Prologue, Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, Part 8, Part 9, Part 10, Part 11 (The Return), Part 12 (Initialization continued), Part 13 (VBA Oddities).]

Mysterious Character: Vertical Tab (VT) — Do These Still Show Up in Word Documents?

In working through the code in MediaWikiConvert_Lists(), I ran across a block of code that purports to “replace manual page breaks”, and is using the Chr(11) construct to do so.  I must’ve been feeling extra-curious ’cause I went digging into what this means, and the harder I looked, the more puzzled I became.

According to, the character represented by decimal “11” is the so-called “vertical tab”.  I’ve never heard of this before (but then, there’s a whole host of ASCII & Unicode characters I’ve never paid attention to before), so I had to check with a half-dozen other references on the ‘net before I was sufficiently convinced that this wasn’t some “off-by-one” problem where the VBA coders were intending to look for Chr(10) (aka “line feed”) or Chr(12) (aka “form feed”).

On the assumption that we’re really and truly looking for “vertical tab”, I had to do some deep digging to figure out what this might actually represent in a Word document.  There’s the obligatory Wikipedia entry, which only said that “The vertical tab is  but is not allowed in SGML (including HTML) or XML 1.0.”.  Then I found this amusing reference to one of the Perl RFCs, which quotes Russ Allbery to say “The last time I used a vertical tab intentionally and for some productive purpose was about 1984.”.  [Sometimes these quotes get better with age…]

OK, so if the vertical tab is so undesirable and irrelevant, what could our VBA predecessors be thinking?  What is the intended purpose of looking for an ASCII character that is so unappreciated?

Mysterious Code Fragment: “If 1 = 2” – WTF?

I started to notice these odd little appendages growing out of some of the newer code in the VBA macro.  At first I figured there must be some special property of VBA that makes “If 1=2” a valid statement under some circumstances, and I just had to ferret out what that was.

Instead, the more I looked at it, the more puzzled I became.  What the hell could this possibly mean?  Under what circumstances would *any* logical programming language ever treat “If 1 = 2” as anything but a comparison of two absolute numbers, that will ALWAYS evaluate to False?

Eventually I had to find out what greater minds that mine thought about this, and so off to Google I go.  As you might expect, there’s not much direct evidence of any programming practices that include adding this “If 1 = 2” statement.  In fact, though it appears in the odd piece of code here and there, it’s surprisingly infrequent.  However, I finally ran across what I take to be the best lesson on what this really means (even if I had to unearth it through the infamous “Google cache”):

>>>Anyone know how to comment out a whole section in VBA rather than just
>>>line by line with a ” ‘ “?
>>If the code is acceptable (won’t break because some control doesn’t
>>exist, etc), I sometimes to
If 1 = 2 then
>> ….existing code
>> End If
>>The code will never fire until the day 1 = 2.
> Thanks, think Id prefer the first option. The second option might
> confuse any programmers that try and read my code.

Now that’s the understatement of the year.

So as far as I’m concerned, I’m going to go back and comment out any and all instances where I find this statement, as it tells me the original programmer didn’t want this code to fire, and was thinking of coming back to it someday after their last check-in.

Mysterious Approach: Localization via Macro?  No way.

There are a few routines that attempt to implement localization at runtime.  While this makes sense for VBA, this makes little if any sense for the use of VB.NET.  Any English-only strings can be substituted in the corresponding Resources file that will accompany this code.

Thus, the MW_LanguageTexts() routine will be skipped, since it had little if any effect anyway.

Mysterious Exception: “add-in could not be found or could not be loaded”

I’ve been struggling for a few days to try to actually run this add-in, and after finding out why, I can say with confidence that there was no good troubleshooting guide for this.

Here’s the setup:

  • I could Build the add-in just fine — no build-time errors, only two compiler warnings (about unused variables).
  • However, when I tried to either (a) Debug the project from within Visual Studio, or (b) add the add-in manually to Word, I was completely stymied.
  • When I started the Debug sequence (F5) from Visual Studio, it would launch Word 2003, which created all its default menus and toolbars, and then threw this error dialog:
    Office document customization is not available - An add-in could not be found or could not be loaded.
  • The details of this exception read:
  • Could not create an instance of startup object Word2MediaWiki__.ThisAddIn in assembly Word2MediaWikiPlusPlus, Version=, Culture=neutral, PublicKeyToken=1a75eafd9e81be84.

    ************** Exception Text **************
    Microsoft.VisualStudio.Tools.Applications.Runtime.CannotCreateStartupObjectException: Could not create an instance of startup object Word2MediaWiki__.ThisAddIn in assembly Word2MediaWikiPlusPlus, Version=, Culture=neutral, PublicKeyToken=1a75eafd9e81be84. —> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.NullReferenceException: Object reference not set to an instance of an object.
       at Word2MediaWiki__.Word2MediaWikiPlusPlus.Convert..ctor() in C:\VS2005 Projects\Word2MediaWiki++\Word2MediaWiki++\Convert.vb:line 44
       at Word2MediaWiki__.ThisAddIn..ctor(IRuntimeServiceProvider RuntimeCallback) in C:\VS2005 Projects\Word2MediaWiki++\Word2MediaWiki++\ThisAddIn.vb:line 29
       — End of inner exception stack trace —

  • If I tried to load the add-in from within Word (using the Tools > COM Add-ins… menu — which you can add with these instructions), Word would only tell me:
  • Load Behavior: Not loaded. A runtime error occurred during the loading of the COM Add-in.

    I won’t even bore you with the details of all the stuff I tried to do to debug this issue.   It turned out that I was instantiating my Application object too early in the code (at least, the way I’d constructed it).

    Broken Code

    ThisAddin.vb (relevant chunk)

    Imports Office = Microsoft.Office.Core
    Imports Word2MediaWiki__.Word2MediaWikiPlusPlus.Convert
    Public Class ThisAddIn
    #Region " Variables "
        Private W2MWPPBar As Office.CommandBar
        WithEvents uiConvert As Office.CommandBarButton
        WithEvents uiUpload As Office.CommandBarButton
        WithEvents uiConfig As Office.CommandBarButton
        Dim DocumentConversion As Word2MediaWikiPlusPlus.Convert = New Word2MediaWikiPlusPlus.Convert ' Line 29
    #End Region

    Convert.vb (relevant chunk)

    Imports Word = Microsoft.Office.Interop.Word
    Namespace Word2MediaWikiPlusPlus
    Public Class Convert
    #Region "Variables"
            Dim App As Word.Application = Globals.ThisAddIn.Application 'PROBLEM - Line 44
            Dim Doc As Word.Document = App.ActiveDocument 'PROBLEM
    #End Region
    #Region "Public Subs"
            Public Sub InitializeActiveDocument()
                If Doc Is Nothing Then
                    Exit Sub
                End If
            End Sub

    #End Region

    #Region “Public Subs”

    Fixed Code

    Convert.vb (relevant chunk)

    Imports Word = Microsoft.Office.Interop.Word
    Namespace Word2MediaWikiPlusPlus
    Public Class Convert
    #Region "Variables"
            Dim App As Word.Application 'FIXED 
            Dim Doc As Word.Document 'FIXED 
    #End Region
    #Region "Public Subs"
            Public Sub InitializeActiveDocument()
                App = Globals.ThisAddIn.Application 'NEW
                Doc = App.ActiveDocument 'NEW
                If Doc Is Nothing Then
                    Exit Sub
                End If
            End Sub
    #End Region

    What I Think Went Wrong

    As much as I understand of this, it seems like when the ThisAddIn class tries to create a new instance of the Convert class as a DocumentConversion object, the ThisAddIn object hasn’t been instantiated yet, so the reference in the Convert class to Globals.ThisAddIn.Application can’t be resolved (how can you get the ThisAddin.Application object if its parent object — ThisAddIn — doesn’t exist yet?) causes the NullReferenceException that is the heart of the problem.

    By pulling out that instantiation code from the App variable declaration, and delaying it instead to one of the Convert class’s Subs, there was no need for the managed code to “chase its tail” — trying to resolve an object reference back through the calling code, which hadn’t been instantiated yet.

    Y’know, I’m sure I read somewhere over the last year that combining the declaration with the instantiation of a variable is bound to lead to subtle debugging issues, but man.  Losing three days to this?  What a disaster.

    Lesson for the day: It never pays to take shortcuts.

    Porting Word2MediaWikiPlus to VB.NET: Part 13 (VBA Oddities)

    [Previous articles in this series: Prologue, Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, Part 8, Part 9, Part 10, Part 11 (The Return), Part 12 (Initialization continued).]

    How to convert the VBA String() Function?

    There’s a more-complicated-than-it-probably-needs-to-be subroutine in the Word2MediaWikiPlus codebase — called MW_SurroundHeader() — that seems to only be there to cleanup and reformat text in a Word document that has one of the Headings styles.  It uses a function from VBA called simply String(), which is one of the first cases of a VBA function for which I cannot find an equivalent in VB.NET.

    It turns out I found out what I needed from an article, and after running into a few brick walls in looking for a reference to this in MSDN, I started a more intelligent search.  I kept coming back to references to the String Data Type, so I next looked at the “Strings in Visual Basic” topic that was referenced by “For more information on string manipulation…”.  From there the next most logical leap was to “Building Strings in Visual Basic“, which led to “How to: Create Strings Using a StringBuilder in Visual Basic“.

    Once there, I figured that since this was so helpful to me, I’d like to save someone the trouble next time so I added a little of that “Community Content” sauce that I myself appreciate so much.

    Converting the Selection Object from VBA?

    The MW_FontFormat() subroutine also uses a no-longer-supported VBA-ism, the Selection object.  This isn’t all that well documented online either — or at least, I wasn’t able to find anything useful online to help figure out how to translate this into VB.NET.  The best I could find was a mention that the Range object in VB shares some common methods & properties with the Selection object in VBA.

    However, I happened to have a copy of an old book called the Microsoft Office XP Developer’s Guide, which was surprisingly results-oriented for an MSPress book.  Pages 176-177 actually discuss “The Selection Object vs. the Range Object”, in which I am told that the Range object is actually superior to the Selection object, and should always be favoured wherever possible.

    I’m not feeling up to the subtleties of Selection vs. Range right now, so I’ll leave this for another time.

    Converting the Font Colour to HTML-compatible values?

    This is another interesting puzzler… It seems that MediaWikiConvert_FontColors() calls RGB2HTML(), which calls OleConvertColor(), which calls OleTranslateColor(), which is a p/invoke to OLEAUT32.DLL.  [Man, this is starting to read like a book of the Old Testament…]

    I have a really strong gut instinct that there’s a managed code equivalent to this that will make the intended conversion in one step, and I intend to find it.  There’s no good reason at this point to (a) have this many calls going on the stack, just to get access to a “simple” math function, or (b) to preserve an unmanaged call just because it’s been used all the way up to now.

    I can think of at least three ways to try to find the managed class I’m after: search on OleTranslateColor, search on “RGB & HTML”, or start browsing books on managed web development.

    According to this “Format Color for HTML” article, the call to OleTranslateColor is only necessary in cases where you’re using “system color constants” or “palette indices”.  Since we’re getting very predictable input here that doesn’t appear to be using either of these two alternatives, right away we should be able to eliminate the unmanaged code.

    That is, if I’m reading this right, then I should just be able to remove OleConvertColor() from the initial call in RGB2HTML() and leave the first line of code as

    nRGBHex = Right("000000" & Hex(rgbColor), 6)

    However, upon double-checking, it seems that other code blocks on the VBA macro are passing in some of the Word.WdColor enumeration constants — which I assume are equivalent to “system color constants”.

    Rather than have the RGB2HTML() routine always thunk down to unmanaged code, it’d be smarter if we checked whether the color value of interest is a member of the Word.WdColor enumeration.  But do the routines that generate the input parameter to RGB2HTML() generate either Long or WdColor values?  Or alternatively, would the code implicitly convert from WdColor to Long as the RGB2HTML() routine initialized?  I didn’t notice any overloaded instances of RGB2HTML() that took the input parameter as a WdColor value, so I have to assume that no matter what goes on outside this routine, all operations inside RGB2HTML() will only operate on colors of type Long.

    If that assumption is correct, then we should be able to safely ignore the possibility that the input parameter may start out as a WdColor datatype, and that means we can safely eliminate the OleConvertColor() and OleTranslateColor() routines.  [For the moment, having already had to dig them back up once, I’ll just comment them out and leave myself a note to delete them once I’ve had time to test these colour conversions and confirm this assumption is true.]

    Colours in VBA vs. Colours in .NET

    A more interesting question, however, is whether we’re losing colour fidelity in the conversions being performed here.  According to VSTO For Mere Mortals, Chapter 4, “In VBA, colors are of type Long, and there are eight constants that can be used… In Visual Studio 2005, colors are of type Color, and there are more than 100 choices”.

    Is it possible that the calls being used to derive the colours from the Active document are limited to the VBA colour constants, and that I should be looking to switch to other calls that return the .NET Color constants?  I’ll just add this as another Task to the CodePlex project list, and deal with it later — it seems to me like this is hardly the biggest problem facing this Addin at the moment.

    Porting Word2MediaWikiPlus to VB.NET: Part 11 (The Return)

    [Previous articles in this series: Prologue, Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, Part 8, Part 9, Part 10.]

    After a few weeks’ hiatus to work on some other projects (including a couple of releases of CacheMyWork — now with more filtering!), I decided to come back to the W2MWPP effort.  And my overwhelming feeling right now is: thank god I was blogging as I coded!  As it is, it took me probably a full hour to figure out where to focus my attention next:

    • while the Configuration dialog still isn’t finished, I’m going to set that aside for now — it should be pretty easy to figure out what I need to add, especially once I know better which functionality is or isn’t required.
    • I’m going to start into the fundamentals of the code that’ll be called when the “Convert to Wiki” button is clicked.  This code will be called by ThisAddIn.uiConvert_Click().
    • The code in question will be derived from the VBA project’s modWord2MediaWikiPlus.bas file, from the Public Sub Word2MediaWikiPlus() routine.

    Sub Word2MediaWikiPlus(): Overview

    There are many types of constructs in this library, many of which I’m sure will be called as part of the base Word2MediaWikiPlus() routine.  However, the routine itself is only about 150 lines of code, so in itself the routine shouldn’t be too difficult to implement.

    However, it’s probably a good idea to familiarize myself with some of the constructs in this library:

    • There are three unmanaged code Functions — two for MessageBox construction, and one called SendMessage.  I’m sure I’ll understand what they’re supposed to be for once I see them in context, and likely I can use some very simple managed code Methods in their place.
    • There are Constants both private and public.  [Thankfully, most of these seem to have been reasonably well documented.]
      • Some, like WMPVersion, will be taken care of by Visual Studio.
      • Others, like WikiOpenPage, should be implemented as configuration settings not code constants.
      • The majority, such as NewParagraphWithBR, will likely be preserved as stylistic choices whose usage choices I may not personally agree with, but likely are things that have been requested over the years and which if I dropped them, would probably piss off a whole bunch of folks who’ve grown to know and love the VBA macro.
    • There are plenty of Variables as well of course, and while the usage of the majority will likely become self-evident, there are a few (e.g. Word97) that I can probably safely drop.
    • There are some constructs labelled as Types as well here — I’m not sure, but they resemble the use of Structures in managed VB, and I’ll likely see them in heavy use.

    At this point, I’m going to implement these constructs as needed.  I’d rather not add all these up front, ’cause (1) it’ll be pretty confusing for me, and (2) there’s likely some code that is no longer needed (or even has been forgotten).

    Sub Word2MediaWikiPlus(): Initialize ActiveDocument

    Even before generating the first set of routines, I figured I should do what I could to setup the Class Library with a little forethought:

    • Added a new Class to the Word2MediaWiki++ project called “Convert.vb”
    • Wanted to add this class to a global W2MWPP namespace (see Part 4 for details)
    • I once tried to add a namespace designation to a project after I’d already started coding it, and it was a disaster — references broke everywhere, and I’m not sure I ever got it cleaned up
    • Not knowing enough about namespace and class naming, I flipped through a couple of the books I had on hand and determined that this should be trivial to do up front
    • I wrapped the Public Class…End Class statements with Namespace Word2MediaWikiPlusPlus…End Namespace statements, and proceeded onwards

    The first sixty lines or so of code in Word2MediaWikiPlus() all have to do with acquiring a handle to an active document.  In the world of Document add-ins, I presume this can be very tricky, since the code itself starts from a single document’s context and has to navigate outwards from there.  However, when we’re working with VSTO Application add-ins, it seems fairly easy to me to get access to an active document.

    In fact, for the purposes of this tool, I’m going to assume that the user wants to convert whichever Word document is the active document.  The only error condition I should need to check is that there is at least one document object open.  This leads to the following code:

                Dim App as Word.Application = Globals.ThisAddIn.Application
                Dim Doc as Word.Document = App.ActiveDocument
                If Doc Is Nothing
                    'When there's no active document open just return back to Word
                    Exit Sub
                End If

    The only code left in the document initialization block is this:

        DocInfo.DocName = ActiveDocument.Name
        DocInfo.DocNameNoExt = DocInfo.DocName
        p = InStrRev(DocInfo.DocName, ".")
        If p > 0 Then DocInfo.DocNameNoExt = Left$(DocInfo.DocName, p - 1)

    I was baffled by this, so a quick trip to MSDN Library cleared it up.  The macro appears to be trying to parse out the document Name without the file extension.  DocInfo itself is an instance of the DocInfoType Type, which is just a structure to store various properties of the document.  There’s no particular reason I can see so far to use a structure for the DocName & DocNameNoExt properties, and the other properties to me don’t seem particularly related to the document itself.  At this point, I’ll assume DocInfo isn’t needed.  [Certainly my searches of the modWord2MediaWikiPlus.bas source only found two references to the DocInfo.DocNameNoExt property: once to assist the MW_GetImagePath() function, and once to set DocInfo.Articlename.  Both should be doable without this Structure, since .NET provides a Path.GetFileNameWithoutExtension function.]

    However, this leads to the MW_Initialize() function, which I would guess also should be part of the Convert class’ initialization code.  I’ll check that out soon.

    The last of the code in this section is the call to MW_LanguageTexts().  This is a localization macro, that will set a series of Registry values and Msg_* variables depending on a language setting retrieved from the Registry.  All this can be managed quite well using Resource files, so there’s no need to mess with setting all this via code at the moment.

    I’m interested in knowing whether all the Registry settings being used by this VBA project are for localization, so let’s enumerate them all…

    Registry value Purpose
    ClickChartText localization
    EditorKeyLoadPic localization
    EditorKeyPastePicAsNew localization
    EditorKeySavePic localization
    EditorPaletteKey localization
    txt_Footnote localization
    txt_PageFooter localization
    txt_PageHeader localization
    txt_TitlePage localization
    UnableToConvertMarker localization
    WikiCategoryKeyWord localization
    WikiSearchTitle localization
    WikiUploadTitle localization

    Wow, what a…symphony of Registry settings here, only a handful of which are directly used for localization.  There’s quite a number of them used for various Image manipulation operations, and others for various application settings etc.  Generally, from what I can tell, there’s no reason these too can’t be stored in the project’s app.config file.

    There’s the Msg_* variables as well – are all these devoted to localization as well?

    Variable name Purpose
    Msg_CloseAll localization
    Msg_Finished localization
    Msg_LoadDocument localization
    Msg_NoDocumentLoaded localization
    Msg_Upload_Info localization

    Yes, that’s all of them.

    Further Config and Init

    The final bit of code I’ll deal with today is the “user dialog on config settings”.  It appears that this calls some additional initialization code:

    1. performs customization if never performed
      • opens the frmW2MWP_Config form
      • sets ImageExtractionPE Registry setting
      • sets the variables EditorPath, OptionHtml, OptionPhotoEditor, OptionPhotoEditor.Enabled
      • sets language settings
      • sets any configured customization settings from the Config form (aka the VSTO UI Config dialog)
    2. sets Article name
    3. sets convertImagesOnly = False
      • this variable is used in Word2MediaWikiPlus() to determine whether to convert the whole document or only the embedded images.
      • Since this is apparently never set to True, I’ll remove the references.
    4. checks Word version and sets some control characters
      • It doesn’t appear that we’ll need this, as VSTO only seems to support Word 2003 and above
    5. sets Image Path
    6. sets the image editor path
    7. caches the value of a couple of Word’s Tools, Options settings
      • I’ve commented in these commands in case they’re necessary later
      • However, I assume there are better ways of preserving Word’s initial configuration than caching before changes and then flushing after changes
      • That is, it should be possible to make per-session changes to such settings, without having to make sure that Word’s settings are explictly reconfigured after the session has completed


    That’s enough for one sitting, eh?

    CacheMyWork enhancements: Managed Stack Explorer ideas

    Wow, what you can learn by perusing someone else’s code…

    Cool Code: Managed Stack Explorer

    I stumbled across the Managed Stack Explorer app on CodePlex, and as soon as I ran it I knew that it had some killer features I wanted to implement in CacheMyWork!

    1. It’s got a two-panel layout for (a) Processes and (b) the Threads in each Process
    2. More importantly, it’s got auto-resizing of these panels when the whole app window is resized (much like I want to have a panel for Applications and another for the Documents open in each Application).  Very slick-looking.
    3. Whenever you click on a Process in the left-hand panel, it updates a lower display of detailed information about the Process (much like I’d wanted to put in tooltips in CacheMyWork, but this might be even better).
    4. It has another panel that is initially collapsed, but that can be opened/expanded by the user, and possibly by the app in response to certain events (much like I’d wanted the Documents panel to initially be hidden and/or hideable, but able to be opened by the user or in response to certain events e.g. (a) click on a Process; (b) if the Process has Documents open, automatically open the Documents panel).
    5. When a new .NET application starts, the list of Processes in this application is automatically updated!


    Makes me want to do it all now, but there are some questions to which I don’t know the answers right yet:

    • Is there such a thing (i.e. a native or custom Class available) to create a CheckListBox (as I have right now) that has multiple Columns?  Could the Columns be resizeable and resortable?
    • Does the auto-update of the Processes list require multi-threaded coding?

    Reverse-engineering the code

    It turns out that, for some unknown reason, when I download the source code and try to open the Design View on any of the WinForms classes (e.g. MainGui.cs, InfoSelect.cs), Visual Studio throws an error and won’t show me the visual design view.  That sucks, ’cause it’s a heckuva lot easier to explore the code by selecting the control(s) you’re interested in and jumping right to the relevant entry point.

    Instead, I’ve spent a couple of hours poring over the MainGui.Designer.cs class, assembling a picture for myself of which components relate to which.  It was very instructive, but it felt kind of like trying to build a Lego model of a Tie Fighter in a pitch-black room:

    • I found out that the SplitContainer control class is what’s used to implement (1) and (2)
    • There are multiple SplitContainer objects in this application, nested one inside the other like those carved Russian dolls.
    • The SplitContainer object hierarchy appears to be like this (I’ve colour-coded the object names to show where they are in the UI below):
      • listsSplitData > ptSplitInfo & FixedPanel.Panel1
        • ptSplitInfo > processSplitThread & groupBox2
          • processSplitThread > processView & threadView
    • The other objects encapsulated in the SplitContainers are arranged:
      • groupBox2 > processDataLayout
        • processDataLayout > label1 – label 12
      • processView > ProcessName column, PID column
      • threadView > tid column, threadState column

    This means the application’s UI is more or less constructed as follows:


    I think I’ve done a decent job mimic’ing the SplitContainer usage demonstrated here, so all I’d like to do at this point is figure out how to create that right-column expand/contract control.  [I’ll leave the auto-update with new Processes behaviour for later.]

    After jumping back & forth through the code a few times, it appears that:

    • The control I’m after is labeled viewStackTraceToolStripMenuItem.
    • It appears be to an instance of ToolStripItem and a child of a ContextMenuStrip, and has a single Click() Event Handler.
    • That event handler calls a routine threadview_DoubleClick(), which calls ShowSelectedThreadStackTraces().
    • The former routine calls ExpandAndCollapse(), which appears to be the magic behind the whole thing.

    If I were to implement this routine or similar, then all I have to figure out is how they created the “>” control that expands & collapses the FixedPanel.