【问题标题】:writing in textbox from anoter class (on another thread)在另一个类的文本框中写入(在另一个线程上)
【发布时间】:2013-02-08 20:35:20
【问题描述】:

我需要在我的程序中创建类来启动一系列事件以在文本框中写入文本。 我知道班级不应该知道表格。怎么做?考虑到我将在另一个线程上安装该类。

我已经尝试过创建一个接口,该接口可以使用 ref 参数在表单上连接类和 make 方法。

*更新:*你们都误解了我的意思——我说的是不在编程中的事件。我需要的只是将文本从另一个类添加到文本框。我添加了“事件链”来从类似问题中定义它,在它们中,他们试图直接从课堂上更改文本。对不起。

【问题讨论】:

    标签: c# .net winforms multithreading class


    【解决方案1】:

    这里有两种可能的选择:

    “其他类”需要在“完成”工作后立即更新文本框。如果是这种情况,它不应该知道表单、任何文本框、界面,什么都没有。它应该只返回一个值并让表单使用该返回值来设置一个文本框,或者做任何事情。

    在大多数情况下,这是您想要做的,它是解决此类问题的最简单和最有效的方法。如果没有必要,不要使用更复杂的东西。

    如果在任务“完成”时信息没​​有出现,而是通过周期性间隔出现,那么您可以将 Progress 类与 IProgress 接口一起使用。

    让Form 创建一个实例:Progress<string> progress = new Progress<string>();。让表单为该实例附加一个事件处理程序。请注意,Progress 实例将“捕获”当前同步上下文,这是一种说它在 UI 线程中运行的奇特方式。可以这样做:

    progress.ProgressChanged += (_, data) => textBox1.Text = data;
    

    然后让其他类接受IProgress<string> 实例并定期调用:

    progress.Report(someString);
    

    如果您需要 Progress 和 IProgress 的 4.5 之前的实现,这里有一个可以在 .NET 3.5+ 中编译和运行的实现:

    public interface IProgress<T>
    {
        void Report(T data);
    }
    
    public class Progress<T> : IProgress<T>
    {
        SynchronizationContext context;
        public Progress()
        {
            context = SynchronizationContext.Current
                ?? new SynchronizationContext();
        }
    
        public Progress(Action<T> action)
            : this()
        {
            ProgressReported += action;
        }
    
        public event Action<T> ProgressReported;
    
        void IProgress<T>.Report(T data)
        {
            var action = ProgressReported;
            if (action != null)
            {
                context.Post(arg => action((T)arg), data);
            }
        }
    }
    

    【讨论】:

    • 请注意,IProgress 是 .NET 4.5 的新功能,因此如果您针对的是较早的框架,它将不可用。
    • @ScottChamberlain 是的,虽然它不需要任何较新的语言功能;只需几十行代码,您就可以编写自己的 Progress 类/接口。
    • 非常正确,我只是不想让 OP 在找不到接口的情况下被抓到。
    • @ScottChamberlain 我添加了一个 Progress 的实现,它编译为 3.5,因为我很无聊。
    • @ScottChamberlain SynchronizationContext.Current 可以为空。一个很好的例子是如果您在控制台应用程序中;没有同步上下文。此外,如果您已经在 UI 应用程序的非 UI 线程中(意味着 Progress 对象是由后台线程创建的),则可能没有当前同步上下文。
    【解决方案2】:

    在对您的应用程序了解不够的情况下,我建议您需要在由表单处理的类中调用一个事件。发生的事情是这样的:

    1. 班级有一个事件。
    2. Form 启动并实例化类。
    3. Form 为类中的事件分配一个处理程序。
    4. 类做它需要做的任何事情,直到它到达它需要与表单通信的点。
    5. 班级引发事件。
    6. 表单中的处理程序被执行并且文本框发生变化。

    所以在类的代码中你需要添加一些定义:

    public delegate void FinishedEventHandler(object sender, string ValueToReturn);
    public event FinishedEventHandler Finished;
    

    第一个是带有事件签名的委托。按照惯例,第一个参数始终是对类本身实例的引用,其余的是您要返回的值。第二个是实际事件。

    现在,在对类进行任何处理的函数中,我们需要在适当的时候引发事件:

    void DoSomething()
    {
        .
        .
        .
        if(Finished!=null) Finished(this, "some value");
    }
    

    if 子句用于确保确实有人在处理我们的事件,否则我们可能会遇到异常。

    现在让我们看一下表格。我们需要添加一个处理事件的函数。它需要与我们之前定义的委托具有相同的签名。在该函数中,我们根据返回的值对表单进行所需的任何更改:

    private void FinishedEventHandler(object sender, string ValueToReturn)
    {
        TextBox1.Text = ValueToReturn;
    }
    

    现在我们已经准备好使用我们刚刚创建的所有管道。首先我们将处理程序添加到事件中,然后我们可以调用类的处理函数。

    MyClass.Finished += FinishedEventHandler;
    MyClass.DoSomething();
    

    这里有更详细的教程:

    http://msdn.microsoft.com/en-us/library/aa645739(v=vs.71).aspx

    【讨论】:

    • 实际上所有这些都可以用三行代码来表达,这肯定可以在简短回答的范围内完成。第 6 步没有与之关联的代码,第 2 步和第 4 步实际上是您可以期望 OP 自行解决的问题;他们不需要在你的答案中。不过,这确实是一种很好的使用方法。
    • System 没有提到的一件事是您正在进行跨线程通信,您需要在表单中的事件处理程序内部执行Invoke。
    • 1) 定义事件时,无需定义自己的委托类型。只需使用Action/Func,或者如果您真的有必要使用基于事件的处理程序,您可以使用EventHandler&lt;string&gt;。 2) 对于分配事件处理程序,您可以使用 lambda,这对于这些真正的小事件更有效,尽管命名方法总是可以用于非平凡的处理程序。
    • 如果您处于多线程环境中,请按照您编写事件引发程序的方式进行更新,如果最后一个人在 null 之间的第二个线程上取消订阅您的事件,则可以引发 NullRefrenceException检查和加注。正确的方法是使用临时变量来存储事件委托。请参阅this post from Eric Lippert 了解更多信息。
    • @user1461837 没有人误解你。即使通过接口,其他类也不应该对表单一无所知。在编程意义上,使用事件是解决这个问题的合适方法。以某种方式传递对表单的引用并直接从诸如此类的处理类访问 UI 是非常糟糕的做法,而且几乎没有那么可维护。
    猜你喜欢
    • 2018-03-10
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    • 2017-12-19
    • 2020-04-29
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    相关资源
    最近更新 更多