Giving owner to MessageBox which is getting called from another thread - c#

I am using CefSharp 37 in winforms. I have implemented IRequestHandler and want to write some ResourceHandler code in method OnBeforeResourceLoad.
in OnBeforeResourceLoad() I check some condition and on that basis I display a MessageBox with OK and Cancel buttons. By pressing Cancel I want to return true otherwise false. Code as below:
public bool OnBeforeResourceLoad(IWebBrowser browser, IRequest request, IResponse response)
{
if (!request.Url.Contains(ContentHelper.requestTrapKey)
{
var handler = browser.ResourceHandler;
if (handler != null)
{
handler.RegisterHandler(request.Url, ResourceHandler.FromStream(File.OpenRead(ContentHelper.contentRootPath), Path.GetExtension(ContentHelper.contentRootPath + final));
}
}
else if (!request.Url.Contains(ContentHelper.requestTrapKey + "course") && request.Url.Contains(ContentHelper.requestTrapKey))
{
if (MessageBox.Show("message", "title", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) == DialogResult.Cancel)
{
return true;
}
}
return false;
}
It is observed that sometime MessageBox() goes behind to main form and user waits for messagebox to come and also next code also in wait state to execute. I know that as MessageBox.Show() is called on other thread than main thread so that's why it's going behind.
So is there any way where I can show MessageBox or a message to user without going it behind and take input from user and accordingly return true or false to load resource.
Update
I tried as #Adil said in answer to wrap the code by MethodInvoker then MessageBox remain on top of form but as when I press any button on message box application get hanged.
I have written an parametrized constructor of MyRequestHandler class as :
public MyRequestHandler(MainForm mainform)
{
this.mainform = mainform;
}
Then as said by #Adil :
bool returnValue = false;
mainform.Invoke((MethodInvoker)(() =>
{
if (MessageBox.Show(mainform,"message", "title", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) == DialogResult.Cancel)
{
returnValue = true;
}
}));
return returnValue;

I tied with checking InvokeRequired of mainform but still it's
throwing same exception.
You not only need to check the InvokeRequired but you need to invoke on GUI thread. You can do that by using MethodInvoker.
If you have return with MethodInvoker delegate it will return from delegate instead of the method having MethodInvoker. You can set value of some bool variable to true, which you can use after delegate finishes its execution to return from the method.
bool returnValue = false;
mainform.Invoke((MethodInvoker)(() =>
{
if (MessageBox.Show(mainform,"message", "title", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) == DialogResult.Cancel)
{
returnValue = true;
}
}));
return returnValue;

Related

How to create MessageBox in Unity without event/delegate callback?

There is one part in MessageBox that I need to re-create in Unity.
DialogResult result = MessageBox.Show(/*parameters*/);
//When code runs - appears MessageBox and next lines of code WAIT for result
//Only if button is pressed - code proceeds next lines.
if (result == DialogResult.Yes) { DoIt(); }
else { DoNotDoIt(); }
I've seen some realization with Action callbacks.
In that realization - MessageBox has Action fields and in MessageBox.Show() method you should add delegates to propriate Action's as parameters.
I can't use it in my case. I need to ensure that dialog result is set and only then continue to run code.
You can run your code in Update. DialogResult could have 3 states, None, Yes and No. With None, you do nothing, Update returns and comes back next frame:
void Update()
{
DialogResult result = MessageBox.Show(/*parameters*/);
if (result == DialogResult.Yes) { DoIt(); }
else if(result == DialogResult.No) { DoNotDoIt(); }
}
other way is coroutine which is basically same as update:
IEnumerator DialogSequence()
{
while(MessageBox.Show(/*parameters*/)== DialogResult.None) { yield return null;}
DialogResult result = MessageBox.Show(/*parameters*/);
if (result == DialogResult.Yes) { DoIt(); }
else if(result == DialogResult.No) { DoNotDoIt(); }
}

Why is this C# code executing out of sequence? Not ASYNC (at least I don't think it is)

Can anyone help me understand why my call to dialogservice executes after the CanNavigateAway function has returned its value? (My goal is to warn the user they are about to navigate away from a view without saving their changes. If they click OK, the navigation is allowed. I'm using MVVM Light.
When I step through the code, it does reach the dialog service, but then proceeds to the end of CanNavigateAway before creating the dialog. The CanNavigateAway method is called by OnNavigatingFrom.
public bool CanNavigateAway()
{
if (!changesSaved && Model.IsModified && !continueNavigation)
{
dialogService.ShowMessage("Are you sure you want to continue?",
"Confirmation",
buttonConfirmText: "Continue", buttonCancelText: "Discard",
afterHideCallback: (confirmed) =>
{
if (confirmed)
{
// User has pressed the "confirm" button.
// ...
continueNavigation = true;
}
else
{
// User has pressed the "cancel" button
// (or has discared the dialog box).
// ...
continueNavigation = false;
}
});
return continueNavigation;
}
}
Here is the OnNavigatingFrom method from the MVVM Light Bindable Page class:
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
if (!navigableViewModel.CanNavigateAway())
{
e.Cancel = true;
}
}
}
I tried this a different way to get the dialog service out of the mix, but showConfirmationDialogAsync still does not seem to execute in time:
public bool CanNavigateAway()
{
continueNavigation = false;
if (!changesSaved && Model.IsModified && !continueNavigation)
{
showConfirmationDialogAsync();
return continueNavigation;
}
private async void showConfirmationDialogAsync()
{
continueNavigation = false;
ContentDialog noSaveConfirmation = new ContentDialog
{
Title = "Warning",
Content = "You have unsaved changes. Are you sure you want to leave this page without saving?",
PrimaryButtonText = "Leave without saving",
SecondaryButtonText = "Stay and finish"
};
ContentDialogResult result = await noSaveConfirmation.ShowAsync();
if (result == ContentDialogResult.Primary)
{
continueNavigation = true;
}
else if (result == ContentDialogResult.Secondary)
{
continueNavigation = false;
}
}
None of the solutions will work if you require a response from the user. The problem is that when the code is inside the navigation event handler, it is running on the UI thread and the user prompt runs asynchronously, so that the UI is free to present the dialog to the user. This however means that the event handler finishes before the user has a chance to respond.
However, you can use a workaround solution. Add a flag bool field like forceNavigation. Then inside the OnNavigatingFrom display the dialog to the user and set Cancel to true right away and display the user the confirmation dialog. If the user says yes, then set forceNavigaiton to true and trigger the navigation manually again. Now it will skip the confirmation part and navigate right away.
protected async override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
//if navigation is forced, skip all logic
if ( !forceNavigation )
{
var navigableViewModel = this.DataContext as INavigable;
if (navigableViewModel != null)
{
e.Cancel = true;
//display the dialog to the user, if he says yes, set
//forceNavigation = true; and repeat the navigation (e.g. GoBack, ... )
}
}
}

In-window popups: How to block UI thread?

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

Overriding standart close button in Unity

I want to create a "Are you sure?" system when user clicks the close button of window. Is it possible to catch FormClosing event in Unity ?
Thanks.
Edit:
void OnApplicationQuit()
{
DialogResult result = MessageBox.Show(
"Are you sure you want to cancel ?",
"Question",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
Application.Quit();
}
}
I tried this to open a dialog when user clicks (X) button. It works but, looks like it creates a new dialog each frame.
This question needs an updated answer as Application.CancelQuit() is deprecated and MonoBehaviour.OnApplicationQuit() doesn't prevent the application from closing.
To accomplish this exit confirmation method, it's better to use: Application.wantsToQuit.
From the docs:
Unity raises this event when the player application wants to quit.
Add an event handler to this event to receive a notification that application is attempting to quit.
When this event is raised the quit process has started but can be cancelled. This means the player is not guaranteed to quit. For a guaranteed quit event take a look at Application.quitting.
Return true and the quit process will continue. Return false and the quit process will cancel.
Example:
// When the application starts, append a method
// that will run when the user attempts to exit
[RuntimeInitializeOnLoadMethod]
static void RunOnStart() {
Application.wantsToQuit += WantsToQuit;
}
public static bool quitConfirmation = false;
static bool WantsToQuit() {
if(quitConfirmation) {
return true;
} else {
RequestQuitConfirmation();
}
return false;
}
static void RequestQuitConfirmation() {
DialogResult result = MessageBox.Show(
"Are you sure you want to cancel ?",
"Question",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
quitConfirmation = true;
Application.Quit();
}
}
Note: The return value of this event (Application.wantsToQuit) is ignored when exiting play mode in the editor. IMPORTANT: The return has no effect on iPhone. Application can not prevent termination under iPhone OS.
Have a look at MonoBehaviour.OnApplicationQuit() and Application.CancelQuit()

Not to execute any further code until the async method is completed its execution

Not to execute any further code until the async method is completed its execution. Please let me know how to achieve it.
Following is sample code :
// Parent Form code
private void btnOpenForm1_Click(object sender, EventArgs e)
{
Form1 form1 = new Form1();
var result = form1.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
// do something
}
}
// Child form code
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
DialogResult result = MessageBox.Show(string.Format("Do you want to save changes?", "Confirmation", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (result == System.Windows.Forms.DialogResult.Cancel)
{
e.Cancel = true;
}
else
{
if (result == DialogResult.Yes)
{
e.Cancel = true;
this.DialogResult = System.Windows.Forms.DialogResult.OK;
// HERE I NEED TO WAIT COMPULSARILY TILL THE OPERATION IS COMPLETED, NO NEXT STATEMENT SHOULD BE EXECUTED (NEITHER IN PARENT FORM)
var isSaveCompleted = await HandleSave().ConfigureAwait(false);
if(isSaveCompleted == true)
{
// dispose some objects
}
}
else // if No is clicked
{
this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
// dispose some objects
}
}
}
public async Task<bool> HandleSave()
{
await doWork();
//
// some code here
//
}
public doWork()
{
//
// some code for I/O operation
//
}
In the above code, I don't want to execute the any of the next statements (not even in the Parent Form) until the HandleSave() method is completed its execution.
There's an XY problem here. Trying to force an asynchronous method to run synchronously in order to block closing the form is the wrong solution.
Instead of fighting window lifetimes, you need to figure out how to work with them. One solution for this scenario is to hide the form rather than closing it; and then actually close it when the asynchronous save completes. You can enable your parent form to detect when the child form is actually closed by using a TaskCompletionSource<DialogResult>.

Categories