C# 更新GUI时冻结

C# 更新GUI时冻结,c#,winforms,C#,Winforms,我有一个带有标题、标签和OK按钮的警报框。每当它弹出时,它应该重复使用同一个窗口,而不是打开一个新窗口 我从外部线程接收到一个对话框事件。它可以接收已知过程之外的消息。每次消息到达时,我都要调用Show() 但是,当调用Show(text,title)时,如下所示: internal class Driver { private readonly AlertBox _alertBox = AlertBox.Instance; public void Initialize()

我有一个带有标题、标签和OK按钮的警报框。每当它弹出时,它应该重复使用同一个窗口,而不是打开一个新窗口

我从外部线程接收到一个对话框事件。它可以接收已知过程之外的消息。每次消息到达时,我都要调用
Show()

但是,当调用
Show(text,title)
时,如下所示:

internal class Driver
{
    private readonly AlertBox _alertBox = AlertBox.Instance;
    public void Initialize()
    {
        // Receive dialog event.
        _connection.OnDialogReceived += (text, title) =>
        {
            _alertBox.Show(text, title);
        };
    }
}
此时将显示警报框,设置窗口标题并冻结。请注意,按钮已不可见

我已尝试使用
调用
。它冻结的结果完全相同

    public void ShowWithInvoke(string text, string title)
    {
        if (!Created)
        {
            CreateControl();
        }

        if (!IsHandleCreated)
        {
            CreateHandle();
        }

        Invoke((MethodInvoker)delegate
        {
            Show();
            BringToFront();
            Text = title;
        });

        if (!MessageLabel.Created || !MessageLabel.IsHandleCreated)
        {
            MessageLabel.CreateControl();
        }

        MessageLabel.Invoke((MethodInvoker)delegate
        {
            MessageLabel.Text = text;
        });
    }

解决此问题的常用方法是将发送警报的代码与显示警报的代码解耦

首先,您应该创建一个类,该类的唯一目的是将更新传递给正在侦听的任何UI

public class MessageUpdater
{
    public event EventHandler<string> MessageSent;
    public void SendMessage(string message)
    {
        this.MessageSent?.Invoke(this, message);
    }
}
这里有两个棘手的部分:处理新的
MessageUpdater
的附件(以及删除现有的附件),然后在需要时将调用编组到UI线程

我为测试这一点而创建的代码相当简单

        var mu = new MessageUpdater();

        var counter = 0;
        var timer = new System.Threading.Timer((System.Threading.TimerCallback)(x =>
        {
            mu.SendMessage((counter++).ToString());
        }), null, TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0));

        var ab = new AlertBox();
        ab.MessageUpdater = mu;
        ab.ShowDialog();

这里比较棘手的部分是
系统.Threading.Timer
,我用它将消息推送到非UI线程上的
消息更新程序
,这样
.ShowDialog
就不会冻结。

“每当它弹出时,它都应该重复使用同一个窗口,而不是打开新窗口。”-确切地说,为什么?警报消息通常显示为模式对话框。从何处触发OnDialogReceived事件?可能是后台线程?@nyrguds,它向用户显示一些输出,并且它可以在短时间内发送许多消息。@但是,它是在外部API调用的方法中调用的。我相信这是另一个线索。我推荐一个状态栏或其他东西,然后。。。或者一个日志窗口。一个窗口显示最新的信息似乎是无用的。无论如何,您是否尝试过在单独的线程上运行该对话框?能否编辑您的问题,并将您的代码与我的代码放在您现有问题之后?在您的示例中,move
ab.Show()编码到计时器回调中。我已经用当前状态更新了问题。@Jam-您不能将
ab.Show()
移动到计时器回调中-它在错误的线程上。您需要在UI线程上调用
.Show()
public partial class AlertBox : Form
{
    public AlertBox()
    {
        InitializeComponent();
    }

    private MessageUpdater _messageUpdater = null;

    public MessageUpdater MessageUpdater
    {
        set
        {
            if (_messageUpdater != null)
            {
                _messageUpdater.MessageSent -= UpdateMessage;
            }
            if (value != null)
            {
                _messageUpdater = value;
                _messageUpdater.MessageSent += UpdateMessage;
            }
        }
    }

    private void UpdateMessage(object sender, string message)
    {
        if (this.InvokeRequired)
        {
            this.Invoke((Action)(() => this.UpdateMessage(sender, message)));
        }
        else
        {
            this.MessageLabel.Text = message;
        }
    }
}
        var mu = new MessageUpdater();

        var counter = 0;
        var timer = new System.Threading.Timer((System.Threading.TimerCallback)(x =>
        {
            mu.SendMessage((counter++).ToString());
        }), null, TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0));

        var ab = new AlertBox();
        ab.MessageUpdater = mu;
        ab.ShowDialog();