Tag Archive for Office

IBF Debugging

The hour is late, so I will be brief.

If you want to debug an Office 2003 IBF solution, you must have C++ installed as part of your VS.NET 2003 installation.

If you click on “Build Solution and Execute Action” and see the build succeed but nothing else, chances are this is your problem.

The Joy of VSTO.

At last, I have a glowingly positive, happily cheerful and satisfyingly pleased entry instead of my usual rant.


I installed the Whidbey Beta 1 build yesterday, and immediately started checking out the beta of VSTOv2. After the grief I went through with the Smart Documents API, I wanted to see if the much-anticipated ActionsPane interface gives me a decent replacement.


Two hours of API-learning and online help-reading later, I had in my hands a rather neat (though not very innovative) CD catalog template, with a fully functional and interactive task-pane

COM Add-Ins : Multiple Madness!

[Don’t you hate it when IE throws your long post to the trash? We need a smart client for updates!]


COM AddIns for Office are a nice feature. A rather easy way to add some buttons and functionality to your documents, and even deployment isn’t too much of a hassle.


But I ran into a problem today when I had two Word documents open at once. It seems that the button was loaded for both, but was only functional for the first instance. The second instance had something that looked like a button, got clicked like a button, but all-in-all did nothing that buttons did – that is, trigger an event.


Several hours of furious debugging followed. VB6 code was translated to VB.NET and then to C#. No go. Code was moved from the OnConnection event to OnStartupComplete – still nothing. A working sample retrieved and compared – nothing relevant seemed to pop up from the page.
Only after going line-by-line over the sample and copying it to my code did we get an answer:


VB controls and similar APIs tend to have a Tag property for all their controls. A convenient, loosely-typed way to store extra data about the control without having to subclass the control or design your own interface.
A great workaround for lazy programmers, up until the time it bites you back. It bites you when you have to go over someone’s code where he uses properties for data other that their intended use. It bites you when you have a “Title” field for a mailbox that is actually used to store an organizational Department. It bites you when your code doesn’t document itself – it does something other than what you expect it to do.


And that’s exactly what Office does – it uses the Tag property of the CommandBarButton to keep track of control instances between multiple open documents.
It doesn’t matter what the Tag holds – the minute you put something in it, the button works fine on as many instances of Word as you’d like.


Why does it rely on the generic, optional, misleading Tag property for this? Couldn’t they add some internal property? Some automatic mechanism? Why do I have to set a random, pointless string to an arbitrary, pointless property to get functionality to work?


Lazy workaround for Lazy programmers. Bah.

Office 2003’s disappearing-reappearing PIAs.

Just in case someone else is running into this problem:


If you’re trying to install Office 2003 Professional and want to make sure you install the PIAs as well, you go to the setup components list and look for .NET Programmability Support and are surprised when you just can’t find it – make sure you have the .NET Framework 1.1 already installed on the computer before you install Office. It won’t show up on the list, and won’t get installed, if the framework isn’t already there – and it’d be a shame to go and reinstall Office now, wouldn’t it?

A Sincere and Permanent Attachment

Our goal for today is automatically attaching a Smart Document to a document – hopefully from a button or a COM add-in or something.


Well, my pre-2003 Office skills are a bit rusty, so I haven’t gotten around to making a COM add-in for this – and I think VSTO deployment these days is still too clunky and awkward to be convenient, but for now, this bit of VBA code will attach our manifest to the active document:


[Note: Some of my actions here might be superfluous. I am not entirely sure as to what is necessary. If you can refine this – please do.]


Sub AttachManifest()


    Dim Namespace As XMLNamespace
    Set Namespace = ActiveDocument.Application.XMLNamespaces.Add(“C:ProjectsSDmy.xsd, “My Schema“, “my“, True)
    Namespace.AttachToDocument (ActiveDocument)
    ActiveDocument.Application.XMLNamespaces.InstallManifest “C:ProjectsSDmanifest_signed.xml”, True
    
     ActiveDocument.SmartDocument.SolutionURL = “C:ProjectsSDmanifest_signed.xml”
    ActiveDocument.SmartDocument.SolutionID = “My Schema”
   
End Sub



 

Communication Breakdown


  • And today, on “Why Smart Documents Are Giving Me Ulcers”: ActiveX controls!

The concept is simple – I want to have an ActiveX control on my Smart Document that can affect the Smart Doc it it initialized from.


Assuming we have the ActiveX linked to an XML element on the document, we can get a handle to it by iterating the SmartTagActions collection on the element, and then attaching an event handler to an event on it, or simply reading a property on it. All would be well.
But unfortunately, as we saw a few posts ago (here), I can’t do that if the ActiveX is linked to the entire document. No way to get a handle, and thus no way to get data back from the ActiveX. Here are some relevant points:



  • You can pass data TO the ActiveX using the ActiveXPropBag object from the PopulateActiveXControl event. The problem is that we can only pass strings here.
  • There’s no simple way to pass data BACK from the ActiveX to the Smart Document.

In my solution, I have an ActiveX TabStrip that controls what is displayed in the Smart Document. I can pass data TO the ActiveX telling it how many tabs should show and what their captions are, but it’s not simple to get that data back to the Smart Document to refresh the display.


There are two problems involved:



  1. Having the Smart Document know which tab was chosen, and
  2. Knowing WHEN the tab was changed so we can refresh the display.

First Try – Remoting


I created a managed WinForms UserControl and wrapped it up as an ActiveX, then added remoting logic in the constructor that exposed the specific instance (using RemotingServices.Marshal()) as a TCP remoting listener. My Smart Document then attempted to get a handle to to object via remoting from the OnPaneUpdateComplete method and call methods such as AddTabs or attach to OnClick events on the remote object.


Problems:



  1. The OnPaneUpdateComplete method would run twice, once before the ActiveX had configured itself, one after. I had to add try/catch logic for standard operation, which is bad and slow.
  2. Work with a remote object is slow. It’s silly that I have to pass through a TCP channel to get to an object in the SAME PROCESS as me, even if it is in a different AppDomain.
  3. The SDK recommends not to use Managed controls wrapped in ActiveX, and I can see why – I had problems with Events on the control not firing (at all, not just via remoting) and every time I tried to set a property relating to the control’s display – visibility, # of tabs, etc – Word would simply hang on an internal Control.ActiveXImpl.GetAmbientProperty() method, or some such.

In short, this approach failed miserably. Time for a second try:


Second Try – VB6


Ahhh. It’s been years.
So I open a new control in VB6 and put a TabControl in it. I initialize the TabControl with the data from the property bag, so far so good. I still have the two goals I mentioned above – refresh the smart doc and pass data back. The solution for both was similar:



  • Send data back by saving in in the document’s CustomProperty collection, and
  • Refresh the data by directly calling the documents’s SmartDocument.RefreshPane() method.

Very nice, but from my ActiveX control, I did not have a handle to my document.


Getting a Document Pointer – First Try


“When in COM“, as the saying (loosely) goes, right? How did we get an instance of the currently running Word instance in COM? GetObject!


So I use GetObject(””, “Word.Application”) to get the running instance and all is well, until I try to have two Word instance open at the same time.


It seems that VB’s GetObject() (as well as the CLR’s Marshal.GetActiveObject()) go over the ROT (Running Objects Table) and retrieve the first handle matching the ProgID given. If I had an instance of Winword loaded BEFORE the one I want, I’d get that instead. What to do, what to do?


Fortunately, some nice people on an internal alias had a solution for me:


Getting a Document Pointer – Second Try


When I’m writing my C# Smart Document code, I DO have a reference to the document, right? Right. And this pointer, this Word.Document object, is a COM object and thus implements IUnknown, right? Right.


So we pull up our sleeves to do some interop:


IntPtr docPointer = Marshal.GetIUnknownForObject(m_Document);
m_DocumentPointerForActiveX = docPointer.ToString();


This will give us a string containing a number that’s actually a pointer to the IUnknown interface of the document in question.
This string we can now pass as part of our ActiveXPropBag to the control, where it will be decoded back to a document pointer, and all will be well.


Unfortunately, I couldn’t find any way to do this from VB6. No pointer handlers, and all the COM in VB is under the hood, not for the likes of us to play with. So I wrote a small C# component called WordHelper that uses the reverse function to get a document pointer – and wrapped that up in COM:


[ComVisible(true)]
public Document GetDocumentByPointer(int Pointer)
{
    IntPtr docPtr = (IntPtr)Pointer;
    return (Document)Marshal.GetObjectForIUnknown(docPtr);
}


Now my VB6 code recieves a string containing a document pointer, converts it to Integer, passes it to the WordHelper and receives a Word.Document object, and from there on it’s (relatively) clear sailing.

*Whew*

Word and Excel – A Wor(l)d Apart

Technically, the same smart document can apply to both Word and Excel. In practice, if you want your smart-doc to actually DO something, chance are half your code will be hacked and kludged to work for both. For example:



  • The XML model is different for the two applications.

  • To get Smart Document controls to respond to the entire document in Word, you’ll use the element #actionPertainsToEntireSchema.
    In Excel, you’ll bind to http://schemas.microsoft.com/office/smartdocuments/2003#default.
    Needless to say, this is not documented anywhere.

  • Assuming we’ve accepted the fact that an ActiveX control bound to one of these generic elements in entirely inaccessible from code (Here, about 80% of the way down. Running a search for “pertains“ should do it), we still can’t use the same code for Word and Excel even for standard elements: XML elements in word are of type Microsoft.Office.Interop.Word.XMLNode. Excel’s XML model is different, and uses element-mapping to cells or ranges. Even if we do get a node, to get the Smart Tag Action from it we’ll use Microsoft.Office.Interop.Word.SmartTag, while Excel has Microsoft.Office.Interop.Excel.SmartTag. Similar? Very. Compatible? Not necessarily.

In short – if you gotta support both Word and Excel, I would suggest having two different Smart Document solutions. It would seem like the less elegant solution, but when you start seeing every function broken up into huge, distinct if/else blocks, you can tell that something’s just not right.

Debug and Conquer!

A few tips to keep in mind when developing and debugging a Smart Document using C# (and probably VB.NET too).
(For my own selfish ease-of-use, I will refer to Word exclusively here. All these steps should apply to Excel equally).



  1. When building the Smart Doc’s Manifest.xml, make sure to include the tag for both the solution DLL and the schema.. This makes sure Word doesn’t copy the DLL and such to the current user’s profile, and thus uses the same DLL that Visual Studio is debugging.
  2. Create an empty document and attach it to the schema and the expansion pack. Then save it somewhere.
  3. In the Project Properties -> Configuration Properties -> Debugging -> Start Action, set Winword.exe as your default application to run when debugging. Pass the name and path of the document from section #2 in the Command Line Arguments field. That way we always launch Word when debugging, and don’t have to mess with attaching to process and so on.
  4. In the Post Build Event for the project, add the following line:
    C:WindowsMicrosoft.NETFrameworkv1.1.4322gacutil.exe /cdl
    (You might need to replace C:Windows with C:WINNT for Windows 2000. Also replace with the version of the framework you’re using).
    This command clears the Download Cache. If we don’t run it, Word will use an older, cached copy of the DLL.
    Note: If Word is running, this command will fail. Don’t forget to close Word.
  5. Once we’re in Word and debugging and all, it’s best to manually close the document rather than just brutally stopping the debugging session. Otherwise we’ll get all those “Word recovered your document from the sudden crash, do you want to restore it?“ questions and taskpanes and all popping up. Best to spare yourself the aggravation and close it politely yourself.

Those should get us started. Now to fight with ActiveX controls…


 

“Smarter than the average Document.”

Smart Document Deployment is definitely an issue that needs a lot of work.


For the past week I’ve been repeatedly stumped in the simple task of loading a Smart Document solution into Word. Not a specific, possibly-buggy SmartDoc – ANY SmartDoc solution. Even ones that worked before. Even the SDK-supplied SimpleSample. ALL.


I followed the MSDN instructions quite specifically. (Here).
I tried to debug using all the information here.
I added Code Access Security.
I changed namespace names.
I tried read the Fusion log to see what’s going on.
I uninstalled and reinstalled Office 2003 TWICE, in two different languages.


And all this time this nagging, teasing, whining headache of an error message popping up. “Missing or invalid Smart Document”. Grrr… I’ll show him missing or invalid!


Hmpf. Well. As always, the answer came from a totally unexpected direction.
Well, a rather expected direction, but in an unexpected manner. The Troubleshooting document listed above mentions the following possible cause:



You have Microsoft Windows® .NET Framework 1.0 and Microsoft Windows® .NET Framework 1.1 installed on your computer. However, you accidentally configured the code access security policy using version 1.0 of the .NET Framework instead of 1.1. Correct this by using the .NET Framework 1.1 to grant Full Trust permissions to the assemblies. Remember to delete the previous settings.


I have never had 1.0 installed on this machine, so I didn’t suspect a thing – but then I remembered that I *DID* install the 2.0 framework a couple of weeks ago, trying to get Whidbey to run here. (Unsuccessfully, I might add). Even though I built my SmartDoc project in Everett linked to Framework1.1, and I set my code-access permissions in the Configuration tool for Framework1.1, and everything was supposed to be side-by-side and fine and dandy – and nevertheless, once I uninstalled the 2.0 framework things started working immediately.


My guess is Word tried to load the managed Smart Document DLL using the 2.0 DLLs. Does this make sense? Is there any reason this happens? Any way to stop it? Do I need to specify explicit binding rules in WINWORD.EXE.config to get it to bind properly when both 1.1 and 2.0 are installed?

“Quoth the SmartDoc: ‘Never Load!'”

These days I’m having the ambiguous pleasure of building Office 2003-based Smart Document Solutions. Despite their hot-buzz name, they seem to lean more towards the dim end of the scale.


While the Smart Document SDK itself is rather well written and covers most points, good documentation can only go so far to describe an awkward programming model where you can’t so much specify controls on your Task Pane as much as create properties that expose the name, type and amount of controls you would like to have and wait for Word or Excel to create them for you. All in all, this makes for a nervous “I have no idea what the hell is going on” development experience.


The advantage to that, of course, is that once I do wrap my head around the concepts and start whipping out SmartDocs by the bucketload, I will earn the awe and respect of my peers. Sorta like the way people always keep a respectful distance around people who can wrap steel bars around their heads.


Anyway, second only to the development model in needless complexity is the deployment model, relying on automatic installation of the smart-doc on demand by arcane XML files and inscrutable security settings. End result is that on a test machine, every time we opened a DOC file linked to a Smart Document solution we had to pass 3-4 pointless dialogs asking us to re-download the solution we had already downloaded and installed. It gets even ickier when the user doesn’t have administrative rights on his workstation – a scenario too exotic for regular Smart Doc installations, it seems.


2 hours and several futile googlings later, a solution was found:
A registry key by the name of NeverLoadManifests, alarming name nonwithstanding, tells Word not to try and re-load the Smart Document Solution from the source every time, and to settle for the version it already has installed. Not the best of solutions, since it prohibits us from using Office’s Automatic update of SmartDoc solutions (which we aren’t using in this deployment anyway), but it sure it better than asking users to click through 3 dialogs whenever they open a document.


Key can be found here:
HKEY_CURRENT_USERSOFTWAREMicrosoftOfficeCommonSmart Tags
NeverLoadManifests=dword:0000001


And Google has apparently never heard of it, which is impressive. It can be found in the SDK, though I found THAT out only after I had tracked it down.


May this page be Googled and Feedstered and help others with the problem, amen.