In-window popups: How to block UI thread? - c#

I'm currently implementing a MessageDialog control. It is there to replace MessageBox entirely and is displayed as an "in-window popup" (correct UX term needed).
Currently, its constructor is private and there is a method Show, just like in MessageBox.Show. However, MessageBox blocks the UI thread and returns a result.
What my MessageDialog control currently does is having a Action<MessageDialogResult> callback parameter which gets called when a button is clicked.
Utilizing MessageDialog
// class MessageDialog
public static void MessageDialog.Confirmation(Window owner, string message, Action<MessageDialogResult> callback);
// When used by other controls
MessageDialog.Confirmation(WindowMain.Singleton, true, (result) =>
{
if (result.Button == MessageDialogButton.Yes)
{
//...
}
});
However, having a callback instead of a blocking method call like in MessageBox.Show yields absolutely no benefits for me. It makes things rather complicated. What I'm rather trying to achieve is something like...
if (MessageDialog.Confirmation(WindowMain.Singleton, true).Button == MessageDialogButton.Yes)
{
//...
}
... which is much cleaner in my opinion.
The current code behind is basically
Create instance of MessageDialog and populate content with text
Add it to the children of Window.Content.Children
On button click, call callback(result) and remove from Window.Content.Children
The question: What I would like to achieve is having a blocking method call instead of one that triggers a callback.

Even though the accepted answer seems to work, I propose a better solution using TaskCompletionSource. This is exactly what await was made for - it's still basically just a callback (won't block the thread), but your code looks a lot simpler when using it.
TaskCompletionSource<DialogResult> taskSource;
Task<DialogResult> ShowAsync()
{
return taskSource.Task;
}
public void OkButton_OnClick(EventArgs e, object sender)
{
taskSource.SetResult(DialogResult.OK);
}
public void CancelButton_OnClick(EventArgs e, object sender)
{
taskSource.SetResult(DialogResult.Cancel);
}
You then have to await the call: await Dialog.ShowAsync()

How about something like this:
//DialogControl.cs
bool _closed = false;
DialogResult _result;
DialogResult ShowModal()
{
this.Show();
while(!_closed) Application.DoEvents(); //Infinite loop
return _result;
}
public void OkButton_OnClick(EventArgs e, object sender)
{
_result = DialogResult.OK;
_closed = true;
}
public void CancelButton_OnClick(EventArgs e, object sender)
{
_result = DialogResult.Cancel;
_closed = true;
}

Related

Returning / setting a value within a thread

I'm trying to open an OpenFileDialog within C# (codebehind, on an asp.net page). Because the regular references and the system.windows.form ones have some conflicts, I'm having to use the OpenFileDialog box within a thread, as below:
protected void Button1_Click(object sender, EventArgs e)
{
Thread newThread = new Thread(new ThreadStart(BrowseForFile));
newThread.SetApartmentState(ApartmentState.STA);
newThread.Start();
}
static void BrowseForFile()
{
System.Windows.Forms.OpenFileDialog MyFile = new System.Windows.Forms.OpenFileDialog();
if (MyFile.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
}
}
The way the page works means that this has to be in C# - using asp.net's fileupload won't work.
Now, the OpenFileDialog appears fine, and I can get values from it, but I ideally need to pass values into the thread (the BrowseForFile) and have it work with other controls on the page, in order to set things up. However, I'm very new to using threads.
Can someone show me, basically, how to get an integer from the Button1_Click into the BrowseForFile, and how I would set a label on the page from BrowseForFile?
If you use a modern version of .net you could use async-await for this. This makes it much easier for you to communicate with your dialog and to pass data to the thread that performs your background work.
To be able to use async-await:
Declare your function async
Let your function return Task instead of void and Task<TResult> instead of TResult.
There is one exception: event handlers may return void
In your async function start your other threads using Task.Run
while the other thread is running you can do other things
if you need the result: call await Task.
In your case, you'll have to change your thread class into a procedure that contains the code in your thread class. This procedure may be in any class. It must be declared async and return Task instead of void:
Of course you'll have to change your thread class into an async procedure:
private async Task MyThreadProcedureAsync(string fileName)
{
// do something really slow with fileName and return void
}
protected async void Button1_Click(object sender, EventArgs e)
{
string fileName = this.BrowseForFile();
if (!String.IsNullOrEmpty(fileName))
{
var myTask = Task.Run( () => MyThreadProcedureAsync(fileName))
// if desired do other things.
// before returning make sure the task is ready:
await myTask;
// during this wait the UI keeps responsive
}
}
private string BrowseForFileName()
{
using (var dlg = new System.Windows.Forms.OpenFileDialog())
{
// if needed set some properties; show the dialog:
var dlgResult = dlg.ShowDialog(this);
if (dlgResult == System.Windows.Forms.DialogResult.OK)
{
return dlg.FileName;
}
else
{
return null;
}
}
}

How to invoke components

In my apps i use backgroundWorker, to set text in some TextBox, I need first to invoke that TextBox.
First I use:
if (someTextBox.InvokeRequired)
{
someTextBox.Invoke((MethodInvoker)delegate
{
someTextBox.Text = "some_text";
});
}
else
{
someTextBox.Text = "some_text";
}
This method work for me fine, but because i have multiple TextBox-es i wrote:
private void invComp(TextBox txtBox, String str)
{
if (txtBox.InvokeRequired)
{
txtBox.Invoke((MethodInvoker)delegate
{
txtBox.Text = str;
});
}
else
{
txtBox.Text = str;
}
}
It is better to invoke it on this way? (invComp(someTextBox, "some_text");
Or maybe i have some third, bether , way?
I invoke some buttons to, I was think to write something like this for button to, if this is ok?
Tnx
Control.InvokeRequired suffers from cargo cult. You are updating a control from a worker thread, you know that invoking is required. So there is absolutely no point in testing it. Except for one reason, there is something fundamentally wrong when it is false. Which happens a lot more often than programmers like, forgetting to stop a worker when the user closes the window is a traditional bug. This causes all kind of mayhem, you want to know about it:
private void invComp(TextBox txtBox, String str) {
if (!this.InvokeRequired) throw new InvalidOperationException("You forgot to stop the worker");
this.BeginInvoke(new Action(() => txtBox.Text = str));
}
Short and snappy and fail-safe and fast. Good qualities of code. Note that it uses the form's BeginInvoke() method, it doesn't depend on a child control being created. And that it uses BeginInvoke() instead of Invoke(), important to not bog down the worker thread and avoid deadlock. Always avoid Invoke(), it is only required when you need to know a method return value.
A completely different take is to focus on you using BackgroundWorker. It already marshals calls to the UI thread, it is just that the method has a clumsy name. You can get the ProgressChanged event to execute any code, it isn't just good enough to show progress. Write your event handler like this:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
((Action)e.UserState).Invoke();
}
Now you can make it execute any code on the UI thread:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
var worker = (BackgroundWorker)sender;
//...
worker.ReportProgress(0, new Action(() => textBox1.Text = "hello"));
}
You can slightly modify your method, to make it generic, so that you can use if for any control.
private void invComp<T>(T control, String str) where T: Control
{
if (control.InvokeRequired)
{
control.Invoke((MethodInvoker)delegate
{
control.Text = str;
});
}
else
{
control.Text = str;
}
}

C# Is action.BeginInvoke(action.EndInvoke,null) a good idea?

If I want to do a "fire and forget" of some code, but still want to ensure that my memory is cleaned up (per Why does asynchronous delegate method require calling EndInvoke?), will the following achieve that goal?
Action myAction = () => LongRunTime();
myAction.BeginInvoke(myAction.EndInvoke,null);
I've looked around but haven't seen that pattern used anywhere. Rather, people use an annonomoyus method as their callback (such as The proper way to end a BeginInvoke?) or they define an actual callback method. Since I haven't seen anyone else do this, it makes me think it either doesn't work or is otherwise a bad idea.
Thanks!
Using a method group conversion instead of a delegate is fine, the EndInvoke will still be called in on your Action. There is nothing else to be done, since this is a fire and forget call.
Unfortunately, it's somewhat hard to directly irrefutably prove that EndInvoke is called, since Action is a delegate and we can't just add a breakpoint on some class in the BCL.
This code will (periodically) inspect some private field of the IAsyncResult that is returned by BeginInvoke, which seems to keep track of whether or not EndInvoke has been called yet:
public partial class MainWindow : Window
{
private Timer _timer = new Timer(TimerCallback, null, 100, 100);
private static IAsyncResult _asyncResult;
public MainWindow()
{
InitializeComponent();
}
static void LongRunTime()
{
Thread.Sleep(1000);
}
void Window_Loaded(object sender, RoutedEventArgs args)
{
Action myAction = () => LongRunTime();
_asyncResult = myAction.BeginInvoke(myAction.EndInvoke, null);
}
static void TimerCallback(object obj)
{
if (_asyncResult != null)
{
bool called = ((dynamic)_asyncResult).EndInvokeCalled;
if (called)
{
// Will hit this breakpoint after LongRuntime has completed
Debugger.Break();
_asyncResult = null;
}
}
}
}
I've double checked using SOS that there aren't any managed memory leaks. I've also tried several other proofs, but they were more circumstantial than this one, I think.
Some interesting I discovered during my investigation: the myAction.BeginInvoke call will show up on profilers using instrumentation, but myAction.EndInvoke does not.
Nowdays it could be done like
BeginInvoke((Action)(async () =>
{
// Show child form
var f = new MyForm();
f.ShowDialog();
// Update parent/current
await UpdateData();
}));

How to block until an event is fired in c#

After asking this question, I am wondering if it is possible to wait for an event to be fired, and then get the event data and return part of it. Sort of like this:
private event MyEventHandler event;
public string ReadLine(){ return event.waitForValue().Message; }
...
event("My String");
...elsewhere...
var resp = ReadLine();
Please make sure whatever solution you provide returns the value directly rather than getting it from something else. I'm asking if the method above is available in some way. I know about Auto/ManuelResetEvent, but I don't know that they return the value directly like I did above.
Update: I declared an event using MyEventHandler (which contains a Message field). I have a method in another thread called ReadLine waiting for the event to fire. When the event fires the WaitForValue method (part of the event handling scene) returns the event args, which contains the message. The message is then returned by ReadLine to whatever had called it.
The accepted answer to that question I asked was what I did, but it just doesn't feel quite right. It almost feels like something could happen to the data between the ManuelResetEvent firing and the program retrieving the data and returning it.
Update: The main problem with the Auto/ManualResetEvent is that it is too vulnerable. A thread could wait for the event, and then not give enough time for anyone else to get it before changing it to something else. Is there a way to use locks or something else? Maybe using get and set statements.
If the current method is async then you can use TaskCompletionSource. Create a field that the event handler and the current method can access.
TaskCompletionSource<bool> tcs = null;
private async void Button_Click(object sender, RoutedEventArgs e)
{
tcs = new TaskCompletionSource<bool>();
await tcs.Task;
WelcomeTitle.Text = "Finished work";
}
private void Button_Click2(object sender, RoutedEventArgs e)
{
tcs?.TrySetResult(true);
}
This example uses a form that has a textblock named WelcomeTitle and two buttons. When the first button is clicked it starts the click event but stops at the await line. When the second button is clicked the task is completed and the WelcomeTitle text is updated. If you want to timeout as well then change
await tcs.Task;
to
await Task.WhenAny(tcs.Task, Task.Delay(25000));
if (tcs.Task.IsCompleted)
WelcomeTitle.Text = "Task Completed";
else
WelcomeTitle.Text = "Task Timed Out";
You can use ManualResetEvent. Reset the event before you fire secondary thread and then use the WaitOne() method to block the current thread. You can then have secondary thread set the ManualResetEvent which would cause the main thread to continue. Something like this:
ManualResetEvent oSignalEvent = new ManualResetEvent(false);
void SecondThread(){
//DoStuff
oSignalEvent.Set();
}
void Main(){
//DoStuff
//Call second thread
System.Threading.Thread oSecondThread = new System.Threading.Thread(SecondThread);
oSecondThread.Start();
oSignalEvent.WaitOne(); //This thread will block here until the reset event is sent.
oSignalEvent.Reset();
//Do more stuff
}
A very easy kind of event you can wait for is the ManualResetEvent, and even better, the ManualResetEventSlim.
They have a WaitOne() method that does exactly that. You can wait forever, or set a timeout, or a "cancellation token" which is a way for you to decide to stop waiting for the event (if you want to cancel your work, or your app is asked to exit).
You fire them calling Set().
Here is the doc.
If you're happy to use the Microsoft Reactive Extensions, then this can work nicely:
public class Foo
{
public delegate void MyEventHandler(object source, MessageEventArgs args);
public event MyEventHandler _event;
public string ReadLine()
{
return Observable
.FromEventPattern<MyEventHandler, MessageEventArgs>(
h => this._event += h,
h => this._event -= h)
.Select(ep => ep.EventArgs.Message)
.First();
}
public void SendLine(string message)
{
_event(this, new MessageEventArgs() { Message = message });
}
}
public class MessageEventArgs : EventArgs
{
public string Message;
}
I can use it like this:
var foo = new Foo();
ThreadPoolScheduler.Instance
.Schedule(
TimeSpan.FromSeconds(5.0),
() => foo.SendLine("Bar!"));
var resp = foo.ReadLine();
Console.WriteLine(resp);
I needed to call the SendLine message on a different thread to avoid locking, but this code shows that it works as expected.
Try it : e.Handled = true; It works to prevent KeyEventArgs, for example.

How to create a control and manage its events in one method?

I'm still stuck.
Assume that I've got a user control with a button. And an event called damnIt_ButtonClicked.
In the main window I want to emulate the control's lifetime like it is a modal dialog, although it's not.
I want to wrap everything into one method, it returns true if the Button on the control clicked.
public bool Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
result = true;
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
return result;
}
Now. As you see the problem is this method will return always false;
But I need to return a result only when damnIt_ButtonClicked event fires. It means I have to put the thread on wait, till the user clicks button.
Right? Or how it should be done. Help me please....
You're going to need to re-architect your solution. Without knowing a broader scope of what you're trying to do, here's a possible solution.
private bool buttonResult;
public void Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
this.ProcessButtonClick();
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
}
private void ProcessButtonClick()
{
this.buttonResult = true;
//do whatever you would have before if Show had returned true
}
You know what? I give up!
I decided to make the control a window, although it was strictly prohibited in given specifications to use any other windows but the Main. Anyway it's gonna be a chromeless, borderless transparent window, so nobody can see the difference.
Thank you so much.

Categories