如何从另一个线程更新GUI上的文本框

12 浏览
0 Comments

如何从另一个线程更新GUI上的文本框

这个问题已经有答案了:

如何从另一个线程更新GUI?

我是C#的新手,我正在努力制作一个简单的客户端服务器聊天应用程序。

我在客户端窗体上有一个RichTextBox控件,我正在尝试从另一个类中的服务器更新该控件。 当我尝试执行此操作时,我会收到错误:“跨线程操作无效:从创建它的线程以外的线程访问Control textBox1。”

这是我Windows窗体的代码:

private Topic topic;  
public RichTextBox textbox1;  
bool check = topic.addUser(textBoxNickname.Text, ref textbox1, ref listitems);

Topic类:

public class Topic : MarshalByRefObject  
{  
    //Some code
 public  bool addUser(string user, ref RichTextBox textBox1, ref List listBox1)  
 {  
     //here i am trying to update that control and where i get that exception  
     textBox1.Text += "Connected to server... \n";  
}

那么怎么做?如何从另一个线程更新文本框控件?


我正在尝试使用.net remoting制作一些基本的聊天客户端/服务器应用程序。

我想将Windows窗体客户端应用程序和控制台服务器应用程序作为单独的.exe文件。在这里,我正在尝试从客户端调用服务器函数AddUser,并且我希望AddUser函数更新我的GUI。我按照您建议的Jon进行了修改,但现在我没有跨线程异常,我得到了这个异常...“SerializationException:程序集中的Topic类型未标记为可序列化”。

我将我的整个代码张贴在下面,将尽可能简单。

任何建议都受欢迎。非常感谢。

服务器:

  namespace Test
{
    [Serializable]
    public class Topic : MarshalByRefObject
    {
        public bool AddUser(string user, RichTextBox textBox1, List listBox1)
        {
            //Send to message only to the client connected
            MethodInvoker action = delegate { textBox1.Text += "Connected to server... \n"; };
            textBox1.BeginInvoke(action);
            //...
            return true;
        }
        public class TheServer
        {
            public static void Main()
            {
                int listeningChannel = 1099;
                BinaryServerFormatterSinkProvider srvFormatter = new BinaryServerFormatterSinkProvider();
                srvFormatter.TypeFilterLevel = TypeFilterLevel.Full;
                BinaryClientFormatterSinkProvider clntFormatter = new BinaryClientFormatterSinkProvider();
                IDictionary props = new Hashtable();
                props["port"] = listeningChannel;
                HttpChannel channel = new HttpChannel(props, clntFormatter, srvFormatter);
                // Register the channel with the runtime            
                ChannelServices.RegisterChannel(channel, false);
                // Expose the Calculator Object from this Server
                RemotingConfiguration.RegisterWellKnownServiceType(typeof(Topic),
                                                    "Topic.soap",
                                                    WellKnownObjectMode.Singleton);
                // Keep the Server running until the user presses enter
                Console.WriteLine("The Topic Server is up and running on port {0}", listeningChannel);
                Console.WriteLine("Press enter to stop the server...");
                Console.ReadLine();
            }
        }
    }
}  

Windows窗体客户端:

// Create and register a channel to communicate to the server
        // The Client will use the port passed in as args to listen for callbacks
        BinaryServerFormatterSinkProvider srvFormatter = new BinaryServerFormatterSinkProvider();
        srvFormatter.TypeFilterLevel = TypeFilterLevel.Full;
        BinaryClientFormatterSinkProvider clntFormatter = new BinaryClientFormatterSinkProvider();
        IDictionary props = new Hashtable();
        props["port"] = 0;
        channel = new HttpChannel(props, clntFormatter, srvFormatter);
        //channel = new HttpChannel(listeningChannel);
        ChannelServices.RegisterChannel(channel, false);
        // Create an instance on the remote server and call a method remotely
        topic = (Topic)Activator.GetObject(typeof(Topic), // type to create
        "http://localhost:1099/Topic.soap" // URI
        );
        private Topic topic;
        public RichTextBox textbox1;
        bool check = topic.addUser(textBoxNickname.Text,textBox1, listitems);

admin 更改状态以发布 2023年5月24日
0
0 Comments

使用Invoke方法

// Updates the textbox text.
private void UpdateText(string text)
{
  // Set the textbox text.
  yourTextBox.Text = text;
}

现在,创建一个与之前定义的方法具有相同签名的委托:

public delegate void UpdateTextCallback(string text);

在线程中,您可以在yourTextBox上调用Invoke方法,传递要调用的委托以及参数。

yourTextBox.Invoke(new UpdateTextCallback(this.UpdateText), 
            new object[]{”Text generated on non-UI thread.”});

0
0 Comments

您需要使用 BackgroundWorker 或者 Control.Invoke/BeginInvoke。使用匿名函数——无论是匿名方法(C# 2.0)还是lambda表达式(C# 3.0),使得这一过程比以前容易了。

在您的情况下,您可以将代码更改为:

public bool AddUser(string user, RichTextBox textBox1, List listBox1)
{
    MethodInvoker action = delegate
         { textBox1.Text += "Connected to server... \n"; };
    textBox1.BeginInvoke(action);
}

需要注意以下几点:

  • 为了符合.NET约定,应将其命名为AddUser
  • 您不需要通过引用传递文本框或列表框。我猜您并不是很理解ref到底意味着什么——请参见我的有关参数传递的文章以获得更多细节。
  • InvokeBeginInvoke之间的区别在于,BeginInvoke在继续之前不会等待UI线程上调用委托,因此在实际更新文本框之前,AddUser可能会返回。如果您不希望出现这种异步行为,请使用Invoke
  • 在许多示例(包括我的一些示例!)中,您会发现人们使用Control.InvokeRequired来确定是否需要调用Invoke/BeginInvoke。在大多数情况下,这实际上是过度的——即使您不需要,调用Invoke/BeginInvoke并不会有任何实质伤害,而且通常处理程序只会从非UI线程调用。省略检查可以使代码更简单。
  • 您还可以使用我之前提到的BackgroundWorker;这特别适用于进度条等情况,但在这种情况下,保持您当前的模型可能同样容易。

有关此及其他线程主题的更多信息,请参见我的线程教程Joe Albahari的线程教程

0