在不同的线程中创建一个新线程来打开一个新窗口并关闭它。

17

目前我有C#代码,可以在不同的线程中生成新窗口,这可以实现,但是一旦新生成的窗口打开,它就会关闭并且线程也结束了。如何使新生成的窗口可以从第一个线程中关闭?

下面是当前生成方式的“树”:

主线程
--使用主线程中的函数来启动另一个函数,该函数在另一个线程中打开一个窗口,导致该窗口使用该线程。

基本上,我只是希望这两个窗口各自拥有自己的线程,并能够从第一个窗口线程中控制生成的第二个窗口。


你是如何在第二个线程中创建新窗口的?在窗口创建后,线程会做什么?没有看到代码,我猜测问题可能是你的第二个线程没有在Windows消息队列上泵送消息。你是否在第二个线程中调用了Application.Run?顺便说一下,你的设计有一些限制。第一个线程将无法直接控制第二个窗口。每当你尝试从第一个线程操纵第二个窗口上的任何UI元素时,你都必须使用Control.Invoke来确保实际的UI操作。 - Ran
5个回答

40

我敢打赌你正在做类似于这样的事情:

new Thread(() => new TestForm().Show()).Start();

因为这会使窗口立即消失,就像你所描述的那样。

尝试使用以下方法:

 new Thread(() => new TestForm().ShowDialog()).Start();

ShowDialog会启动自己的消息循环,只有在窗口关闭时才返回。


我该如何确保关闭这个新开启的线程? - Deniz
1
@Deniz,威胁将在函数结束时自动终止-在这种情况下,当窗口关闭时,ShowDialog返回即可。 - Roman Starkov

14

这只是一个简单的示例。它比我写的第一个例子更强大一些。它通过使用p/invoke消除了现有的竞态条件。

class MainUIThreadForm : Form
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainUIThreadForm());
    }

    private IntPtr secondThreadFormHandle;

    public MainUIThreadForm()
    {
        Text = "First UI";
        Button button;
        Controls.Add(button = new Button { Name = "Start", Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) });
        button.Click += (s, e) =>
        {
            if (secondThreadFormHandle == IntPtr.Zero)
            {
                Form form = new Form
                {
                    Text = "Second UI",
                    Location = new Point(Right, Top),
                    StartPosition = FormStartPosition.Manual,
                };
                form.HandleCreated += SecondFormHandleCreated;
                form.HandleDestroyed += SecondFormHandleDestroyed;
                form.RunInNewThread(false);
            }
        };
        Controls.Add(button = new Button { Name = "Stop", Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40), Enabled = false });
        button.Click += (s, e) =>
        {
            if (secondThreadFormHandle != IntPtr.Zero)
                PostMessage(secondThreadFormHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        };
    }

    void EnableStopButton(bool enabled)
    {
        if (InvokeRequired)
            BeginInvoke((Action)(() => EnableStopButton(enabled)));
        else
        {
            Control stopButton = Controls["Stop"];
            if (stopButton != null)
                stopButton.Enabled = enabled;
        }
    }

    void SecondFormHandleCreated(object sender, EventArgs e)
    {
        Control second = sender as Control;
        secondThreadFormHandle = second.Handle;
        second.HandleCreated -= SecondFormHandleCreated;
        EnableStopButton(true);
    }

    void SecondFormHandleDestroyed(object sender, EventArgs e)
    {
        Control second = sender as Control;
        secondThreadFormHandle = IntPtr.Zero;
        second.HandleDestroyed -= SecondFormHandleDestroyed;
        EnableStopButton(false);
    }

    const int WM_CLOSE = 0x0010;
    [DllImport("User32.dll")]
    extern static IntPtr PostMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam);
}

internal static class FormExtensions
{
    private static void ApplicationRunProc(object state)
    {
        Application.Run(state as Form);
    }

    public static void RunInNewThread(this Form form, bool isBackground)
    {
        if (form == null)
            throw new ArgumentNullException("form");
        if (form.IsHandleCreated)
            throw new InvalidOperationException("Form is already running.");
        Thread thread = new Thread(ApplicationRunProc);
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = isBackground;
        thread.Start(form);
    }
}

3
你可以像这样做:
在 Program.cs 中:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;

namespace TwoWindows
{
    static class Program
    {
        public static Form1 form1;
        public static Form2 form2;
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false); 

            form1 = new Form1();
            form2 = new Form2();

            form1.Form2Property = form2;
            form2.Form1Property = form1;

            form1.Show();
            form2.Show();

            Application.Run();
        }
    }
}

在 Form1.cs 中:
namespace TwoWindows
{
    public partial class Form1 : Form
    {
        public Form2 Form2Property { get; set; }

        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnClosed(EventArgs e)
        {
            if (Form2Property.IsDisposed)
                Application.Exit();
        }
    }
}

并且,Form2.cs:

namespace TwoWindows
{
    public partial class Form2 : Form
    {
        public Form1 Form1Property { get; set; }

        public Form2()
        {
            InitializeComponent();
        }

        protected override void OnClosed(EventArgs e)
        {
            if (Form1Property.IsDisposed)
                Application.Exit();
        }
    }
}

这样你就可以在同一线程上获取两个表单并使用一个表单来控制另一个表单。 如果需要使用线程,我建议使用专用线程而不是在可以被多次调用的方法中生成线程。然后使用ManualResetEvent或AutoResetEvent来控制线程处理。我非常喜欢使用这种方法,因为它是安全的,并且不会花费太多资源初始化线程。

public class MyClassOrForm
{
    Thread myProcessingThread;
    public AutoResetEvent startProcessing = new AutoResetEvent(false);
    public AutoResetEvent processingFinished = new AutoResetEvent(false);
    public AutoResetEvent killProcessingThread = new AutoResetEvent(false);

    public MyClassOrForm()
    {
        myProcessingThread = new Thread(MyProcess);
    }

    private void MyProcess()
    {
        while (true)
        {
            if (startProcessing.WaitOne())
            {
                // Do processing here

                processingFinished.Set();
            }

            if (killProcessingThread.WaitOne(0))
                return;
        }
    }
}

然后,一旦您设置了要处理的数据,请从另一个类或方法中调用该数据。

MyClassOrMethodInstance.startProcessing.Set();

如果您需要等待处理完成,那么请插入:

MyClassOrMethodInstance.processingFinished.WaitOne(time_out_ms);

这相当于调用Thread.Join(),只是您不必每次分配一个新线程,并避免了线程依赖本地数据或未完成的子线程带来的风险。

1

我正在处理一个项目,创建了一个表单,它会在任务运行期间弹出,保持打开状态,然后关闭。

它包含一个ProgressBar,并具有以下设置:

  • progressBar1.Style=ProgressBarStyles.Marquee
  • progressBar1.MarqueeAnimationSpeed = <-- 在此处设置您自定义的速度(以毫秒为单位)

如果需要,您可以将表单的TopMost属性设置为true

以下是该表单的代码:

public partial class BusyForm : Form
{
    public BusyForm(string text = "Busy performing action ...")
    {
        InitializeComponent();
        this.Text = text;
        this.ControlBox = false;
    }

    public void Start()
    {
        System.Threading.Tasks.Task.Run(() =>
        {
            this.ShowDialog();
        });
    }

    public void Stop()
    {
        BeginInvoke((Action)delegate { this.Close(); });
    }

    public void ChangeText(string newText)
    {
        BeginInvoke((Action)delegate { this.Text = newText; });
    }
}

这里是在你的代码中使用表单的代码:

        BusyForm busyForm = new BusyForm(text: "Opening database ...");

        busyForm.Start();

        //do your stuff here

        busyForm.Stop();

更新:我在线程方面遇到了一些潜在的问题。这是代码的更新版本。背景信息是,当任务繁忙时,此表单会显示一个进度条。我添加了ChangeText命令,以显示如何从另一个表单与此表单交互。还应该提到,在Program.cs中的Main应该像下面所示那样带有[STAThread]属性。

    [STAThread]
    static void Main(string[] args)
    {
        System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

1

我正在编写一个应用程序,它是多线程的,并使用UI在创建的线程上来分派绘图功能到DC。

当我们将该应用程序移植到命令提示符上时,我们自然会遇到一些问题,因为调度程序线程未被创建或不需要 - 因此我从应用程序入口点创建了另一个线程,实质上调用ShowDialog()(旋转消息泵的唯一方法)在主窗体上 - 带有覆盖的OnShown,以永久隐藏和最小化窗体,当进行调用时。这使我仍然可以将表单分派到其他多个线程中并处理所有绘图。

这确实是一种丑陋的方法,但这是快速完成工作的方法,而且其效果如预期那样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接