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