As my English is not well, I explain my question simple and paste a code snippet here to describe the problem.
The problem is a multiple threading issue in our winForm application. I simple the logic as following code sample.
In the test code, there are 1 mainForm Form1 and a button named "Start" in the mainForm. When user click the button, two forms form2 and form3 will be shown from 2 background threads. After form2 was closed, the Form1 will be triggered to close. But form3 is shown here, so I need user to close form3 by himself. So I handled form.Closing event and use Application.DoEvents() to let user close form3. It looks work in mind. But actually, the form3 can accept user's actions but form3 will not be closed as expected.
Please explain why form3 cannot be closed here and how to modify the code to make user's close operation work.
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace CloseFormFromMainThread
{
public partial class Form1 : Form
{
private Form2 _form2;
private Form2 _form3;
private SynchronizationContext _synchronizationContext;
public Form1()
{
InitializeComponent();
Closing += Form1Closing;
}
void Form1Closing(object sender, CancelEventArgs e)
{
while (_form3 != null)
{
Application.DoEvents();
Thread.Sleep(100);
}
}
private void ButtonStartClick(object sender, EventArgs e)
{
var thread = new Thread(StartForm3);
thread.Start();
var thread2 = new Thread(StartForm2);
thread2.Start();
}
private void StartForm3()
{
Thread.Sleep(200);
var action = new Action(() =>
{
_form3 = new Form2();
_form3.Text = "form 3";
_form3.ShowDialog();
_form3 = null;
});
ExecuteActionInUiThread(action);
}
private void Form1Load(object sender, EventArgs e)
{
_synchronizationContext = SynchronizationContext.Current;
}
private void StartForm2()
{
Thread.Sleep(500);
var action = new Action(() =>
{
_form2 = new Form2();
_form2.Text = "form 2";
_form2.ShowDialog();
Close();
});
ExecuteActionInUiThread(action);
}
private void ExecuteActionInUiThread(Action action)
{
var sendOrPostCallback = new SendOrPostCallback(o => action());
_synchronizationContext.Send(sendOrPostCallback, null);
}
}
}
First suggestion: Do not use Application.DoEvents(). Ever. Whenever you think you need it, you have a conceptual problem in your code flow that you should fix first. I guess your code is just creating a deadlock because it waits for the OnClosing callback to return before it can process more events (like the closing of another form).
As I checked the source code of Form.Close(), for modal dialog, there only raised FormClosing event, no Closed event raised. No signal was sent to let form3.ShowDialog to go ahead. So I think only use main thread and Application.DoEvents cannot make code go ahead. Then it is a soft deadlock in the main thread.
Currently, I use a another thread to do check (_form3 != null) and let main thread to execute the _form3.ShowDialog() logic. Following is my code about Form1Closing.
private bool _isFormClosing;
void Form1Closing(object sender, CancelEventArgs e)
{
if (_form3 == null) return;
e.Cancel = true;
if (!_isFormClosing)
{
_isFormClosing = true;
Task.Factory.StartNew((() =>
{
while (_form3 != null)
{
Thread.Sleep(50);
}
ExecuteActionInUiThread(Close);
}));
}
}
Related
I'm currently working with forms and mdi. In my project there is a mainform (a mdiContainer) which can have x subforms. I want to reach, that everytime, a subform is closed, all other subforms where arranged again.
You can do that with writing this into the mainform:
public void resetToolStripMenuItem_Click(object sender, EventArgs e)
{
this.LayoutMdi(System.Windows.Forms.MdiLayout.TileVertical);
}
In the subform, i do this:
private void subform_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
Form1 mainform = new Form1();
mainform.resetToolStripMenuItem_Click(mainform, EventArgs.Empty);
}
catch
{
System.Windows.Forms.MessageBox.Show("error");
}
}
It does not give any error, but also wont arrange the subforms again. I also tried to call the method with other parameters.
Any idea how i can make this work?
This line should make you pause:
Form1 mainform = new Form1();
You made a new form, so you aren't referencing the existing one.
But I think there are issues trying to do this from the child form.
It's probably better to listen to the Closed event of the child from the MDIParent, like this:
ChildForm childForm = new ChildForm();
childForm.FormClosed += childForm_FormClosed;
childForm.MdiParent = this;
childForm.Show();
And then in the Closed method, call the code:
void childForm_FormClosed(object sender, FormClosedEventArgs e) {
this.BeginInvoke(new Action(() => {
resetToolStripMenuItem_Click(null, null);
}));
}
I used BeginInvoke because otherwise, the closed child form is still being included in the layout tiling.
My goal is to respond to the last child form of an mdi container closing(for example, close the parent itself or show something new). The problem I face is that the mdi container's MdiChildren collection still indicates that the container contains children.
The approach I have tried is
void childMdiForm_FormClosed(object sender, FormClosedEventArgs e)
{
if (this.MdiChildren.Any())
{
//Do stuff
}
}
MdiChildren.Count() is still 1 after the last child form is closed.
I have the same results by trying to handle the parentform.MdiChildActivate event.
The MdiChildren collection appears to not be updated yet when the child form has closed. The same occurs when there are multiple children: it will still contain all the crildren, it appears to update the collection at a later moment.
Is this the right approach? If not, how can I get an accurate count of the number of mdi children after closing a form?
Yes, the MdiChildren property doesn't get updated until after the FormClosed event is delivered. There's a universal solution for event order issues like this, you can elegantly get code to run after event handing is completed by using the Control.BeginInvoke() method. This code solves your problem:
protected override void OnMdiChildActivate(EventArgs e) {
base.OnMdiChildActivate(e);
this.BeginInvoke(new Action(() => {
if (this.MdiChildren.Length == 0) {
// Do your stuff
//...
MessageBox.Show("None left");
}
}));
}
WinForms can be a little quirky at times and this is one example of that. I am not entirely sure why closing the last MDI child does not make the MdiChildren property return an empty array immediately thereafter.
In most cases you're going to be keeping track of either the children forms or the data models yourself, so I would simply keep a local array for managing this:
List<Form> childForms = new List<Form>();
void AddChildWindow()
{
var window = new ChildForm();
window.MdiParent = this;
window.Tag = Guid.NewGuid();
window.FormClosed += (sender, e) => { OnMdiChildClosed(sender as Form, e.CloseReason); };
window.Shown += (sender, e) => { OnMdiChildShown(sender as Form); };
window.Show();
}
void OnMdiChildShown(Form window)
{
childForms.Add(window);
Trace.WriteLine(string.Format("Child form shown: {0}", window.Tag));
Trace.WriteLine(string.Format("Number of MDI children: {0}", childForms.Count));
}
void OnMdiChildClosed(Form window, CloseReason reason)
{
childForms.Remove(window);
Trace.WriteLine(string.Format("Child form closed: {0} (reason: {1})", window.Tag, reason));
Trace.WriteLine(string.Format("Number of MDI children: {0}", childForms.Count));
if (childForms.Count == 0)
{
// Do your logic here.
}
}
Here's a full test example:
namespace WindowsFormsApplication1
{
public partial class mdiMainForm : Form
{
private List<Form> _children = new List<Form>();
public mdiMainForm()
{
InitializeComponent();
}
private void mdiMainForm_Shown(Object sender, EventArgs e)
{
Form3 f3 = new Form3();
f3.MdiParent = this;
f3.FormClosed += mdiChildClosed;
_children.Add(f3);
f3.Show();
Form4 f4 = new Form4();
f4.MdiParent = this;
f4.FormClosed += mdiChildClosed;
_children.Add(f4);
f4.Show();
Form5 f5 = new Form5();
f5.MdiParent = this;
f5.FormClosed += mdiChildClosed;
_children.Add(f5);
f5.Show();
}
private void mdiChildClosed(Object sender, FormClosedEventArgs e)
{
if (_children.Contains((Form)sender))
_children.Remove((Form)sender);
if (_children.Count == 0)
MessageBox.Show("all closed");
}
}
}
You're code seems to imply you are checking the child Form's MdiChildren collection. But perhaps I'm misreading your code.
Use the MDI Form's 'MdiChildActivate' event:
private void Form1_MdiChildActivate(object sender, EventArgs e) {
if(this.MdiChildren.Length == 0) {
// replace with your "Do Stuff" code
MessageBox.Show("All Gone");
}
}
I know this answer is very late, but I have a much simpler solution to the problem than what has been provided so I'll share it here.
private void ChildForm_FormClosing(object sender, FormClosingEventArgs e)
{
// If WinForms will not remove the child from the parent, we will
((Form)sender).MdiParent = null;
}
I've been trying to figure this out for the past day or so.
I have a program which has Form1, and a button that spawns Form2 in a new thread.
I also have another button on Form1 that should close Form2, but since Form2 is in another thread, I cannot touch that object directly.
I could do t.Abort() but that throws an exception.
How can I gracefully touch the other thread? Do stuff to it?
For example, how do I close the form from within Form1?
I've searched google for "how to close a form from within another thread" and found several links hinting at Invoke and Delegate, but after trying some things, I obviously can't figure out how to use it properly.
Can anyone help me to understand how it would apply to the code I have, so that I can understand how they can be used? in which context, etc?
I've uploaded the project to github for your convenience: https://github.com/powercat/WindowsFormsApplication7/archive/master.zip
--
Code:
[Form1.cs]
public void FormThread()
{
Application.Run(new Form2());
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(FormThread));
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
//Need to close Form2 from here.
}
[Form2.cs]
has the other form code.
In general, having two threads for two forms is not a good idea. It's almost always a better idea to have all of your forms on the main, UI thread, and move your logic and work onto background threads instead.
That being said, if the Form is being run in the separate thread, you should be able to use BeginInvoke to close it:
otherForm.BeginInvoke(new Action(() => otherForm.Close()));
Edit:
In your case, you'd need to save the instance:
Form2 otherForm;
public void FormThread()
{
otherForm = new Form2();
Application.Run(otherForm);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(FormThread));
t.SetApartmentState(ApartmentState.STA); // THIS IS REQUIRED!
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
//Need to close Form2 from here.
if (otherForm != null)
{
otherForm.BeginInvoke(new Action( () => otherForm.Close() ));
otherForm = null;
}
}
I'm working in C# with WinForms in a large application with multiple forms.
At several points I have another form coming up as a progress screen. Because I can't freeze my UI thread, I have to start the new form in a new thread. I'm using progressform.ShowDialog() to start the form, but because it's in a new thread, it is possible to Click or Alt + Tab back to the main form. I want to disable this.
My thought is that I can put an EventHandler on the mainForm.GotFocus event and redirect focus to progressForm if it is shown. However, the GotFocus event isn't being triggered when you switch applications or move between the progressForm and mainForm. I'm guessing that it's because some element in mainForm has focus, not the form itself.
If anyone knows of a better way to do this (I'm not committed to the EventHandler approach) or working code for the EventHandler approach, it would solve my problem.
Edit
As per the comment, I tried using the Activated event.
// in InitializeForm()
this.Activated += FocusHandler;
// outside of InitializeForm()
void FocusHandler(object sender, EventArgs e)
{
if (ProgressForm != null)
{
ProgressForm.Focus();
}
}
But it still allowed me to click back to the main form and click buttons.
I've tried some ways and found this which does work as you want, the whole idea is to filter some message from your main UI when your progress form is shown:
public partial class Form1 : Form
{
[DllImport("user32")]
private static extern int SetForegroundWindow(IntPtr hwnd);
public Form1()
{
InitializeComponent();
}
ChildUI child = new ChildUI();
bool progressShown;
IntPtr childHandle;
//I suppose clicking on the button1 on the main ui form will show a progress form.
private void button1_Click(object sender, EventArgs e)
{
if(!progressShown)
new Thread(() => { progressShown = true; childHandle = child.Handle; child.ShowDialog(); progressShown = false; }).Start();
}
protected override void WndProc(ref Message m)
{
if (progressShown&&(m.Msg == 0x84||m.Msg == 0xA1||m.Msg == 0xA4||m.Msg == 0xA3||m.Msg == 0x6))
//0x84: WM_NCHITTEST
//0xA1: WM_NCLBUTTONDOWN
//0xA4: WM_NCRBUTTONDOWN
//0xA3 WM_NCLBUTTONDBLCLK //suppress maximizing ...
//0x6: WM_ACTIVATE //suppress focusing by tab...
{
SetForegroundWindow(childHandle);//Bring your progress form to the front
return;//filter out the messages
}
base.WndProc(ref m);
}
}
if you want to show your progress form normally (not a Dialog), using Application.Run(), showing form normally (using Show()) without processing some while loop will terminate the form almost right after showing it:
private void button1_Click(object sender, EventArgs e)
{
//progressShown = true;
//child.Show();
if (!progressShown)
{
new Thread(() => {
progressShown = true;
if (child == null) child = new ChildUI();
childHandle = child.Handle;
Application.Run(child);
child = null;
progressShown = false;
}).Start();
}
}
I've tested and it works like a charm.
I have form which is opened using ShowDialog Method. In this form i have a Button called More.
If we click on More it should open another form and it should close the current form.
on More Button's Click event Handler i have written the following code
MoreActions objUI = new MoreActions ();
objUI.ShowDialog();
this.Close();
But what is happening is, it's not closing the first form. So, i modified this code to
MoreActions objUI = new MoreActions ();
objUI.Show();
this.Close();
Here, The second form is getting displayed and within seconds both the forms getting closed.
Can anybody please help me to fix issue. What i need to do is, If we click on More Button, it should open another form and close the first form.
Any kind of help will be really helpful to me.
In my opinion the main form should be responsible for opening both child form. Here is some pseudo that explains what I would do:
// MainForm
private ChildForm childForm;
private MoreForm moreForm;
ButtonThatOpenTheFirstChildForm_Click()
{
childForm = CreateTheChildForm();
childForm.MoreClick += More_Click;
childForm.Show();
}
More_Click()
{
childForm.Close();
moreForm = new MoreForm();
moreForm.Show();
}
You will just need to create a simple event MoreClick in the first child. The main benefit of this approach is that you can replicate it as needed and you can very easily model some sort of basic workflow.
If I got you right, are you trying like this?
into this?
in your Form1, add this event in your button:
// button event in your Form1
private void button1_Click(object sender, EventArgs e)
{
Form2 f2 = new Form2();
f2.ShowDialog(); // Shows Form2
}
then, in your Form2 add also this event in your button:
// button event in your Form2
private void button1_Click(object sender, EventArgs e)
{
Form3 f3 = new Form3(); // Instantiate a Form3 object.
f3.Show(); // Show Form3 and
this.Close(); // closes the Form2 instance.
}
ok so I used this:
public partial class Form1 : Form
{
private void Button_Click(object sender, EventArgs e)
{
Form2 myForm = new Form2();
this.Hide();
myForm.ShowDialog();
this.Close();
}
}
This seems to be working fine but the first form is just hidden and it can still generate events. the "this.Close()" is needed to close the first form but if you still want your form to run (and not act like a launcher) you MUST replace it with
this.Show();
Best of luck!
I would use a value that gets set when more button get pushed closed the first dialog and then have the original form test the value and then display the the there dialog.
For the Ex
Create three windows froms
Form1 Form2 Form3
Add One button to Form1
Add Two buttons to form2
Form 1 Code
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private bool DrawText = false;
private void button1_Click(object sender, EventArgs e)
{
Form2 f2 = new Form2();
f2.ShowDialog();
if (f2.ShowMoreActions)
{
Form3 f3 = new Form3();
f3.ShowDialog();
}
}
Form2 code
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public bool ShowMoreActions = false;
private void button1_Click(object sender, EventArgs e)
{
ShowMoreActions = true;
this.Close();
}
private void button2_Click(object sender, EventArgs e)
{
this.Close();
}
}
Leave form3 as is
Try this..
//button1 will be clicked to open a new form
private void button1_Click(object sender, EventArgs e)
{
this.Visible = false; // this = is the current form
SignUp s = new SignUp(); //SignUp is the name of my other form
s.Visible = true;
}
private void Button1_Click(object sender, EventArgs e)
{
NewForm newForm = new NewForm(); //Create the New Form Object
this.Hide(); //Hide the Old Form
newForm.ShowDialog(); //Show the New Form
this.Close(); //Close the Old Form
}
you may consider this example
//Form1 Window
//EventHandler
Form1 frm2 = new Form1();
{
frm2.Show(this); //this will show Form2
frm1.Hide(); //this Form will hide
}
For example, you have a Button named as Button1. First click on it it will open the EventHandler of that Button2 to call another Form you should write the following code to your Button.
your name example=form2.
form2 obj=new form2();
obj.show();
To close form1, write the following code:
form1.visible=false;
or
form1.Hide();
You could try adding a bool so the algorithm would know when the button was activated. When it's clicked, the bool checks true, the new form shows and the last gets closed.
It's important to know that forms consume some ram (at least a little bit), so it's a good idea to close those you're not gonna use, instead of just hiding it. Makes the difference in big projects.
You need to control the opening of sub forms from a main form.
In my case I'm opening a Login window first before I launch my form1. I control everything from Program.cs. Set up a validation flag in Program.cs. Open Login window from Program.cs. Control then goes to login window. Then if the validation is good, set the validation flag to true from the login window. Now you can safely close the login window. Control returns to Program.cs. If the validation flag is true, open form1. If the validation flag is false, your application will close.
In Program.cs:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
///
//Validation flag
public static bool ValidLogin = false;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Login());
if (ValidLogin)
{
Application.Run(new Form1());
}
}
}
In Login.cs:
private void btnOK_Click(object sender, EventArgs e)
{
if (txtUsername.Text == "x" && txtPassword.Text == "x")
{
Program.ValidLogin = true;
this.Close();
}
else
{
MessageBox.Show("Username or Password are incorrect.");
}
}
private void btnExit_Click(object sender, EventArgs e)
{
Application.Exit();
}
Use this.Hide() instead of this.Close()
Do this to Program.cs
using System;
namespace ProjectName
{
public class Program
{
[STAThread]
public static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetDefaultCompatibleTextRendering(false);
new Form1().Show();
Application.Run();
}
}
}