After reading some other posts, I decided to suggest another solution. My previous answer still contains usable information, so I'll leave it there. I'm not familiar with VB.NET syntax, so the samples are in C#. I have tested the code in a VSTO plugin for PowerPoint, but it should run in any office application.
Forget the Progress class and background threads. Run everything on the UI thread!
Now use some async code. To stay on the UI thread, we need a "good" SynchronizationContext.
private static void EnsureWinFormsSyncContext()
{
// Ensure that we have a "good" SynchronisationContext
// See https://stackoverflow.com/a/32866156/10318835
if (SynchronizationContext.Current is not WindowsFormsSynchronizationContext)
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
}
This is the event handler of a button. Note the manually added async
keyword. The SynchronizationContext.Current
gets resetted again and again, so ensure the good one in the EventHandler:
private async void OnButtonClick(object sender, EventArgs e)
{
EnsureWinFormsSyncContext();
// Return from event handler, ensure that we are really async
// See https://stackoverflow.com/a/22645114/10318835
await Task.Yield();
await RunLongOnUIThread();
}
This will be the worker method, also running on the UI thread.
private async Task RunLongOnUIThread()
{
//Dummy code, replace it with your code
var pres = addIn.Application.Presentations.Add();
for (int i = 0; i < 100; i++)
{
Debug.Print("Creating slide {0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId);
// If you have some workloads that can be run on a background
// thread, execute them with await Task.Run(...).
try
{
var layout = pres.Designs[1].SlideMaster.CustomLayouts[1];
var slide = pres.Slides.AddSlide(i + 1, layout);
var shape = slide.Shapes.AddLabel(Microsoft.Office.Core.MsoTextOrientation.msoTextOrientationHorizontal, 0, 15 * i, 100, 15);
shape.TextFrame.TextRange.Text = $"Text on slide {i + 1}";
}
catch (Exception ex)
{
Debug.Print("I don't know what am I doing here, I'm not familiar with PowerPoint... {0}", ex);
}
// Update UI
statusLabel.Text = $"Slide {i + 1} done";
progressBar1.Value = i + 1;
// This is the magic! It gives the main thread the opportunity to update the UI.
// It also processes input messages so you need to disable unwanted buttons etc.
await IdleYield();
}
}
The following method is for Windows Forms Applications where it does the job perfect. I've tried it also in PowerPoint. If you are facing problems, try the WPF flavour with await Dispatcher.Yield(DispatcherPriority.ApplicationIdle)
instead of await IdleYield()
.
private static Task IdleYield()
{
var idleTcs = new TaskCompletionSource<bool>();
void handler(object s, EventArgs e)
{
Application.Idle -= handler;
idleTcs.SetResult(true);
}
Application.Idle += handler;
return idleTcs.Task;
}
Here are the (clickable) links to the answers that I used (I can't put them in the code-blocks...).
If in your real code something runs not as expected, check the thread you are running on and SynchronizationContext.Current
.