5

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:

enter image description here

frmMain form screenshot:

enter image description here

Problem GIF :

enter image description here

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
}
jsanalytics
  • 13,058
  • 4
  • 22
  • 43
  • Two comments 1) First yo uhave to use an instance of the form. See my two form project : https://stackoverflow.com/questions/34975508/reach-control-from-another-page-asp-net 2) You could be getting event before the DGV are create so make sure row count and column count > 0. A DGV rows/columns are -1 initially and then goes to 0 when the rows/columns are created. – jdweng Jan 25 '19 at 18:14
  • Yes, I'm using the instance of the `frmMain` in the Settings form. but its again saying the same error. (edited) –  Jan 25 '19 at 18:20
  • Put a break point on the line that contains error and see if object is null. – jdweng Jan 25 '19 at 18:24
  • I've added break point at `if (dtGroups == null) return;` and checked if the `dtGroups` is null but it have rows data in table. –  Jan 25 '19 at 18:31
  • 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.*' – Reza Aghaei Jan 26 '19 at 05:48
  • `settingWindow.ShowDialog();` is called on button click inside `frmMain` form. –  Jan 26 '19 at 07:38
  • But we cannot see it and we cannot reproduce the problem. – Reza Aghaei Jan 26 '19 at 19:39
  • 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). – Reza Aghaei Feb 08 '19 at 13:14
  • @RezaAghaei I have tried it with TreeView its not working then I Tried it with ListBox but the proble is still same. **Problem:** I've a method (which fill or populate my Tree or ListBox) in WinForm `settingsWindow` and I want to call that method in my second WinForm `frmMain`. –  Feb 08 '19 at 13:35

2 Answers2

2

Subscribe to HandleCreated event and fill the tree in the event handler.

enter image description here

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.

enter image description here

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) enter image description here

jsanalytics
  • 13,058
  • 4
  • 22
  • 43
  • WoW good example up-voted. What if the `Form2` two is already shown and it has a TextBox for group name and a Button for create group. Now when I click on the Button it gets the group name from TextBox then I pass that group name to the Form1. In Form1 I have a Database query which insert the new group in database and all inserted groups in DataTable (like `dtGroups`) which pass as parameter in `FillTree` then I call that `FillTree` method from Form1 which refreshes the TreeView from Form1? –  Feb 10 '19 at 08:09
  • If `Form2` is already shown, handling `HandleCreated` is pointless and has nothing to do with the exception *`Invoke` or `BeginInvoke` cannot be called on a control until the window handle has been created.* The point is you need to call the invoke after the form handle created. It will not happen until after the form gets visible. – Reza Aghaei Feb 10 '19 at 10:12
  • @jsanalytics , effort Nice solution. One thing I want to know in your code. What if I call `UpdateData` method within the `form1` ? Actually, I'm working on a client-server application ; in that when I create a group from `form1` on group creation button it sends a message to server for creating new entered group. And when server creates group it sends the message to that client **"Group is Created"** and that message is in `form2` so `UpdateData` method will be triggered automatically on reading the response method (like `onCompleteReadFromServerStream`) from server. it'll work? –  Feb 10 '19 at 14:29
  • @user5377037 : please see our updated answer to your 2nd follow-up question. – jsanalytics Feb 10 '19 at 22:38
1

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.";
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Please, see I've updated my problem with latest code. If you can help me. –  Feb 08 '19 at 14:28
  • Up-votted for your answer but my problem is still persist. –  Feb 08 '19 at 14:52
  • 2
    You have an open bounty for the question which attracts more attention to your question, so wait for more answers, hope someone else can share a better answer to help you to solve the problem. Upvote means the answer is useful. accept means it solved my problem, so feel free to upvote when you find a useful answer, but don't hurry in accepting the best answer. For me, regarding to the current format of the question, this was the best thing which I could share. – Reza Aghaei Feb 08 '19 at 15:09
  • 2
    Just an advice, create [MCVE]. At the first step, it will help you to find the problem yourself in a clean environment with minimum possible dependencies. If for any reason you couldn't solve the problem yourself, it will help other users to reproduce the problem and try to solve the problem. What I shared in the answer, is an example of MCVE. – Reza Aghaei Feb 08 '19 at 15:13