ShowDialog using invoke is making the application unresponsive - c#

I'm using Control.Invoke() to show a dialog. The code is a handler to get credentials from the user and it can be execute in a thread, this is the reason I perform the call into an InvokeRequired/Invoke snippet.
Sometimes, and only in some machines, when I close the dialog, the application becomes unresposive (it does not manage some mouse clicks, but manage others). If I execute some "allowed" actions, the application start to be responsive again. It seems that processing any event, the application fixes itself.
Do you know any known bug in the .NET framework, or something that could cause this issue?
Thanks in advance.
EDIT: This is the code I'm using:
public class GuiCredentialsHandler
{
// control used to invoke if needed
private static Control mInvokeControl;
// control used as parent for showDialog (could be null)
private static Control mParentControl;
/// <summary>
/// Initialize a GetCredentials handler for current process.
/// This method should be always called from the UI thread, for
/// a correctly handling for invokes (when the handler is called
/// from a thread).
/// </summary>
/// <param name="parentControl">Application top form.
/// Can be null if unknown</param>
public static void Initialize(Control parentControl)
{
if (parentControl != null)
{
mInvokeControl = parentControl;
}
else
{
mInvokeControl = new Control();
// force to create window handle
// otherwise, invoke required always
// return false
mInvokeControl.CreateControl();
}
mParentControl = parentControl;
}
public static Credentials GetCredentials(
string servername, SEIDWorkingMode serverWorkingMode)
{
if (mInvokeControl.InvokeRequired)
{
return mInvokeControl.Invoke(
new GetCredentialsDelegate(DoGetCredentials),
new object[] { servername, serverWorkingMode })
as Credentials;
}
else
{
return DoGetCredentials(servername, serverWorkingMode);
}
}
private static Credentials DoGetCredentials(
string servername, SEIDWorkingMode serverWorkingMode)
{
GetCredentialsDialog dialog = new GetCredentialsDialog();
dialog.Server = servername;
dialog.WorkingMode = serverWorkingMode;
DialogResult result = dialog.ShowDialog(mParentControl);
if (result == DialogResult.Cancel) return null;
UserInfoRetriever retriever = new UserInfoRetriever(
servername, serverWorkingMode,
dialog.UserName, dialog.Password);
SEID seid = retriever.GetCurrentUser();
return new Credentials(seid, serverWorkingMode);
}
public delegate Credentials GetCredentialsDelegate(
string serverName,
SEIDWorkingMode mode);

Is Control.Invoke actually needed in this case?
I was always under the impression that invoke was used to ensure that UI elements are accessed by the thread that creates the control which is usually the UI thread but does not have to be.
In this case it looks like you are trying to create a dialog from a thread and hence you should be able to update it from the thread. (Obviously you can't access it from outside your thread, which will include the main UI thread).
If I'm wrong no doubt this will get downvoted very quickly.

mParentControl will always be set equal to parentControl even if its NULL which does not seem right.
The reason your program becomes unresposive is because your mParentControl is NULL:
DialogResult result = dialog.ShowDialog(mParentControl);
One solution around this problem is to only show the dialog if the parent is known.
if ( mParentControl != NULL )
DialogResult result = dialog.ShowDialog(mParentControl);
else
DialogResult result = dialog.ShowDialog(mInvokeControl);
I based my answer on the following code:
if (parentControl != null)
{
mInvokeControl = parentControl;
}
I think you mean my answer makes no sense. What makes more sense that Hans Passant comment doesn't hold some truth or your code is actually correct and you discovered a bug. Since you are being rude I will just take my experience and help somebody else. Humor yourself and add code to avoid the mParentControl is Null situation because IT CAN HAPPEN. mParentControl is ALWAYS set to parentcontrol even when its NULL.
Application top form. /// Can be null if
unknown

Related

Find out which winforms controls are accessed from a background thread

We have built a huge winforms project, already in progress for multiple years.
Sometimes, our users get an exception which looks like this one.
The resolution of this problem seems to be:
don't acces UI components from a background thread
.
But since our project is a very big project with a lot of different threads, we don't succeed in finding all these.
Is there a way to check (with some tool or debugging option) which components are called from a background thread?
To clarify:
I created a sample winforms project with a single Form, containing two Button
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Clicked!";
}
private void button2_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
button2.BackColor = Color.Red; //this does not throw an exception
//button2.Text = "Clicked"; //this throws an exception when uncommented
});
}
}
The background color of button2 is set to red when the button is clicked. This happens in a background thread (which is considered bad behavior). However, it doesn't (immediately) throw an exception. I would like a way to detect this as 'bad behavior'. Preferably by scanning my code, but if it's only possible by debugging, (so pausing as soon as a UI component is accessed from a background thread) it's also fine.
I've got 2 recommendations to use together, the first is a Visual Studio Plugin called DebugSingleThread.
You can freeze all the threads and work on one at a time (obviously the non-main-UI threads) and see each threads access to controls. Tedious I know but not so bad with the second method.
The second method is to get the steps in order to reproduce the problem. If you know the steps to reproduce it, it will be easier to see whats causing it. To do this I made this User Action Log project on Github.
It will record every action a user makes, you can read about it here on SO: User Activity Logging, Telemetry (and Variables in Global Exception Handlers).
I'd recommend you also log the Thread ID, then when you have been able to reproduce the problem, go to the end of the log and work out the exact steps. Its not as painful as it seems and its great for getting application telemetry.
You might be able to customise this project, eg trap a DataSource_Completed event or add a dummy DataSource property that sets the real Grids DataSource property and raises an INotifyPropertyChanged event - and if its a non-main thread ID then Debugger.Break();.
My gut feeling is you're changing a control's (eg a grid) data source in a background thread (for that non-freeze feel) and thats causing a problem with synchronisation. This is what happened to the other DevExpress customer who experienced this. Its discussed here in a different thread to the one you referenced.
Is your app set to ignore cross threading intentionally?
Cross-thread operations should be blowing up all the time in winforms. It checks for them like crazy in just about every method. for a starting point check out https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs.
Somewhere in your app, somebody might have put this line of code:
Control.CheckForIllegalCrossThreadCalls = False;
Comment that out and run the app, then follow the exceptions.
(Usually you can fix the problem by wrapping the update in an invoke, e.g., in a worker thread if you see textbox1.text=SomeString; change it to `textbox.invoke(()=>{textbox1.text=SomeString;});.
You may also have to add checking for InvokeRequired, use BeginInvoke to avoid deadlocks, and return values from invoke, those are all separate topics.
this is assuming even a moderate refactor is out of the question which for even a medium sized enterprise app is almost always the case.
Note: it's not possible to guarantee successful discovery of this case thru static analysis (that is, without running the app). unless you can solve the halting problem ... https://cs.stackexchange.com/questions/63403/is-the-halting-problem-decidable-for-pure-programs-on-an-ideal-computer etc...
I did this to search for that specific situation but of course, need to adjust it to your needs, but the purpose of this is to give you at least a possibility.
I called this method SearchForThreads but since it's just an example, you can call it whatever you want.
The main idea here is perhaps adding this Method call to a base class and call it on the constructor, makes it somewhat more flexible.
Then use reflection to invoke this method on all classes deriving from this base, and throw an exception or something if it finds this situation in any class.
There's one pre req, that is the usage of Framework 4.5.
This version of the framework added the CompilerServices attribute that gives us details about the Method's caller.
The documentation for this is here
With it we can open up the source file and dig into it.
What i did was just search for the situation you specified in your question, using rudimentary text search.
But it can give you an insight about how to do this on your solution, since i know very little about your solution, i can only work with the code you put on your post.
public static void SearchForThreads(
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
var startKey = "this.Controls.Add(";
var endKey = ")";
List<string> components = new List<string>();
var designerPath = sourceFilePath.Replace(".cs", ".Designer.cs");
if (File.Exists(designerPath))
{
var designerText = File.ReadAllText(designerPath);
var initSearchPos = designerText.IndexOf(startKey) + startKey.Length;
do
{
var endSearchPos = designerText.IndexOf(endKey, initSearchPos);
var componentName = designerText.Substring(initSearchPos, (endSearchPos - initSearchPos));
componentName = componentName.Replace("this.", "");
if (!components.Contains(componentName))
components.Add(componentName);
} while ((initSearchPos = designerText.IndexOf(startKey, initSearchPos) + startKey.Length) > startKey.Length);
}
if (components.Any())
{
var classText = File.ReadAllText(sourceFilePath);
var ThreadPos = classText.IndexOf("Task.Run");
if (ThreadPos > -1)
{
do
{
var endThreadPos = classText.IndexOf("}", ThreadPos);
if (endThreadPos > -1)
{
foreach (var component in components)
{
var search = classText.IndexOf(component, ThreadPos);
if (search > -1 && search < endThreadPos)
{
Console.WriteLine($"Found a call to UI thread component at pos: {search}");
}
}
}
}
while ((ThreadPos = classText.IndexOf("Task.Run", ++ThreadPos)) < classText.Length && ThreadPos > 0);
}
}
}
I hope it helps you out.
You can get the Line number if you split the text so you can output it, but i didn't want to go through the trouble, since i don't know what would work for you.
string[] lines = classText.Replace("\r","").Split('\n');
Try that:
public static void Main(string[] args)
{
// Add the event handler for handling UI thread exceptions to the event.
Application.ThreadException += new ThreadExceptionEventHandler(exception handler);
// Set the unhandled exception mode to force all Windows Forms errors to go through the handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// Add the event handler for handling non-UI thread exceptions to the event.
AppDomain.CurrentDomain.UnhandledException += // add the handler here
// Runs the application.
Application.Run(new ......);
}
Then you can log the message and the call stack and that should give you enough information to fix the issue.
I recommend you update your GUI to handle this situation automatically for your convenience. You instead use a set of inherited controls.
The general principle here is to override the property Set methods in a way to make them Thread Safe. So, in each overridden property, instead of a straight update of the base control, there's a check to see if an invoke is required (meaning we're on a separate thread the the GUI). Then, the Invoke call updates the property on the GUI thread, instead of the secondary thread.
So, if the inherited controls are used, the form code that is trying to update GUI elements from a secondary thread can be left as is.
Here is the textbox and button ones. You would add more of them as needed and add other properties as needed. Rather than putting code on individual forms.
You don't need to go into the designer, you can instead do a find/replace on the designer files only. For example, in ALL designer.cs files, you would replace System.Windows.Forms.TextBox with ThreadSafeControls.TextBoxBackgroundThread and System.Windows.Forms.Button with ThreadSafeControls.ButtonBackgroundThread.
Other controls can be created with the same principle, based on which control types & properties are being updated from the background thread.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ThreadSafeControls
{
class TextBoxBackgroundThread : System.Windows.Forms.TextBox
{
public override string Text
{
get
{
return base.Text;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.Text = value; });
else
base.Text = value;
}
}
public override System.Drawing.Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });
else
base.ForeColor = value;
}
}
public override System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.BackColor = value; });
else
base.BackColor = value;
}
}
}
class ButtonBackgroundThread : System.Windows.Forms.Button
{
public override string Text
{
get
{
return base.Text;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.Text = value; });
else
base.Text = value;
}
}
public override System.Drawing.Color ForeColor
{
get
{
return base.ForeColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });
else
base.ForeColor = value;
}
}
public override System.Drawing.Color BackColor
{
get
{
return base.BackColor;
}
set
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate { base.BackColor = value; });
else
base.BackColor = value;
}
}
}
}

c# async property call

I have a ListBox, where my SelectedValue is set to a class DefaultStrediska which has IEditableObject implemented. What I am doing every time user selects a new item under this particular ListBox (SelectedValue changes), I first check if any change has been made, and if yes; then I ask user if he wants to save temporary changes (otherwise I discard them and return back to the original values).
I am using Mahapps.Metro async method for displaying a message (rather than using traditional System.Windows.MessageBox) and getting the result. The problem is, that this is an asynchronous method that I have to call from my property. Here it is how I do it:
private async Task<bool> GetResult()
{
if (await Window.ShowMessageAsync("Zmena v údajoch", "Pozor! Nastala zmena v údajoch. Prajete si ich dočasne uložiť zmeny?", MessageDialogStyle.AffirmativeAndNegative) == MessageDialogResult.Affirmative)
_SelectedStredisko.EndEdit();
return true;
}
private DefaultStrediska _SelectedStredisko;
public DefaultStrediska SelectedStredisko
{
get { return _SelectedStredisko; }
set
{
//check if any changes have been made
if (value != null && _SelectedStredisko != null)
{
if (_SelectedStredisko.WasChangeMade())
{
var x = GetResult().Result;
}
}
_SelectedStredisko = value;
//create backup of current data
_SelectedStredisko.BeginEdit();
OnPropertyChanged("SelectedStredisko");
}
}
However the problem is, that now my var x = GetResult().Result completely blocks the UI thread and I neither get the messagebox, nor can do anything else. If I remove .Result, then the code first goes to _SelectedStredisko = value and only afterwards calls the GetResult() method, which is unacceptable.
What am I doing wrong in here?
There are a number of ways to avoid the deadlock, I go through a few of them here. I think in your case it might be best to use ConfigureAwait(false) when you are showing the message, but I haven't used that API myself.
await Window.ShowMessageAsync(..).ConfigureAwait(false)

Visual Studio during Debugging: The function evaluation requires all threads to run

I'm suddenly getting a strange error while debugging. Up to now the variable in the watch windows has been showing correctly. Now I am always getting this error message in the watch windows:
The function evaluation requires all threads to run
I am not able to check any variable anymore. I am not explicitly working with threads. What can I do to get it working again?
I already disabled, as mentioned in some forums, the function: "Enable property Evaluation and other implicit function Calls" in the option window of the debugger. But without success, and it gives me this error:
Error Implicit Function evaluation disabled by the user
From the msdn forum:
This isn't an error in and of itself, but more of a feature of your debugger.
Some properties require code to be executed in order for the property to be read, but if this requires cross-thread interaction, then other threads may have to run as well. The debugger doesn't do this automatically, but certainly can, with your permission.
Just click the little evaluate icon and it will run your code and evaluate the property.
For further details on this behaviour check this excelent article
I ran into this issue when just trying to get items from a table called "AGENCY" using Entity Framework:
var agencies = db.AGENCY.OrderBy(e => e.FULLNAME);
Hovering over agencies in debug mode, clicking to expand the options, and clicking Results would give the dreaded "The function evaluation requires all threads to run" with a "Do Not Enter" icon at the end that, on which, clicking did nothing.
2 possible solutions:
Add .ToList() at the end:
var agencies = db.AGENCY_TABLE.OrderBy(e => e.FULLNAME).ToList();
List<AGENCY_TABLE> agencies = db.AGENCY_TABLE.OrderBy(e => e.FULLNAME).ToList();
Credit goes to Hp93 for helping me come to this solution. In the comments on MUG4N's answer where I found this solution, it also mentions trying .Any() instead of .ToList(), but this gives a Boolean instead of a <T>, like <AGENCY> is, so it probably wouldn't help.
Workaround - try a different path in the debug options. I found that I could click on the "Non-Public Members" > "_internalQuery" > ObjectQuery > Results View and get my values that way.
MUG4N has indeed provided a correct answer however if you hover over the line of code in debug, you may be looking at something like the below. If so, click the little re-evaluate icon highlighted in the image below...
NB: I obtained this image by pinning, normally the re-evaluate icone are in the middle of the window and not down the left hand column.
You should make thread safe call because accessing Windows form controls are not Thread safe in multithreading.
This is my simple code which makes Thread safe call and sets Progress bar.
public partial class Form1 : Form
{// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void StringArgReturningVoidDelegate(string text);
private Thread demoThread = null;
public int Progresscount = 0;
static EventWaitHandle waithandler = new AutoResetEvent(false);
public Form1()
{
InitializeComponent();
}
public static bool CheckForInternetConnection()
{
try
{
using (var client = new WebClient())
{
using (var stream = client.OpenRead("http://www.google.com"))
{
return true;
}
}
}
catch
{
return false;
}
}
public void Progressincrement()
{
waithandler.WaitOne();
while (CheckForInternetConnection()==true)
{
if (Progresscount==100)
{
break;
}
SetLabel("Connected");
Progresscount += 1;
SetProgress(Progresscount.ToString());
Thread.Sleep(TimeSpan.FromSeconds(1));
}
if (Progresscount <100)
{
Startthread();
}
SetLabel("Completed");
}
public void Startthread ()
{
this.demoThread= new Thread(new ThreadStart(Progressincrement));
this.demoThread.Start();
SetLabel("Waiting for connection");
while (CheckForInternetConnection() == false) ;
waithandler.Set();
}
private void SetLabel(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.label1.InvokeRequired)
{
StringArgReturningVoidDelegate d = new StringArgReturningVoidDelegate(SetLabel);
this.Invoke(d, new object[] { text });
}
else
{
this.label1.Text = text;
}
}
private void SetProgress(string Value)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.progressBar1.InvokeRequired)
{
StringArgReturningVoidDelegate d = new StringArgReturningVoidDelegate(SetProgress);
this.Invoke(d, new object[] {Value});
}
else
{
this.progressBar1.Value = Convert.ToInt32(Value);
}
}
private void Form1_Load(object sender, EventArgs e)
{
Startthread();
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Responsive");
}
}
For more information MSDN
This isn't an error, but more of a feature of your debugger.
The debugger doesn't do this automatically, but certainly can, with users permission. Just click the little space icon and it will run the code and evaluate the property.
I use the next workaround to pass:
var OtherThreadField = "";
Invoke(new MethodInvoker(delegate
{
OtherThreadField = ExecuteNeededMEthod();
}));
Now i have a value for OtherThreadField.
I faced the same issue and solved .The Issue arise due to username and password ,in SQL connection there is user and password but in code there no user and password. so I enable the user and the password and the issue solved
For me, this happened when trying to break on a line that accesses a complex object instance contained by a Settings Class.
A breakpoint on the following if results in Settings.Default.FindSettings with the value being "The function evaluation requires all threads to run." If I press the force eval button, it is null. Stepping with the force eval button click or not enters the if block and initializes the object. If I remove the breakpoint and add a new breakpoint following the if block, the Settings.Default.FindSettings deserializes properly with the expected values.
if (Settings.Default.FindSettings == null)
{
Settings.Default.FindSettings = new FindSettings();
}
After trial and error, I added the following code before the above if block to access the settings prior to breaking. This seems to reliably fix the problem. I do not need it in production so I wrap in conditional compiler directive. I have a comment in the code instead of a non-descript discard:
#if DEBUG
var _ = Settings.Default.FindSettings;
#endif
I am not sure if the above line would be optimized out in production since it has side effects. As I only need it while debugging, I have not checked.

Threading with WinForms?

In my application I startup a hidden dummyForm that is merely created to keep track of the main UI thread. So If a new form is about to be created InvokeRequired is used on the dummy form to make sure that we are on the main UI thread when creating the new form.
Directly after instantiating my frmStart form I check the frmStart.InvokeRequired and it is set to false so no need for invoke here (the same goes for dummyForm.InvokeRequired).
Then I got a frmMyDialog that will use frmStart as parent/owner something like this:
using(Create frmMyDialog on main UI thread)
{
frmMyDialog.Show(frmStart);
}
This will throw a cross thread exception and the strange thing here is that:
frmMyDialog.InvokeRequired = false
dummyForm.InvokeRequired = false
frmStart.InvokeRequired = true
And this is even when I'm checking that dummyForm.InvokeRequired is false when creating the frmStart?
The frmMyDialog.InvokeRequired should always be the same value as dummyForm.InvokeRequired? What is happening here?
I have checked that the frmStart and dummyForm is not re-created at all after the first instance has been created.
Edit1:
This is how the application starts :
public static void Main(string[] args)
{
_instance = new MyClientMain(parameters);
Application.Run(_instance);
}
The constructor of MyClientMain class will run Setup on a static class called MainControl. MainControler will in the setup method instanciate a dummyform like this :
if (_dummyForm == null)
_dummyForm = new Form();
After this is done a login form will handle a login and this form is multithreaded. When the login is finished the MainController will be called again to instanciate and open the main MDI windo that holds frmStart. To make sure that we are on the same thread the following is done :
public static StartApplication()
{
if (_dummyForm.InvokeRequired)
_dummyForm.Invoke(new MethodInvoker(delegate { OpenMainOrbitWindow(); }));
//Instanciate mainform and frmStart then open mainForm with frmStart as a MDI child
}
There is no multithreading here.
Then when the service goes offline a event will be triggered and I need to popup a frmMyDialog but when using .ShowDialog() it dialog will be placed behind forms so the parent/owner most be found and set like this :
public static Form GetActiveForm()
{
Form activeForm = Form.ActiveForm;
if (activeForm != null)
return activeForm;
if (MainOrbitForm.TopMost)
return MainOrbitForm;
else
{
FormCollection openForms = Application.OpenForms;
for (int i = 0; i < openForms.Count && activeForm == null; ++i)
{
Form openForm = openForms[i];
if (openForm.IsMdiContainer)
return openForm.ActiveMdiChild;
}
}
if (_patientForm != null)
{
if (_patientForm.TopMost)
return _patientForm;
}
return null;
}
public static string ShowOrbitDialogReName()
{
frmMyDialog myDialog;
Form testForm;
//Makes sures that the frmOrbitDialog is created with the same thread as the dummyForm
//InvokeRequired is used for this
using (myDialog = MainController.CreateForm<frmOrbitDialog>())
{
//Settings...
testForm = GetActiveForm();
myDialog.ShowDialog(GetActiveForm(testForm));
}
}
The problem is that
myDialog.InvokeRequired = false
testForm.InvokeRequired = true;
MainController.DummyForm.InvokeRequired = false;
Edit2:
Startup and creates the dummyform :
dummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9
After success login we create the mainform
_mainForm.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9
Everything looks fine so far. Then a callback is received(WCF) and a event creates a frmMyDialog on the same thread(Invoke is used on the dummyForm) and then the ShowDialog is used :
frmMyCustomDialog.ShowDialog(_mainForm)
This throws a CrossThreadException and this is how it looks like at this point :
_mainForm.InvokeRequired = true
frmMyCustomDialog.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 12
Why is the MainControl.DummyForm not true? The ManageThreadId is not 9 but 12?
You should use System.Threading.SynchronizationContext.Current. It was created directly for purposes like this.
You may access it anywhere, after the first form of your app have been created. Judging by your example below, this should not be a problem, since you create a form right o the start of application.
public static void Main(string[] args)
{
_instance = new MyClientMain(parameters);
Application.Run(_instance);
}
Then anywhere, you need the code to be executed on UI thread, you simply use
System.Threading.SynchronizationContext.Current.Send() // To execute your code synchronously
System.Threading.SynchronizationContext.Current.Post() // To execute your code synchronously
Mind, that SynchronizationContext is smartenough to see, that you call it already from UI thread, and then it will simply execute your delegate directly.
And also, remember that you need to create some WinForms form or control before you use SynchronizationContext for the first time, becase when you do this, context will be initialized to appropriate implementation.
There are 3 implementation: Default, that does nothing - just always run code in sync, it stays in the Current until you create WinForms control or WPF control.
Then Current will be populated with either context for Winforms, or context for WPF dispatcher.
This is just off the top of my head, but as Vladimir Perevalov has stated in a different discussion, you have to make your form visible.
If your frmDummy is never shown, then it never has it's window handle created and assigned, and therefore it will always reply False to "InvokeRequired". This would mean that all the code you mean to sync through frmDummy is never actually sent to the initial UI thread, but is always run in the current thread. (which becomes an UI thread of its own for the control it has just created).
The important thing to note is that InvokeRequired tries to determine if the said control's window handle is owned by another thread. It has nothing to do with the constructor.
If you do not want to show the frmDummy, you can call CreateControl right after you instantiate it, to make sure it has it's handle assigned.
I didn't fully understand your question, because your examples don't really show anything about multithreading - however, if you want to create a form where the parent is another form from another thread, you could use this code:
public void CreateShowDialogForm()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(CreateShowDialogForm));
}
else
{
Form frmMyDialog = new Form();
frmMyDialog.Show(this);
}
}
private void Form4_Load(object sender, EventArgs e)
{
Task t = new Task(() => CreateShowDialogForm());
t.Start();
t.ContinueWith(task => true);
}

Notify Modal forms' parent that it needs to action something

I have a parent form open a modal form which basically allows a user to change the database settings of the application.
When the user clicks the save button on the modal (child) form it saves the Settings object with the new settings but I need to have the Main form check that the database settings are correct.
I currently do this through a function which tries to simply connect to the database and if successful return true, false if this failed. That function I execute within the applications constructor so it runs fine whenever the application is closed and restarted.
I tried the following within the modal form after saving the settings but get a NullReference exception for object myManager.
This is the function which gets the new settings and saves them and then attempts to call the parent forms CheckDatabaseIsSetup() public function to test the db connection.
/// <summary>
/// Save the settings and then hide the Settings window
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Save_Click(object sender, EventArgs e)
{
// TRUE: User indicates that we are to connect using a trusted connection
// FALSE: User wants to use Integrated security to connect.
if (rb_UseTrustedConnection.Checked)
{
AppSettings.DatabaseName = tb_Trusted_DbName.Text;
AppSettings.Server = tb_Trusted_Server.Text;
AppSettings.UseIntergratedSecurity = false;
}
else
{
AppSettings.DatabaseName = tb_Secure_DbName.Text;
AppSettings.Server = tb_Secure_Server.Text;
AppSettings.Username = tb_Secure_Username.Text;
AppSettings.Password = tb_Secure_Password.Text;
AppSettings.UseIntergratedSecurity = true;
}
try
{
AppSettings.SaveSettings();
BushBreaksLodgeManagerMain myManager = (BushBreaksLodgeManagerMain)this.ParentForm;
myManager.CheckDatabaseIsSetup();
}
catch (Exception ex)
{
log.LogAppendWithException(ex);
}
this.Hide();
}
You should instead use the Owner property in the modal form rather than the ParentForm property as follows:
BushBreaksLodgeManagerMain myManager = (BushBreaksLodgeManagerMain)this.Owner;
The Owner property defines the actual relationship between the owned(modal) form and the parent(owner) form.
Its better to define an event in child form and handle this event in main form
and when ever you raise this event in child form mainform can does its own job
BushBreaksLodgeManagerMain myManager = (BushBreaksLodgeManagerMain)this.ParentForm;
You can check the above line whether ParentForm is of Type/can cast to BushBreaksLodgeManagerMain. I presume the case wasn't successful hence return null
I'd usually do this by using object intercommunication such as provided with my Emesary library; The design is to use notifications in such a way that the request is sent and handled by anything that knows it needs to process these notifications, so for example it's easy to add in extra event handlers that are disconnected.
In which case the code for checking the database settings would become:
if (ReceiptStatus.OK ==
GlobalNotifier.NotifyAll(new CheckDatabaseIsSetupNotification(tb_Secure_DbName.Text,
tb_Secure_Server.Text,
tb_Secure_Username.Text,
tb_Secure_Password.Text,
true))
{
// do something.
}
To make this work you'd need to implement IReceiver in BushBreaksLodgeManagerMain and in the constructor call
GlobalTransmitter.Register(this);
then implement the interface receive:
public ReceiptStatus Receive(INotification _message)
{
if (_message is CheckDatabaseIsSetupNotification)
{
var message = _message as CheckDatabaseIsSetupNotification;
if (connect_to(message.DatabaseName, message.Server, Message.Username, message.Password, message.UseIntergratedSecurity))
return ReceiptStatus.OK;
else
return ReceiptStatus.Fail;
}
return ReceiptStatus.NotProcessed;
}
You can do this using Windows events - but this way is clearer and allows inter operation with objects that don't necessarily have windows.

Categories

Resources