Where should TreeView's Invoke method between two WinForms be handled? - c#

I have two WinForms (Setting and frmMain). I have a TreeView in Setting's form and I want to call its FillTree method in the second form frmMain.
I'm using the TreeView.Invoke for the threading purposes.
Here is my code for TreeView filling data in Setting's form :
TreeNode parentNode;
public void FillTree(DataTable dtGroups, DataTable dtGroupsChilds)
{
treeViewGroups.Nodes.Clear();
if (dtGroups == null) return;
foreach (DataRow rowGroup in dtGroups.Rows)
{
parentNode = new TreeNode
{
Text = rowGroup["Groupname"].ToString(),
Tag = rowGroup["Groupid"]
};
treeViewGroups.Invoke(new Add(AddParent), new object[] { parentNode });
if (dtGroupsChilds == null) continue;
foreach (DataRow rowUser in dtGroupsChilds.Rows)
{
if (rowGroup["Groupid"] == rowUser["Groupid"])
{
TreeNode childNode = new TreeNode
{
Text = rowUser["Username"].ToString(),
Tag = rowUser["Phone"]
};
treeViewGroups.Invoke(new Add(AddParent), new object[] { childNode });
System.Threading.Thread.Sleep(1000);
}
}
}
treeViewGroups.Update();
}
public delegate void Add(TreeNode tn);
public void AddParent(TreeNode tn)
{
treeViewGroups.Nodes.Add(tn);
}
public void AddChild(TreeNode tn)
{
parentNode.Nodes.Add(tn);
}
FillTree method from above code, I wants call it in my second form frmMain which I tried like so:
Settings settingsWindow;
public frmMain()
{
InitializeComponent();
settingsWindow = new Settings(this);
}
private void SomeMethod()
{
//Two DataTables (dt1 and dt2) are passed from frmMain form
settingWindow.FillTree(dt1, dt2);
}
When I call FillTree method it show me error like this:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
I real wants to know, where should be handle TreeView's Invoke method in my second winform frmMain?
I'm following these links (but no avail):
1) Populating TreeView on a Background Thread
2) How to add object in treeview from another thread
3) How can i invoke a method called from backgroundworker dowork event?
Edited for Visualizing Problem
I have tried it with TreeView its not working then I Tried it with ListBox but the problem is still same.
Problem: I've a method (which populate my ListBox) in WinForm settingsWindow and I want to call that method in my second WinForm frmMain.
Settings form screenshot:
frmMain form screenshot:
Problem GIF :
Settings form Code for ListBox Populating :
public void PopulateGroupListData(DataTable dt)
{
listGroups.DataSource = null;
listGroups.DisplayMember = "GroupName";
listGroups.ValueMember = "Groupid";
listGroups.DataSource = dt;
if (listGroups.Items.Count > 0)
listGroups.SelectedItem = listGroups.Items[0];
}
Calling PopulateGroupListData in second form frmMain 's method:
void onCompleteReadFromServerStream(IAsyncResult iar)
{
/... some code
String[] arr1 = ServerMessage[1].Split('&');
string Groupid1 = arr1[0];
string GroupName1 = arr1[1];
GroupsList.Rows.Add(Groupid1, GroupName1);
settingsWindow.PopulateGroupListData(GroupsList);
/... some code
}

Subscribe to HandleCreated event and fill the tree in the event handler.
public partial class Form1 : Form
{
Form2 settingsWindow;
public Form1()
{
InitializeComponent();
}
private void SettingsWindow_HandleCreated(object sender, EventArgs e)
{
var dt1 = new SampleTable1();
var dt2 = new SampleTable2();
settingsWindow.FillTree(dt1, dt2);
}
private void button1_Click(object sender, EventArgs e)
{
settingsWindow = new Form2();
settingsWindow.HandleCreated += SettingsWindow_HandleCreated;
settingsWindow.ShowDialog();
}
}
As a bonus, we also fixed a couple problems with FillTree method, so the tree can be built correctly.
public void FillTree(DataTable dtGroups, DataTable dtGroupsChilds)
{
treeViewGroups.Nodes.Clear();
if (dtGroups == null) return;
foreach (DataRow rowGroup in dtGroups.Rows)
{
parentNode = new TreeNode
{
Text = rowGroup["Groupname"].ToString(),
Tag = rowGroup["Groupid"]
};
treeViewGroups.Invoke(new Add(AddParent), new object[] { parentNode });
if (dtGroupsChilds == null) continue;
foreach (DataRow rowUser in dtGroupsChilds.Rows)
{
if ((int)rowGroup["Groupid"] == (int)rowUser["Groupid"])
{
TreeNode childNode = new TreeNode
{
Text = rowUser["Username"].ToString(),
Tag = rowUser["Phone"]
};
treeViewGroups.Invoke(new Add(AddChild), new object[] { childNode });
//System.Threading.Thread.Sleep(1000);
}
}
}
treeViewGroups.Update();
}
Answering your follow-up question: if Form2 is already visible, the exception you reported wouldn't happen because at this point the window's handle has been created, as we show below.
public partial class Form1 : Form
{
Form2 settingsWindow;
SampleTable1 dt1;
SampleTable2 dt2;
int groupid = 1;
int userid = 101;
public Form1()
{
InitializeComponent();
dt1 = new SampleTable1();
dt2 = new SampleTable2();
dt1.AddGroup(groupid);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
dt1.AddGroup(++groupid);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
}
private void SettingsWindow_HandleCreated(object sender, EventArgs e)
{
settingsWindow.FillTree(dt1, dt2);
}
private void button1_Click(object sender, EventArgs e)
{
settingsWindow = new Form2(this);
settingsWindow.HandleCreated += SettingsWindow_HandleCreated;
settingsWindow.ShowDialog();
}
public void UpdateData(string groupname)
{
dt1.AddGroup(++groupid, groupname);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
settingsWindow.FillTree(dt1, dt2);
}
}
Form2 stays the same you already have, just adding an event handler for the new button:
private void button1_Click(object sender, EventArgs e)
{
form1.UpdateData(textBox1.Text);
}
Answering your 2nd follow-up question: UpdateData was created for the specific use case of the user submitting a new group through Form2. I would rather have specific code for a different use case. Note that it would be fairly elementary to have UpdateData to consult with a server, or even better, with some abstract interface, that could be in a remote server or not (good design dictates it should be irrelevant/transparent...) and then return a string message to be presented in Form2. However, doing so wouldn't add any insight into the purposes of the limited scope of this sample. It would actually mud the waters about the root problem you're reporting and its corresponding solution. Let's not forget that without it, the reported exception in your original question comes right back at you...:O)

The code which you have shared doesn't help us to reproduce the problem. The only thing that someone can do is creating an example, showing you how to load a TreeView in another form in another thread (if thread is really necessary).
There are some points that you should consider:
You should first show the form, then call Invoke method of the form or one of its controls. Otherwise you will receive 'Invoke or BeginInvoke cannot be called on a control until the window handle has been created.'
When adding a lot of nodes to a TreeView, first call BeginUpdate then add all the nodes then call EndUpdate. This way it will be quite faster with less UI rendering.
Condiser using async/await pattern to have a non-blocking UI.
When you want to update UI thread from another thread, use Invoke.
Don't do blocking time-consuming non-UI tasks in the UI thread.
When using Invoke, keep in mind that the code is running in the UI thread. So don't call blocking time-consuming non-UI tasks in the Invoke. Just call the UI code in Invoke.
In the following example, I've created a form having a button. On click of button, I load data and then open another window which has a TreeView inside. Then loading tree with 10000 nodes:
private async void button1_Click(object sender, EventArgs e)
{
DataTable table = null;
//Load data
this.Text = "Loading ...";
await Task.Run(async () => {
await Task.Delay(2000); //Simulating a delay for loading data
table = new DataTable();
table.Columns.Add("C1");
for (int i = 0; i < 10000; i++)
table.Rows.Add(i.ToString());
});
this.Text = "Load data successfully.";
//Show the other form
var f = new Form();
var tree = new TreeView();
tree.Dock = DockStyle.Fill;
f.Controls.Add(tree);
f.Show();
//Load Tree
f.Text = "Loading tree...";
await Task.Run(async () => {
await Task.Delay(2000); //Simulating a delay for processing
Invoke(new Action(() => { tree.BeginUpdate(); }));
foreach (DataRow row in table.Rows) {
//DO NOT processing using invoke, just call UI code in INvoke.
Invoke(new Action(() => { tree.Nodes.Add(row[0].ToString()); }));
}
Invoke(new Action(() => { tree.EndUpdate(); }));
});
f.Text = "Load tree successfully.";
}

Related

The FormClosing event seems to run after a different Form is started

I did a lot of research about this problem and tried methods but none of them worked. First I will show you what is in my application and what I want it to be with some pictures.
Picture 1
As you can see in the first picture, I open new childForms using the buttons on the main form. One of them is the form named "Price". This form saves the prices entered by the user to the json file. I do the saving process in the FormClosing event. (Yes i know but I don't want to use the "Save" button.)
Picture 2
As you can see in the second picture, the "Result" form did some mathematical operations by reading the values entered by the user in the "Price" form over json.
Now let's come to the problem, I open the "Price" form, change the values and press the result button. Results are coming, everything is great! But the results are not correct because when the "Result" Form was run it didn't wait for the current values to be saved in json. So it didn't wait for the "Price" form to close (i.e. it didn't wait for the Price.FormClosing event to complete).
To avoid this error, I open the results form after opening a different form, but this is amateurish.
I hope I was able to explain my problem clearly.
Thanks in advance for your answers and thoughts.
Code to Write Current Values to JSON File:
private void ProductPricesForm_FormClosing(object sender, FormClosingEventArgs e)
{
string jsonString = JSONOperations.getItemsAsString(products);
File.WriteAllText(JSONOperations.productsJSONPath, jsonString);
}
Code to Read JSON File:
private static void getItems()
{
using (StreamReader r = new StreamReader(JSONOperations.productsJSONPath))
{
string json = r.ReadToEnd();
products = JSONOperations.getItemsAsClass<Product>(json);
}
}
Form Opening Code:
private void buttonResult_Click(object sender, EventArgs e)
{
openChildForm(new ResultForm());
}
private void buttonProductPrices_Click(object sender, EventArgs e)
{
openChildForm(new ProductPricesForm());
}
private Form activeForm;
private void openChildForm(Form childForm)
{
if (activeForm == null)
{
startChildForm(childForm);
}
else
{
if (String.Equals(activeForm.Name, childForm.Name))
{
return;
}
else if (!String.Equals(activeForm.Name, childForm.Name))
{
activeForm.Close();
startChildForm(childForm);
}
}
}
private void startChildForm(Form childForm)
{
childForm.TopLevel = false;
childForm.FormBorderStyle = FormBorderStyle.None;
childForm.Dock = DockStyle.Fill;
panelChildForm.Controls.Add(childForm);
panelChildForm.Tag = childForm;
childForm.BringToFront();
childForm.Show();
activeForm = childForm;
}
You can simply Hide the dialog instead of close.
First, create your childs types:
private enum FormType
{
Result = 0,
Products = 1,
//...
}
An a method to create each form:
private Form CreateChildForm(FormType formType)
{
switch (formType)
{
case FormType.Result:
return new ResultForm();
case FormType.Products:
return new ProductPricesForm();
default:
return null;
}
}
Now, in your form, add this fields:
private int _activeChildIndex = -1;
private readonly Form[] _childsForms = new Form[Enum.GetNames(typeof(FormType)).Length];
_childsForms will store each type of ChildWindow that you create. _activeChildIndex indicates the _childsForms which is active now.
private void ShowChildWindow(FormType formType)
{
var index = (int)formType;
if (this._activeChildIndex == index)
{
return;
}
if (this._activeChildIndex >= 0)
{
var activeChild = this._childsForms[this._activeChildIndex];
// TODO: Hide form
this.OnHideForm(this._activeChildIndex);
}
this._activeChildIndex = index;
if (this._childsForms[index] != null)
{
// TODO: Reset to default (or leave with last state, as you prefer)
// TODO: And show
}
else
{
// TODO: Create child form
var childForm = this.CreateChildForm(formType);
if (childForm != null)
{
this._childsForms[index] = childForm;
}
}
this.OnShowForm(this._activeChildIndex);
}
This method create a child form if not exists and show when was previously created.
In your ProductsForm, add a method to get the JSON:
public string GetProductsJson()
{
return JSONOperations.getItemsAsString(products);
}
Now, you can use OnShowForm in this way:
private void OnShowForm(int index)
{
var formType = (FormType)index;
if (formType == FormType.Result)
{
var productsForm = this._childsForms[(int)FormType.Products];
if (productsForm != null)
{
var json = productsForm.GetProductsJson();
}
}
}
When you activate your result form, search your products form. If was previously created, get the Json. You don't need save to file the json to use here. Save it if you need for other reason.
Do some changes in FormClosing:
private void ProductPricesForm_FormClosing(object sender, FormClosingEventArgs e)
{
// Do this if you need
string jsonString = GetProductsJson();
File.WriteAllText(JSONOperations.productsJSONPath, jsonString);
// TODO: You need a flag to know when are you closing the app.
// In that case, don't cancel
e.Cancel = true;
}
UPDATE
I don't like this solution but may be that you are waiting for:
First, add a method to get the Json in your Product form:
public string GetJson()
{
return JSONOperations.getItemsAsString(products);
}
And a field in your main form:
private string _productsJson;
Instead of a file, use this string. You also can save to file if you need for other purposes.
Change this other method:
private void buttonProductPrices_Click(object sender, EventArgs e)
{
var form = new ProductPricesForm();
form.FormClosing += (sender2, e2) => this.OnProductsPrices(form.GetJson());
openChildForm(form);
}
It do the same as you was doing and also get the closing event to get the Json and invoke to OnProductsPrices with that string.
private void OnProductsPrices(string json)
{
if (_productsJson != json)
{
_productsJson = json;
var form = activeForm as ResultForm;
if (form != null)
{
form.DoSomethingWithTheJson(json);
}
}
}
If the Json doesn't change, you don't need to do anything. When it's changed, you update your _productsJson and check if current form is ResultForm. In this case, you load the Json or do whatever you do with the Json in that Form.
Also, you must use _productsJson when you create the ResultForm, like know, that I suppose you get from file. In this way, you use the json always when you create ResultForm or later, when the ProductsForm is closed.
private void buttonResult_Click(object sender, EventArgs e)
{
var form = new ResultForm();
openChildForm(form);
if (!string.IsNullOrEmpty(_productsJson))
form.DoSomethingWithTheJson(_productsJson);
// Or do the same in the constructor and manage inside the form, calling to DoSomethingWithTheJson
//var form = new ResultForm(_productsJson);
}

Using variables in primary Form from secondary From

I am having issues transfering data points from one form to another. I have made sure that the button inside of addTagsForm has a public modifier.
I've looked at multiple solutions of using data values from one form to another and I must be missing something from them.
Here is what I have in Form1:
//Inside Form1
XMLDocGen.PLCData PLC = new XMLDocGen.PLCData();
List<XMLDocGen.TagData> tags = new List<XMLDocGen.TagData>();
AddTagsForm addTagsForm = new AddTagsForm();
addMoreTagsSelected = addTagsForm.addMoreTagsEnabled;
if(addMoreTagsSelected)
{
for(int i= 0; i < 8; i++)
tags.Add(new XMLDocGen.TagData(addTagsForm.addTags[i], addTagsForm.addDataTypes[i], addTagsForm.addElemSizes[i], addTagsForm.addElemCounts[i]));
}
Here is what is inside of addTagsForm
public void button1_Click(object sender, EventArgs e)
{
addMoreTagsEnabled = true;
var tagNames = new List<TextBox>() { tagNameBoxAMT1, tagNameBoxAMT2, tagNameBoxAMT3, tagNameBoxAMT4, tagNameBoxAMT5, tagNameBoxAMT6, tagNameBoxAMT7, tagNameBoxAMT8 };
var dataTypes = new List<ComboBox>() { dataTypeBoxAMT1, dataTypeBoxAMT2, dataTypeBoxAMT3, dataTypeBoxAMT4, dataTypeBoxAMT5, dataTypeBoxAMT6, dataTypeBoxAMT7, dataTypeBoxAMT1 };
var elemSizes = new List<TextBox>() { elemSizeBoxAMT1, elemSizeBoxAMT2, elemSizeBoxAMT3, elemSizeBoxAMT4, elemSizeBoxAMT5, elemSizeBoxAMT6, elemSizeBoxAMT7, elemSizeBoxAMT8 };
var elemCounts = new List<TextBox>() { elemCountBoxAMT1, elemCountBoxAMT2, elemCountBoxAMT3, elemCountBoxAMT4, elemCountBoxAMT5, elemCountBoxAMT6, elemCountBoxAMT7, elemCountBoxAMT8 };
for (int i = 0; i < 8; i++)
{
addTags.Add(tagNames[i].Text);
addDataTypes.Add(dataTypes[i].Text);
addElemSizes.Add(elemSizes[i].Text);
addElemCounts.Add(elemCounts[i].Text);
}
this.Hide();
}
I have checked to make sure each list is populated correctly and they are. As well as the Lists being public. The problem is trying to grab these values from Form1. There has to be something simple that I'm missing! Thanks for the help!
With the reference of your comment i've generated an idea for you. By writing a simple public Action in your Second form Form2 you can achieve your goal. Below i'm showing an example:
Declare a public Action in your Form2 global scope with your desired collection type, like this way:
public Action<List<TextBox>, List<ComboBox>> actGetCollection;
Keep a method with some of your desired collection type parameter in your Form1 like this way:
private void GetCollectionItems(List<TextBox> addTags, List<ComboBox> addDataTypes)
{
//you will get your list items here and do whatever you want with these
}
In your Form1 from where your second form will open bind your GetCollectionItems() method with your Action in Form2 (assuming you do this in a button's click event) like this way:
private void button1_Click(object sender, EventArgs e)
{
//create an instance of your Form2 Form
Form2 obj = new Form2();
//bind your function with the action
obj.actGetCollection = GetCollectionItems;
//then call your Form2's ShowDialog() method to show the form
obj.ShowDialog();
//now your Form2 is opended
}
Now in your Form2's button_click event do this:
public void button1_Click(object sender, EventArgs e)
{
addMoreTagsEnabled = true;
var tagNames = new List<TextBox>() { tagNameBoxAMT1, tagNameBoxAMT2, tagNameBoxAMT3, tagNameBoxAMT4, tagNameBoxAMT5, tagNameBoxAMT6, tagNameBoxAMT7, tagNameBoxAMT8 };
var dataTypes = new List<ComboBox>() { dataTypeBoxAMT1, dataTypeBoxAMT2, dataTypeBoxAMT3, dataTypeBoxAMT4, dataTypeBoxAMT5, dataTypeBoxAMT6, dataTypeBoxAMT7, dataTypeBoxAMT1 };
var elemSizes = new List<TextBox>() { elemSizeBoxAMT1, elemSizeBoxAMT2, elemSizeBoxAMT3, elemSizeBoxAMT4, elemSizeBoxAMT5, elemSizeBoxAMT6, elemSizeBoxAMT7, elemSizeBoxAMT8 };
var elemCounts = new List<TextBox>() { elemCountBoxAMT1, elemCountBoxAMT2, elemCountBoxAMT3, elemCountBoxAMT4, elemCountBoxAMT5, elemCountBoxAMT6, elemCountBoxAMT7, elemCountBoxAMT8 };
for (int i = 0; i < 8; i++)
{
addTags.Add(tagNames[i].Text);
addDataTypes.Add(dataTypes[i].Text);
addElemSizes.Add(elemSizes[i].Text);
addElemCounts.Add(elemCounts[i].Text);
}
//call the action
if(actGetCollection != null)
actGetCollection(addTags, addDataTypes);
this.Hide();
}
When your Form2 wil disappear your code will get back to your Form1's event from where you're called your Form2. Now in your GetCollectionItems() you've the collection items that you're wanted.
You can set stuff in your second form from your first:
class Form1
{
...
public void OnButtonPress()
{
var anotherForm = new Form2();
anotherForm.AList = mylist;
anotherForm.BList = myBList;
anotherForm.ShowDialog();
}
}
Alternatively, you could create a class that encapsulates everything you want to pass between the two and so only pass on thing. If it's mandatory I would put it in Form2's constructor:
public Form2(MyImportantStuff stuff)

Trying to grab information from a Data Table in Form 2 and set Textboxes in Form 1 using C#

I have two forms (Form 1 and Form 2), I am successful in passing a data table from Form 1 to Form 2 by filling a data grid view in a dialog box. I also have an event handler to capture double click events on a selected row. When the event occurs I want to set textboxes in form 1 from the click event in form 2. No matter what I try I can not seem to show the text inside the textboxes. Below is my code:
//Code begins here
//....Function to fill data table from form 1 and pass to form 2
private void buttonNewEntryLookUp_Click(object sender, EventArgs e)
{
try
{
cs.Open();
da.SelectCommand = new SqlCommand("Select ctx_customername AS Customer, ctx_contactname AS Contact, ctx_custaddress1 AS Address, ctx_custcity AS City, ctx_custstate AS State, nno_custzip AS ZIP, ctx_custemail AS Email FROM Customers WHERE nno_custphone = '" + maskedTextBoxNewLogTel.Text + "'", cs);
dt.Clear();
da.Fill(dt);
}
catch
{
MessageBox.Show("Connection to Database could not be established, please close this application and try again. If problem continues please contact server Admin. Thank you.", "AAMP");
//Display this message if connection could not be made
}
cs.Close();//close connection to db
if (dt.Rows.Count == 0)//if there are no returned results then this must be a new entry into the database
{
MessageBox.Show("Phone Number Not Found in Database.", "AAMP");
}
else//number was found
{
Form2 form2 = new Form2(dt);//create object of form 2 and pass the data table.
form2.ShowDialog();//show form 2 with data table in the grid view.
}
}
public void getContactInfo(string[] contactInfo)
{
textBoxNewLogCustomerName.Text = contactInfo[0];
textBoxNewLogContactName.Text = contactInfo[1];
textBoxNewLogAddress.Text = contactInfo[2];
textBoxNewLogCity.Text = contactInfo[3];
textBoxNewLogState.Text = contactInfo[4];
textBoxNewLogZIP.Text = contactInfo[5];
textBoxNewLogEmail.Text = contactInfo[6];
}
//code for form 2
public partial class Form2 : Form
{
/*Globals for Form 2*/
DataTable g_dt;
public Form2(DataTable dt)
{
InitializeComponent();
dataGridViewLookUp.DataSource = dt;
g_dt = dt;
}
private void dataGridViewLookUp_RowHeaderMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
{
Form1 form1 = new Form1();
string[] contactInfo = new string[7];
contactInfo[0] = Convert.ToString(g_dt.Rows[e.RowIndex]["Customer"]);
contactInfo[1] = Convert.ToString(g_dt.Rows[e.RowIndex]["Contact"]);
contactInfo[2] = Convert.ToString(g_dt.Rows[e.RowIndex]["Address"]);
contactInfo[3] = Convert.ToString(g_dt.Rows[e.RowIndex]["City"]);
contactInfo[4] = Convert.ToString(g_dt.Rows[e.RowIndex]["State"]);
contactInfo[5] = Convert.ToString(g_dt.Rows[e.RowIndex]["ZIP"]);
contactInfo[6] = Convert.ToString(g_dt.Rows[e.RowIndex]["Email"]);
form1.getContactInfo(contactInfo);//return the row number being clicked.
this.Close();
}
}
I am successful in passing the data table to form 2 and capturing the correct information to fill the string array but when I pass the string array back by calling the getContactInfo function I can not seem to set my textboxes with the data. Can someone please, please help!
Thanks.
Your OP is a little vague, but I have some code that does this, takes some data in form 2 and sends it back to form 1.
Here's the code from form1:
private void btnGroupNameLookup_Click(object sender, EventArgs e)
{
//instantiate an instance of the grp name lookup form
frmGroupNameLookup lookupName = new frmGroupNameLookup();
//add an event handler to update THIS form when the lookup
//form is updated. (This is when LookupUpdated fires
lookupName.GroupNamesFound += new frmGroupNameLookup.LookupHandler(lookupName_GroupNamesFound);
//rc.ReconCFUpdated += new ReconCaseFileChecklist.ReconCFListHandler(ReconCFForm_ButtonClicked);
lookupName.Show();
}
void lookupName_GroupNamesFound(object sender, GroupNameLookupUpdateEventArgs e)
{
//update the list boxes here
foreach (string s in e.Parents)
{
lstFilteredGroupParents.Items.Add(s);
}
foreach (string s in e.Groups)
{
lstFilteredGroups.Items.Add(s);
//link supgroups and plan ids
GetFilteredSubgroupNos(s);
GetFilteredPlanIds(s);
}
//ensure dupes are stripped out
//filter out duplicates
var noDupeSubgroups = subgroupList.Distinct().ToList();
noDupeSubgroups.Sort();
foreach (string s in noDupeSubgroups)
{
lstFilteredSubgroups.Items.Add(s);
}
var noDupePlanIDs = planIDList.Distinct().ToList();
noDupePlanIDs.Sort();
foreach (string s in noDupePlanIDs)
{
lstFilteredPlanID.Items.Add(s);
}
}
From Form2
public partial class frmGroupNameLookup : Form
{
//add a delegate, the GroupNameLookupUpdateEventArgs class is defined at the bottom
//of this file
public delegate void LookupHandler(object sender, GroupNameLookupUpdateEventArgs e);
//add an event of the delegate type
public event LookupHandler GroupNamesFound;
//this event closes the forms and passes 2 lists back to form 1
private void btnCommit_Click(object sender, EventArgs e)
{
List<string> prnt = new List<string>();
List<string> grp = new List<string>();
//get selected rows
if (grdLookup.SelectedRows.Count > 0)
{
foreach (DataGridViewRow row in grdLookup.SelectedRows)
{
prnt.Add(row.Cells[0].Value.ToString());
grp.Add(row.Cells[1].Value.ToString());
}
//filter out duplicates
var noDupeParentGroups = prnt.Distinct().ToList();
noDupeParentGroups.Sort();
// instance the event args and pass it each value
GroupNameLookupUpdateEventArgs args =
new GroupNameLookupUpdateEventArgs(noDupeParentGroups, grp);
// raise the event with the updated arguments
this.GroupNamesFound(this, args);
this.Dispose();
}
}
}
public class GroupNameLookupUpdateEventArgs : System.EventArgs
{
// add local member variables to hold text
private List<string> mParents = new List<string>();
private List<string> mGroups = new List<string>();
// class constructor
public GroupNameLookupUpdateEventArgs(List<string> sParents, List<string> sGroups)
{
this.mParents = sParents;
this.mGroups = sGroups;
}
// Properties - Viewable by each listener
public List<string> Parents
{
get { return mParents; }
}
public List<string> Groups
{
get { return mGroups; }
}
}
Simple. Make the contact information a method public in Form2 and then just assign it after the ShowDialog() in form1.
else//number was found
{
Form2 form2 = new Form2(dt);//create object of form 2 and pass the data table.
form2.ShowDialog();//show form 2 with data table in the grid view.
getContactInfo(form2.ContactInfoFromForm2);
}
You're passing the datatable into the constructor of Form2:
Form2 form2 = new Form2(dt);
You could instead pass a reference to the entire Form1:
Form2 form2 = new Form2(this);
Then you make public methods on Form1 that Form2 can use to update data. Something like this:
//In Form1
public DataTable getDataTable(){
//return datatable
}
public void setTextBoxValue(string Value){
//Set the value
}
And then in Form2
private Form1 _form1;
public Form2(Form1 form1){
_form1 = form1;
dataGridViewLookUp.DataSource = _form1.getDataTable();
g_dt = dt;
}
private void dataGridViewLookUp_RowHeaderMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
{
_form1.setTextBoxValue(Convert.ToString(g_dt.Rows[e.RowIndex]["Customer"]));
//etc
}
Are you getting an error or is the data just not displaying. Maybe try a ToString() method behind each stringarray index in the getcontactinfo method. (i.e. contactInfo[0].ToString();)

Fill dataGridView thank's to backGroundWorker

I have this code snippet:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
remplirDataGrid();
}
private void frmChercherActesLoad(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void remplirDataGrid()
{
dataGridView1.DataSource = ActeServices.getAllActes(0, 40);
dataGridView1.Columns[0].Visible = false;
dataGridView1.Columns[1].HeaderText = "Code acte";
dataGridView1.Columns[2].HeaderText = "Désignation";
dataGridView1.Columns[3].HeaderText = "Pris en charge";
dataGridView1.Columns[4].HeaderText = "Id article";
dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
}
And here is the method getAllActe:
public static IEnumerable<Acte> getAllActes(int skipCount, int takeCount)
{
var myTableAdapter = new SmartDocDALServices.SmartDocDataSetTableAdapters.actesTableAdapter();
myTableAdapter.Fill(myDataSet.actes);
var myResult = from q in myDataSet.actes.AsEnumerable()
select new Acte
{
code = q.code,
designation = q.designation,
priseEnCharge = q.prise_en_charge,
idArticle = q.id_article,
};
if (skipCount != -1)
myResult.Skip(skipCount);
if (takeCount != -1)
myResult.Take(takeCount);
IEnumerable<Acte> myResultRet = myResult.ToList();
return myResultRet;
What I like to do is to fill my datagridview using the background worker once I run the application I got this error:
Inter-thread operation not valid: Control 'dataGridView1 has been the subject of an access from a thread other than the one it was created.
I try this
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
IEnumerable<Acte> result = ActeServices.getAllActes(0, 40);
backgroundWorker1.ReportProgress(0, result);
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
dataGridView1.DataSource = (IEnumerable<Acte>)e.UserState;
dataGridView1.Columns[0].Visible = false;
dataGridView1.Columns[1].HeaderText = "Code acte";
***
but nothing gained in time?. I'd like that the datagrid update when the BGW loads data foreash data load it add it to the DGV
You can't update the UI from a BackgroundWorker thread.
You need to send an event to the UI and then have something like:
private void EventHandler(object sender, YourEventArgs e)
{
if (this.dataGridView1.InvokeRequired)
{
this.dataGridView1.Invoke((MethodInvoker)delegate { this.AddToGrid(e.YourData); });
}
else
{
this.AddToGrid(e.YourData);
}
}
The DataGridView is not thread safe.
However, setting the DataSource if the data is already available should be fast enough.
I would recommend:
Only use your BackgroundWorker to load the data in another thread
Set the DataSource and the other modifications of the datagridview in the RunWorkerCompleted Event (you can pass the result from the DoWork method to the Completed event by setting
e.Result = ActeServices.getAllActes(0, 40);
Optional: Set dataGridView1.AutoGenerateColumns to false and manually add the columns either in the Windows Forms Designer or in code to avoid flicker.
It is because GUI stuff cannot be modified from other threads than the GUI thread. To fix this, you need to invoke the changes on the GUI thread by using the Dispatcher.
The DataGrid should be setup beforehand, so all you do in your async operation is fill the data.
var data = ActeServices.getAllActes(0, 40);
Dispatcher.BeginInvoke( new Action( () => { dataGridView1.DataSource = data; }))
The BackgroundWorker class was designed to run a long-standing operation on a background thread. Since you are only allowed to access UI components from the thread that created them, you can use the RunWorkerCompleted event of the BackgroundWorker class to update your UI once your DoWork handler has completed. Also, you can safely update a progress UI using the ProgressChanged event of the BackgroundWorker class.

Get list of open windows form instance that are excuted from different assembly

I have a 'loader app' that loads a menu and when user clicks the menu image button a list view opens based on the text
(if text = employee)
(Go to class A)
(Go to class B)
...
...
(Show List View Window)
if he clicks again on the same button it opens again, I would like to prevent this.
i.e but this for a WPF application
If you want a list of the open forms, that is Application.OpenForms. You could iterate over this, using GetType() and checking the .Assembly to find those from a different assembly. Beyond that, I'm not entire clear on the question...
Assembly currentAssembly = Assembly.GetExecutingAssembly();
List<Form> formsFromOtherAssemblies = new List<Form>();
foreach (Form form in Application.OpenForms) {
if (form.GetType().Assembly != currentAssembly) {
formsFromOtherAssemblies.Add(form);
}
}
If you just want to track forms you have opened yourself, then cache that instance. Or if you use "owned forms", you can just check by name:
private void button1_Click(object sender, EventArgs e) {
foreach (Form form in OwnedForms) {
if (form.Name == "Whatever") {
form.Activate();
return;
}
}
Form child = new Form();
child.Name = "Whatever";
child.Owner = this;
child.Show(this);
}
NewProduct newproduct;
private void button1_Click(object sender, EventArgs e)
{
if(!isOpened())
{
newproduct = new NewProduct();
newproduct.Show();
}
}
private bool isOpened()
{
foreach (Form f in Application.OpenForms)
{
if (f == newproduct)
{
return true;
}
}
return false;
}
Another simple example
private Boolean FindForm(String formName)
{
foreach (Form f in Application.OpenForms)
{
if (f.Name.Equals(formName))
{
f.Location = new Point(POINT.X, POINT.Y + 22);
return true;
}
}
return false;
}
You can use a Command pattern. The loader assembly will search for commands in loaded assemblies.
For every command the loader create menu item ( or anything else, you want ), and click event will run the concrete command.
The command must know if should be created new form or used some already existing.
Mark Garvell's answer helped me to figure out what I should do, but it needed adjusting for WPF.
(In my case I wanted to close any windows not owned by the main one when it closes, but the principle is the same.)
private void EmployeeMenuItemClick(object sender, RoutedEventArgs e)
{
bool found = false;
foreach(Window w in Application.Current.Windows)
{
if(w.GetType() == typeof(EmployeeListViewWindow)
{
found = true;
break;
}
}
if(!found)
{
EmployeeListViewWindow ew = new EmployeeListViewWindow();
ew.Show();
}
}

Categories

Resources