不久前,我正在为我为信使编写的一些代码添加一个 UI。除了我尝试显示来自不同线程的新消息之外,它似乎一切正常。

程序是这样运行的:

  • MainWindow 被创建和实例化。 MainWindow 的构造函数:
    public MainWindow()
    {
        InitializeComponent();
    
        _messageContainer = (StackPanel)FindName("Messages");
        _messageStatus = (StatusBarItem)FindName("MessageStatus");
        _messageCountElement = (StatusBarItem)FindName("MessageCount");
        _messageBox = (TextBox)FindName("MessageToSend");
        _sendButton = (Button)FindName("SendMessage");
    
        _ipAddress = GetIPAddress();
        try
        {
            Client.Initialize(_ipAddress, this);
            _sendButton.IsEnabled = true;
            _messageBox.IsEnabled = true;
        }
        catch (Exception e)
        {
            DisplayMessage("Could not connect to " + _ipAddress.ToString() + ". The error given was '" + e.Message + "'.", "Server", Colors.Red);
        }
    }
    
  • 发送客户端(Client 类)使用 Client.Initialize 构造函数中的 MainWindow 进行初始化。 Client.Initialize :
    public static void Initialize(IPEndPoint endPoint, MainWindow window)
    {
        windowInstance = window;
        client = new TcpClient();
        client.ReceiveTimeout = 500;
        listenerThread = new Thread(new ParameterizedThreadStart(Receiver.Start));
        listenerThread.Start((object)new StartupData(endPoint, windowInstance));
        client.Connect(endPoint);
    }
    
  • 监听器线程从 Client.Initialize 开始。监听器线程的 Start 方法又长又复杂,但工作正常。它归结为调用另一个方法 ProcessMessage 来处理它接收到的内容。 ProcessMessage :
    public static void ProcessMessage(string response)
    {
        response = response.Trim();
    
        MessageBox.Show(response);
    
        if (response.StartsWith("[Message]"))
        {
            MessageBox.Show("Message");
            response = response.Substring(9);
    
            int openIndex = response.IndexOf("<");
            int closeIndex = response.IndexOf(">");
    
            if (openIndex < 0 || closeIndex < 0 || closeIndex < openIndex)
            {
                throw new FormatException("Could not find ID tag in message");
            }
    
            int diff = closeIndex - openIndex;
            int id = Int32.Parse(response.Substring(openIndex + 1, diff - 1));
            if (id != Client.GetClientId())
            {
                MessageBox.Show("Them");
                string message = response.Substring(closeIndex + 1);
                window.DisplayMessage(message, "Them", Colors.Yellow);
            }
            else
            {
                MessageBox.Show("You");
                string message = response.Substring(closeIndex + 1);
                window.DisplayMessage(message, "You", Colors.Blue);
            }
        }
        else if (response.Length == 5 && response.StartsWith("[") && response.EndsWith("]"))
        {
            MessageBox.Show("Response code");
            Client.HandleResponse(ResponseCodes.GetResponse(response));
        }
        else
        {
            try
            {
                Int32.Parse(response);
                MessageBox.Show("ID");
                Client.SetClientId(Int32.Parse(response));
                return;
            }
            catch (Exception)
            {
                window.DisplayMessage(response, "Server", Colors.Red);
            }
        }
    }
    
  • 调用 DisplayMessage 方法。 DisplayMessage :
    public void DisplayMessage(string message, string name, Color nameColor)
    {
        MessageBox.Show("Called");
    
        UpdateMessageStatus(ProcessingStatus.Processing);
    
        Grid fullMessage = new Grid();
        fullMessage.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(50.00) });
        fullMessage.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(600.00) });
    
        Label nameLabel = new Label
        {
            Content = string.Format("[{0}]", name),
            Foreground = new SolidColorBrush(nameColor)
        };
        Grid.SetColumn(nameLabel, 0);
    
        TextBlock textLabel = new TextBlock
        {
            Text = message,
            TextWrapping = TextWrapping.Wrap,
            Margin = new Thickness(0, 5, 0, 5)
        };
        Grid.SetColumn(textLabel, 1);
    
        fullMessage.Children.Add(nameLabel);
        fullMessage.Children.Add(textLabel);
    
        UpdateMessageStatus(ProcessingStatus.Displaying);
        Dispatcher.BeginInvoke(new Action(delegate()
        {
            _messageContainer.Children.Add(fullMessage);
        }));
    
        _messageCount += 1;
        UpdateMessageCount();
        UpdateMessageStatus(ProcessingStatus.Ready);
    }
    

  • 这是问题所在。当我从监听器线程调用 window.DisplayMessage 时,实际上什么也没发生。甚至不是一个异常(exception) - 但重要的是,消息 Grid 没有被创建。

    这是怎么回事?我根据另一个 SO 问题的建议添加了 Dispatcher.BeginInvoke 以确保线程所有权不是问题,尽管在此之前发生了同样的事情。

    (注意:所有 MessageBox es 仅用于调试。有趣的是,当从监听器线程调用时,MessageBox 顶部的 DisplayMessage 确实显示。)

    最佳答案

    我确实怀疑这是跨线程 UI 元素调用的问题。由于您是在不同的线程上创建新的 UI 元素( fullMessage 及其子元素)。

    如果您的 window 实例是 WPF UserControl 或 WPF Window ,您可以使用它的同步上下文来执行跨线程编码(marshal)处理。

  • 在您的 MainWindow 构造函数中捕获其同步上下文

    _syncContext = SynchronizationContext.Current;
  • 添加一个使用捕获的上下文的方法,以编码到正确的调度程序。

    public void InvokeDisplayMessage(string message, string name, Color nameColor)
    {
    //本着已经开始的消息框调试精神;-)
    MessageBox.Show("调用显示消息");
    this._syncContext.Post(
    new SendOrPostCallback(x => DisplayMessage(message, name, nameColor)),
    空值);
    }
  • 最后将 window.DisplayMessage 中的所有 ProcessMessage 调用更改为window.InvokeDisplayMessage
  • 关于c# - 为什么不创建此 Grid/TextBlock?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/30017352/

    10-10 06:14