在阅读了其他一些帖子后,我决定提出另一种解决方案。我之前的答案仍然包含有用的信息,所以我将它留在那里。我不熟悉 VB.NET 语法,因此示例使用 C#。我已经在 PowerPoint 的 VSTO 插件中测试了代码,但它应该可以在任何办公应用程序中运行。
忘记 Progress 类和后台线程。在 UI 线程上运行所有内容!
现在使用一些异步代码。为了留在 UI 线程上,我们需要一个“好的”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());
}
这是一个按钮的事件处理程序。注意手动添加的async 关键字。 SynchronizationContext.Current 被一次又一次地重置,所以请确保 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();
}
这将是工作方法,也在 UI 线程上运行。
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();
}
}
以下方法适用于 Windows 窗体应用程序,它可以完美地完成工作。我也在 PowerPoint 中尝试过。如果您遇到问题,请尝试使用 await Dispatcher.Yield(DispatcherPriority.ApplicationIdle) 而不是 await IdleYield() 的 WPF 风格。
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;
}
这是我使用的答案的(可点击)链接(我不能将它们放在代码块中......)。
如果在您的真实代码中运行不符合预期,请检查您正在运行的线程和SynchronizationContext.Current。