同时运行两个WinForm窗口

23

我有两个C# Winform (.NET 4.0)表单,每个都在单独但类似的自动化任务中持续运行。这些表单是不同的进程/工作流程,但在项目中操作方式相似,可以共享相同的资源(方法、数据模型、程序集等)。

这两个表单已经完成,但现在我不确定如何运行程序,使每个窗口在启动时打开并独立运行。该程序将在部署时一直运行。

这可能看起来有点基础,但我大部分的开发经验是Web应用程序。线程等还有点陌生。我已经进行了研究,但我找到的大多数答案都与用户交互和顺序使用情况有关——这将只是连续运行两个不同进程的一个系统,并需要与外界独立交互。

我发现的潜在解决方案可能涉及多线程,或者可能是某种MDI,或者一些人建议DockPanelSuite(尽管在超级公司环境中,下载第三方文件比说起来容易)。

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

        // Rather than specifying frmOne or frmTwo,
        // load both winforms and keep them running.
        Application.Run(new frmOne());
    }
}

3
你为什么不能只创建两个表单并展示它们? - wRAR
@wRAR 嗯,我想到的一件事是程序可能不应该在一个窗体关闭后就停止,而应该等到两个窗体都关闭。 - Servy
1
如果你的意思是....frmOne one = new frmOne(); frmTwo two = new frmTwo(); one.Show(); two.Show();... 我试过了,但它不能保持程序运行。程序会显示两个窗体一秒钟,然后就结束了。我相信很可能是我的操作有误。 - RJB
@StingyJack 不错的问题。我是指独特的业务流程。 - RJB
1
你可能想考虑将代码拆分为两个单独的窗体项目,并将所有公共代码放入第三个类库项目中。 - StingyJack
显示剩余4条评论
3个回答

44
您可以创建一个新的ApplicationContext来表示多个表单:
public class MultiFormContext : ApplicationContext
{
    private int openForms;
    public MultiFormContext(params Form[] forms)
    {
        openForms = forms.Length;

        foreach (var form in forms)
        {
            form.FormClosed += (s, args) =>
            {
                //When we have closed the last of the "starting" forms, 
                //end the program.
                if (Interlocked.Decrement(ref openForms) == 0)
                    ExitThread();
            };

            form.Show();
        }
    }
}

使用这个,现在您可以写:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MultiFormContext(new Form1(), new Form2()));

1
+1 有趣的解决方案,允许表单彼此独立(即一个表单不必生成和控制另一个表单的生命周期)。 - Paul Sasik
我现在正在尝试测试两个答案,但是这段代码实际上并没有打开任何一个表单。抱歉,我已经按照这里看到的方式编写了它(当然带有正确的参数),是否有什么我可能遗漏的东西? - RJB
@RJB 噢,它创建了表单但没有显示它们;添加一行代码就可以解决这个问题。 - Servy
@RJB 有什么问题是它们不能正常工作的?你希望它们独立的方式是什么?如果你依赖于多个UI线程,长期来看你会遇到更多的问题而不是解决问题。 - Servy
1
@AlexanderAleksandrovičKlimov 如果您没有通过另一个线程公开应用程序上下文,那么它只能通过UI线程访问。 - Servy
显示剩余7条评论

10

如果你确实需要在两个独立的UI线程上运行两个窗口/表单,你可以像这样做:

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        var thread = new Thread(ThreadStart);
        // allow UI with ApartmentState.STA though [STAThread] above should give that to you
        thread.TrySetApartmentState(ApartmentState.STA); 
        thread.Start(); 

        Application.Run(new frmOne());
    }

    private static void ThreadStart()
    {
        Application.Run(new frmTwo()); // <-- other form started on its own UI thread
    }
}

这真的有效吗?看起来很麻烦。这意味着你有“2个主线程”或者什么?你会自己使用这个吗?(只是询问,不是试图贬低你的回答) - bas
尽可能避免使用多个UI线程。特别是考虑到这些窗体很可能需要相互交互,因为它们具有共享资源。 - Servy
@bas:是的,这个方法是可行的。我只会在非常罕见和特殊的情况下使用它。从OP的问题中我无法确定是否真的需要一个单独的UI线程。 - Paul Sasik
@Servy:我完全同意你的线程陈述。我只是提供一个选项。如果可能的话,一个线程中有两个单独的表格会更好。 - Paul Sasik
我已将此选为答案。由于“frmOne”上一个暴躁的ActiveX控件的无关问题,我需要将ThreadStart函数移动到加载frmOne之后。除此之外,这似乎非常有效。分离的UI线程是必要的,以便表单可以异步地与数据库交互并驱动独立的主机控件,正如我在Servy的答案中所描述的那样。谢谢Paul和大家的时间!你们太棒了。 - RJB

1

假设

你并不需要这两个不同的进程,只是因为你想要有两种不同的形式,并且希望能够保持应用程序运行,直到两个表单都退出。

另一种解决方案

依赖于Form.Closed事件机制。您可以添加一个事件处理程序,允许您指定在窗体关闭时要执行的操作。例如,在两个表单都关闭时退出应用程序。

关于一些代码

    public Form1()
    {
        InitializeComponent();

        _form2 = new Form2();
        _form2.Show(this);

        this.Closed += Form1Closed;
        _form2.Closed += Form2Closed;
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        e.Cancel = true;
        Hide();
        Form1Closed(this, new EventArgs());
        base.OnFormClosing(e);
    }

    private void Form1Closed(object sender, EventArgs eventArgs)
    {
        form1IsClosed = true;

        TryExitApplication();
    }

    private void Form2Closed(object sender, EventArgs eventArgs)
    {
        _form2IsClosed = true;

        TryExitApplication();
    }

    private void TryExitApplication()
    {
        if (form1IsClosed && _form2IsClosed)
        {
            Dispose();
            Application.Exit();
        }
    }

请注意,这应该被重构以使其成为更好的解决方案。

更新

Servy提供的评论使我修改了这个“应该是简单的解决方案”,他指出他的解决方案比这个更好。由于我被支持留下答案,所以我将使用这个答案,我也会解决使用此解决方案时出现的问题:

  • 取消关闭事件
  • 从一个事件重定向到另一个事件
  • 强制调用Dispose
  • 正如Servy所指出的:维护不友好(需要检查哪个表单已关闭)

  1. 这实际上比仅使用应用程序上下文需要更多的代码。
  2. 它强制逻辑嵌入到各个表单中,而不是位于单个逻辑位置。
  3. 它目前无法工作;为使其正常工作,您需要停止关闭主窗体,这意味着它无法被销毁、收集资源等。
  4. 这方案没有可重用性或可扩展性。您需要为每个项目重新从头开始完成所有工作。如果编写自定义应用程序上下文,只需编写一次即可永久使用。
  5. 这相当容易出现错误;很容易漏掉一些小细节。
- Servy
  1. 但这并不容易维护;它更难维护。要添加一个新的启动表单,您需要添加一个新的自定义处理程序、一个新的布尔字段,并编辑“TryExitApplication”中的检查。
  2. 你是说像我回答中所做的那样吗?是的,你可以,我的意思是你选择不这么做。
  3. 你提供了这个作为另一种已经合理、可行和优化的解决方案的替代方法。如果没有其他解决方案被提出,那么当然比没有好。我知道我曾经使用过这个模型。
  4. 考虑到您在代码中有一个错误,而且还没有修复它……请参见第3点。
- Servy
@Servy 好的,我会删除我的回答。我原以为你的方法有些“过度”,但是在访问了MSDN之后,我发现你的解决方案更好。我去喂孩子了,然后删除它,这样我就能确信你看到了这条评论 :) - bas
@Servy,我很高兴我开始了这场辩论。一旦我开始解决你提出的问题,就更明显了这变得混乱不堪。重新测试了这个有些可疑的解决方案,它确实有效...但是知道了所有这些,我肯定会选择你的解决方案(在我看来是最好的)。无论如何,对此我表示歉意。积极的一面是我也在这里学到了一些东西。 - bas
2
@bas 这就是我留下这些评论的原因。我很高兴你(和我)学到了一些东西。 - Servy
显示剩余4条评论

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