Generic / reusable form open/close events - c#

Whilst developing a current project which contains a number of WinForms, I'm finding myself becoming cluttered with lines of code simply to handle open / close events for the forms. Currently I'm handling them like so..
//Declare forms
myForm mForm1;
myForm2 mForm2;
private void btnSomething_Click(object sender, EventArgs e)
{
if (mForm1 == null)
{
mForm1 = new myForm();
mForm1.FormClosed += new FormClosedEventHandler(mForm1_FormClosed);
mForm1.Show();
}
else
if (mForm1.WindowState == FormWindowState.Minimized)
mForm1.WindowState = FormWindowState.Normal;
mForm1.Focus();
}
void mForm1_FormClosed(object sender, FormClosedEventArgs e)
{
mForm1 = null;
}
And then another set of voids to handle each forms open / close. Now imagine that instead of 2 forms, I've got, say, 5 forms. Now I'm even more cluttered. Is there a way to generalize this to have all forms have the same event handlers?
I've thought of perhaps using the object sender in an "as" statement, but i'm not sure how i'd find the relevant declared form instance from there.
sender as (form)
Any ideas?

Make your Forms implementing some IFormWithMyEvents.

You can generalize that code easily:
//Declare forms
myForm mForm1;
myForm mForm2;
private void btnSomething_Click(object sender, EventArgs e)
{
ShowOrUpdateForm<myForm>(ref mForm1);
}
void ShowOrUpdateForm<T>(ref Form form) where T : Form
{
if (form == null)
{
form = new T();
form.FormClosed += new FormClosedEventHandler(mForm1_FormClosed);
form.Show();
}
else if (form.WindowState == FormWindowState.Minimized)
form.WindowState = FormWindowState.Normal;
form.Focus();
}
void mForm1_FormClosed(object sender, FormClosedEventArgs e)
{
// you cannot refactor this easily
if (sender == mForm1)
mForm1 = null;
else if (sender == mForm2)
mForm2 = null;
}
If you want to further generalize the closed event handler, you should consider moving forms' declarations to some sort of an array, list of dictionary. That way you can easily generalize that method.
EDIT: converted the ShowOrUpdateForm function to generic.

Related

Restore form from secondary form

I have a form that minimizes when I open a secondary form (using a button click event). I want to be able to restore the original form when closing the secondary form. I have not been able to figure out how to restore the original. Here is the code I have (in example form)
The code from the main form:
private void BtnSecondaryForm_Click(object sender, EventArgs e)
{
// Open the secondary form.
FrmSecondaryForm fsf = new FrmSecondaryForm ();
fsf.Show();
this.WindowState = FormWindowState.Minimized;
}
Code from the secondary form:
private void FrmSecondaryForm_FormClosing(object sender, FormClosingEventArgs e)
{
// I need this code to be able to restore the main form.
}
Please don't just redirect me to someone else's similar question without explaining how I can get this to work in my application. I have looked at the other similar questions here on Stack Overflow already and don't understand how to get this to work.
You should find the form you want or restore, e.g.
using System.Linq;
...
private void FrmSecondaryForm_FormClosing(object sender, FormClosingEventArgs e)
var mainForm = Application
.OpenForms
.OfType<MainForm>()
.LastOrDefault();
if (mainForm != null) {
mainForm.WindowState = FormWindowState.Normal;
}
}
Move FormClosing event handler to primary form. This form is interested in the event anyway. I also changed event from FormClosing to FormClosed. Former can be called many times, but latter is called only once, when the form is actually closed.
private FrmSecondaryForm Fsf = null;
private void BtnSecondaryForm_Click(object sender, EventArgs e)
{
// Open the secondary form.
Fsf = new FrmSecondaryForm();
Fsf.Show();
Fsf.FormClosed += PrimaryForm_SecondaryFormClosed;
this.WindowState = FormWindowState.Minimized;
}
private void PrimaryForm_SecondaryFormClosed(object sender, FormClosedEventArgs e)
{
Fsf.FormClosed -= PrimaryForm_SecondaryFormClosed;
Fsf.Dispose();
Fsf = null;
this.WindowState = FormWindowState.Normal;
}
So you need Form2 to interact with Form1, which means that Form2 needs a reference to it.
Best is to do this during construction. But with forms, you should always keep a default no-parameter constructor, so we need to add a new one and make the original private.
//Add a new property (or field if you wish)
private Form formToMinimise { get; }
//Change this to be private
private SecondaryForm()
{
InitializeComponent();
}
//Add this new constructor as public
public SecondaryForm(Form form): this()
{
formToMinimise = form;
}
Now closing and restore original. We do a null check, just in case
private void FrmSecondaryForm_FormClosing(object sender, FormClosingEventArgs e)
{
formToMinimise?.WindowState = FormWindowState.Normal ;
}
Now you amend the creation and calling of your second form like this
private void BtnSecondaryForm_Click(object sender, EventArgs e)
{
// Create the secondary form with reference to this form
FrmSecondaryForm fsf = new FrmSecondaryForm(this);
fsf.Show();
this.WindowState = FormWindowState.Minimized;
}

C# WinForm : Reload form after submit another form?

I'm newbie of winform. I have opened form2 form a linklabel in form1 using :
private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
FrmAddMov frmAddMov = new FrmAddMov();
if(frmAddMov.ShowDialog() == DialogResult.OK)
{
this.Invalidate();
//or
this.Refresh();
}
}
I thought form1 will reload after I submit form2, but not. Please tell me the right way to do it. Thanks a lot, and sorry if my english is too bad.
Gentleman's answer will work, but it can be improved.
When showing a form using ShowDialogthan it is best practice to dispose of that form, and the easiest way to do that is by the using statement
private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
using (FrmAddMov frmAddMov = new FrmAddMov())
{
if (frmAddMov.ShowDialog() == DialogResult.OK)
{
FormLoad();
}
}
}
This way you are always 100% sure that all resources for frmAddMov will be cleaned up.
Move everything in your form load event to a method say FormLoad. You may want to add few other statements which you are expecting form reload will do for you. Call this method when your 2nd form closes.
Something like this
private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
FrmAddMov frmAddMov = new FrmAddMov();
if(frmAddMov.ShowDialog() == DialogResult.OK)
{
FormLoad();
}
}

Cannot access a disposed object C# (showdialog dispose)

I am new to c# and kind of winging it. using Microsoft Visual C# 2010
I have checked many similar posts and none of the suggestions seem to help
I am getting the following error: "Cannot access a disposed object"
which references the main form here
private void btn_RunPkgs_Click(object sender, EventArgs e)
{
RunPackages rp = new RunPackages();
this.Hide();
rp.ShowDialog();//The error points to this line
this.Show();
}
here is the code that blows up when the security check fails.
private void securityCheck()
{
if (MyGlobals.FormCheck("RUN_JOBS") == 1)
{
InitializeComponent();
}
else
{
//this.BeginInvoke(new MethodInvoker(this.Close));
//this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
MessageBox.Show("You do not have permission to access this form!");
//this.Close();
this.Dispose();
}
}
EDIT
It looks like I am going to go with Adriano Repetti's idea of putting the security where I call the page, but I am a little nervous now having any security on the page.
private void btn_RunPkgs_Click(object sender, EventArgs e)
{
if (MyGlobals.FormCheck("RUN_JOBS") == 1)
{
RunPackages rp = new RunPackages();
this.Hide();
rp.ShowDialog();
this.Show();
}
else
{
MessageBox.Show("Not for You!");
}
}
private void btn_ListUpdater_Click(object sender, EventArgs e)
{
if (MyGlobals.FormCheck("MDM") == 1)
{
ListUpdater lu = new ListUpdater();
this.Hide();
lu.ShowDialog();
this.Show();
}
else
{
MessageBox.Show("Private!");
}
}
EDIT2
Came up with the following possible solution but am nervous to use it because I am new at this and don't know what issues there might be. Any problems with just creating an event handler for form load?
namespace RunPackages
{
public partial class ListUpdater : Form
{
public ListUpdater()
{
InitializeComponent();
this.Load += new EventHandler(securityCheck);
}
private void securityCheck(object sender, EventArgs e)
{
if (MyGlobals.FormCheck("MDM1") == 0)
{
MessageBox.Show("Not Allowed!");
this.Close();
}
}
You can't dispose of the form within the form itself. The ShowDialog() method tries to access the form on exit for things such as DialogResult.
After a form has been disposed almost all of its methods can't be accessed (and most of its properties are invalid).
In your first line of btn_RunPkgs_Click() you create an object and you dispose it inside its constructor. Per se, even if pretty bad habit you may even call Dispose() from within constructor, it may even work but then you try to use such object ShowDialog() will generate ObjectDisposedException. Note that this code will also lead to same result (an exception):
RunPackages rp = new RunPackages();
rp.Dispose();
Yes you may check IsDisposed but that won't make code readable and problem (IMO) is you're mixing things. Constructor shouldn't contain such logic.
The point isn't just where you dispose your form. What's better is to don't even create such form (let me assume, because you call InitializeComponent(), that securityCheck() is called inside form constructor), for this you may use a factory static method:
public static bool TryShowDialog(Form currentForm)
{
if (MyGlobals.FormCheck("RUN_JOBS") != 1)
return false;
if (currentForm != null)
currentForm.Hide();
RunPackages dlg = new RunPackages();
dlg.ShowDialog();
if (currentForm != null)
currentForm.Show();
return true;
}
Your calling function will then be reduced to:
private void btn_RunPkgs_Click(object sender, EventArgs e)
{
RunPackages.TryShowDialog(this);
}
Note that such function is highly eligible for some refactoring (for example to extract code to hide/show existing form). Something like this:
public static bool ShowDialog<T>(Form currentForm, string authorizationId)
where T : Form, new()
{
if (MyGlobals.FormCheck(authorizationId) != 1)
return false;
if (currentForm != null)
currentForm.Hide();
T dlg = new T();
T.ShowDialog();
if (currentForm != null)
currentForm.Show();
return true;
}
Used like this (now code is reused everywhere):
SecurityHelpers.ShowDialog<RunPackages>(this, "RUN_JOBS");
Please note that calling code may be simplified (authorizationId may be an attribute on RunPackages, for example, and also currentForm can be deduced from current active form).
EDIT Calling Close() isn't better, if window handle has not been created (let's simplify little bit: it's created when window is shown) internally it'll call Dispose() (then above applies).
I would not try to disrupt the chaining of events that lead to the form creation.
The side effects are difficult to predict and what works today could not work in future versions.
Instead I would try a different approach
private void securityCheck()
{
if (MyGlobals.FormCheck("RUN_JOBS") == 1)
{
InitializeComponent();
}
else
{
Label message = new Label();
message.Dock = DockStile.Fill;
message.Text("You do not have permission to access this form!.");
message.TextAlign = ContentAlignment.MiddleCenter;
this.Controls.Add(message);
}
}
In this way I let the form show with just one label that covers the entire form surface with your message. The user could only close the form (provided that you have not removed the Control Box)
By the way, this has the advantage of avoiding dangerous oversights because it doesn't require any change on the calling code and the final effect is to effectively block the use of the form.
If you insist in closing the form during its constructor phase then you could get some advices from this question
I came up with the following, can anyone tell me if there are any issues with this?
namespace RunPackages
{
public partial class ListUpdater : Form
{
public ListUpdater()
{
InitializeComponent();
this.Load += new EventHandler(securityCheck);
}
private void securityCheck(object sender, EventArgs e)
{
if (MyGlobals.FormCheck("MDM1") == 0)
{
MessageBox.Show("Not allowed!");
this.Close();
}
}
etc...
Use a flag. For example change the code, like this:
public bool IsDisposed;
private void securityCheck()
{
if (MyGlobals.FormCheck("RUN_JOBS") == 1)
{
InitializeComponent();
}
else
{
//this.BeginInvoke(new MethodInvoker(this.Close));
//this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
MessageBox.Show("You do not have permission to access this form!");
//this.Close();
this.Dispose();
this.IsDisposed = true;
}
}
Then:
private void btn_RunPkgs_Click(object sender, EventArgs e)
{
RunPackages rp = new RunPackages();
if(rp.IsDisposed)
return;
this.Hide();
rp.ShowDialog();//The error points to this line
this.Show();
}

How do I respond to the last MDI childform closing?

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

Is there a way to show a "blocking" WinForms ContextMenu?

Is there a way to show a ContextMenu and block further execution until an item has been selected? In particular, I want to get behavior similar to ShowDialog() but for a ContextMenu.
The straight forward approach doesn't work:
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);
since the Click callback isn't called directly from Show() but instead at some later point when the message loop processes the click event.
If you are unlucky, menu is garbage collected before the event is processed and in that case the event is just silently lost. (Meaning you can't really use local variables for ContextMenus in this way.)
This seems to work, but feels "unclean":
using (ContextMenu cm = new ContextMenu()) {
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);
Application.DoEvents();
}
Is there a better way?
Sorry for the first answer. Here is what I've tried. I made another Form where I put the context menu and a timer.Form2 is displayed as modal from Form1 then the timer shows the context menu on Form2.
Note that Form 2 has some properties set : to not be visible in task bar, not have boarders and the size should be equal with the size of the context menu.
Hope this helps.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
Form2 ctxForm = new Form2();
ctxForm.Location = this.PointToScreen(e.Location);
ctxForm.Size = new Size(0, 0);
ctxForm.ShowDialog();
}
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
private void timer1_Tick(object sender, EventArgs e)
{
//show menu once
contextMenuStrip1.Show(this, PointToClient(Location));
contextMenuStrip1.Focus();
timer1.Enabled = false;
}
private void contextMenuStrip1_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
this.Close();
}
}
You can easily prevent the garbage collection of the ContextMenu whilst it is still being shown.
The problem is that you are using a lambda as the event handler for the menu item. This is an
anonymous method and so not itself attached to any object instance that would cause the ContextMenu to be referenced and so kept alive. Add a method to the enclosing object and then create a standard EventHandler. That way the existence of the enclosing instance will keep the ContextMenu alive. Not as concise and very C# 1.0 but it will solve the problem.
Just wait for the menu to not be visiable.
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);
while (cm.Visible == true) Application.DoEvents();

Categories

Resources