使用C#和WinForms在主窗体的单独线程上创建新的临时窗体

使用C#和WinForms在主窗体的单独线程上创建新的临时窗体,c#,multithreading,winforms,C#,Multithreading,Winforms,很难找到这个特定情况的答案。我需要创建一个临时表单(稍后将被销毁),它位于与主表单不同的线程中 此表单用于向用户显示帐户登录信息。在屏幕上显示此表单的同时,还向用户显示一个模式输入框。模态输入框的存在防止了与登录信息表单的任何交互(复制/粘贴),这是用户所必需的功能 我怎样才能: A) 在与主窗体完全分离的线程上创建和显示新窗体 B) 用户在模式对话框中输入输入后,是否从主窗体的线程中销毁该窗体 注意:我已经研究了MainForm.Invoke/BeginInvoke,但这并没有给出我需要的结果

很难找到这个特定情况的答案。我需要创建一个临时表单(稍后将被销毁),它位于与主表单不同的线程中

此表单用于向用户显示帐户登录信息。在屏幕上显示此表单的同时,还向用户显示一个模式输入框。模态输入框的存在防止了与登录信息表单的任何交互(复制/粘贴),这是用户所必需的功能

我怎样才能:

A) 在与主窗体完全分离的线程上创建和显示新窗体

B) 用户在模式对话框中输入输入后,是否从主窗体的线程中销毁该窗体

注意:我已经研究了MainForm.Invoke/BeginInvoke,但这并没有给出我需要的结果,正如其他一些帖子所声称的那样

模式输入框的代码:

class InputBox
{
    public static DialogResult Show(string prompt, bool hideInput, out string userInput, Form parent = null)
    {
        InputBoxForm frm = new InputBoxForm(prompt, hideInput);

        if (parent != null)
            frm.ShowDialog(parent);
        else
            frm.ShowDialog();

        if (frm.DialogResult == DialogResult.OK)
        {
            userInput = frm.txtInput.Text;
            frm.Dispose();
            return DialogResult.OK;
        }
        else
        {
            userInput = "";
            frm.Dispose();
            return DialogResult.Cancel;
        }
    }
}
以及程序中使用的代码:

Form loginDisplay = LoginInfoForm(user, pass);
loginDisplay.Show(null);
string input = "";
InputBox.Show("Enter info:", false, out input, parent: this);
                Form loginDisplay = null; 

                this.Invoke(new Action(() =>
                    {
                        loginDisplay = LoginInfoForm(user, pass);
                        loginDisplay.Show(this);
                    }));

                if (loginDisplay2 != null)
                {
                    loginDisplay2.Enabled = false;
                }

                string input = "";
                InputBox.Show("Input info", false, out input, parent: this, enable: loginDisplay);

logininform
只是一个动态创建表单并对其进行格式化的函数。

这是一种人为的情况。为什么需要在单独的线程上使用新表单

您仍然可以在主UI线程上同时拥有一个模式对话框(由主窗体作为父窗体)和一个非模式弹出窗体。用户将能够独立地与两者进行交互

只需为对话框指定相应的父对象:
dialogForm.ShowDialog(mainForm)
,而不为无模式窗体指定父对象:
form.Show(null)

无论哪种情况,这种UI都可能会让用户感到困惑。

更新了,下面是我描述的一个例子,其中有一个重要的修改。实际上,禁用同一线程所拥有的所有顶级可见和已启用的窗口(而不是像Win32那样仅禁用对话框的直接父窗口)

诚然,这对我来说是一个相当意外的行为,尽管我看到了背后的原因:我上面提到的一致的UI体验。有关更多详细信息,请参阅的实现

解决方法非常简单:如果弹出窗体当前可见,请在显示对话框之前禁用它,并在显示对话框时重新启用它。请注意,这一切都是在同一个线程上完成的:

var dialog = new ModalDialog { Width = 200, Height = 100 };

if (popup != null)
{
    popup.Enabled = false;
    dialog.Load += delegate { 
        popup.Enabled = true; };
}

dialog.ShowDialog(this);
完整的WinForms应用程序:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsForms_22340190
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            var cts = new CancellationTokenSource();

            this.Load += async (s, e) =>
            {
                // start the background thread in 1s
                await Task.Delay(1000);

                Form popup = null;

                var task = Task.Run(() => 
                {
                    // background thread
                    this.Invoke(new Action(() => 
                    {
                        // create a popup on the main UI thread
                        popup = new Popup { Width = 300, Height = 200 };
                        popup.Show(this);
                    }));

                    // imitate some work
                    var i = 0;
                    while (true)
                    {
                        Thread.Sleep(1000);
                        cts.Token.ThrowIfCancellationRequested();

                        var n = i++;
                        this.BeginInvoke(new Action(() =>
                        {
                            // update the popup UI on the main UI thread
                            popup.Text = "Popup, step #" + n;
                        }));
                    }
                });

                // wait 2s more and display a modal dialog
                await Task.Delay(2000);

                var dialog = new ModalDialog { Width = 200, Height = 100 };

                if (popup != null)
                {
                    popup.Enabled = false;
                    dialog.Load += delegate { 
                        popup.Enabled = true; };
                }

                dialog.ShowDialog(this);
            };

            this.FormClosing += (s, e) =>
                cts.Cancel();
        }
    }

    public partial class ModalDialog : Form
    {
        public ModalDialog() 
        { 
            this.Text = "Dialog";
            this.Controls.Add(new TextBox { Width = 50, Height = 20 });
        }
    }

    public partial class Popup : Form
    {
        public Popup() 
        { 
            this.Text = "Popup";
            this.Controls.Add(new TextBox { Width = 50, Height = 20 });
        }
    }
}

这是一个有点做作的情况。为什么需要在单独的线程上使用新表单

您仍然可以在主UI线程上同时拥有一个模式对话框(由主窗体作为父窗体)和一个非模式弹出窗体。用户将能够独立地与两者进行交互

只需为对话框指定相应的父对象:
dialogForm.ShowDialog(mainForm)
,而不为无模式窗体指定父对象:
form.Show(null)

无论哪种情况,这种UI都可能会让用户感到困惑。

更新了,下面是我描述的一个例子,其中有一个重要的修改。实际上,禁用同一线程所拥有的所有顶级可见和已启用的窗口(而不是像Win32那样仅禁用对话框的直接父窗口)

诚然,这对我来说是一个相当意外的行为,尽管我看到了背后的原因:我上面提到的一致的UI体验。有关更多详细信息,请参阅的实现

解决方法非常简单:如果弹出窗体当前可见,请在显示对话框之前禁用它,并在显示对话框时重新启用它。请注意,这一切都是在同一个线程上完成的:

var dialog = new ModalDialog { Width = 200, Height = 100 };

if (popup != null)
{
    popup.Enabled = false;
    dialog.Load += delegate { 
        popup.Enabled = true; };
}

dialog.ShowDialog(this);
完整的WinForms应用程序:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsForms_22340190
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            var cts = new CancellationTokenSource();

            this.Load += async (s, e) =>
            {
                // start the background thread in 1s
                await Task.Delay(1000);

                Form popup = null;

                var task = Task.Run(() => 
                {
                    // background thread
                    this.Invoke(new Action(() => 
                    {
                        // create a popup on the main UI thread
                        popup = new Popup { Width = 300, Height = 200 };
                        popup.Show(this);
                    }));

                    // imitate some work
                    var i = 0;
                    while (true)
                    {
                        Thread.Sleep(1000);
                        cts.Token.ThrowIfCancellationRequested();

                        var n = i++;
                        this.BeginInvoke(new Action(() =>
                        {
                            // update the popup UI on the main UI thread
                            popup.Text = "Popup, step #" + n;
                        }));
                    }
                });

                // wait 2s more and display a modal dialog
                await Task.Delay(2000);

                var dialog = new ModalDialog { Width = 200, Height = 100 };

                if (popup != null)
                {
                    popup.Enabled = false;
                    dialog.Load += delegate { 
                        popup.Enabled = true; };
                }

                dialog.ShowDialog(this);
            };

            this.FormClosing += (s, e) =>
                cts.Cancel();
        }
    }

    public partial class ModalDialog : Form
    {
        public ModalDialog() 
        { 
            this.Text = "Dialog";
            this.Controls.Add(new TextBox { Width = 50, Height = 20 });
        }
    }

    public partial class Popup : Form
    {
        public Popup() 
        { 
            this.Text = "Popup";
            this.Controls.Add(new TextBox { Width = 50, Height = 20 });
        }
    }
}

InputBox类的更新代码:

    public static DialogResult Show(string prompt, bool hideInput, out string userInput, Form parent = null, Form enable = null)
    {
        InputBoxForm frm = new InputBoxForm(prompt, hideInput);

        if (enable != null)
        {
            frm.Load += delegate { enable.Enabled = true; };
        }

        if (parent != null)
            frm.ShowDialog(parent);
        else
            frm.ShowDialog();

        if (frm.DialogResult == DialogResult.OK)
        {
            userInput = frm.txtInput.Text;
            frm.Dispose();
            return DialogResult.OK;
        }
        else
        {
            userInput = "";
            frm.Dispose();
            return DialogResult.Cancel;
        }
    }
程序中的更新代码:

Form loginDisplay = LoginInfoForm(user, pass);
loginDisplay.Show(null);
string input = "";
InputBox.Show("Enter info:", false, out input, parent: this);
                Form loginDisplay = null; 

                this.Invoke(new Action(() =>
                    {
                        loginDisplay = LoginInfoForm(user, pass);
                        loginDisplay.Show(this);
                    }));

                if (loginDisplay2 != null)
                {
                    loginDisplay2.Enabled = false;
                }

                string input = "";
                InputBox.Show("Input info", false, out input, parent: this, enable: loginDisplay);

感谢@nosratio为我提供了解决方案的代码。

更新了InputBox类的代码:

    public static DialogResult Show(string prompt, bool hideInput, out string userInput, Form parent = null, Form enable = null)
    {
        InputBoxForm frm = new InputBoxForm(prompt, hideInput);

        if (enable != null)
        {
            frm.Load += delegate { enable.Enabled = true; };
        }

        if (parent != null)
            frm.ShowDialog(parent);
        else
            frm.ShowDialog();

        if (frm.DialogResult == DialogResult.OK)
        {
            userInput = frm.txtInput.Text;
            frm.Dispose();
            return DialogResult.OK;
        }
        else
        {
            userInput = "";
            frm.Dispose();
            return DialogResult.Cancel;
        }
    }
程序中的更新代码:

Form loginDisplay = LoginInfoForm(user, pass);
loginDisplay.Show(null);
string input = "";
InputBox.Show("Enter info:", false, out input, parent: this);
                Form loginDisplay = null; 

                this.Invoke(new Action(() =>
                    {
                        loginDisplay = LoginInfoForm(user, pass);
                        loginDisplay.Show(this);
                    }));

                if (loginDisplay2 != null)
                {
                    loginDisplay2.Enabled = false;
                }

                string input = "";
                InputBox.Show("Input info", false, out input, parent: this, enable: loginDisplay);

多亏@nosertio的代码,我才找到了解决方案。

我只是尝试了一下,但没有成功。我修改了输入表单的函数,以便为其
ShowDialog()
提供父表单,并使用
.Show(null)
作为登录显示表单。当模式输入表单出现时,登录显示表单无法与交互@NoseratioAlso,它需要在一个单独的线程上,因为显示窗体需要在模式对话框阻止主线程继续运行,同时等待用户的回答时进行交互,我认为如果不使用单独的线程,这是不可能的@一瞬间@Noseratio我已经添加了相关代码@Noseratio无法使用示例代码的许多部分(
Task.Run
Task.Delay
await
未被识别),但我能够将您所做的调整应用到我的代码中,这非常有效。非常感谢你的帮助!我只是试了一下,但没用。我修改了输入表单的函数,以便为其
ShowDialog()
提供父表单,并使用
.Show(null)
作为登录显示表单。当模式输入表单出现时,登录显示表单无法与交互@NoseratioAlso,它需要在一个单独的线程上,因为显示窗体需要在模式对话框阻止主线程继续运行,同时等待用户的回答时进行交互,我认为如果不使用单独的线程,这是不可能的@一瞬间@Noseratio我已经添加了相关代码@Noseratio无法使用示例代码的许多部分(
Task.Run
Task.Delay
await
未被识别),但我能够将您所做的调整应用到我的代码中,这非常有效。非常感谢你的帮助!我很欣赏你的观点,通常也会同意