【问题标题】:Updating GUI from different Class and Thread in c#在 c# 中从不同的类和线程更新 GUI
【发布时间】:2016-04-19 09:57:53
【问题描述】:

我是编程新手,只是想知道我遇到的问题的解决方案是否合适。

我想将一个状态(字符串)从一个正在创建 Socket 的类中写入一个文本框,并且该类侦听要接收的数据(在另一个线程中)。

这就是我所做的:

通过单击按钮在 Form.cs 中创建类:

 private void button_Create_Click(object sender, EventArgs e)
    {
        int port;
        Int32.TryParse(textBox_Port.Text, out port);
        ServerSocketClass serverSocket = new ServerSocketClass(port, this);
    }

ServerSocketClass 看起来像:

class ServerSocketClass
{
    Socket ServerSocket;
    Socket Accepted;
    IPEndPoint LocalEndpoint;
    int Port = 1337; // just for fun
    Messenger MainForm;

    public ServerSocketClass(int port, Messenger form)
    {

        MainForm = form;

        if (port != 0)
            Port = port;
        ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        LocalEndpoint = new IPEndPoint(IPAddress.Any, Port);

        MainForm.writeToMessages("Binding Endpoint to Socket...");
        ServerSocket.Bind(LocalEndpoint);

        MainForm.writeToMessages("Starting ServerListener Thread...");
        Thread ServerListenThread = new Thread(startListening);
        ServerListenThread.Name = "ServerListenerThread";
        ServerListenThread.Start();        
    }

    private void startListening()
    {            
        ServerSocket.Listen(5);

        MainForm.writeToMessages("Whaiting for incoming connections...");
        Accepted = ServerSocket.Accept();
        whaitForData();
    }

为了更新表单类中的 GUI,我创建了一个委托和一个带有调用的“更新”方法:

public delegate void writeMessege(string message);    
public writeMessege MessegeDelegate;
public void writeToMesseges(string messege)
    {
        if (InvokeRequired)
        {
            this.Invoke(MessegeDelegate, new object[] { messege });
            return;
        }
        textBox_Messeges.AppendText("SYSTEM: " + messege + "\n");
    }

它有效,但我想知道这是否是一种“有效”的方式,或者我是否应该去开发者地狱;-)

提前致谢

洛克

【问题讨论】:

  • 为什么不使用异步方法?您将如何处理多个客户?
  • 一个接一个 ;-) 只是想知道这种方式是否可以从其他类/线程更新 UI。
  • 不,这不是因为有更好的方法,比如使用 async/await。同样对于套接字,最好使用异步方法,例如 BeginAccept。
  • 不幸的是 Socket 没有任何基于任务的方法,但你可以用Task.FromAsyn 方法包装Begin/End 方法

标签: c# multithreading class user-interface


【解决方案1】:

这是一种完全有效的方法,尽管它是否“正确”在很大程度上取决于上下文 - 您调用它的频率、您想要在其中做什么以及您需要调用它的代码。没有调用有许多不同的方法,但使用 InvokeRequired/Invoke 并没有错——这就是它的用途。您可以只使用一个调用自身的更新方法,这与您的代码几乎相同,但稍微不那么冗长:

public void WriteMessages(string message)
{ 
  if (InvokeRequired)
  { this.Invoke(new Action<string>(WriteMessages), new object[] { message }); }
  else
  { textBox_Messages.AppendText("SYSTEM: " + message + "\n"); }
}

Invoke/InvokeRequired 上已有很多帖子。作为起点,请检查: Isn't blindly using InvokeRequired just bad practice?

【讨论】:

  • 而不是使用 Invoke,可以使用 Progress&lt;T&gt; 类并将消息发布到 UI 线程。混合 UI 和网络代码不是一个好主意。事实上,创建 MVC、MVVM 模式是为了将 UI 与后台发生的任何事情解耦
  • 谢谢,我更怀疑是因为将表单传递给新类,只是为了调用此表单的方法来更新 GUI。
  • 这很有趣,我不知道 Progress。这就是我喜欢堆栈溢出的原因:)
【解决方案2】:

我遇到过类似的情况,我有一个从其他类调用的类,它有许多单独的线程,我必须从所有这些其他线程更新一个特定的表单。因此,在类中创建一个委托和一个事件,并在表单中使用一个处理程序就是答案。所以我想分享它,因为它看起来更简单(即使不一定是更好的解决方案)。

对我有用的解决方案:

  1. 我在班级中创建了一个事件,我想在另一个表单上进行更新。 (当然首先我在课堂上实例化了表单(称为SubAsstToolTipWindow)。

  2. 然后我使用此事件 (ToolTipShow) 在要更新标签的表单上创建事件处理程序。像魅力一样工作。

我使用this description进行更新的类中设计了我自己的代码

public static class SubAsstToolTip
{
    private static SubAsstToolTipWindow ttip = new SubAsstToolTipWindow();
    public delegate void ToolTipShowEventHandler();
    public static event ToolTipShowEventHandler ToolTipShow;

    public static void Show()
    {
        // This is a static boolean that I set here but is accessible from the form.
        Vars.MyToolTipIsOn = true; 
        if (ToolTipShow != null)
        {
            ToolTipShow();
        }
    }

    public static void Hide()
    {
        // This is a static boolean that I set here but is accessible from the form.
        Vars.MyToolTipIsOn = false;
        if (ToolTipShow != null)
        {
            ToolTipShow();
        }
    }
}

然后我的表单中的代码更新了

public partial class SubAsstToolTipWindow : Form
{
    public SubAsstToolTipWindow()
    {
        InitializeComponent();
        // Right after initializing create the event handler that 
        // traps the event in the class
        SubAsstToolTip.ToolTipShow += SubAsstToolTip_ToolTipShow;
    }

    private void SubAsstToolTip_ToolTipShow()
    {
        if (Vars.MyToolTipIsOn) // This boolean is a static one that I set in the other class.
        {
            // Call other private method on the form or do whatever
            ShowToolTip(Vars.MyToolTipText, Vars.MyToolTipX, Vars.MyToolTipY);     
        }
        else
        {
            HideToolTip();
        }

    }

【讨论】:

  • @Locke -- 当然欢迎。如果答案/cmets 有用,您可以而且应该投票。这是感谢并确保其他人获得有用解决方案的最佳方式。最好的,
  • 我不能,因为在我获得 15 个声望点之前不允许这样做:-(
【解决方案3】:

很久以前,但我想让你们都知道我是如何最终解决这个问题的(通过事件解决了 - 当然 ;-)):

我定义了一个 EventArgs 来传递我想传递的所有信息:

public class IncomingMessageEventArgs : EventArgs
{
    private Message _message;
    public Message Message
    {
        get
        {
            return _message;
        }
    }

    public IncomingMessageEventArgs(Message message)
    {
        _message = message;
    }

}    

在发布信息的类(到 WPF - 表单)上定义事件及其处理程序:

public delegate void IncomingMessageEventHandler(object sender, IncomingMessageEventArgs e);
    public event IncomingMessageEventHandler IncomingMessageEvent;
    protected void OnIncomingMessageEvent(IncomingMessageEventArgs e)
    {
        if (IncomingMessageEvent != null)
            IncomingMessageEvent(this, e);
    }

当然,如果需要更新 WPF 表单(也在“信息发送类”上),则引发事件:

 OnIncomingMessageEvent(new IncomingMessageEventArgs(message));

在 WPF 类上,您需要监听事件,但首先定义一个 EventHandler,因为您的信息来自不同的线程! : 私人委托 void writeMessageToChatEventHandler(object sender, IncomingMessageEventArgs e);

现在我们编写方法来处理引发的事件:

// Write to Chat
    private void writeMessageToChat(object sender, IncomingMessageEventArgs e)
    {
        try
        {
            if (!Dispatcher.CheckAccess())
            { 
                Dispatcher.Invoke(new writeMessageToChatEventHandler(writeMessageToChat), new object[] { sender, e } );
                return;
            }
            textBox_Chat.AppendText(e.Message.getFormatedMessageText() + "\n");
        }
        catch (Exception ex)
        {
            writeLogToChat(this, new IncomingLogEventArgs("ERROR: " + ex.Message));
        }
    }

最后,我们当然需要订阅事件(第一种方法,你可以忽略,它只是为了满足 MS 命名约定:

private void ClientSocket_IncomingMessageEvent(object sender, IncomingMessageEventArgs e)
    {
        writeMessageToChat(sender, e);
    }

ClientSocket.IncomingMessageEvent += ClientSocket_IncomingMessageEvent;

希望我能理解这一点:P 感谢所有帮助过我的人!

再见

【讨论】:

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