When I call the Method idload() from another instance it does not update the list view list its supposed to. I know the method is getting called correctly because I placed a MessageBox after each statement in idload() and it shown. if idload() is called from Form2.cs [the form that it's in] it works fine but If I call it from Form4.cs it does not update the listview.
I used MessageBox.Show(xmlReader.GetAttribute("id")); and when idload() gets called from Form2.cs it loops through every id in the xml once and updates the list view as expected. when it gets called from Form4.cs it loops through everything twice and does not update the listview.
Here are the Relevant parts of code:
Form4.cs
public void myMethod()
{
Form2 form2 = new Form2();
form2.idload();
}
public void idwrite()
{
XElement xml = XElement.Load("settings.xml");
xml.Add(new XElement("Chat",
new XAttribute("id", textBox1.Text),
new XAttribute("name", textBox2.Text)));
xml.Save("settings.xml");
myMethod();
this.Close();
}
Form2.cs
public void idload()
{
listView1.Items.Clear();
XmlReader xmlReader = XmlReader.Create("settings.xml");
while (xmlReader.Read())
{
if ((xmlReader.NodeType == XmlNodeType.Element) && (xmlReader.Name == "Chat"))
{
if (xmlReader.HasAttributes)
{
// listView1.Items.Add(xmlReader.GetAttribute("id"));
// listView1.Items.Add(xmlReader.GetAttribute("name"));
string[] arr = new string[4];
ListViewItem itm;
arr[0] = (xmlReader.GetAttribute("id"));
arr[1] = (xmlReader.GetAttribute("name"));
itm = new ListViewItem(arr);
MessageBox.Show(xmlReader.GetAttribute("id"));
listView1.Items.Add(itm);
}
}
}
xmlReader.Close();
}
Here is the Project if needed: https://ufile.io/8dc20
Really confused why this is happening as there are no errors when debugging so any help is appreciated.
Thanks.
This is a common problem that is rooted in the OOP concept of instance. You have already an instance of Form2 displayed, but your code creates a NEW instance of Form2 and never shows it. Now the code calls the method using that new instance and works with the its ListView.
Of course, being the instance never shown, you don't see any change.
If you call form2.Show after the call to idload you will see your changes on the different instance.
The simplest workaround is to ask the Winforms engine to give you back a reference to the already shown instance of Form2 through the Application.OpenForms collection. Here you can select the reference to the Form2 instance with the extension OfType and getting the first form of that type available. If there is none then create and show it.
public void myMethod()
{
Form2 form2 = Application.OpenForms.OfType<Form2>().FirstOrDefault();
if(form2 != null)
form2.idload();
else
{
form2 = new Form2();
form2.Show();
form2.idload();
}
}
Related
Wierd behaviour when passing values to and from second form.
ParameterForm pf = new ParameterForm(testString);
works
ParameterForm pf = new ParameterForm();
pf.testString="test";
doesn't (testString defined as public string)
maybe i'm missing something? Anyway I'd like to make 2nd variant work properly, as for now - it returns null object reference error.
Thanks for help.
Posting more code here:
calling
Button ParametersButton = new Button();
ParametersButton.Click += delegate
{
ParameterForm pf = new ParameterForm(doc.GetElementById(ParametersButton.Tag.ToString()));
pf.ShowDialog(this);
pf.test = "test";
pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit);
};
definition and use
public partial class ParameterForm : Form
{
public string test;
public XmlElement node;
public delegate void ParameterSubmitResult(object sender, XmlElement e);
public event ParameterSubmitResult Submit;
public void SubmitButton_Click(object sender, EventArgs e)
{
Submit(this,this.node);
Debug.WriteLine(test);
}
}
result:
Submit - null object reference
test - null object reference
pf.ShowDialog(this); is a blocking call, so pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit); is never reached: switch the order.
Submit(this,this.node); throws a null object reference because no event is assigned to it (see above). Generally, you should always check first: if (Submit != null) Submit(this,this.node);
You should change ``pf.ShowDialog(this);topf.Show(this);` so that your main form isn't disabled while your dialog box is open, if that's what you want, or use the model below (typical for dialog boxes.)
I'm not sure what pf_Submit is supposed to do, so this might not be the best way to go about it in your application, but it's how general "Proceed? Yes/No" questions work.
Button ParametersButton = new Button();
ParametersButton.Click += delegate
{
ParameterForm pf = new ParameterForm(testString);
pf.ShowDialog(this); // Blocks until user submits
// Do whatever pf_Submit did here.
};
public partial class ParameterForm : Form
{
public string test; // Generally, encapsulate these
public XmlElement node; // in properties
public void SubmitButton_Click(object sender, EventArgs e)
{
Debug.WriteLine(test);
this.Close(); // Returns from ShowDialog()
}
}
When you want to use your second variant, you have to use a getString()-Method, where you can put the e.g. "testString". The way you wrote it, "testString" should be a method (and got brackets).
EDIT (a bit more precise):
You could write:
pf.getString(testString);
, if "pf" is an instance of your own class, otherwise you had to look up, whether you can retrieve a String in this class.
the thing was in line order :)
pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit);
and
pf.Test = "test";
should have been set before
pf.ShowDialog(this);
my mistake thingking that parameter can be passed after 2nd form was displayed
thnx for answers
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();
I have this code, in which I read the data i deserialized in a gridview, let's name it FormReadDatabases
It gets populated like this:
xmlData = (xml.ServiceConfig)serializer.Deserialize(reader);
dataGridView1.DataSource = xmlData.Databases;
Then in each row of the grid I have a button 'Tables'
After I click it a new form appears FormReadTables
It gets populated like this:
BindingList<xml.Table> table = new BindingList<xml.Table>();
dataGridView4.DataSource = table;
Then I have a button which helps me add a new table, it works fine, the new row appears in the FormReadTables, But when i close the form and I am now at the FormReadDatabases if I click again on the Table button the changes are not saved.
Any idea how to avoid this?
This should be simple, data binding needs to happen using a mechanism that can hold value even when forms are opened or closed:
First way could be use a static type as follows:
static BindingList<xml.Table> table;
public BindingList<xml.Table> FetchTable()
{
if(table == null)
{
table = new BindingList<xml.Table>();
}
return table
}
dataGridView4.DataSource = FetchTable();
There's a catch out here what if the form can have multiple instances than can access the static variable, then while updating the table type it needs to be locked / synchronized
Another option would be table type is part of the main form, which loads the child form and in the constructor of the child form it gets the instance of the parent form, which is used updated and is retained even after closing child form. This will also need synchronization for multiple user / thread access
public class ParentForm
{
public BindingList<xml.Table> table = new BindingList<xml.Table>();
}
public class ChildForm
{
ParentForm localPf;
pulic ChildForm(ParentForm pf)
{
localPf = pf;
}
dataGridView4.DataSource = localPf.table;
}
Noe any change to parent form object's table variable will persist till the point Parent form is in the memory, but please note this implementation is not yet threads safe
Each time the form opens, you are creating a new BindingList.
BindingList<xml.Table> table = new BindingList<xml.Table>();
Instead, have the other page contain a variable for this, and when you 'new' the other form, pass in the variable.
The Actions taken on the opened form are byref, and therefore will update your host forms variable. This means the next time you open the form, the variable you pass to it will already have the previous changes already stored in it.
Example as requested:
I don't have my WinForms environment at hand, but this shows the important concepts.
namespace Bob
{
public class FormLucy
{
private BindingList<xml.Table> table = new BindingList<xml.Table>();
// your form stuff..
protected void ButtonClick(object sender, EventArgs e)
{
var frm = new FormTracy(table);
// init your forms properties, position etc
fmr.ShowDialog();
}
}
}
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.
I realize this has been partly covered, although I believe the problem I have has not been fully covered:
I have an event that creates an object of a Form when the event occurs:
private void hostView_AfterSelect(object sender, TreeViewEventArgs e)
{
string selectedNodeText = e.Node.Text;
if (selectedNodeText == "Internal Hosts" || selectedNodeText == "External Hosts")
{
// ignore, parent nodes have no corrosponding tab!
}
else
{
Form1 Form1Object = new Form1(selectedNodeText);
Form1Object.Show();
}
}
Although I need to create many objects of Form1 depending on the "selectedNodeText" string value.
For example: If "selectedNodeText" was == "Cars" I would like the object name of Form1 to be called something like "Form1ObjectCars" and if it was "Dogs" the object name would be "Form1ObjectDogs".
You can use a Dictionary<TKey,TValue> class for this, for example
//Initialize
Dictionary<string, Form1> forms = new Dictionary<string, Form1>();
//Add objects
if(selectedNodeText == "foo")
forms.Add("foo", new Form1("foo"));
if(selectedNodeText == "bar")
forms.Add("bar", new Form1("bar"));
//Get objects
Form1 bar = forms["bar"];
It sounds like you're using the same form, but just want to title or caption it differently.
string caption = string.Format("Form1Object{0}", selectedNodeText);
Form1 Form1Object = new Form1(caption);
// in the Form's constructor you are doing a this.Text = caption?
Form1Object.Show();
Or to avoid passing it to the form:
Form1 Form1Object = new Form1();
Form1Object.Text = string.Format("Form1Object{0}", selectedNodeText);
Form1Object.Show();