从另一个类和线程的接口回调更新WinForm

4

我有一个winform和一个接口回调,不断发送更新。我希望能够从回调接口更新label1.Text。但是,由于接口在单独的线程上运行,因此我认为无法直接执行此操作,因此我尝试使用委托和invoke。

然而,我遇到了一个错误:

在窗口句柄创建之前,无法在控件上调用Invoke或BeginInvoke - 在

 form1.Invoke(form1.myDelegate, new Object[] { so.getString() });

这是完整的代码。

     public partial class Form1 : Form
     {

         MyCallBack callback;
         public delegate void UpdateDelegate(string myString);
         public UpdateDelegate myDelegate;

         public Form1()
         {
             InitializeComponent();

             myDelegate = new UpdateDelegate(UpdateDelegateMethod);
             callback = new MyCallBack(this);
             CallBackInterfaceClass.SetCallBack(callback);

            callback.OnUpdate();
        }

         public void UpdateDelegateMethod (String str)
         {
             label1.Text = str;
         }
     }

     class MyTestCallBack : Callback
     {
         public Form1 form1;
         public SomeObject so;

         public MyTestCallBack(Form1 form)
         {
             this.form1 = form;
         }

         public void OnUpdate(SomeObject someobj)
         {
             so = someobj;
             OnUpdate();
         }

         public void OnUpdate()
         {
             form1.Invoke(form1.myDelegate, new Object[] { so.getString() });
         }

     }

两个问题。

  1. 有人能解释一下我做错了什么吗?

  2. 这是做这件事的最佳方法吗?

以下是基于 bokibeg 的回复(见下文)进行修改后的答案,以使其正常工作:

public partial class Form1 : Form {
MyTestCallBack _callback;

public Form1()
{
    InitializeComponent();

    _callback = new MyTestCallBack();
    _callback.MyTestCallBackEvent += callback_MyTestCallBackEvent;
    _callback.OnUpdate();
}

private callback_MyTestCallBackEvent(MyTestCallBackEventArgs e)
{
     if (InvokeRequired)
     {
        Invoke(new Action(() =>
        {
            callback_MyTestCallBackEvent(sender, e);
        }));
        return;
     }
     label1.Text = e.SomeObject.GetDisplayString();
}

class MyTestCallBackEventArgs : EventArgs 
{
     public SomeObject SomeObj { get; set; } 
}

class MyTestCallBack : Callback 
{
    public event EventHandler<MyTestCallBackEventArgs> MyTestCallBackEvent;
    private SomeObject _so;

protected virtual void OnMyTestCallBackEvent(MyTestCallBackEventArgs e)
{
    if (MyTestCallBackEvent != null)
        MyTestCallBackEvent(this, e);
}

public void OnUpdate(SomeObject someobj)
{
    _so = someobj;
    OnMyTestCallBackEvent(new MyTestCallBackEventArgs { SomeObject = _so });
} }

4
如果我是你,我不会调用表单的代理,而会采用另一种方式。Callback 派生类应该简单地引发一个事件,而你的表单应该订阅该事件。我不知道 MyTestCallBack 在哪里实例化,但如果它很可能在 Form1 中被实例化,那么你就更应该使用事件。编辑:好的,我看到它在哪里被实例化了,所以是的,我肯定会从我的回调类中触发事件,而不是你试图在上面的代码中做的事情。 - bokibeg
1个回答

1
这是我会做的事情:
public partial class Form1 : Form
{
    MyTestCallBack _callback;

    public Form1()
    {
        InitializeComponent();

        _callback = new MyTestCallBack();
        _callback.MyTestCallBackEvent += callback_MyTestCallBackEvent;
        _callback.OnUpdate();
    }

    private callback_MyTestCallBackEvent(MyTestCallBackEventArgs e)
    {
        // TODO: Invoke code here with e.g. label1.Text = e.SomeObj.ToString()...
    }
}

class MyTestCallBackEventArgs : EventArgs
{
    public SomeObject SomeObj { get; set; }
}

class MyTestCallBack : Callback
{
    public event EventHandler<MyTestCallBackEventArgs> MyTestCallBackEvent;
    private SomeObject _so;

    public MyTestCallBack()
    {
        //
    }

    protected virtual void OnMyTestCallBackEvent(MyTestCallBackEventArgs e)
    {
        if (MyTestCallBackEvent != null)
            MyTestCallBackEvent(e);
    }

    public void OnUpdate(SomeObject someobj)
    {
        _so = someobj;
        OnMyTestCallBackEvent(new MyTestCallBackEventArgs { SomeObject = _so });
    }
}

它将GUI逻辑与线程正在执行的任何内容分离开来。它触发了一个事件,而这个窗体可以随心所欲地处理它。

我不确定这是否编译通过,我是在记事本中编写的。如果您有问题,请告诉我。

您可能刚学习委托并被它带走了,这很类似,因为它使用了一个事件,但是这个事件是正确地放置在“后端”逻辑中- 窗体可以使用也可以不使用它。另请注意,窗体的代码要好得多,它没有太多的样板代码来实现一些后台服务类。

然而,请注意,MyTestCallBackEvent事件可能会在关闭窗体后继续触发,因此请确保在窗体关闭或释放(或者在您认为窗体不再需要它的任何时间)时取消订阅它。

哦,我差点忘了:您最初遇到的错误是因为在不需要调用Invoke的情况下调用了它,并且窗体绝对没有准备好。阅读此问题以了解 如何安全地调用控件


你好,有几个问题:我在if语句中添加了"this",使得OnMyTestCallBackEvent方法中的MyTestCallBackEvent(this, e)可以编译通过。但是当我尝试将label1.Text设置为e.SomeObject.ToString()时,出现了"跨线程操作无效: 从非创建控件线程访问控件 'label1'"的错误提示。 - azuric
我确实理解你的观点,这种方式比委托方法更清晰。 - azuric
1
如果 (InvokeRequired) { Invoke(new Action(() => { callback_MyTestCallBackEvent(sender, e); })); return; } 解决了这个问题,我会更新我的答案。 - azuric
顺便说一句,非常感谢。 - azuric
1
@azuric 没问题,// TODO: Invoke code here 是你应该放置 if (InvokeRequired) { Invoke(...) } 代码的地方。无论哪种方式,请确保调用 InvokeRequired 或者更好的方法是使用我在帖子最后一段中提供链接中可以找到的辅助函数。 - bokibeg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接