23

I have a button on an Excel sheet which starts a new thread to do some processing. If I want to make any changes to Excel (e.g. write data to a cell using Worksheet.Range("A1").Value = "info";), I think I must use the main UI thread.

How can this be done?

Typically in Winforms I would call Invoke on a control, but the Excel.Application or Worksheet or Range objects don't have an Invoke method.

Laurent
  • 5,953
  • 14
  • 43
  • 59

3 Answers3

29

That work doesn't 'need' to be done on the UI thread, .net will marshal the call for you, but if you make repeated calls from a background thread you may hit performance issues.

But to answer your question specifically, if you have .net 3.5, in your add-in load event add this:

Dispatcher _dispatcher = Dispatcher.CurrentDispatcher;

And then add:

public Dispatcher Dispatcher { get {return _dispatcher;} }

Then you can dispatch to the UI thread by going

Globals.ThisAddIn.Dispatcher.Invoke(()=>{/*stuff*/});

If you don't have .net 3.5, then there are a few other thread synchronisation techniques, like using SynchronizationContext.Current instead of the Dispatcher.

JDR
  • 1,094
  • 1
  • 11
  • 31
Jake Ginnivan
  • 2,112
  • 16
  • 20
  • Great answer.I have been banging my head on a wall for something similar (xll add-in) and this sorted it. Whilst work doesn't 'need' to be done on the UI thread as you say, if you do it on another thread you will probably find that Excel.exe is still hanging around in Task Manager after closing the application. – MT. Dec 07 '12 at 02:10
  • 6
    For anyone else confused about `Dispatcher`, you have to add reference to `WindowBase` to get access to `Dispatcher` class. – dotNET Dec 07 '15 at 15:29
  • The problem I had when invoking from a different thread was that Excel was often busy and threw a COMException. This solved it. – Laurent Apr 14 '17 at 08:02
  • 1
    @JakeGinnivan I'm using your suggestion on my `VSTO` project in `VS2019`. You are asking to add `_dispatcher ` variable declaration in the load event of add-in. Did you mean declaring on class level? Otherwise, the property declaration `public Dispatcher Dispatcher { get {return _dispatcher;} }` does not recognize `_dispatcher` as `VS2019` is complaining on my `VSTO` project. Please let me know if I'm missing something here. – nam Apr 06 '20 at 21:13
  • @nam you aren't missing anything. JDR's edit added the type which made that line a declaration instead of an assignment as it was meant to be. – gusmally supports Monica Jan 24 '23 at 22:17
13

This is my solution for a VSTO AddIn using WindowsForms. You don't need any System.Windows.Forms.Control to use it:


Initialization in class ThisAddIn:

Add this line to "ThisAddIn_Startup" function:

this.TheWindowsFormsSynchronizationContext = WindowsFormsSynchronizationContext.Current 
                                           ?? new WindowsFormsSynchronizationContext();

Add this new property:

public SynchronizationContext TheWindowsFormsSynchronizationContext { get; private set; }

Then the usage in the worker thread is:

        Globals.ThisAddIn.TheWindowsFormsSynchronizationContext.Send(d =>
        {
            MyMethodToInvoke();
        }, null);   

A second solution (Not tested): You could maybe also use:

        var invokerControl = new Control();
        invokerControl.CreateControl(); //Forces the control handle to be created
        invokerControl.Invoke(new MethodInvoker(MyMethodToInvoke));

Hope it helps, Jörg

jreichert
  • 1,466
  • 17
  • 31
  • Thank you so much! After searching for half an hour this was the only working solution for VSTO! – Pali Feb 09 '16 at 14:22
  • The SynchronisationContext seems to be more generic and safer then the Dispatcher (can be called from outside the UI). Here's VS answer https://stackoverflow.com/questions/24671883/difference-between-synchronization-context-and-dispatcher – rémy Apr 13 '18 at 16:42
  • Works also with ExcelDna based addins. Use AutoOpen() function to initialise the context instead of ThisAddIn_Startup. – EylM Jul 25 '19 at 10:46
2

Have you tried starting a BackgroundWorker from your button? This makes it very easy as the ProgressChanged and RunWorkerCompleted events will fire on the main thread.

I haven't tried this in an Excel/VSTO environment but I don't see why it wouldn't work.

Joe
  • 122,218
  • 32
  • 205
  • 338