WPF项目通过Dispatcher.BeginInvoke更新ProgressBar值不显示问题
问题根源
- 你在非UI线程中直接修改
ProgressBarProcess的Minimum、Maximum属性,以及最后直接修改insertButton、clearButton的IsEnabled属性,属于跨线程访问UI控件,会触发异常导致后续逻辑终止,这是进度条不工作的核心原因。 - 后台线程执行的
fillExcelLists方法中直接读取partNoComboBox、dataStartRowTextBox等UI控件的属性,同样属于跨线程访问,会触发异常。 - WinForm对跨线程访问的校验默认比WPF宽松很多,所以旧代码能正常运行,迁移到WPF后就会失效。
修复代码
以下是最小改动即可生效的代码版本:
1. 修改Process按钮点击事件
private void ProcessButton_Click(object sender, RoutedEventArgs e) { if (targetBOMTextBox.Text.Length != 11) { MessageBox.Show("Target BOM part number missing or incorrect format.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } // 提前在UI线程读取所有需要的UI参数,不要传到后台线程读取 var partNoCol = partNoComboBox.SelectedItem.ToString(); var quantityCol = quantityComboBox.SelectedItem.ToString(); var designatorCol = designatorsComboBox.SelectedItem.ToString(); var startRow = Convert.ToInt16(dataStartRowTextBox.Text); var endRow = Convert.ToInt16(dataEndRowTextBox.Text); // 提前在UI线程设置进度条基础属性 ProgressBarProcess.Minimum = 1; ProgressBarProcess.Maximum = 5; ProgressBarProcess.Visibility = Visibility.Visible; ProgressBarProcess.Value = 0; Task.Factory.StartNew(() => ProcessButtonFunction(partNoCol, quantityCol, designatorCol, startRow, endRow)); }
2. 修改ProcessButtonFunction方法
private void ProcessButtonFunction(string partNoCol, string quantityCol, string designatorCol, int startRow, int endRow) { // 所有UI操作都走Dispatcher Dispatcher.Invoke(() => ProgressBarProcess.Value = 1); fillExcelLists(partNoCol, quantityCol, designatorCol, startRow, endRow); Dispatcher.Invoke(() => ProgressBarProcess.Value = 2); convertTofloatList(); Dispatcher.Invoke(() => ProgressBarProcess.Value = 3); splitDesignatorList(); Dispatcher.Invoke(() => ProgressBarProcess.Value = 4); fillRecordsList(); Dispatcher.Invoke(() => { ProgressBarProcess.Value = 5; insertButton.IsEnabled = true; clearButton.IsEnabled = true; // 执行完可自行添加隐藏进度条的逻辑 }); }
3. 修改fillExcelLists方法
去掉方法内的UI控件访问逻辑,改用传入的参数:
private void fillExcelLists(string partNoCol, string quantityCol, string designatorCol, int startRow, int endRow) { ExcelAccess ExcelAccessOBJ = new ExcelAccess(); ExcelAccessOBJ.ExcelReadColumn(partNoCol, startRow, endRow, GlobalData.excelPartNoList); ExcelAccessOBJ.ExcelReadColumn(quantityCol, startRow, endRow, GlobalData.excelQuantityList); ExcelAccessOBJ.ExcelReadColumn(designatorCol, startRow, endRow, GlobalData.excelDesignatorList); }
4. 可选优化:修复Excel进程残留问题
你当前的Excel操作代码如果中途抛异常,会导致Excel进程残留在任务管理器,占用文件锁,建议修改ExcelAccess类的实现:
public class ExcelAccess { public void ExcelReadColumn(string column, int rowStart, int rowEnd, List<String> saveData) { Microsoft.Office.Interop.Excel.Application ExcelObj = null; Microsoft.Office.Interop.Excel.Workbook theWorkbook = null; try { ExcelObj = new Microsoft.Office.Interop.Excel.Application(); theWorkbook = ExcelObj.Workbooks.Open(GlobalData.excelFilenameAndPath, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); Microsoft.Office.Interop.Excel.Worksheet sheet = (Microsoft.Office.Interop.Excel.Worksheet)theWorkbook.Worksheets.get_Item(1); for (int i = rowStart; i <= rowEnd; i++) { var data_range = sheet.get_Range(column + i.ToString(), column + i.ToString()); string tempString = Convert.ToString(data_range.Cells.Value2); if (!string.IsNullOrEmpty(tempString)) { saveData.Add(tempString); } // 即时释放COM对象 System.Runtime.InteropServices.Marshal.ReleaseComObject(data_range); } } finally { if (theWorkbook != null) { theWorkbook.Close(); System.Runtime.InteropServices.Marshal.ReleaseComObject(theWorkbook); } if (ExcelObj != null) { ExcelObj.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(ExcelObj); } } } }
实现优化建议
- 所有UI控件的读写操作必须在UI线程执行,WPF对跨线程访问的校验没有宽松容错空间,不要在后台线程直接操作任何UI属性。
- 后台线程执行逻辑前,提前把所有需要的UI参数读取出来传递到后台方法,避免后续重复写Dispatcher调用。
- 简单场景下用Dispatcher更新UI即可,如果你后续需要更清晰的结构,可以用
IProgress<T>接口实现进度通知,不用在业务代码里到处写Dispatcher调用。
内容的提问来源于stack exchange,提问作者Rodo
相关产品推荐
相关产品推荐

