Change parent thread of a form - c#

I am trying to find a way to change the parent of a thread of a winform back to the GUI thread. Right now i have to create the form from another thread but accessing become impossible from the main form that has the reference to it.
here a sample of what i have
public partial class MainForm : Form
{
// local instance of the sub form
private ViewForm SubForm { get; set;} = null;
public MainForm()
{
InitializeComponent();
Task.Run(() =>
{
// set the sub form
SubForm = new ViewForm();
}
// call the rest of the initialization of main form
InitializeCustomControls();
}
private void OpenViewWindow_Click(object sender, EventArgs e)
{
// if the window is instanciated
if (SubForm != null)
{
SubForm.Show();
}
}
}
The ViewForm window is not a Form object. it's a custom third party window. It has a lot of controls and templates mixed with themes. The sole call to a new empty constructor can take up to 7 seconds hence why i need to create it on another thread while i continue loading my main window.
Right now i can call any method in the window except .Show() which always fail due to thread creation restriction. I would like to stay away from creating the thread as an endless running thread that will wait and read some object to will tell him when to show and hide the window.
the is the .Show() error :
Cross-thread operation not valid: Control 'ViewForm' accessed from a thread other than the thread it was created on.
I did try the following instead but it still freeze my interface :
Task.Run(() =>
{
// set the sub form
this.Invoke(new MethodInvoker(delegate
{
SubForm = new ViewForm();
}));
}
What i would like is something like a fire and forget instantiation of a GUI object.

HereĀ“s a solution using a BackGroundWorker:
public partial class MainForm : Form
{
// local instance of the sub form
private ViewForm SubForm { get; set;} = null;
public MainForm()
{
InitializeComponent();
backGroundWorker1.RunWorkerAsync();
InitializeCustomControls();
}
private void OpenViewWindow_Click(object sender, EventArgs e)
{
// if the window is instanciated
if (SubForm != null)
{
SubForm.Show();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
SubForm = new ViewForm();
}
}
Tested creating two forms, the MainForm pretty much like the one in your sample and the SubView form with a Threading.Sleep(10000) on the constructor.

Related

c# calling function from the main form in a childform isn't working

I am stuck between a rock and hard place right now.
I have my childform called and docked into the contentspanel under the main form.
After the async timer I close the first childform and call a function from the main form to display the next child form but nothing works.
This is the function in the mainform I want to call
public void ShowMainMenu()
{
FormMainMenu MM = new FormMainMenu();
OpenChildForm(MM, new object());
}
This is the function in the childform that is meant to close the first child form which is does but then call the above function, to show the next child form.
(They are commented out for you can notice the function line)
public async Task DisplayMainMenu(int interval)
{
await Task.Delay(interval);
this.Close();
//FormMainBuild MB = new FormMainBuild();
//MB.ShowMainMenu();
}
Please help, I have searched online for hours, tried multiple fixes and nothing is allowing me to call the ShowMainMenu function from my parent form.
(All childforms are docked under a panel control in the mainform)
When I want to accomplish what I understand you to be trying to accomplish I use the event pattern.
In your child form you would want an event to be called after your delay:
public partial class ChildForm : Form
{
public event EventHandler OnWaitCompleted;
public ChildForm(int interval)
{
InitializeComponent();
WaitTime(interval);
}
public void WaitTime(int interval)
{
Task.Delay(interval).ContinueWith((MethodInvoker) =>
{
OnWaitCompleted?.Invoke(this, EventArgs.Empty);
});
}
}
Then in your parent form you would setup your event handling action before you display the child form:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void btnSpawnChild_Click(object sender, EventArgs e)
{
ChildForm cf = new ChildForm(5000);
cf.OnWaitCompleted += (obj, args) =>
{
LoadNextChild();
};
cf.Show(this);
}
private void LoadNextChild()
{
}
}
I think this method is better as it avoids having child forms trying to control the content of their parent, which should be the responsibility of the parent.

Worker Thread: Disabling the parent form when child form is active in a worker thread

I have windows application with two forms FormA and formB.
FormA contains a button btnGet which when clicked starts a worker thread as follows
private void cmdGet_Click(object sender, EventArgs e)
{
if (pdBackgroundWorker.IsBusy == false)
{
pdBackgroundWorker.RunWorkerAsync();
}
}
The above worker thread invokes a method which should display FormB as follows
private void pdBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
var _verifyID = new frmFormB();
_verifyID.ShowDialog();
if (!_verifyID.blnVerified)
{
return;
}
else { //do something }
}
Now, as formB is displayed from worker thread, its parent FormA will still be active even though I used ShowDialog method to display formB.
Is there a better way to handle this such that when FormB is active, FormA should be restricted for access just like ShowDialog works for non threaded cases?
You want to use SynchronizationContext
here is an example.
public partial class Form1 : Form
{
BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
var worker = new Worker(SynchronizationContext.Current);
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += (sender, e) => worker.DoWork();
}
private void button1_Click(object sender, EventArgs e)
{
if (_backgroundWorker.IsBusy == false)
{
_backgroundWorker.RunWorkerAsync();
}
}
}
public class Worker
{
private readonly SynchronizationContext _synchronizationContext;
private Form2 form2;
public Worker(SynchronizationContext synchronizationContext)
{
_synchronizationContext = synchronizationContext;
form2 = new Form2();
}
public void DoWork()
{
_synchronizationContext.Send(callback => OpenForm(), null);
}
public void OpenForm()
{
form2.ShowDialog();
if (!form2.blnVerified)
{
return;
}
else
{
//do something
}
}
}
from msdn
The SynchronizationContext class is a base class that provides a free-threaded context with no synchronization.
The purpose of the synchronization model implemented by this class is to allow the internal asynchronous/synchronous operations of the common language runtime to behave properly with different synchronization models. This model also simplifies some of the requirements that managed applications have had to follow in order to work correctly under different synchronization environments.
Providers of synchronization models can extend this class and provide their own implementations for these methods.
hope this helps :)

C# Opening a Form, then closing it from another method

I am pretty new to C# and having a little issue with something. I believe threading may be the answer, but thats just a Buzz-Word I have picked up when looking for solutions.
namespace Test
{
public partial class Form1 : Form
{
private Form2 form2;
public Form1()
{
InitializeComponent();
form2 = new Form2();
}
private void runCheck(object source, System.Timers.ElapsedEventArgs e)
{
form2.ShowDialog();
form2.TopMost = true;
}
private void runCheckFalse()
{
form2.Hide();
}
}
This is only a quick code snippet of my stripped out application, however when trying this I get an error: Cross-thread operation not valid: Control 'Form2' accessed from a thread other than the thread it was created on.
Also as a side note I am using form2.TopMost = true; to attempt to open the window on top of everything else, but this often ends up at the back behind Visual Studio etc
You need to use Invoke in order to work with the form from a different thread.
Here is a nice article explaining how to work with Windows Forms controls from multiple threads: How to: Make Thread-Safe Calls to Windows Forms Controls
Try this:
namespace Test
{
public partial class Form1 : Form
{
private Form2 form2;
public Form1()
{
InitializeComponent();
form2 = new Form2();
}
private void runCheck(object source, System.Timers.ElapsedEventArgs e)
{
if (form2.InvokeRequired)
{
form2.Invoke(new EventHandler(delegate { form2.ShowDialog(); form2.TopMost = true; }));
}
else
{
form2.ShowDialog();
form2.TopMost = true;
}
}
private void runCheckFalse()
{
if(form2.InvokeRequired)
{
form2.Invoke(new EventHandler(delegate { form2.Hide(); }));
}
else
{
form2.Hide();
}
}
}
}
You can modify your runCheckFalse method in the following way - this is a fairly standard pattern for Windows Forms:
private void runCheckFalse()
{
if(InvokeRequired)
{
BeginInvoke(new MethodInvoker(runCheckFalse));
return;
}
form2.Hide();
}
Effectively, what this does is check to see if we are running on the GUI thread (" if InvokeRequired"). If we aren't, we call ourselves on the GUI thread and immediately return. If we are running on the GUI thread, then we don't need to do anything and just continue with the method as normal.
If you need to use parameters:
private void runCheckFalse(bool someParameter)
{
if(InvokeRequired)
{
BeginInvoke(new MethodInvoker(() => { runCheckFalse(someParameter);}));
return;
}
form2.Hide();
}
WinForm controls can only be updated from the UI thread. Take a look at this blog post, it gives a number of approaches to making sure that the update occurs on the UI thread. It is a long post, but worth the read. The quick and dirty approach is the first one if you don't have time to read it.
Erick

Send parametny from one form to another in C #

I have two forms (C#). In one form, there is a method that takes data and stores them in a database after closing the form that I want to be on the other (the main form) to update the data. How to do it using OOP or simply to make the most beautiful and well.
Generally, when you want to let main form to be updated, you create a public method on that form and call it from the other form when it has the new data and can send them to main form. It shouldn't be a problem.
Note that if you want to send data to somewhere, you need a reference to that place, i.e. you need a reference to main form in the other form. Either pass this from main form to the constructor of the other form, or you can also store the reference in a static field in Program class (do it in Main method where you create the main form) etc.
The most OOP-friendly solution would probably be to have an event on whichever form "triggers" a data update, that is subscribed to and handled by another form's method. Here's a basic wire-up:
public class Form1:Form
{
public event EventHandler<MyDataObject> DataChanged;
...
public override void OnClosing(CancelEventArgs e)
{
//Put in logic to determine whether we should fire the DataChanged event
try
{
if(DataChanged != null) DataChanged(this, myFormCurrentData);
base.OnClosing(e);
}
catch(Exception ex)
{
//If any handlers failed, cancel closing the window until the errors
//are resolved.
MessageBox.Show(ex.Message, "Error While Saving", MessageBoxButtons.OK);
e.Cancel = true;
}
}
}
...
public class Form2:Form
{
//Called from wherever you would open Form1 from Form2
public void LaunchForm1()
{
var form1 = new Form1();
form1.DataChanged += HandleDataChange;
form1.Show();
}
private void HandleDataChange(object sender, MyDataObject dataObj)
{
//Do your data validation or persistence in this method; if it fails,
//throw a descriptive exception, which will prevent Form1 from closing.
}
}
You don't have to use an event; a simple delegate could be used as well and it would do pretty much the same thing, while also being able to be specified in the form's constructor (thus requiring the handler to be provided).
You can do something like this for updating the values in one form from another form...
Form 2 code
public event EventHandler<UpdatedEventArgs> updateEvent;
public class UpdatedEventArgs : EventArgs
{
public string SomeVal { get; set; } // create custom event arg for your need
}
protected virtual void OnFirstUpdateEvent(UpdatedEventArgs e)
{
if (updateEvent != null)
updateEvent(this, e);
}
private void button1_Click(object sender, EventArgs e)
{
UpdatedEventArgs eventData = new UpdatedEventArgs();
eventData.SomeVal = "test"; // set update event arguments, according to your need
OnFirstUpdateEvent(eventData);
}
public Form2()
{
InitializeComponent();
}
Form 1 code
public Form1()
{
InitializeComponent();
Form2 form2 = new Form2();
form2.updateEvent += new EventHandler<Form2.UpdatedEventArgs>(form2_updateEvent); // create event handler to update form 1 from form 2
form2.Show();
}
void form2_updateEvent(object sender, Form2.UpdatedEventArgs e)
{
if (e != null && e.SomeVal != null)
{
// Do the update on Form 1
// depend on your event arguments update the grid
//MessageBox.Show(e.SomeVal);
}
}

Open a second winform asynchronously but still behave as a child to the parent form?

I am creating an application and I would like to implement a progress window that appears when a lengthy process is taking place.
I've created a standard windows form project to which I've created my app using the default form. I've also created a new form to use as a progress window.
The problem arises when i open the progress window (in a function) using:
ProgressWindow.ShowDialog();
When this command is encountered, the focus is on the progress window and I assume it's now the window who's mainloop is being processed for events. The downside is it blocks the execution of my lengthy operation in the main form.
If I open the progress window using:
ProgressWindow.Show();
Then the window opens correctly and now doesn't block the execution of the main form but it doesn't act as a child (modal) window should, i.e. allows the main form to be selected, is not centered on the parent, etc..
Any ideas how I can open a new window but continue processing in the main form?
You probably start your lengthy operation in a separate worker thread (e.g. using a background worker). Then show your form using ShowDialog() and on completion of the thread close the dialog you are showing.
Here is a sample - in this I assume that you have two forms (Form1 and Form2). On Form1 I pulled a BackgroundWorker from the Toolbox. Then I connected the RunWorkerComplete event of the BackgroundWorker to an event handler in my form. Here is the code that handles the events and shows the dialog:
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
Thread.Sleep(5000);
e.Result = e.Argument;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
var dlg = e.Result as Form2;
if (dlg != null) {
dlg.Close();
}
}
private void button1_Click(object sender, EventArgs e) {
var dlg = new Form2();
this.backgroundWorker1.RunWorkerAsync(dlg);
dlg.ShowDialog();
}
}
I implemented something very similar to this for another project. This form allows you to popup a modal dialog from within a worker thread:
public partial class NotificationForm : Form
{
public static SynchronizationContext SyncContext { get; set; }
public string Message
{
get { return lblNotification.Text; }
set { lblNotification.Text = value; }
}
public bool CloseOnClick { get; set; }
public NotificationForm()
{
InitializeComponent();
}
public static NotificationForm AsyncShowDialog(string message, bool closeOnClick)
{
if (SyncContext == null)
throw new ArgumentNullException("SyncContext",
"NotificationForm requires a SyncContext in order to execute AsyncShowDialog");
NotificationForm form = null;
//Create the form synchronously on the SyncContext thread
SyncContext.Send(s => form = CreateForm(message, closeOnClick), null);
//Call ShowDialog on the SyncContext thread and return immediately to calling thread
SyncContext.Post(s => form.ShowDialog(), null);
return form;
}
public static void ShowDialog(string message)
{
//Perform a blocking ShowDialog call in the calling thread
var form = CreateForm(message, true);
form.ShowDialog();
}
private static NotificationForm CreateForm(string message, bool closeOnClick)
{
NotificationForm form = new NotificationForm();
form.Message = message;
form.CloseOnClick = closeOnClick;
return form;
}
public void AsyncClose()
{
SyncContext.Post(s => Close(), null);
}
private void NotificationForm_Load(object sender, EventArgs e)
{
}
private void lblNotification_Click(object sender, EventArgs e)
{
if (CloseOnClick)
Close();
}
}
To use, you'll need to set the SyncContext from somewhere in your GUI thread:
NotificationForm.SyncContext = SynchronizationContext.Current;
Another option:
Use ProgressWindow.Show() & implement the modal-window behavior yourself. parentForm.Enabled = false, position the form yourself, etc.

Categories

Resources