Catch opened word file event - events

I want to make an application as a word add-in that changes files when they are opened.
So I created a word add-in project in visual Studio, and this is basically the code I have :
namespace WordAddIn1
{
public partial class ThisAddIn
{
private void Application_DocumentOpen(Microsoft.Office.Interop.Word.Document Doc)
{
MessageBox.Show("doc opened");
// do my stuff
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Application.DocumentOpen += new Word.ApplicationEvents4_DocumentOpenEventHandler(Application_DocumentOpen);
}
#endregion
}
}
The problem is, this works well if you start an empty word application (double click word.exe), then open a document, but not if the word application is started together with the document opening (double click a .doc file).

The DocumentOpen won't fire if you open Word by double-clicking on a document.
To Work around this you can check if a document was opened in Word and if so pass the document to Application_DocumentOpen method.
BTW - You seem to have changed the code in the InternalStartup method. As indicated by the comments you should not do this, but instead uses the ThisAddIn_Startup.

Here's code to do what Abbey suggested:
private void ThisAddIn_Startup(object sender, System.EventArgs a)
{
try
{
Word.Document doc = this.Application.ActiveDocument;
if (String.IsNullOrWhiteSpace(doc.Path))
{
logger.Debug(String.Format("Word initialized with new document: {0}.", doc.FullName));
ProcessNewDocument(doc);
}
else
{
logger.Debug(String.Format("Word initialized with existing document: {0}.", doc.FullName));
ProcessOpenedDocument(doc);
}
}
catch (COMException e)
{
logger.Debug("No document loaded with word.");
}
}

You often will have the DocumentOpen event triggered before your AddIn is loaded and sometime before the document is actually loaded into the Word Interop API. This means that on ThisAddIn_Startup, WordApp.ActiveDocument still returns Null.
A trick is to schedule a Task (System.Func<Boolean>) in your ThisAddIn_Startup event: I use a Windows.Forms.Timer and that works quite well. On Timer.Tick, try to execute the Task. If it returns false, then try again 500ms later, and so on until it works.
My Task is:
If WordApp.ActiveDocument Is Nothing Then Return False
DoSomething()
Return True
The timer will keep trying until you have an active document, i.e. until you can do something with the document the user opened by double-click.
There are other ways to expose this same mechanism: you could for example wrap this into a WordStartup object that exposes a DocumentOpenBeforeAddInStartUp event and listen to this. You could also have a wrapper of all Document changes events and trigger your own DocumentChange event when either WordStartup.DocumentOpenBeforeAddInStartUp, WordApp.DocumentChange, WordApp.DocumentOpen or WordApp.NewDocument triggers.

Related

Outlook VSTO Handling SelectionChange correctly (currently doubleclick crashes Addin)

From what I understand you need to track Activation and Deactivation of the Explorers. During activation, you need to add SelectionChange event handlers for the current explorer.
This seems to work perfectly for single clicks on AppointmentItems. But it crashes the Addin when double-clicking on an appointment series and selecting a single Appointment.
Here is the source:
On class level
private Outlook.Explorer currentExplorer = null;
private Outlook.AppointmentItem currentAppointmentItem = null;
within Startup:
currentExplorer = this.Application.ActiveExplorer();
((Outlook.ExplorerEvents_10_Event)currentExplorer).Activate +=
new Outlook.ExplorerEvents_10_ActivateEventHandler(
Explorer_Activate);
currentExplorer.Deactivate += new
Outlook.ExplorerEvents_10_DeactivateEventHandler(
Explorer_Deactivate);
The event handlers:
void Explorer_Activate()
{
currentExplorer.SelectionChange += new Outlook.ExplorerEvents_10_SelectionChangeEventHandler(Selection_Change);
}
void Explorer_Deactivate()
{
currentExplorer.SelectionChange -= new Outlook.ExplorerEvents_10_SelectionChangeEventHandler(Selection_Change); ;
}
private void Close_Explorer()
{
}
private void Selection_Change()
{
Outlook.MAPIFolder selectedFolder = currentExplorer.CurrentFolder;
if (currentExplorer.Selection.Count > 0)
{
Object selObject = currentExplorer.Selection[1];
if (selObject is Outlook.AppointmentItem)
{
currentAppointmentItem = (Outlook.AppointmentItem)selObject;
}
else
{
currentAppointmentItem = null;
}
}
}
What am I overlooking? Is the form of deregistering a problem?
Try to add try/catch blocks to the event handlers. The Outlook object model can give you unpredictable results sometimes. It is worth adding them and find where an exception is thrown.
currentExplorer.Selection.Count
Also, you may subscribe to the SelectionChange event in the NewExplorer event and don't switch between explorers when they are activated or deactivated. The event is fired whenever a new explorer window is opened, either as a result of user action or through program code.
The only thing which I added was a handler for NewInspector and InspectorClose events along with Marshal.ReleaseComObject(). The only thing which I can imagine that double clicking while debugging I got in some kind of race condition (because double clicking also triggers the Selection_Change event). But this is only a guess.
You do not need to add and remove event handlers as an explorer is activated / deactivated. Are you trying to support multiple explorers? In that case, create a wrapper class that hold the Explorer object as it member and uses its methods as event handlers.

Outlook Add-In - Enable/Disable Button during Runtime/from Code

Initial Situation:
We are developing an Add-in for Outlook 2010 in C# with VS.NET 2010 based on Framework 4.0, VSTO, DevExpress WinForm Controls. In Designer we have a Ribbon with a RibbonTab, then a RibbonGroup then a RibbonButton. We're consuming WebServices from within this Outlook Add-in.
Objective:
We need to enable/disable the RibbonButtons when the WebService is available/unavailable (from/out of the code)
we've found the following links:
Links
Ribbon Object Model Overview: http://msdn.microsoft.com/en-us/library/bb608623.aspx
Ribbon Overview: http://msdn.microsoft.com/en-us/library/bb386097.aspx
Walkthrough: Updating the Controls on a Ribbon at Run Time: http://msdn.microsoft.com/en-us/library/bb608628.aspx
After hours of trying to figure out how to implement this we deciced to post/ask this question here on SO. Does anybody have a sample code? We tried the IRibbonExtensibility and CreateRibbonExtensibilityObject => we added the RibbonTab, Group and Button and added a subscription to the Click Event => The Event is fired but not handled (in button_Click(...) => System.Diagnostics.Debugger.Break() is not breaking the code execution)
Thanks!
Christian
You'll want to invalidate the Ribbon at a fairly frequent rate in order to refresh the visibility of each tab/button. You can do this by subscribing to the Click event (as you've done) and then calling RibbonObject.Invalidate();. Then add a getEnabled="yourTestFunction" parameter to each button, with public bool yourTestFunction(Office.IRibbonControl control) (Defined in the Ribbon.cs file) returning whether the web service is available or not.
Keep in mind if the web service is down, each click could hang your application for the amount of time you set on your timeout in the web service check
Edit:
Just realized the _Click event isn't mapped in the Excel COM library, so here's a bit of code that will run each time the cell selection is changed (not as frequent as every click, but hopefully good enough).
ThisAddIn.cs:
public static Excel.Application e_application;
public static Office.IRibbonUI e_ribbon;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
e_application = this.Application;
e_application.SheetSelectionChange += new Excel.AppEvents_SheetSelectionChangeEventHandler(e_application_SheetSelectionChange);
}
void e_application_SheetSelectionChange(object Sh, Excel.Range Target)
{
e_ribbon.Invalidate();
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
e_application.SheetSelectionChange -= new Excel.AppEvents_SheetSelectionChangeEventHandler(e_application_SheetSelectionChange);
e_application = null;
}
Ribbon1.cs:
public void Ribbon_Load(Office.IRibbonUI ribbonUI)
{
this.ribbon = ribbonUI;
ThisAddIn.e_ribbon = ribbonUI; //Add this line
}
and
public bool getEnabledTest(Office.IRibbonControl control)
{
//Whatever you use to test your Web Service
//return false;
}
Ribbon1.xml:
<button id="WebService" label="Use The Web Service" onAction="executeWebService" getEnabled="getEnabledTest" />
The following article titled Adding Custom Dynamic Menus to the Office Fluent User Interface will point you in the right direction.
The below is an example of a dynamically created menu, you can modify the tutorial to fit your particular need.
ok, thanks for the tips. Finally i solved it like this:
i declared a static ribbon object like:
public static RibbonIet ribbon {get; set; }
in the load event of the Ribbon i assign the Ribbon (this) like:
Session.Common.ribbon = this;
now i can control the RibbonButton like:
Session.Common.ribbon.buttonCreateIncident.Enabled = true;
Since the webService call is running in a seperate thread, i had to use a MethodInvoker to change enable/disable the buttons. it goes like this:
If (InvokeRequired)
{
Invoke(new MethodInvoker(() => Session.Common.ribbon.buttonCreateIncident.Enabled = true));
}
maybe this is of help for someone else.

C# - Is there any OnShapeMoved or OnShapeDeleted event in Visio?

I think the title or the question is clear enough. I saw something about the EventSink, but I found it difficult to use. Any hint?
The Visio Primary Interop Assembly exposes these events as C# events therefore you can simply hook the event with a delegate.
See this simple example:
namespace VisioEventsExample
{
using System;
using Microsoft.Office.Interop.Visio;
class Program
{
public static void Main(string[] args)
{
Application app = new Application();
Document doc = app.Documents.Add("");
Page page = doc.Pages[1];
// Setup event handles for the events you are intrested in.
// Shape deleted is easy.
page.BeforeShapeDelete +=
new EPage_BeforeShapeDeleteEventHandler(onBeforeShapeDelete);
// To find out if a shape has moved hook the cell changed event
// and then check to see if PinX or PinY changed.
page.CellChanged +=
new EPage_CellChangedEventHandler(onCellChanged);
// In C# 4 for you can simply do this:
//
// page.BeforeShapeDelete += onBeforeShapeDelete;
// page.CellChanged += onCellChanged;
// Now wait for the events.
Console.WriteLine("Wait for events. Press any key to stop.");
Console.ReadKey();
}
// This will be called when a shape sheet cell for a
// shape on the page is changed. To know if the shape
// was moved see of the pin was changed. This will
// fire twice if the shape is moved horizontally and
// vertically.
private static void onCellChanged(Cell cell)
{
if (cell.Name == "PinX" || cell.Name == "PinY")
{
Console.WriteLine(
string.Format("Shape {0} moved", cell.Shape.Name));
}
}
// This will be called when a shape is deleted from the page.
private static void onBeforeShapeDelete(Shape shape)
{
Console.WriteLine(string.Format("Shape deleted {0}", shape.Name));
}
}
}
If you haven't already downloaded the Visio SDK you should do so. Recent versions of the SDK it contains many useful examples include one called "Shape Add\Delete Event". If you have the 2010 version can browse the examples by going to Start Menu\Programs\Microsoft Office 2010 Developer Resources\Microsoft Visio 2010 SDK\Microsoft Visio Code Samples Library.
I believe that you have to implement EvenSink to get access to "ShapesDeleted", i.e.
(short)Microsoft.Office.Interop.Visio.VisEventCodes.visEvtCodeShapeDelete
the code above will help you if you are looking for the event "BeforeShapeDelete" not the "after"ShapeDelete ;)

Subscription to DTE events doesn't seem to work - Events don't get called

I've made an extension inside a package and I am calling the following code (occurs when a user presses a button in the toolbar):
DocumentEvents documentEvents = (DTE2)GetService(typeof(DTE));
_dte.Events.DebuggerEvents.OnEnterBreakMode += DebuggerEvents_OnEnterBreakMode;
_dte.Events.DebuggerEvents.OnEnterDesignMode += DebuggerEvents_OnEnterDesignMode;
_dte.Events.DebuggerEvents.OnContextChanged += DebuggerEvents_OnContextChanged;
_dte.Events.DocumentEvents.DocumentSaved += new _dispDocumentEvents_DocumentSavedEventHandler(DocumentEvents_DocumentSaved);
_dte.Events.DocumentEvents.DocumentOpened += new _dispDocumentEvents_DocumentOpenedEventHandler(DocumentEvents_DocumentOpened);
void DocumentEvents_DocumentOpened(Document Document)
{
}
void DocumentEvents_DocumentSaved(Document Document)
{
}
void DebuggerEvents_OnEnterBreakMode(dbgEventReason Reason, ref dbgExecutionAction ExecutionAction)
{
}
void DebuggerEvents_OnContextChanged(Process NewProcess, Program NewProgram, Thread NewThread, StackFrame NewStackFrame)
{
}
private void DebuggerEvents_OnEnterDesignMode(dbgEventReason reason)
{
}
The first and the major problem is that the subscription to the event doesn't work. I've tried:
Opening new documents
Detaching from debug (thus supposedly triggering OnEnterDesignMode
Saving a document
None of these seem to have any effect and the callback functions were never called.
The second issue is that the subscription to the event line works USUALLY (the subscription itself, the callback doesn't work as described above) but after a while running the subscription line, e.g:
_dte.Events.DebuggerEvents.OnEnterBreakMode -= DebuggerEvents_OnEnterBreakMode;
Causes an exception:
Exception occured!
System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used.
at System.StubHelpers.StubHelpers.StubRegisterRCW(Object pThis, IntPtr pThread)
at System.Runtime.InteropServices.UCOMIConnectionPoint.Unadvise(Int32 dwCookie)
at EnvDTE._dispDebuggerEvents_EventProvider.remove_OnEnterDesignMode(_dispDebuggerEvents_OnEnterDesignModeEventHandler A_1)
Any ideas will be welcome
Thanks!
Vitaly
Posting an answer that I got from MSDN forums, by Ryan Molden, in case it helps anyone:
I believe the problem here is how the
CLR handles COM endpoints (event
sinks). If I recall correctly when
you hit the
_applicationObject.Events.DebuggerEvents
part of your 'chain' the CLR will
create a NEW DebuggerEvents object for
the property access and WON'T cache
it, therefor it comes back to you, you
sign up an event handler to it (which
creates a strong ref between the
TEMPORARY object and your object due
to the delegate, but NOT from your
object to the temporary object, which
would prevent the GC). Then you don't
store that object anywhere so it is
immediately GC eligible and will
eventually be GC'ed.
I changed the code to store DebuggerEvents as a field and it all started to work fine.
Here is what #VitalyB means using code:
// list where we will place events.
// make sure that this variable is on global scope so that GC does not delete the evvents
List<object> events = new List<object>();
public void AddEvents(EnvDTE dte)
{
// create an event when a document is open
var docEvent = dte.Events.DocumentEvents;
// add event to list so that GC does not remove it
events.Add(docEvent );
docEvent.DocumentOpened += (document)=>{
Console.Write("document was opened!");
};
// you may add more events:
var commandEvent = dte.Events.CommandEvents;
events.Add(commandEvent );
commandEvent.AfterExecute+= etc...
}

How to see Word document text changes in ThisDocument_New()

I'm considering migration of all of our Word templates from VBA to VSTO and have the following question: How can I debug code in a VSTO project?
Unlike debugging in VBA, I can't see the results of code executing line-by-line when stepping through a procedure.
For example, I build a prototype Word document in VS 2019:
using ...;
namespace MyCompany.OfficeTemplates.MyTemplate
{
public partial class ThisDocument
{
private void ThisDocument_Startup(object sender, System.EventArgs e)
{
}
private void ThisDocument_Shutdown(object sender, System.EventArgs e)
{
}
private void ThisDocument_New()
{
var currentSelection = Application.Selection;
currentSelection.TypeText("This text was added by using code.");
}
#region VSTO Designer generated code
/// ...<summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisDocument_Startup);
this.Shutdown += new System.EventHandler(ThisDocument_Shutdown);
this.New += new Microsoft.Office.Interop.Word.DocumentEvents2_NewEventHandler(this.ThisDocument_New);
}
#endregion
}
}
I plan to put way more code (showing a dialog box, choosing customers, choosing language and other information) into the ThisDocument_New() event. Why is the text not inserted into the word document when I step over the command ...TypeText()... with the debugger?!?
Instead, It's inserted when debugger leaves ThisDocument_New()?
What am I missing and where else to put my code when ThisDocument_New() does not allow propper debugging?
That one cannot see what's happening during code execution is typical for VSTO. Debugging is different than with VBA - the results are only visible after the code has finished. Nothing can be done to change this - it's in the nature of the thing (.NET <-> COM interaction).
I struggled with this, myself, when starting with VSTO, and had to learn to work around it.
Generally, if during trouble-shooting it's necessary to see what's happening as it happens, I first test in VBA, then transfer that code to VSTO.
If it's necessary to check values being generated by code as it runs I stick some Debug.Print lines in that I can follow in the Visual Studio output window when stepping through the code.
It's also possible to track what's assigned to objects when stepping through the code.

Resources