Где должен обрабатываться метод Invoke TreeView между двумя WinForms? - PullRequest
0 голосов
/ 25 января 2019

У меня есть две WinForms (Setting и frmMain). У меня есть TreeView в форме Setting, и я хочу вызвать его FillTree метод во второй форме frmMain.

Я использую TreeView.Invoke для работы с потоками.

Вот мой код для заполнения данных TreeView в форме Настройки:

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 метод из приведенного выше кода, я хочу вызвать его во второй форме frmMain, которую я пытался сделать так:

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);
}

Когда я вызываю FillTree метод, он показывает мне такую ​​ошибку:

Invoke или BeginInvoke не могут быть вызваны для элемента управления, пока не создан дескриптор окна.

Я действительно хочу знать, где должен быть обработан метод Invoke TreeView в моей второй winform frmMain?


Я перехожу по этим ссылкам (но безрезультатно):

1) Заполнение TreeView в фоновом потоке

2) Как добавить объект в виде дерева из другого потока

3) Как вызвать метод, вызываемый из события backgroundworker dowork?


Отредактировано для визуализации проблемы

Я попробовал его с TreeView, он не работает, затем я попробовал его с ListBox, но проблема все та же.

Проблема: У меня есть метод (который заполняет мой ListBox) в WinForm settingsWindow, и я хочу вызвать этот метод во втором WinForm frmMain.

Settings скриншот формы:

enter image description here

frmMain скриншот формы:

enter image description here

Проблема GIF:

enter image description here

Форма настроек Код для заполнения списка:

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];
}

Вызов PopulateGroupListData во второй форме * Метод 1080 *:

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
}

Ответы [ 2 ]

0 голосов
/ 10 февраля 2019

Подписаться на событие HandleCreated и заполнить дерево в обработчике событий.

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();
    }
}

В качестве бонуса мы также исправили пару проблем с методом FillTree, поэтому дерево можно построить правильно.

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();
}

Отвечая на ваш дополнительный вопрос: , если Form2 уже виден, исключение, о котором вы сообщили, не произойдет, потому что в этот момент был создан дескриптор окна, как мы покажем ниже.

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 остается прежним, просто добавив обработчик событий для новой кнопки:

    private void button1_Click(object sender, EventArgs e)
    {
        form1.UpdateData(textBox1.Text);
    }

Отвечая на второй вопрос: UpdateData был создан для конкретного случая использования пользователем, отправляющим новую группу через Form2. Я бы предпочел иметь конкретный код для другого варианта использования. Обратите внимание, что было бы довольно элементарно иметь UpdateData, чтобы консультироваться с сервером, или даже лучше, с некоторым абстрактным интерфейсом, который мог бы быть на удаленном сервере или нет (хороший дизайн требует, чтобы это было неактуально / прозрачно ...) а затем вернуть строковое сообщение, которое будет представлено в Form2. Однако это не добавит понимания целей ограниченного объема этого образца. Это на самом деле затуманило бы проблему корня, о которой вы сообщаете, и соответствующее решение. Давайте не будем забывать, что без этого сообщение об исключении в вашем исходном вопросе вернется прямо к вам ...: O) enter image description here

0 голосов
/ 08 февраля 2019

Код, которым вы поделились, не помогает нам воспроизвести проблему. Единственное, что кто-то может сделать, - это создать пример, показывающий, как загрузить TreeView в другой форме в другом потоке (если поток действительно необходим).

Есть несколько моментов, которые вы должны учитывать:

  • Сначала вы должны показать форму, а затем вызвать метод Invoke формы или один из ее элементов управления. В противном случае вы получите 'Invoke или BeginInvoke, который нельзя вызвать на элементе управления, пока не будет создан дескриптор окна.'

  • При добавлении большого количества узлов к TreeView сначала вызовите BeginUpdate, затем добавьте все узлы, затем вызовите EndUpdate. Таким образом, это будет намного быстрее с меньшим количеством визуализации интерфейса.

  • Кондизатор, использующий шаблон асинхронности / ожидания, чтобы иметь неблокирующий интерфейс.

  • Если вы хотите обновить поток пользовательского интерфейса из другого потока, используйте Invoke.

  • Не блокируйте трудоемкие задачи, не связанные с пользовательским интерфейсом, в потоке пользовательского интерфейса.

  • При использовании Invoke имейте в виду, что код выполняется в потоке пользовательского интерфейса. Поэтому не вызывайте блокировку трудоемких задач, не связанных с пользовательским интерфейсом, в Invoke. Просто наберите код UI в Invoke.

В следующем примере я создал форму с кнопкой. При нажатии кнопки я загружаю данные, а затем открываю другое окно с TreeView внутри. Затем загрузка дерева с 10000 узлами:

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.";
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...