我有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";
}
}
}
我怎样才能让它发挥作用?
答案 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();
}