关闭主窗口时,如何重新创建Windows Form应用程序



我有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";
        }
    }
}

我该如何使它起作用?

代码的问题是,当您再次创建窗口时,它是在用于筹集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()。无论如何,这是第一次多余的,因为您将在随后的实例中使用FileSystemWatcher.Changed事件处理程序中的SynchronizationContext,因此它也不需要:

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

最新更新