【问题标题】:Why does this code throw an exception? I thought RunWorkerCompleted could access UI elements为什么这段代码会抛出异常?我认为 RunWorkerCompleted 可以访问 UI 元素
【发布时间】:2015-07-16 00:04:51
【问题描述】:

我的 WPF 应用程序中有一个数据网格,当应用程序启动时,我从数据库中获取部署记录并将它们加载到绑定到数据网格的 ObservableCollection 中。

通过计时器,我使用 BackgroundWorker 从数据库中获取任何新记录并将它们放入新的 ObservableCollection。

通过 RunWorkerCompleted,我尝试使用新 ObservableCollection 中的项目更新数据网格。

但是,我收到“System.NotSupportedException”。

附加信息:这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。

我以为我可以从 RunWorkerCompleted 方法访问 UI 控件,但似乎发生了一些我不明白的事情。

我的代码如下:

private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
    {
        //Init the deployment collection
        deployments = DataAccess.GetDeployments();
        dgDeployments.ItemsSource = deployments;

        //Setup the background worker
        bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.WorkerReportsProgress = true;
        bw.WorkerSupportsCancellation = true;

        //Setup timer
        System.Threading.Timer t = new System.Threading.Timer(new System.Threading.TimerCallback(TimerProc));
        t.Change(10000, 0);           
    }

    private void TimerProc(object state)
    {
        //MessageBox.Show("Timer fired!");
        //Get the latest currentTime from the items in the grid
        DateTime? latestTime = DataAccess.GetDeployments().ToList()[0].CurrentTime;
        bw.RunWorkerAsync(latestTime);
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {         
        //Get new records that have changed since that time
        e.Result = (ObservableCollection<Deployment>)DataAccess.GetDeployments((DateTime)e.Argument);
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if(e.Error != null)
        {
            MessageBox.Show(e.Error.Message);
        }
        else if (e.Cancelled)
        {
            MessageBox.Show("The operation was cancelled");
        }
        else
        {
            ObservableCollection<Deployment> newDeployments = (ObservableCollection<Deployment>)e.Result;

            foreach (Deployment d in newDeployments)
            {
                //Remove this new/changed deployment from the collection bound to the datagrid
                int index = deployments.IndexOf(deployments.Where(x => x.UniqueID == d.UniqueID).FirstOrDefault());
                if (index > -1)
                {
                    deployments.RemoveAt(index);
                }

                //Now add the new deployments
                deployments.Add(d);
                deployments.Move(deployments.IndexOf(d), 0);
            }
        }
    }

【问题讨论】:

标签: c# wpf timer datagrid backgroundworker


【解决方案1】:

定时器进程starts后台工作线程在不同的线程中。尝试在 GUI 线程中启动后台工作程序,以便完成的操作将在 GUI 线程上运行,而不是在计时器启动的线程上运行。

Invoke 对 GUI 线程的操作或者只是将计时器设为 DispatchTimer

【讨论】:

  • 谢谢!这么说让灯泡熄灭了。此外,MSDN 关于 DispatcherTimer 的这句话也澄清了很多事情。
  • 如果在 WPF 应用程序中使用 System.Timers.Timer,值得注意的是 System.Timers.Timer 运行在与用户界面 (UI) 线程不同的线程上。为了访问用户界面 (UI) 线程上的对象,有必要使用 Invoke 或 BeginInvoke 将操作发布到用户界面 (UI) 线程的 Dispatcher。使用 DispatcherTimer 而不是 System.Timers.Timer 的原因是 DispatcherTimer 与 Dispatcher 在同一线程上运行,并且可以在 DispatcherTimer 上设置 DispatcherPriority。
【解决方案2】:

感谢大家提供的好信息。它帮助我更好地理解了这个问题。这是我使用的最终为我工作的代码。

Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()
                    {
                        //Remove the old deployment
                        deployments.RemoveAt(index);

                        //Now add the new deployments
                        deployments.Add(d);
                        deployments.Move(deployments.IndexOf(d), 0);
                    });

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多