C# Closing a specific form in a multiform application - c#

I hope you can help me with this one. My application is monitoring a database for alerts. When a alert shows up in the database, my application will add it to the main form in a datagridview, and depending on its priority it will also create a small winform popup with the event.
In the datagridview is a button to flag the alert as "seen", it will then update the database and be it will be gone from the list. However the popup form for this event is still open.
Does anyone know how to close this form? I need a way to find a specific form between the possible multiple alert forms that are open.
The closest I have come this far is the following code:
private void closeForm(int id)
{
foreach (Form f in Application.OpenForms)
{
if (Convert.ToString(id) == f.Name)
{
this.Close();
}
}
}
Which works up until the point that it closes the correct form. then it gives an error saying "Collection was modified; enumeration operation may not execute." This kinda makes sense, but I simply can't figure out another way to do it.
I have a winform class called Alert, wich makes the new forms. As you can see they will get a standard text "Alarm" and a unique Name based on the alert ID.
Alert alertform = new Alert(id);
alertform.Name = formid;
alertform.Text = "Alarm";
alertform.Show();
I hope someone has some good ideas how I can go about this. I have searched but cannot realy find a simple and elegant way to do this.

You need to add break; to your loop after you close the form. The collection is modified when you close the form (that form is removed from the collection), thus rendering the foreach loop invalid. And should you not be calling f.Close, rather than this.Close?
private void closeForm(int id)
{
foreach (Form f in Application.OpenForms)
if (Convert.ToString(id) == f.Name)
{
f.Close();
break;
}
}

You should simply be able to store a reference to your form in the DataGridView or its DataSource then close the form using that reference. This approach is less likely to break in the future than iterating over all the application forms.
What would probably work best is to add a hidden column to the DataGridView that holds the form id, then also have a Dictionary<int, Form> which you use to get the reference to the Form you want to close.
Then you can simply get the form reference out of the dictionary and call close on it:
private void CloseAlertForm(int id)
{
Form f = dict[id];
f.Close();
dict.Remove(id);
}
Additionally you could store Action delegates instead of form references allowing you to slightly decouple the alert forms and the grid form.

just get ref. from foreach loop and close the form outside it.
private void closeForm(int id)
{
Form formtoclose=null;
foreach (Form f in Application.OpenForms)
{
if (Convert.ToString(id) == f.Name)
{
formtoclose = f;
}
}
if(formtoclose!=null)
formtoclose.Close();
}

A Close modifies your OpenForms collection, so instead of enumeration over the OpenForms collection directly, you could enumerate over a copy.
LINQ is very handy into making copies, like this:
foreach (Form f in Application.OpenForms.Where(i => Convert.ToString(id) == i.Name).ToList()) {
// Save to close the form here
}
The ToList executes the query, and stores the copy.

var names = Application.OpenForms.Select(rs=>rs.name).ToList()
foreach (string name in names)
if (Convert.ToString(id) == name)
{
Application.OpenForms[name].Close();
}

You could use the type of the forms to find them (And ToArray to create a new collection an avoid changing the collection you are enumerating).
private void CloseAlerts()
{
var openForms = Application.OpenForms.Cast<Form>();
foreach (var f in openForms.Where(f => f is Alert).ToArray())
{
f.Close();
}
}
In this case you don't need to set a name :
Alert alertform = new Alert(id);
alertform.Text = "Alarm";
alertform.Show();

Related

How to add/remove items from a list from a different form?

Say I have a list called listOfFruits in my main form. In a second form I've made I want the user to be able to remove items from that list to a second list called removedFruits. Currently I know I can access these lists in my second form simply passing them as parameters in the form constructor. However c# can't do pointers (correct?) so how can I effect the main form's copy of these lists from my second form? Because currently any changes to those lists in my second form don't effect the main form's original copy of the lists. If I were to remove 5 fruits from the listOfFruits passed to my second form then after finishing my work the main form would still still have a full listOfFruits and an empty removedFruits. Is there a simple fix to this? Maybe a get/set or a way to add/remove items from the original lists from the second form? Maybe the answer is in some sort of accessor stuff?
EDIT: To clarify; I want to add to one list, and remove from another. Not add/remove to the same list. Not sure if this matters entirely but I figured I'd be specific here in case it does.
EDIT2: I think the issue is I'm copying the original list from the first form and not editing it directly. Can someone fix my code so I can access the original list from my second form instead of making a copy of the list?
public partial class ListSelector : Form
{
private string windowName = Form1.typeOfModuleAdded;
public List<IOModule> innerIOList;
IOModule cardAdded = null;
public ListSelector(List<IOModule> cardList)
{
this.Text = windowName;
innerIOList = cardList;
InitializeComponent();
InitializeList();
}
private void InitializeList()
{
if (windowName == "Drive")
{
string[] listDrives = { "ACS880", "test" };
listBox1.Items.AddRange(listDrives);
}
else if (windowName == "IOBlock")
{
if (!innerIOList.Any())
{
MessageBox.Show("No cards loaded! Please import cards from IO List.", "Error Empty Data", MessageBoxButtons.OK, MessageBoxIcon.Error);
this.Close();
}
foreach (IOModule card in innerIOList)
{
cardAdded = card;
listBox1.Items.Add(card.name);
}
}
else if (windowName == "Local Card")
{
string[] listLocals = { "1756-EN2T", "test" };
listBox1.Items.AddRange(listLocals);
}
else if (windowName == "Processor")
{
string[] listProcessors = { "1756-L71S", "test" };
listBox1.Items.AddRange(listProcessors);
}
}
private void addBtn_Click(object sender, EventArgs e)
{
if (listBox1.SelectedItem != null)
{
Form1.SetModule(listBox1.SelectedItem.ToString());
Form1.confirmedAdd = true;
this.Close();
}
else if (cardAdded != null)
{
innerIOList.Remove(cardAdded);
}
else
{
MessageBox.Show("No module selected!");
}
}
and here's how I pass the list to that form from my first form:
ListSelector test = new ListSelector(ioList);
test.ShowDialog();
where ListSelector is the name of my second form, and ioList is the list im passing to it.
EDIT3: added more code
"However c# can't do pointers (correct?) so how can I effect the main form's copy of these lists from my second form?"
No, not correct. Any object reference (for instance, of a List<Fruit>) is still very much a pointer to a place in memory, and if you pass the same List<Fruit> object to both Forms, they share the same List.
I don't know why your changes to your listOfFruits don't chow up in your first Form. I would check the following things:
Are you 100% sure you use the same List<Fruit> object in both Forms. (If you create a new List like this: new List<Fruit>(listOfFruits) it is NOT the same List)
Does the first Form have any way of finding out, that the List has changed? Possible using a Timer with recurring checks, or (my favorite) triggering an event when you change something, and subscribe an EventHandler in the first Form to the event.
I assume that you have created a second list in your second form that is filled with the items of the first form's list. Then changes on the second list aren't reflected in the first list. You have to use the same reference of the list.
public Form2(List<Fruit> listOfFruits)
{
this._listOfFruits = listOfFruits;
}
private List<Fruit> _listOfFruits;
Instead using a public field, try to use property and on creating your new ListSelector pass the list to the property.
public partial class ListSelector : Form
{
private string windowName = Form1.typeOfModuleAdded;
private List<IOModule> innerIOList;
IOModule cardAdded = null;
public List<IOModule> CardList
{
get
{
return innerIOList;
}
set
{
innerIOList = value;
InitializeList();
}
}
public ListSelector()
{
this.Text = windowName;
InitializeComponent();
}
When creating your new ListSelector object
ListSelector ls = new ListSelector();
ls.CardList = your mainform list of IOModule here
ls.ShowDialog();

How to close all open instances of a modeless dialog box?

I have a C# winform project that displays a list of results based on a user's search criteria. For each item on the list, the user can open a modeless dialog box showing more details about the selected item.
Every time the user opens an instance of my details window, this code runs:
public void showDetails()
{
GetDetails route = new GetDetails();
route.myParent = this;
route.Show();
}
In order to compare details between two or more items, the user is allowed to open as many instances of this dialog box as it likes. I'd like to be able to close any and all open instances of this window when the user conducts a new search from the main form window? I've tried Googling, but no luck ... does anyone know how to do this?
Application.OpenForms is a collection of open forms owned by the application
try find all details dialogs and close them like this:
foreach(var f in Application.OpenForms.OfType<GetDetails>().ToList())
{
f.Close();
}
You don't really tell, but I assume your GetDetails is a System.Windows.Forms.Control (probably a form, a dialog box, a message box, etc).
If you look closely to your Form.InitializeComponent, you'll see that Form has a property Controls. All child controls are added to the control collection.
If you add each created route to your control collection you can ask this collection for all objects of type GetDetails and order them to close:
public void ShowDetails()
{
var route = new GetDetails();
route.myParent = this;
this.Controls.Add(route);
route.Show();
}
public void CloseAllRoutes()
{
foreach (var route in this.Controls.Where( control => control is GetDetails))
{
route.Close();
}
}
You need to be certain that when a rout is closed, or disposed or something the following code is called:
private void OnRouteClosed (object sender, ...)
{
if (sender is GetDetails)
{
this.Controls.Remove(sender);
}
}

Create a new instance of a control dynamically

Ok so I have a form I'm building that's going to change it's interface by using custom controls. What I'm trying to do is make several checks before a new control is created, like checking if one is already up. I have everything working just fine but I can't seem to create the new control dynamically, without creating it before running the checks which defeats the purpose.
The controls all implement an interface called ICustomControl and inherit from a MasterControl. Then I have a custom control called JobForm, and a button on the main form that calls the method like so: Check_Controls(newJobForm)
JobForm newJobForm;
private void Check_Controls(Control control) // Checks current controls to see if a new one can be opened
{
bool currentControl = false;
foreach (Control c in this.Controls)
{
if (c is ICustomControl && c != masterControl)
currentControl = true;
}
if (currentControl)
{
TimedMessageBox timedMessage = new TimedMessageBox("There is currently an open form. Please close the current control before opening another.");
timedMessage.ShowDialog();
}
else
{
Control c = (Control)Activator.CreateInstance(control.GetType());
this.Controls.Add(c);
Position_Control(c);
c.Show();
}
}
I dont't want to create a new instance of the custom control like: JobForm newJobForm = new JobForm(); before running the check method, I want to pass the reference to the check method and then have it create the new instance after it's checks are complete. In this way no matter how many new custom controls I wind up adding to the application, all I have to do to set one up is create the reference variable, then for the button call the check method and pass it the reference.
Anyone know how I can do this?
I think you are thinking about the problem backwards. Instead of saying "I have control X, is it valid?" think "would control X be valid, if so create". You are wanting to make checks to see if a control is valid, yet you want to send in a reference to that control.
Your code doesn't check for specific types of control, but rather just that at least one control belonging to the current form implements your interface. If this is the intended behavior, just have a function that does your initial check to see if any ICustomControl exist on your form. If that function return false, then go along with your creation.
You can accomplish this with a single function by using a constrained generic. This also gets you away from the less-than-ideal practice of using Activator and other reflection methods for dynamic type generation:
private void CheckAndAddControl<ControlType>()
where ControlType : MasterControl, new()
{
bool currentControl = false;
foreach (Control c in this.Controls)
{
if (c is ControlType)
{
currentControl = true;
break;
}
}
if (currentControl)
{
TimedMessageBox timedMessage = new TimedMessageBox("There is currently an open form. Please close the current control before opening another.");
timedMessage.ShowDialog();
}
else
{
var c = new ControlType();
this.Controls.Add(c);
Position_Control(c);
c.Show();
}
}
You would then use this function as follows:
CheckAndAddControl<JobForm>();

Check if form is opened and update if opened

I want to check if an instance of a form is opened and open the existing instance to update a textbox else create a new instance.
After searching I found : How to check if a windows form is already open, and close it if it is?
From the accepted answer I tried
try
{
foreach (Form fm in Application.OpenForms)
{
if (fm is Form2)
{
Form2 n1 = (Form2)Application.OpenForms["Form2"];
n1.textBox1.Text = textBox1.Text;
break;
}
else
{
Form2 n1 = new Form2();
n1.textBox1.Text = textBox1.Text;
n1.Show();
}
}
}
catch (InvalidOperationException)
{
}
Apart from that this code throws an InvalidOperationException (which i am already catching), The code doesn't work because if an instance already exists it still creates a new instance.
What am i doing wrong?
A better approach would be to filter the OpenForms based on the form type:
var form2collection = Application.OpenForms.OfType<Form2>();
You can then loop over those, or if the collection is empty, open a new form. The advantage is that you aren't relying on the form name, but the actual class definition of the form, which is more reliable.
Additionally, I tend to avoid direct manipulation of controls from other code. I find it more reliable if others call a method, such as
public void setSomeControl(string value)
{
this.controlName.Text = value;
}
then call
form2collection[0].setSomeControl("new value");
which allows your form to do all the housekeeping and the calling code can ignore those details.

Single reusable function to open a single instance of form

I am trying to create a reusable function that can open a single instance of form. Means if a form is not already open it should create and show the new form and if already open it should bring the existing form to front.
I was using the following function,
if (Application.OpenForms["FPSStorageDemo"] == null)
{
FPSStorageDemo fp = new FPSStorageDemo();
fp.Name = "FPSStorageDemo";
fp.Show();
}
else
{
((FPSStorageDemo)Application.OpenForms["FPSStorageDemo"]).BringToFront();
}
But I have to write this code again and again whereever I have to open a form. But I need a single reusable function that can do this job.
I wrote a function like,
void OpenSingleInstanceForm(Type TypeOfControlToOpen)
{
bool IsFormOpen = false;
foreach (Form fm in Application.OpenForms)
{
if (fm.GetType() == TypeOfControlToOpen)
{
IsFormOpen = true;
fm.BringToFront();
break;
}
}
if (!IsFormOpen)
{
Object obj = Activator.CreateInstance(TypeOfControlToOpen);
//obj.Show(); //Here is the problem
}
}
But at the end I don't know how to show the new form instance. Can anybody suggest how to do it? Is this wrong or there is another way to do this?
Thanks in advance.
public static class FormUtility
{
public static FormType GetInstance<FormType>() where FormType : Form, new()
{
FormType output = Application.OpenForms.OfType<FormType>().FirstOrDefault();
if(output == null)
{
output = new FormType();
}
//you could add the show/bring to front here if you wanted to, or have the more general method
//that just gives a form that you can do whatever you want with (or have one of each).
return output;
}
}
//elsewhere
FormUtility.GetInstance<Form1>.BringToFront();
I'd also like to take the time to mention that while having methods like this are quick and easy to use, in most cases they are not good design. It leads you to the practice of just accessing forms globally rather than ensuring that when forms need to communicate with each other they do so by exposing the appropriate information through the appropriate scope. It makes programs easier to maintain, understand, extend, increases reusability, etc. If you have trouble determining how best for two or more forms to communicate without resorting to public static references to your forms (which is exactly what Application.OpenForms is) then you should feel free to post that question here for us to help you solve.
You are looking for Singleton
Check this Implementing Singleton in C#

Categories

Resources