Reputation:
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.
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.
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
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