user9940552
user9940552

Reputation:

Freeze when update GUI

I have an alert box with a title, a label and OK button. Whenever it should pop up, it should re-use the same window, not open a new window.

I receive a dialog event from an external thread. It can receive messages outside of known procedures. I want to call Show() every time a message arrives.

enter image description here

public partial class AlertBox : Form
{
    private static AlertBox instance;
    public static AlertBox Instance => instance ?? (instance = new AlertBox());

    private AlertBox()
    {
        InitializeComponent();
    }

    private void AlertBox_Load(object sender, EventArgs e)
    {
        MessageLabel.Text = "";
        Text = "";
    }

    public void Show(string text, string title)
    {
        Show();
        BringToFront();
        Text = title;
        MessageLabel.Text = text;
    }

    private void OkButton_Click(object sender, EventArgs e)
    {
        MessageLabel.Text = "";
        Text = "";
        Hide();
    }
}

However, when calling Show(text, title) as shown:

internal class Driver
{
    private readonly AlertBox _alertBox = AlertBox.Instance;
    public void Initialize()
    {
        // Receive dialog event.
        _connection.OnDialogReceived += (text, title) =>
        {
            _alertBox.Show(text, title);
        };
    }
}

The alert box shows up, sets window title, and freezes. Note that the button has become invisible.

enter image description here

I have tried to use Invoke. It freezes with the exact same result.

    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;
        });
    }

Upvotes: 0

Views: 589

Answers (1)

Enigmativity
Enigmativity

Reputation: 117144

A common way to solve this problem is to decouple the code that sends the alert from the code that displays the alert.

To start with you should create a class that has the sole purpose in life to pass on an update to whatever UI is listening.

public class MessageUpdater
{
    public event EventHandler<string> MessageSent;
    public void SendMessage(string message)
    {
        this.MessageSent?.Invoke(this, message);
    }
}

It's very simple. It just takes the message and, if there are handlers attached to the event it raises the event.

Now for your AlertBox you just accept a MessageUpdater instance and update the Label whenever the MessageSent event is fired.

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;
        }
    }
}

The two tricky parts here are handling the attachment of a new MessageUpdater (and the removal of an existing one) and then marshalling the calls to the UI thread if need be.

The code I created to test this was fairly simple.

        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();

The tricky part here is the System.Threading.Timer that I used to push messages to the MessageUpdater on a non-UI thread so that the .ShowDialog wouldn't freeze up.

Upvotes: 1

Related Questions