如何在主窗口关闭时重新创建窗体应用程序

时间:2018-02-06 22:02:49

标签: c# .net multithreading winforms

我有Windows窗体应用。当它关闭时,主窗口被处理掉,然后当用户点击托盘窗口被重新创建时 - 它可以工作。但是当我在使用FileSystemWatcher时尝试恢复应用程序时,我遇到了问题。想法很简单,当文件被更改时应用程序被带回来。但是在这种情况下应用程序出现但挂起然后消失。窗口的形状回来了,但没有反应,移动鼠标在窗口显示,如应用程序是"思考"或者"挂着",应用程序在任务栏上没有图标。 我的猜测是这与线程/同步相关,但我不知道如何让它再次工作。我尝试了许多与线程相关的不同内容,但都失败了。我无法在UI线程中再次创建此窗口,因为据我所知,我可以编写_mainWindow.BeginInvoke但我在创建此表单之前无法执行此操作。 我创建了演示该问题的最小工作示例。可在https://gitlab.com/virtual92/getting-forms-up或此处获取:

Program.cs的

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GettingFormsUp
{
    static class Program
    {
        private static bool hideFlag = true;

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var initApplicationContext = new InitApplicationContext();

            Console.WriteLine();
            var fileSystemWatcher = new FileSystemWatcher(Path.GetFullPath("../.."), "watcher");
            fileSystemWatcher.Changed += (sender, args) =>
            {
                Console.WriteLine("Watched");
                initApplicationContext.Show();
            };
            fileSystemWatcher.EnableRaisingEvents = true;

            Application.Run(initApplicationContext);
        }

        private class InitApplicationContext : ApplicationContext
        {
            private static MainWindow _mainWindow;

            public InitApplicationContext()
            {
                NewForm();
            }

            private void NewForm()
            {
                Console.WriteLine("Creating new MainWindow");
                _mainWindow = new MainWindow();
                _mainWindow.Invoke((MethodInvoker) delegate
                {
                    _mainWindow.Show(); 
                });
            }

            public void Show()
            {
                if (_mainWindow == null || _mainWindow.IsDisposed)
                {
                    NewForm();
                }
                else if (!_mainWindow.Visible)
                {
                    _mainWindow.BeginInvoke((MethodInvoker) delegate
                    {
                        Console.WriteLine("showing");
                        _mainWindow.Show();
                    });
                }
            }

            public void Delete()
            {
                if (_mainWindow != null && !_mainWindow.IsDisposed)
                {
                    _mainWindow.Dispose();
                }
            }
        }
    }
}

MainWindow.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GettingFormsUp
{
    public class MainWindow : Form
    {
        public MainWindow()
        {
            CreateHandle();
            InitializeComponent();
        }

        private System.ComponentModel.IContainer components = null;
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Text = "Form1";
        }
    }
}

我怎样才能让它发挥作用?

1 个答案:

答案 0 :(得分:2)

您的代码的问题在于,当您再次创建窗口时,它将在用于引发FileSystemWatcher.Changed事件的线程中创建,该事件是后台线程,而不是Application.Run()的线程方法用于抽取​​窗口消息。因此背景线程最终拥有了窗口。

窗口的消息被发送到拥有它的线程,但该线程没有消息循环,因此窗口永远不会看到消息。这些消息是至关重要的,因为它们处理与窗口交互的所有,用户输入和绘制窗口所涉及的所有内容(Windows桌面管理器处理的最低限度除外)。这些消息甚至用于处理调用Control.Invoke()Control.BeginInvoke()之类的内容。如果没有消息循环,BeginInvoke()代理将永远不会被处理,Invoke()将永远不会返回。

有很多方法可以解决这个问题。只是不首先处理窗口可能是一个选项(您可以覆盖OnFormClosing(),取消事件并隐藏窗口)。然后,对_mainWindow.Invoke()的调用将始终转到正确的线程并按预期工作:

public partial class MainWindow : Form
{
    // ...

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        Visible = false;
        e.Cancel = true;

        base.OnFormClosing(e);
    }

    // ...
}

或者,您可以捕获主线程的SynchronizationContext对象,该对象可用于执行与Control.Invoke() / BeginInvoke()相同的操作。该技术的关键是,在线程中创建Winforms组件之前,该线程不会有SynchronizationContext。在您的代码中,表单是在您创建InitApplicationContext对象时创建的,因此在此之后捕获SynchronizationContext将起作用:

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    var initApplicationContext = new InitApplicationContext();
    SynchronizationContext context = SynchronizationContext.Current;

    Console.WriteLine();
    string path = Path.GetFullPath("../..");
    Console.WriteLine($"Watching {path}");
    var fileSystemWatcher = new FileSystemWatcher(path, "watcher");
    fileSystemWatcher.Changed += (sender, args) =>
    {
        context.Post(o =>
        {
            Console.WriteLine("Watched");
            initApplicationContext.Show();
        }, null);
    };
    fileSystemWatcher.EnableRaisingEvents = true;

    Application.Run(initApplicationContext);
}

如果你采用这种方法,那么当然你在创建窗口时不需要调用Control.Invoke()。无论如何,这是多余的,并且因为你将在后续实例中使用来自SynchronizationContext事件处理程序的FileSystemWatcher.Changed,所以它也是不需要的:

private void NewForm()
{
    Console.WriteLine("Creating new MainWindow");
    _mainWindow = new MainWindow();
    _mainWindow.Show();
}