In my winform application I have one form that creates other forms during a for loop. This parent form remains hidden is there purely for processing.
I moved the processing code away from the program class because Application.Run didn't seem to place nice in a loop as more than one instance would be open.
When the child forms are done with they are closed. What I want to know is whether I can get the application to exit when these forms are closed even though the parent form is still open. I have tried exposing a List<bool> on the parent form to store which of the forms have closed but the child forms cannot access the list due to the parent form not having an instance name - it is called by Application.Run(new FormProcessor(args));
More generally I guess I am asking is there a way to access properties of the parent form from the child forms.
Inject a reference to the parent form in the constructor of each child form, thus allowing access to it.
Or when each child form is created add a reference to a list in the parent form then run a background task to wait for all the child forms to close. You could do this by subscribing to the form closed event on each of the child forms and waiting for these to fire
A simple example of what I have tried to show in the comments would be the following.
You create an extra class that handles the registration to the FormClosed event as for example:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Forms;
namespace wfAutoClose {
public class FormSubscriber : INotifyPropertyChanged, IDisposable {
public event PropertyChangedEventHandler PropertyChanged;
private readonly IList<Form> _forms = new ObservableCollection<Form>();
public IList<Form> Forms {
get {
return _forms;
}
}
public int FormCount {
get {
return _forms.Count;
}
}
private void RaisePropertyChanged(string propertyName) {
var localEvent = PropertyChanged;
if (localEvent != null) {
localEvent.Invoke( this, new PropertyChangedEventArgs( propertyName: propertyName ) );
}
}
private void OnChildFormClosed(object sender, FormClosedEventArgs e) {
Forms.Remove( sender as Form );
}
private void SubscribeToFormClosedEvent(Form childForm) {
childForm.FormClosed += OnChildFormClosed;
}
private void UnsubscribeFromFormClosedEvent(Form childForm) {
childForm.FormClosed -= OnChildFormClosed;
}
private void OnChildFormCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if (e.OldItems != null) {
foreach (var item in e.OldItems) {
UnsubscribeFromFormClosedEvent( item as Form );
}
}
if (e.NewItems != null) {
foreach (var item in e.NewItems) {
SubscribeToFormClosedEvent( item as Form );
}
}
RaisePropertyChanged( "FormCount" );
}
public void Dispose() {
( Forms as INotifyCollectionChanged ).CollectionChanged -= OnChildFormCollectionChanged;
}
public FormSubscriber() {
( Forms as INotifyCollectionChanged ).CollectionChanged += OnChildFormCollectionChanged;
}
}
}
this one can then be used inside your parent form, where you simply add the forms in your loop, and your parent form can register itself to the INotifyPropertyChanged event of the FormSubscriber. When there are no more forms available, it will show FormCount == 0 where this is the place you will exit from your application (by either calling Application.Exit() or this.Close())
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace wfAutoClose {
public partial class Form1: Form {
FormSubscriber formSubscriber = new FormSubscriber();
public Form1() {
InitializeComponent();
formSubscriber.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged( object sender, PropertyChangedEventArgs e ) {
if (formSubscriber.FormCount == 0) {
Application.Exit();
}
}
private void Form1_Load( object sender, EventArgs e ) {
for ( int i = 0; i < 3; i++ ) {
Form form = new Form2() { Text = "Dynamic Form " + i };
form.Show();
formSubscriber.Forms.Add( form );
}
}
private void Form1_FormClosed( object sender, FormClosedEventArgs e ) {
formSubscriber.Dispose();
}
}
}
Of course, in my example they are just basic windows, and I do not care about any threading or other GUI specifics, but it should show you the basics of what you could do with the event registration and the usage of the ObservableCollection<T> (see FormSubscriber)
Use ApplicationContext and subscribe to the FormClosed() event of all the child forms. Check the Application.OpenForms collection and Exit() when appropriate...
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyContext());
}
}
public class MyContext : ApplicationContext
{
public MyContext()
{
// Open your Forms...
for(int i = 1; i <= 5; i++)
{
Form frm = new Form();
frm.Text = "Form #" + i.ToString();
frm.FormClosed += Frm_FormClosed;
frm.Show();
}
}
private void Frm_FormClosed(object sender, FormClosedEventArgs e)
{
if (Application.OpenForms.Count == 0)
{
Application.Exit();
}
}
}
Simplest of all would be to keep information about opened/closed forms in some other global class like:
public static class Helper
{
public static List<int> ChildFormsOpened { get; private set; }
static Helper()
{
ChildFormsOpened = new List<int>();
}
}
You can simply add form hashcode when you open it. You can do this in ctor or load event handler of your child form when it is opened :
Helper.ChildFormsOpened.Add(this.GetHashCode());
So at some point in your code you can remove the form that is closing from collection and check if all other forms are closed as well. If they are, so you can close your application by invoking Application.Exit() method:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Helper.ChildFormsOpened.Remove(this.GetHashCode());
if(Helper.ChildFormsOpened.Count < 1) Application.Exit();
}
Thanks to all who helped with this. I have come up with an answer that works for me. I've added an event handler on the back of form closed. The origList variable stores the original data for formList otherwise the foreach will proceed to the next entry of the list which it may have removed.
for ( int i =0; i < formList.Count; i++)
{
string formName = formList[i];
Form1 frm = (new Form1(formName...);
frm.Show();
string contextName= formName;
frm.FormClosed += new FormClosedEventHandler((sender, e) => FrmClosed_Event(sender, e, contextName));
}
public void FrmClosed_Event(object sender, FormClosedEventArgs e, string name)
{
foreach(string thisForm in origList)
{
if (thisForm == name)
{ formList.Remove(thisForm); }
}
if (formList.Count == 0)
{ Application.Exit(); }
}
Related
I have a parent form that loads different user controls but when I am trying to access a method on the parent form from a button on the user controller which is not working but if the same method is accessed from the own parent form its all good I excluded the rest of code and here is the code not working
method on Parent Form:
using IT_HUB_MANAGEMENT_SOLUTION.SERVICES;
namespace IT_HUB_MANAGEMENT_SOLUTION.APP_FORMS
{
public partial class FIRST_RUN_FRM : Form
{
public FIRST_RUN_FRM()
{
InitializeComponent();
}
public void NEXT_STEP_CMD()
{
MAIN_PANEL.Controls.Clear();
FIRST_RUN_OBJECTS.CRYSTAL_REPORTS_CONTROL cRYSTAL_REPORTS_CONTROL = new();
cRYSTAL_REPORTS_CONTROL.Dock = DockStyle.Fill;
MAIN_PANEL.Controls.Add(cRYSTAL_REPORTS_CONTROL);
}
}
here is the code on user controls:
public partial class DOTNET_INSTALLER_CONTROL : UserControl
{
public DOTNET_INSTALLER_CONTROL()
{
InitializeComponent();
}
private void NEXT_BTN_Click(object sender, EventArgs e)
{
FIRST_RUN_FRM fff = new();
fff.NEXT_STEP_CMD();
}
}
First of all, this is not a normal behavior (at least for me) that a UserControl calls a parent form method directly. Usually user controls are not aware of the parent that is using them, and for me, it's a really smelly code.
In your code:
public partial class DOTNET_INSTALLER_CONTROL : UserControl
{
public DOTNET_INSTALLER_CONTROL()
{
InitializeComponent();
}
private void NEXT_BTN_Click(object sender, EventArgs e)
{
FIRST_RUN_FRM fff = new();
fff.NEXT_STEP_CMD();
}
}
You're simply creating a new instance of the FIRST_RUN_FORM (Also try to follow the naming convention) and then calling it's function. This won't throw a compiler error since it's a valid .Net syntax. You're creating a new class and calling a public function inside it. However, it won't show anything on your initial form because it's not the same instance.
There's two ways to fix this code, and I don't like the first way I'll show you.
You can add the parent form instance to the user control and pass it on when creating a new instance of the UC. Something like this:
public partial class DOTNET_INSTALLER_CONTROL : UserControl
{
private FIRST_RUN_FRM _parent = null;
public DOTNET_INSTALLER_CONTROL(FIRST_RUN_FRM parent)
{
InitializeComponent();
_parent = parent;
}
private void NEXT_BTN_Click(object sender, EventArgs e)
{
if (_parent == null)
{
return;
}
_parent.NEXT_STEP_CMD(); //This might throw an error not related to this question
}
}
I wouldn't recommend this though.
What I'd go for is using events, this way you don't need the UserControl to know who their parent is or what should be done to the parent when a button inside the UC is clicked.
Event:
In the user control:
public partial class DOTNET_INSTALLER_CONTROL : UserControl
{
public event EventHandler NextButtonClicked;
public DOTNET_INSTALLER_CONTROL(FIRST_RUN_FRM parent)
{
InitializeComponent();
_parent = parent;
}
private void NEXT_BTN_Click(object sender, EventArgs e)
{
OnNextButtonClicked();
}
protected virtual void OnNextButtonClicked()
{
EventHandler handler = NextButtonClicked;
handler?.Invoke(this, null);
}
}
And in the parent form you'll subscribe to this event and react when it's fired.
For example if I have a form called frmOne and I am there and I do some things then click a button event and it goes and takes me to a second form called frmTwo. Then I am in frmTwo and I do some things then I click a button in frmTwo and this button creates a new instance of frmOne. Since now I have two forms of frmOne open what will Visual Studios call the second instance of frmOne. I need to figure this out because I need to access it in code. I have tried using frmOne as the name to reference it in code but it doesnt work on the second instance. Any ideas how I can find this name or what Visual Studios calls it? I am assuming Visual Studios does something like calling the second instance frmOne1 or something like that. Thanks in advance.
From the example above here below frmShoppingCart is my first form. I have two instances of it open. I am trying to close the second instance from the closing event of another form. I can close the first instance with the code below and I can use it to close the second instance if I knew what the name was of the second instance which I am assuming is different from frmShoppingCart. I just am assuming Visual Studios is calling the name of my second instance of frmShoppingCart something else than frmShoppingCart.
private void frmViewer_FormClosing(object sender, FormClosingEventArgs e)
{
//close shopping cart form and refresh and open the shop now form
frmShoppingCart obj = (frmShoppingCart)Application.OpenForms["frmShoppingCart"];
obj.Close();
}
It's not clear why you need to reference a form indirectly by name. You could instead hold a reference directly to the form.
If you still want to reference by name, you can, but you need some other piece of data to disambiguate the form as #Steve said in the comments. To do that you could add another property to the form. Here's a small demo form
public partial class Form1 : Form
{
public Form1() => InitializeComponent();
private void button1_Click(object sender, EventArgs e)
{
var f = new Form1 { Instance = ++Counter };
f.ShowDialog();
}
private void button2_Click(object sender, EventArgs e)
{
var forms = Application.OpenForms;
}
public int Instance { get; set; }
public static int Counter { get; set; }
}
that shows, under the debugger with a breakpoint after
var forms = Application.OpenForms;
that you can see the disambiguation.
Note that searching by name alone will return the first form with that name. To get the correct one, do a search using linq against Application.OpenForms (again, credit #Steve):
var myForm = Application.OpenForms
.FirstOrDefault(x => x.Name == "Form1" && x.Instance == 1);
This is but one way to find the form. You could instead change Name when you create the form. Here's an example using the Counter above.
private void button1_Click(object sender, EventArgs e)
{
var f = new Form1();
f.Name += ++Counter;
f.ShowDialog();
}
This gives us
You can use Singleton to have a list of named Forms opened from the first ...
public partial class Form1 : Form {
public FormsOpened Forms => FormsOpened.Instance;
public Form1() {
this.InitializeComponent();
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
for (int i = 0; i < 10; i++) {
var f = default(Form);
// Form2 is a simple empty form
this.Forms.formsOpened.Add(f = new Form2() { Name = "Pippo" + i });
f.Show();
}
}
protected override void OnClosing(CancelEventArgs e) {
base.OnClosing(e);
foreach (var f in this.Forms.formsOpened) {
f.Close();
}
}
}
public sealed class FormsOpened {
private static FormsOpened instance = null;
public List<Form> formsOpened = new List<Form>();
private int counter = 0;
public static FormsOpened Instance {
get {
if (instance == null) {
instance = new FormsOpened();
}
return instance;
}
}
private FormsOpened() {
this.counter++;
}
}
In this way in every class you want you can access the FormsOpened Forms => FormsOpened.Instance and use it ;)
I'm quite new in C#, so I'm struggling with this more than two days. I hope that some one can help me out with this one.
Below some simplified code from my application.
I want to pass a List from Form1 to Form2 using delegate and event.
How can I do this? I read tons of explanations about events and delegates, but I still can't figure it out, how this really works.
Form1:
public delegate List<string> ProfileImportEventHandler();
public event ProfileImportEventHandler ProfileImported;
private void btnImport_Click(object sender, EventArgs e)
{
// raise an event
OnProfileImported();
}
protected virtual void OnProfileImported()
{
if (ProfileImported != null) // check if there are subscribers
{
ProfileImported();
}
}
Form2:
public partial class Form2 : Form
{
Form1 frm1;
public Form1()
{
// Constructor logic
frm1.ProfileChanged += new Form1.ProfileImportEventHandler(Form1_OnProfileImported);
}
}
List<string> Form1_OnProfileImported()
{
// TO DO
}
UPDATE
None of the solutions worked so far. Here is what I have already tried:
Form 2
// use generic list for profiles that will be imported from USB-Stick
private List<string> profilePaths = new List<string>();
public delegate void ProfileImportEventHandler(object sender, ProfileImportEventArgs e);
public event ProfileImportEventHandler ProfileImported;
public delegate void ImportButtonClickedEventHandler();
public event ImportButtonClickedEventHandler ButtonImportClicked;
public delegate void HaveDataDelegate(IList<string> data);
public event HaveDataDelegate HaveData;
//....
private void btnImport_Click(object sender, EventArgs e)
{
// do something...
// raise an event
var ea = new ProfileImportEventArgs(profilePaths);
OnProfileImported(ea);
OnButtonImportClicked();
// When there is data:
var copy = HaveData; // Use copy to avoid race conditions
if (copy != null)
{
copy(profilePaths);
}
// close form
this.Dispose();
}
protected virtual void OnProfileImported(ProfileImportEventArgs ea)
{
if (ProfileImported != null) // check if there are any subscribers
{
ProfileImported(this, ea);
}
}
protected virtual void OnButtonImportClicked()
{
if (ButtonImportClicked != null)
{
// fire event
ButtonImportClicked();
}
}
Form 1
public partial class frm_1 : Form
{
// child form
frm_2 frm2;
public frm_1()
{
InitializeComponent();
// do something...
// not sure if this is correct code and the correct place for it
frm2 = new frm_2();
frm2.ProfileImported += new frm_2.ProfileImportEventHandler(frm2_OnProfileImported);
//frm2.ProfileImported += frm2_OnProfileImported;
frm2.ButtonImportClicked += new frm_2.ImportButtonClickedEventHandler(frm2_ButtonImportClicked);
// In creation/init:
frm2.HaveData += DataFromForm2;
}
void frm2_OnProfileImported(object sender, ProfileImportEventArgs e)
{
// do something
}
void frm2_ButtonImportClicked()
{
// do something
}
private void DataFromForm2(IList<string> data)
{
// Process the data from Form2.
}
}
What am I still missing? Thank you for your help.
frm1.ProfileChanged += new Form1.ProfileImportEventHandler(Form1_OnProfileImported);
[…]
List<string> frmLoadProfileUSB_OnProfileImported()
First those names do not match. Second, with matching signatures you do not need (since C#2 if I recall correctly) to explicitly create the delegate. Thus:
frm1.ProfileChanged += frmLoadProfileUSB_OnProfileImported;
However, I think you have the event in the wrong place. It appears it is Form2 trying to pass data to Form1. Thus the event needs to be on Form2, with a delegate that is passed the data. Thus:
In Form2
public delegate void HaveDataDelegate(IList<string> data);
public event HaveDataDelegate HaveData;
// When there is data:
var copy = HaveData; // Use copy to avoid race conditions
if (copy != null) {
copy(data);
}
In Form1
// In creation/init:
Form2Instance.HaveData += DataFromForm2;
private void DataFromForm2(IList<string> data) {
// Process the data from Form2.
}
It's better not to use strong coupling.
So best solution here would be to store data in database or create proxy-object (class/struct).
like:
public (static) class ProfileChangesMonitor
{
...your logic here
}
If you want to use event handlers, you should follow the general pattern, defining a class that inherits EventArgs (supposing you want to involve a list in the event) in this way:
// Event Args
public class ProfileImportEventArgs : EventArgs {
private IList<string> list;
public ProfileImportEventArgs(IList<string> list) {
this.list = list;
}
public IList<string> List {
get {
return this.list;
}
}
}
// Event Handler Delegate
public delegate void ProfileImportEventHandler(object sender, ProfileImportEventArgs e);
// Form1:
public event ProfileImportEventHandler ProfileImported;
// ...
private void btnImport_Click(object sender, EventArgs e)
{
// raise an event
List<string> list = new List();
// Add something to list if needed
var ea = new ProfileImportEventArgs(list);
OnProfileImported(ea);
// Use ea.list here if necessary
}
protected virtual void OnProfileImported(ProfileImportEventArgs ea)
{
if (ProfileImported != null) { // check if there are subscribers
ProfileImported(this, ea);
}
}
// Form2:
public partial class Form2 : Form
{
Form1 frm1;
public Form1()
{
// Constructor logic
// TODO: Instantiate frm1 first.
frm1.ProfileImported += new Form1.ProfileImportEventHandler(Form1_OnProfileImported);
}
}
private void frmLoadProfileUSB_OnProfileImported(object sender, ProfileImportEventArgs e)
{
// Use and/or modify e.List if needed
}
Would you look at my code and tell me where I went wrong? in following code I am trying to send a notification to myMethod() method when Form1 gets maximized.
Thanks!
namespace WindowsDelegate1
{
public delegate void ChangedEventHandler(object sender, EventArgs e);
class myForm : Form
{
public event ChangedEventHandler Changed;
protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
Changed(this,e);
}
public override System.Drawing.Size MaximumSize
{
//get
//{
// return base.MaximumSize;
//}
set
{
base.MaximumSize = value;
OnChanged(EventArgs.Empty);
}
}
}
}
namespace WindowsDelegate1
{
class EventListener
{
private myForm TheForm;
public EventListener(myForm theform)
{
TheForm = theform;
TheForm.Changed += new ChangedEventHandler(myMethod);
}
private void myMethod(object sender, EventArgs e)
{
MessageBox.Show("hey, window should be maximized now!");
}
public void Detach()
{
TheForm.Changed -= new ChangedEventHandler(myMethod);
TheForm = null;
}
}
}
Here is the testing unit / or main()
namespace WindowsDelegate1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
myForm f = new myForm();
EventListener listener = new EventListener(f);
f.ShowDialog();
f.WindowState = FormWindowState.Maximized;
listener.Detach();
}
}
}
What's probably happening is the event is either fired after your .Detach() call, or is never fired at all. I would start by removing the listener.Detach() call. Generally, you attach to events when the form is created or when it loads and detach when it is unloading.
Other than that, your Detach method is problematic because it tries to remove a different ChangedEventHandler instance than the one added. If you're wrapping your methods in ChangedEventHandler you need to store the instance you added.
Thank you for sharing your ideas!
I fixed it by removing the property (not idea why I used that!!) and using method instead by:
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
OnChanged(EventArgs.Empty);
}
I have updated my source code above too
In a parent form A, there's the following code to call a child form "B":
Window frmChildB;
frmChildB = new FormB();
frmChildB.ShowDialog();
In the same form: the following code to call a child form "C":
Window frmChildC;
frmChildC = new FormC();
frmChildC.ShowDialog();
Now I want to make a button in form B, so that if I click that button, it automatically navigate to form C.
Using a reference object of form C in form B like in the answer of this question should be avoided if possible. The reason is that there are more than ten forms like B, C... and each of them must be able to navigate to another. Having 10 form-referenced objects inside a form isn't good.
I think there must be some way to achieve the effect. Does anyone know about this?
If I understand your question correctly, you would like to have a single instance of each of the forms and just navigate back and forth between them.
If this is what you want, you can implement a static FormManager class that creates instances of the forms and shows them as needed. You can even use an enum to further reduce the complexity.
Here is an example of this class (it will need some additional work, but should give you a good idea):
public class FormManager
{
private static FormB m_FormB;
public static FormB formB
{
get
{
if (m_FormB == null)
{
m_FormB = new FormB();
}
return m_FormB;
}
}
private static FormC m_FormC;
puClic static FormC formC
{
get
{
if (m_FormC == null)
{
m_FormC = new FormC();
}
return m_FormC;
}
}
public enum FormId
{
FormB,
FormC
}
public static Form ShowForm(FormId whichForm)
{
Form oForm;
switch (whichForm)
{
case FormId.FormB:
oForm = FormManager.formB;
break;
case FormId.FormC:
oForm = FormManager.formC;
break;
default:
oForm = null;
break;
}
if (oForm != null)
{
oForm.ShowDialog();
}
return oForm;
}
}
This can be called from the child forms as:
FormManager.ShowForm(FormManager.FormId.FormB);
Try creating an Event in frmChildB and Subscribe to it in the Parent. You can then do what you want without having an reference to frmChildC in frmChildB.
Look at this MSDN link;
This is very rough but should give you an idea.
creating the event in the child forms
public delegate void SwapEventHandler(object sender, EventArgs e);
public partial class Form2 : Form
{
public event SwapEventHandler Swap;
public Form2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Swap(sender, e);
}
}
Consumming it in the Parent Form
private void button1_Click(object sender, EventArgs e)
{
frmChildB = new Form2();
frmChildB.Name = "frmChildB";
frmChildB.Swap += new SwapEventHandler(frmChildB_Swap);
frmChildB.ShowDialog();
}
private void frmChildB_Swap(object sender, EventArgs e)
{
frmChildB.Swap -= new SwapEventHandler(frmChildB_Swap);
frmChildB.Close();
frmChildB.Dispose();
frmChildB = null;
frmChildC = new Form2();
frmChildC.Name = "frmChildC";
frmChildC.Swap += new SwapEventHandler(frmChildC_Swap);
frmChildC.ShowDialog();
}
void frmChildC_Swap(object sender, EventArgs e)
{
frmChildC.Swap -= new SwapEventHandler(frmChildC_Swap);
frmChildC.Close();
frmChildC.Dispose();
frmChildC = null;
frmChildB = new Form2();
frmChildB.Name = "frmChildC";
frmChildB.Swap += new SwapEventHandler(frmChildB_Swap);
frmChildB.ShowDialog();
}
At a primitive level it appears that you would benefit more from using the standard 'Wizard' pattern than having separate forms for each question. The exception being that instead of just having the typical next and back buttons, you should have buttons to jump to any of the questions. Here is a good tutorial that will walk you through the normal steps of creating a wizard.