在WPF中使用BackgroundWorker更新UI

我目前正在编写一个简单的WPF 3.5应用程序,该应用程序利用SharePoint COM调用SharePoint站点并生成组和用户信息。 由于此过程需要一段时间,因此我希望在生成组时显示ProgressBar。 期望的过程如下:

  1. 用户输入url并单击按钮以获取站点数据。
  2. ProgressBar开始动画
  3. 生成组并将名称添加到ListView
  4. 完成后,ProgressBar动画结束

我遇到的问题是UI永远不会更新。 ProgressBar或ListView都不做任何更改。 如果有人有任何想法来帮助下面的代码,将不胜感激。

private void GetGroupsAndUsersButton_Click(object sender, RoutedEventArgs e) { siteUrl = ""; if (SiteURLTextBox.Text.Length > 0) { FetchDataProgressBar.IsIndeterminate = true; mWorker = new BackgroundWorker(); mWorker.DoWork += new DoWorkEventHandler(worker_DoWork); mWorker.WorkerSupportsCancellation = true; mWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); mWorker.RunWorkerAsync(); } else { System.Windows.MessageBox.Show("Please enter a URL for the SharePoint site you wish to retrieve data"); } } private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { siteUrl = SiteURLTextBox.Text; GroupListView.ItemsSource = null; try { using (SPSite site = new SPSite(siteUrl)) { SPWeb web = site.OpenWeb(); SPGroupCollection collGroups = web.SiteGroups; if (GroupNames == null) GroupNames = new List(); foreach (SPGroup oGroup in collGroups) { GroupListView.Items.Add(new ListViewItem() { Content = oGroup.Name }); } foreach (ListViewItem item in GroupListView.Items) { item.MouseLeftButtonUp += item_MouseLeftButtonUp; } } } catch (Exception ex) { System.Windows.MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl); } } private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) { FetchDataProgressBar.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action( delegate() { FetchDataProgressBar.IsIndeterminate = false; } )); } 

首先,您需要支持ProgressChanged事件。 将BackgroundWorker初始化更新为:

 GroupListView.ItemSource = null; mWorker = new BackgroundWorker(); mWorker.DoWork += new DoWorkEventHandler(worker_DoWork); mWorker.WorkerSupportsCancellation = true; mWorker.WorkerReportsProgress = true; mWorker.ProgressChanged += OnProgressChanged; mWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted); mWorker.RunWorkerAsync(SiteURLTextBox.Text); 

之后,您必须添加一个OnProgressChanged处理程序:

 private void OnProgressChanged(object sender, ProgressChangedEventArgs e) { FetchDataProgressBar.Value = e.ProgressPercentage; ListViewItem toAdd = (ListViewItem)e.UserState; toAdd.MouseLeftButtonUp += item_MouseLeftButtonUp; GroupListView.Items.Add(toAdd); } 

因此,您必须更改您的DoWork

 private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { BackgroundWorker worker = (BackgroundWorker)sender; try { using (SPSite site = new SPSite((String)e.Argument)) { SPWeb web = site.OpenWeb(); SPGroupCollection collGroups = web.SiteGroups; if(GroupNames == null) GroupNames = new List(); int added = 0; foreach(SPGroup oGroup in collGroups) { added++; ListViewItem tmp = new ListViewItem() { Content = oGroup.Name }; worker.ReportProgress((added * 100)/collGroups.Count,tmp); } } } catch (Exception ex) { MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl); } } 

那是因为你不允许在DoWork上更改GUI。

之后,每个ListViewItem都单独添加到ListView 。 我还建议您将URL作为参数传递给RunWorkerAsync

编辑 :向OnProgressChanged添加百分比。

在您的DoWork方法中,您正在操作背景线程中的代码中的WPF控件,您不应该这样做。 实际上,您应该收到“无法从其他线程访问控制”等错误。 可能这些exception是由catch-allerror handling程序捕获的,甚至MessageBox也无法在后台线程中运行。

作为快速修复,您必须创建siteURL和collGroups类字段,将使用块之前的所有内容移动到GetGroupsAndUsersButton_Click方法,并将所有内容从第一个foreach循环开始循环到RunworkerCompleted事件,以便访问控件的所有代码都在UI线程。

你应该改变的另一件事是你不应该在代码中创建ListViewItems,而是使用DataTemplate代替……但这与你的问题无关。

你需要:

 mWorker.WorkerReportsProgress = true; mWorker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); 

然后在你的DoWork你需要打电话:

 var worker = (BackgroundWorker)sender; worker.ReportProgress(progressAmount); 

这里有好的例子: http : //msdn.microsoft.com/en-us/library/cc221403(v = vs.95).aspx