Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
}
like image 831
youpilat13 Avatar asked Jan 25 '19 18:01

youpilat13


Video Answer


1 Answers

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

like image 199
jsanalytics Avatar answered Nov 10 '22 00:11

jsanalytics