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

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

Related

Change parent thread of a form

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.

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 :)

Progress bar form not fully loading

I'm writing a program that can take quite a bit to load(5-30 seconds). I'm wanting to have a form that starts with a marquee style progress bar, so that the user knows there's something being done.
public FrmMain()
{
var loading = new FrmLoading();
loading.Show();
InitializeComponent();
// other start up code here
loading.Close()
}
Now, loading pops up just fine. The problem is that the label and the progress bar show as nothing, and it almost looks like they are crashing. I've looked into the BackgroundWorker, but, honestly, I don't fully comprehend how to make that work for me. I tried to follow the tutorials and examples at:
BackgroundWorker Basics in C#
C# BackgroundWorker
ProgressBar
BackgroundWorker Class
But my head just isn't grasping it. Is there any other way to get the form to load properly? Thanks for any and all help.
EDIT
My BackgroundWorker attempt included me creating a function which performed all the actions that were done in my FrmMain. So, I had:
public FrmMain()
{
Loading = new FrmLoading();
Loading.Show();
backgroundWorker1.RunWorkerAsync();
}
private void FormLoading()
{
// Initialize and loading elements of the form
InitializeComponent();
}
private void BackgroundWorker1DoWork(object sender, DoWorkEventArgs e)
{
FormLoading();
}
private void BackgroundWorker1RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Loading.Close();
}
But I kept getting NullReferenceException on the backgroundWorker1.RunWorkerAsync();
An little-known or often-forgotten framework library is Microsoft.VisualBasic. You can use it with C# apps. Just add a Reference ... and this class. It handles all background worker threading and the rest.
public class MyApplication : WindowsFormsApplicationBase
{
private static MyApplication _application;
public static void Run(Form form)
{
_application = new MyApplication{ MainForm = form };
_application.Run(Environment.GetCommandLineArgs());
}
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new MySplashScreenForm();
}
}
Finally, change the app launch under Program.Main() from:
Application.Run(new Form1());
to
MyApplication.Run(new Form1());
If you aren't familiar, this class also offers more features like Single-Instance Handling that are definitely worth a look.
Perhaps this will help:
Add a:
Shown += new EventHandler(Form1_Shown);
after your InitializeComponent();.
And add:
void Form1_Shown(object sender, EventArgs e)
{
Refresh();
}
Also, if you didn't subscribe to the BackgroundWorker1DoWork event handler in the Designer, you'll have to add:
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
What I ended up doing may not be elegant, but it accomplished what I needed.
public FrmMain()
{
InitializeComponent();
backgroundWorker1.RunWorkerAsync();
var loading = new FrmLoading();
loading.ShowDialog();
}
private void BackgroundWorker1DoWork(object sender, DoWorkEventArgs e)
{
DbQuery();
}
private void BackgroundWorker1RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Loading.Close();
}
This brings up my loading form so that it has focus and fully loads. Since I called the DoWork before I called the ShowDialog(), the BackgroundWorker continues to work. Once DbQuery() is done, it calls the BackgroundWorker1RunWorkerCompleted(), which closes my FrmLoading and continues on with my program. Thanks to everyone who posted.

WinForm New instance of form

I have this block of code:
Form1 newForm = new Form1();
newForm.Show();
//The rest of this code has been omitted
more or less. Form1 is just for example purposes. My issue is that I am creating a text editor (continuation of my last question, basically) and I want to be able to have more than one copy of Form1 open at once.
This code allows me to do that, which is great, but I want to be able to close the old one and still have the newForm on the screen. Currently, if I close Form1, I say goodbye to newForm.
Is this possible? I don't want to have to just white-out all the next like Notepad does.
I'm guessing your Form1 is also the one created and passed to Application.Run() in your main method, correct? Something like Application.Run(new Form1());
In that case (which is the default), you are telling the framework to exit the application when that form closes.
You probably want to change your main method to use the overload of Application.Run() that takes an ApplicationContext, and within the app context, create your forms - then when your last form closes, you can exit the application. The example in the link to MSDN shows a very similar case.
You could simply change main to:
Form1 newForm = new Form1();
newForm.Show();
Application.Run();
but the issue with this is that the application may not exit correctly without more housekeeping by you.
Added:
This is a simple example, but shows the concept. Create a ApplicationContext class like this one:
public class CustomContext : ApplicationContext
{
private readonly List<Form1> _openForms = new List<Form1>();
public CustomContext()
{
CreateForm();
}
private void CreateForm()
{
Form1 form = new Form1();
form.OpenNewForm += (sender, args) => CreateForm();
form.Closed += (sender, args) => FormClosed(sender as Form1);
_openForms.Add(form);
form.Show();
}
private void FormClosed(Form1 form)
{
_openForms.Remove(form);
if (_openForms.Count == 0)
{
ExitThread();
}
}
}
In your Program class, change main to Run(..) your new context class:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new CustomContext());
}
And then in your Form1, make sure you have an event, and trigger that event to open new forms (note that in "real" code I'd probably abstract this away, or most likely let my DI container handle registration, but this shows the point much clearer) :
public event EventHandler OpenNewForm;
private void ButtonClick(object sender, EventArgs e)
{
//instead of showing a new form, we just raise this event
if (OpenNewForm != null) OpenNewForm(this, EventArgs.Empty);
}
What this does, is let the ApplicationContext manage your open forms, so that closing the first one (or any of them) does not exit the application until you close the last one. Once the last form closes, it exits the app.
You could do this so many different ways. For a working example of what you want to do, in the Program.cs file change the Main method():
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
Form1 frm = new Form1();
GlobalVariables.FormsList = new List<Form1>(); //new
GlobalVariables.FormsList.Add(frm); //new
frm.Show();
Application.Run();
}
Then on each of your Form1's have a button (or other event) that instantiates a new instance of the form1:
private void button1_Click(object sender, EventArgs e)
{
Form1 frm = new Form1();
GlobalVariables.FormsList.Add(frm); //new
frm.Show();
}
Edit: Also add the following code to the FormClosing event:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (GlobalVariables.FormsList.Count == 1)
{
Application.Exit();
}
else
{
GlobalVariables.FormsList.RemoveAt(GlobalVariables.FormsList.Count - 1);
}
}
Edit: Here is the new GlobalVariables class with the list of Forms to aid form management:
public class GlobalVariables
{
public static List<Form1> FormsList { get; set; }
}
If you are closing the form using it's 'x' button it will not affect the other form.
If you are closing by code you should get the object of that form properly and close it. It would be better if you can keep a catalog of forms so that the management will be easy.
If there are any static variables involved in between try changing that logic without static variables as they are shared across the objects.
Depending on what exactly you want to do, you can have the form launched by Application.Run be a form that just launches other forms, (e.g. your text-editor form).
Something like: (This is just an example to demonstrate the idea.)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Shown += Form1_Shown;
}
void Form1_Shown(object sender, EventArgs e)
{
TextEditorForm frm2 = new TextEditorForm(this);
frm2.Show();
this.Hide();
}
}
And:
public partial class TextEditorForm : Form
{
Form1 frm1;
public TextEditorForm(Form1 frm1)
{
InitializeComponent();
this.frm1 = frm1;
}
private void button1_Click(object sender, EventArgs e)
{
frm1.Show();
this.Close();
}
}

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