部分WinForm UI冻结时,通过Windows任务栏的Excel VSTO切换窗口或应用程序

我有一个奇怪的问题(可能是线程问题),一直困扰着我。 我想为我在Excel / VSTO中运行的任务创build一个进度条,通过单击function区上的button启动它。

由于所有对Excel对象模型的访问都必须在主线程中进行,所以我在单独的线程上以模态方式显示我的进度表单。 这在大多数情况下都很有效,但是当我通过Windows任务栏切换应用程序或窗口时会发生奇怪的事情。 例如,如果我处于运行中并且Excel最大化,并且在任务栏上单击了Chrome以使其聚焦,那么我将单击回到Excel,然后在任务栏预览中单击打开的Excel工作簿,有时UI会在进度条中部分冻结。

在Excel 2007/2010中,我仍然可以看到正在更新的进度栏,但是我不能拖动工具栏或单击“取消”button。 这就是为什么我说UI部分冻结。 类似的东西发生在Excel 2013/2016,但我还没有testing过。

如果Excel具有焦点,并且在任务执行期间不切换到另一个窗口,则进度条可以很好地工作。

有没有人有一个想法可能会出错?

有没有另外一种方法可以在Excel中显示进度条来提供一致和可靠的行为? 请注意,实际任务需要在Excel主要UI线程上运行,并且进度表单是交互式并正确显示的限制,它需要位于辅助UI线程上。 正因为如此,像使用BackgroundWorker的解决scheme将无法正常工作。

class RunSampleProgressTask { private ProgressForm _form; internal volatile bool CancelPending; internal volatile AutoResetEvent SignalEvent = new AutoResetEvent(false); internal RunSampleProgressTask() { //create a new workbook Globals.ThisAddIn.Application.Workbooks.Add(Microsoft.Office.Interop.Excel.XlWBATemplate.xlWBATWorksheet); int hwnd = GetHwnd(); var thread = new Thread(() => ShowProgressForm(hwnd)); thread.SetApartmentState(ApartmentState.STA); thread.Priority = ThreadPriority.Highest; thread.Name = "ProgressFormThread"; thread.IsBackground = true; thread.Start(); SignalEvent.WaitOne(); //In SDI Excel a newly created workbook won't be shown until the function ends so we use a timer to show the new workbook before running Run() if (IsSDI) ExecuteTaskIn(200,Run); else Run(); } internal static void ExecuteTaskIn(int milliseconds, Action action) { var timer = new System.Windows.Forms.Timer(); timer.Tick += (s, e) => { ((System.Windows.Forms.Timer)s).Stop(); //s is the Timer action(); }; timer.Interval = milliseconds; timer.Start(); } //returns true if it is Excel 2013 or 2016 that use the new Single Document Interface public static bool IsSDI { get { string appVersion = Globals.ThisAddIn.Application.Version; return appVersion.StartsWith("15") || appVersion.StartsWith("16"); } } private void ShowProgressForm(int hwnd) { _form = new ProgressForm(this) {StartPosition = FormStartPosition.CenterScreen}; _form.ShowInTaskbar = false; _form.FormBorderStyle = FormBorderStyle.FixedSingle; _form.ShowDialog(new Win32Window(hwnd)); } protected void ReportProgress(int percent) { if (_form == null || !_form.IsHandleCreated) return; _form.BeginInvoke(new Action(() => _form.SetProgress(percent))); } protected void CloseForm() { if (_form == null || !_form.IsHandleCreated) return; _form.BeginInvoke(new Action(() => _form.Close())); } internal static int GetHwnd() { if (IsSDI) { var window = Globals.ThisAddIn.Application.ActiveWindow; int hwnd = (int)window.GetType().InvokeMember("Hwnd", BindingFlags.GetProperty, null, window, null); //late binding call to get Window.Hwnd return hwnd; } else { return Globals.ThisAddIn.Application.Hwnd; } } private const int BufRowSize = 16384; //must be a factor of RowsPerPage private const int RowsPerPage = 1048576; private const int BufColSize = 10; private const int Repetitions = 80; internal void Run() { ReportProgress(0); Globals.ThisAddIn.Application.ScreenUpdating = false; Globals.ThisAddIn.Application.EnableEvents = false; var buf = new string[BufRowSize, BufColSize]; //fill the buffer with sample data int cnt = 0; for (int i = 0; i < BufRowSize; i++) for (int j = 0; j < BufColSize; j++) buf[i, j] = "String" + (++cnt); var workbook = Globals.ThisAddIn.Application.ActiveWorkbook; var sheet = workbook.ActiveSheet; int currRow = 1; for (int i = 0; i < Repetitions; i++) { if (CancelPending) { CloseForm(); Globals.ThisAddIn.Application.ScreenUpdating = true; Globals.ThisAddIn.Application.EnableEvents = true; return; } sheet.Range[sheet.Cells[currRow, 1], sheet.Cells[currRow + BufRowSize - 1, BufColSize]].Value2 = buf; currRow += BufRowSize; if (currRow > RowsPerPage) { currRow = 1; sheet = workbook.Sheets.Add(Missing.Value, sheet); } int percent = 100 * (i + 1) / Repetitions; ReportProgress(percent); } CloseForm(); Globals.ThisAddIn.Application.ScreenUpdating = true; Globals.ThisAddIn.Application.EnableEvents = true; } } public partial class ProgressForm : Form { private ProgressBar _progressBar; private Label _lblStatus; private Button _btnCancel; private RunSampleProgressTask _task; internal ProgressForm(RunSampleProgressTask task) { InitializeComponent(); this.Width = 320; this.Height = 120; this.ControlBox = false; _task = task; _progressBar = new ProgressBar(){Left=10, Top = 10, Width= 300, Height = 30}; _lblStatus = new Label(){Left = 10, Top = 50}; _btnCancel = new Button(){Text = "Cancel", Left = _lblStatus.Right + 10, Top = 50, Width = 100}; _btnCancel.Click += _btnCancel_Click; this.Controls.Add(_progressBar); this.Controls.Add(_lblStatus); this.Controls.Add(_btnCancel); this.Shown += ProgressForm_Shown; } void ProgressForm_Shown(object sender, EventArgs e) { _task.SignalEvent.Set(); } void _btnCancel_Click(object sender, EventArgs e) { _task.CancelPending = true; _lblStatus.Text = "Cancelling..."; } internal void SetProgress(int percent) { _progressBar.Value = percent; _lblStatus.Text = percent + "%"; } } public class Win32Window : IWin32Window { public Win32Window(int hwnd) { Handle = new IntPtr(hwnd); } public Win32Window(IntPtr handle) { Handle = handle; } public IntPtr Handle { get; private set; } }