更新:只是总结一下我的问题归结为:
我希望构建.NET表单和控件不会创建任何窗口句柄 - 希望该过程延迟到Form.Show/Form.ShowDialog
任何人都可以确认或否认这是否属实?
我有一个带有选项卡控件的大型WinForms表单,表单上有许多控件,在加载几秒钟后会暂停。我已经将它缩小到InitializeComponent中设计器生成的代码,而不是构造函数或OnLoad中的任何逻辑。
我很清楚我不能尝试在主UI线程以外的任何线程上与UI交互,但我想做的是让应用程序预先加载这个表单(运行后台的构造函数),一旦用户想要打开它,它就可以立即在UI线程上显示。但是,在后台线程中构建时,在设计器的这一行:
this.cmbComboBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
我收到了错误
当前线程必须设置为单个 OLE之前的线程单元(STA)模式 可以打电话。确保你的 主要功能有STAThreadAttribute 标记在上面。
现在这是设计器文件的一半,这让我希望一般这个策略可行。但是这条特殊的线似乎试图立即启动某种OLE调用。
有什么想法吗?
编辑:
我想我在这里并没有说清楚。延迟似乎发生在设计人员生成的代码中构建bazillion控件期间。
我希望所有这些初始化代码都是在没有实际尝试触摸任何真正的Win32窗口对象的情况下发生的,因为表单尚未实际显示。
我可以设置(例如)来自此后台线程的标签文本和位置的事实让我希望这是真的。然而,对于所有房产而言可能并非如此。
答案 0 :(得分:16)
虽然无法在一个线程上创建表单,并使用另一个线程显示它,但 当然可以在非主GUI线程中创建表单。目前接受的答案似乎表明这是不可能的。
Windows Forms强制执行单线程单元模型。总之,这意味着每个线程只能有一个Window消息循环,反之亦然。另外,如果例如threadA想要与threadB的消息循环进行交互,它必须通过诸如BeginInvoke之类的机制来封送调用。
但是,如果您创建一个新线程并为其提供自己的消息循环,该线程将很乐意独立处理事件,直到它被告知结束消息循环。
因此,为了演示,下面是用于在非GUI线程上创建和显示表单的Windows窗体代码:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
label1.Text = Thread.CurrentThread.ManagedThreadId.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
ThreadStart ts = new ThreadStart(OpenForm);
Thread t = new Thread(ts);
t.IsBackground=false;
t.Start();
}
private void OpenForm()
{
Form2 f2 = new Form2();
f2.ShowDialog();
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
label1.Text = Thread.CurrentThread.ManagedThreadId.ToString() ;
}
}
OpenForm方法在新线程中运行并创建Form2的实例。
Form2实际上通过调用ShowDialog()给它自己独立的消息循环。如果您要调用Show(),则不会提供任何消息循环,而Form2会立即关闭。
另外,如果您尝试在OpenForm()中访问Form1(例如使用“this”),则在尝试进行跨线程UI访问时会收到运行时错误。
t.IsBackground=false
将线程设置为前台线程。我们需要一个前台线程,因为在没有首先调用FormClosing或FormClosed事件的情况下关闭主窗体时会立即杀死后台线程。
除了这些要点之外,Form2现在可以像任何其他形式一样使用。您会注意到Form1仍然像往常一样快乐地运行它自己的消息lopp。这意味着您可以单击按钮并创建Form2的多个实例,每个实例都有自己独立的消息循环和线程。
你需要注意跨表单访问,现在实际上是跨线程的。您还需要确保处理主窗体的关闭,以确保正确关闭任何非主线程形式。
答案 1 :(得分:4)
我认为你的理解有点偏差。必须从创建它们的线程触摸控件,而不是主UI线程。您可以在应用程序中拥有大量UI线程,每个线程都有自己的控件集。因此,在不同的线程上创建控件将不允许您从主线程使用它,而不使用Invoke或BeginInvoke对所有调用进行编组。
EDIT 多个UI线程的一些引用:
MSDN on Message Loops MSDN social discussion Multiple threads in WPF
答案 2 :(得分:3)
答案是否定的。
如果在GUI线程以外的任何线程上创建窗口句柄,则无法显示它。
编辑:完全可以创建表单和控件 将它们显示在主GUI线程以外的线程中。当然,如果 如果这样做,您只能从线程访问多线程GUI 它创造了它,但它是可能的。 - 阿什利亨德森
您需要在bg线程上执行任何繁重的工作,然后将数据加载到GUI小部件
答案 3 :(得分:1)
通常,需要从运行消息循环的同一线程访问表单的属性。这意味着,为了在另一个线程上构造表单,您需要使用BeginInvoke封送任何实际设置属性的调用。对于构造函数中的属性集也是如此,如果它们最终生成需要处理的消息(现在发生在你身上)。
即使你开始工作,它还能为你带来什么?总体而言,它会慢一点,而不是更快。
也许只是在加载此表单时显示启动画面?
或者,回顾一下为什么您的表单需要很长时间才能构建。这需要几秒钟才是常见的。
答案 4 :(得分:1)
我相信可以将在非UI线程上创建的组件添加到主UI中,我已经完成了。
所以有两个主题,'NewCompThread'和'MainThread'。
你旋转NewCompThread并为你创建组件 - 所有这些都准备好显示在MainUI上(在MainThread上创建)。
但是......如果你在NewCompThread上尝试这样的话,你会得到一个例外:
ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread;
但你可以加上这个:
if (ComponentCreatedOnMainThread.InvokeRequired) {
ComponentCreatedOnMainThread.Invoke(appropriate delegate...);
} else {
ComponentCreatedOnNewCompTHread.parent = ComponentCreatedOnMainThread;
}
它会起作用。我做到了。
对我来说奇怪的是,然后ComponentCreatedOnNewCompTHread'认为'它是在MainThread上创建的。
如果您从NewCompThread执行以下操作:
ComponentCreatedOnNewCompTHread.InvokeRequired
它将返回TRUE,您需要创建一个委托并使用Invoke返回MainThread。
答案 5 :(得分:0)
可以在后台线程中创建控件,但只能在STA线程上创建。
我创建了一个扩展方法,以便将其与async / await模式一起使用
private async void treeview1_AfterSelect(object sender, TreeViewEventArgs e)
{
var control = await CreateControlAsync(e.Node);
if (e.Node.Equals(treeview1.SelectedNode)
{
panel1.Controls.Clear();
panel1.Controls.Add(control);
}
else
{
control.Dispose();
}
}
private async Control CreateControlAsync(TreeNode node)
{
return await Task.Factory.StartNew(() => CreateControl(node), ApartmentState.STA);
}
private Control CreateControl(TreeNode node)
{
// return some control which takes some time to create
}
这是扩展方法。任务不允许设置公寓,所以我在内部使用一个线程。
public static Task<T> StartNew<T>(this TaskFactory t, Func<T> func, ApartmentState state)
{
var tcs = new TaskCompletionSource<T>();
var thread = new Thread(() =>
{
try
{
tcs.SetResult(func());
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.IsBackground = true;
thread.SetApartmentState(state);
thread.Start();
return tcs.Task;
}