两个WinForm之间的TreeView的Invoke方法应该在哪里处理?
Where should TreeView's Invoke method between two WinForms be handled?
我有两个 WinForms(Setting
和 frmMain
)。我有一个设置形式的 TreeView,我想在第二种形式 frmMain
.
中调用它的 FillTree
方法
我将 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 or BeginInvoke cannot be called on a control until the window handle has been created.
我真的很想知道,在我的第二个 winform 中应该在哪里处理 TreeView 的 Invoke 方法 frmMain
?
我正在关注这些链接(但无济于事):
1) Populating TreeView on a Background Thread
2)
3) How can i invoke a method called from backgroundworker dowork event?
为可视化问题编辑
我用 TreeView 试过它不起作用,然后我用 ListBox 试过,但问题仍然相同。
问题: 我在 WinForm settingsWindow
中有一个方法(填充我的 ListBox),我想在我的第二个 WinForm frmMain
.
Settings
表格截图:
frmMain
表格截图:
问题 GIF:
列表框填充的设置表单代码:
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
frmMain
的方法:
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
}
您分享的代码无法帮助我们重现问题。有人唯一能做的就是创建一个示例,向您展示如何在另一个线程中以另一种形式加载 TreeView
(如果确实需要线程)。
您应该考虑以下几点:
您应该首先显示窗体,然后调用窗体或其控件之一的Invoke 方法。否则您将收到 'Invoke
or BeginInvoke
cannot be called on a control until the window handle has been created.'
向TreeView
添加大量节点时,先调用BeginUpdate
,然后添加所有节点,然后调用EndUpdate
。这样它会更快,渲染更少 UI。
考虑使用 async/await 模式以获得非阻塞 UI。
当你想从另一个线程更新UI线程时,使用Invoke
。
不要在UI线程中做阻塞耗时的非UI任务。
当使用Invoke
时,请记住UI线程中的代码是运行。所以不要在Invoke
中调用阻塞耗时的非UI任务。只需调用Invoke
中的UI代码即可。
在下面的示例中,我创建了一个带有按钮的表单。单击按钮,我加载数据,然后打开另一个 window,其中有一个 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.";
}
订阅 HandleCreated
事件并在事件处理程序中填充树。
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
已经可见,则不会发生您报告的异常,因为此时 window的句柄已创建,如下所示。
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
与服务器进行协商是相当基本的,或者更好的是,使用一些抽象接口,它可能在远程服务器中,也可能不在远程服务器中(良好的设计表明它应该是 irrelevant/transparent ...) 然后 return 将在 Form2 中显示的字符串消息。但是,这样做不会增加对该示例的有限范围的目的的任何了解。它实际上会使您报告的根本问题及其相应的解决方案变得混乱。别忘了,如果没有它,您原始问题中报告的异常会立即返回给您...:O)
我有两个 WinForms(Setting
和 frmMain
)。我有一个设置形式的 TreeView,我想在第二种形式 frmMain
.
FillTree
方法
我将 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 or BeginInvoke cannot be called on a control until the window handle has been created.
我真的很想知道,在我的第二个 winform 中应该在哪里处理 TreeView 的 Invoke 方法 frmMain
?
我正在关注这些链接(但无济于事):
1) Populating TreeView on a Background Thread
2)
3) How can i invoke a method called from backgroundworker dowork event?
为可视化问题编辑
我用 TreeView 试过它不起作用,然后我用 ListBox 试过,但问题仍然相同。
问题: 我在 WinForm settingsWindow
中有一个方法(填充我的 ListBox),我想在我的第二个 WinForm frmMain
.
Settings
表格截图:
frmMain
表格截图:
问题 GIF:
列表框填充的设置表单代码:
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
frmMain
的方法:
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
}
您分享的代码无法帮助我们重现问题。有人唯一能做的就是创建一个示例,向您展示如何在另一个线程中以另一种形式加载 TreeView
(如果确实需要线程)。
您应该考虑以下几点:
您应该首先显示窗体,然后调用窗体或其控件之一的Invoke 方法。否则您将收到 '
Invoke
orBeginInvoke
cannot be called on a control until the window handle has been created.'向
TreeView
添加大量节点时,先调用BeginUpdate
,然后添加所有节点,然后调用EndUpdate
。这样它会更快,渲染更少 UI。考虑使用 async/await 模式以获得非阻塞 UI。
当你想从另一个线程更新UI线程时,使用
Invoke
。不要在UI线程中做阻塞耗时的非UI任务。
当使用
Invoke
时,请记住UI线程中的代码是运行。所以不要在Invoke
中调用阻塞耗时的非UI任务。只需调用Invoke
中的UI代码即可。
在下面的示例中,我创建了一个带有按钮的表单。单击按钮,我加载数据,然后打开另一个 window,其中有一个 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.";
}
订阅 HandleCreated
事件并在事件处理程序中填充树。
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
已经可见,则不会发生您报告的异常,因为此时 window的句柄已创建,如下所示。
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
与服务器进行协商是相当基本的,或者更好的是,使用一些抽象接口,它可能在远程服务器中,也可能不在远程服务器中(良好的设计表明它应该是 irrelevant/transparent ...) 然后 return 将在 Form2 中显示的字符串消息。但是,这样做不会增加对该示例的有限范围的目的的任何了解。它实际上会使您报告的根本问题及其相应的解决方案变得混乱。别忘了,如果没有它,您原始问题中报告的异常会立即返回给您...:O)