【问题标题】:C# Backgroundworker: What way to periodically update a PictureBox?C# Backgroundworker:定期更新 PictureBox 的方法是什么?
【发布时间】:2012-09-16 21:16:46
【问题描述】:

我正在使用 BackgroundWorker 定期检查硬件开关。由于它是通过慢速 RS485 网络连接的,我不得不延迟下一次状态更新。 在切换状态更改时,我想更新 OK/nOK 图片框。这被实现为一个绿色的 OK pictureBox 在一个 noOK pictureBox 上。这里没有做任何实际的工作。

为了可扩展性,我决定使用 Backgroundworker。最后我想要一个隐藏的工人,它

  1. 全局提供三个开关的状态和
  2. StatusChange 图片框的更新。

问题描述 一旦启动了 BackgroundWorker,它就会按预期工作。但是 GUI 冻结。

我尝试了什么? MSDN BackgroundWorker Class Note 1 说,GUI 应该通过 ProgressChanged 更新。我试图通过 Worker_Switch.ReportProgress(fakeProgress++) 引发此事件但失败了。 PictureBox 不再更新。

设计师的片段

this.Worker_Switch = new System.ComponentModel.BackgroundWorker();
// 
// Worker_Switch
// 
this.Worker_Switch.WorkerSupportsCancellation = true;
this.Worker_Switch.DoWork += new System.ComponentModel.DoWorkEventHandler(this.Worker_Switch_DoWork);

来自主窗体的片段

delegate void SetEventCallback(object sender, DoWorkEventArgs e);   // Threadsafe calls for DoWork

private void btnBackgroundworker_Click(object sender, EventArgs e)
{
    if (!Worker_Switch.IsBusy)
    {
        Worker_Switch.RunWorkerAsync();                              
    }
}

private void Worker_Switch_DoWork(object sender, DoWorkEventArgs e)
{
    // Worker Thread has no permission to change PictureBox "pictureBoxSwitchrightOK"
        // Therefore this method calls itsself in the MainThread, if necessary.
    while (!Worker_Switch.CancellationPending)
    {
      if (this.pictureBoxSwitchrightOK.InvokeRequired)              // Worker Thread
    {
            System.Threading.Thread.Sleep(400);
        SetEventCallback myCall = new SetEventCallback(Worker_Switch_DoWork);
        this.Invoke(myCall, new object[] { sender, e });
    }
    else                                                            // Main Thread
    {
        // Turns OK Picture Box invisible, if nOk State (Switch pushed)
        pictureBoxSwitchrightOK.Visible = SwitchOK("right");  // true: OK (green)
        this.Refresh();

    }
}

private bool SwitchOK(string rightOrLeft)               // select one of the switches
{ (...)}                                    // gets hardware switch status

编辑:特别感谢 laszlokiss88(3 种可能性)和 JMK(为了简单起见,使用工具箱中的 System.Windows.Forms Timer)

Toolbox 中的这种替代方法也有效:

this.timer_Switch.Enabled = true;
    this.timer_Switch.Interval = 400;
    this.timer_Switch.Tick += new System.EventHandler(this.timer_Switch_Tick);

    private void timer_Switch_Tick(object sender, EventArgs e)
{
    motorSwitchControl.Init();        // globally available Switch status                                               
    SwitchRight = SwitchOK("right");
    SwitchRightOK.Visible = SwitchRight; 

    SwitchLeft = SwitchOK("left");    // globally available Switch status
    SwitchLeftOK.Visible = SwitchLeft;  
    SwitchAllOK = SwitchRight & SwitchLeft;
    this.Refresh();
}

a) Sleep() 实际上发生在工作线程中是否正确? - 没有主线程

b) 如果我在 DoWork 中操作用户界面对象,会出现什么问题? (与 MSDN 注释相反) - 在主线程中工作?

c) 定期更新 PictureBox 的正确方法是什么? DoWork、ProgressChanged、RunWorkerCompleted...? - 来自 laszlokiss88 答案的三种可能性。

【问题讨论】:

  • 如果你想在线程中操作控件,那么你需要 Dispatcher 类来做到这一点。
  • 不一定。 ProgressChanged 事件处理程序在创建 BackgroundWorker 的线程上执行(很可能是 UI 线程)。
  • 我建议去代理。只需在 Google 上搜索“代表更新 ui”
  • Windows 窗体有它自己的线程安全计时器。你不能用它来检查你的交换机的状态,然后根据需要更新图片框吗?
  • 有多个定时器,System.Timers 中的一个在单独的线程上运行,System.Windows.Forms 中的一个与 UI 在同一个线程中运行,从而更容易更新 UI 元素.如果您的应用程序是 Windows 窗体,我将使用工具箱中的 Windows 窗体计时器,并检查和更新 Timer_Tick 事件中的图片框

标签: c# .net multithreading backgroundworker picturebox


【解决方案1】:

您可以通过 Dispatcher 或 Control.Begininvoke(winforms) 从 DoWork 事件更新 UI,也可以通过 BackgroundWorker 的 ProgressChanged 事件更新 UI:

    public MainWindow()
    {
        InitializeComponent();

        var bw = new BackgroundWorker();
        bw.WorkerReportsProgress = true;
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.RunWorkerAsync();
    }

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // You are in the main thread
        // Update the UI here
        string data = (string)e.UserState;
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        // You are in a worker thread
        (sender as BackgroundWorker).ReportProgress(0, "right");
    }

【讨论】:

    【解决方案2】:

    首先,您几乎不需要让活动的背景广告进入睡眠状态。我也不确定您为什么要以这种方式构建/定义委托,尝试类似

    public delegate void UpdatePictureBox();
    myDelegate = new UpdatePictureBox(UpdatePictureboxMethod);
    

    那么你有一个方法UpdatePictureBoxMethod

    private void UpdatePictureBoxMethod()
    {
        this.pictureBox1.Image = Properties.Resources.SomeImage;
    }
    

    或类似的东西,你传入要更新到的图像。

    您也可以使用(bgWorker as BackgroundWorker).ReportProgress(progress, object); 方法。所以从你调用的后台线程

    (bgWorker as BackgroundWorker).ReportProgress(progressBarValue, infoBall);
    

    IfoBall 类将保存您所有的重要信息

    class InfoBall
    {
        public int nProgressBar { get; set; } 
        public int nMaxProgressBar { get; set; }
        public Image image { get; set; }
    }
    

    然后您可以将此对象传递回 UI 线程并进行更新

    void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // On UI thread.
        InfoBall someBall = (InfoBall)e.UserState;
        this.pictureBox1.Image = someBall.image;
        // etc...
    }
    

    我希望这会有所帮助。

    【讨论】:

    • 我正在通过慢速 RS485 网络获取硬件开关信息。我是 C# 新手,但我认为延迟到下一个切换状态请求是个好主意?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多