C# 如何在主窗口关闭时重新创建windows窗体应用程序
我有windows窗体应用程序。当它关闭时,主窗口被释放,然后当用户点击托盘窗口被重新创建-它工作。然而,当我在使用FileSystemWatcher时尝试将应用程序恢复时,我遇到了一个问题。这个想法很简单,当文件被更改时,应用程序被带回来。但在这种情况下,应用程序会出现,但会挂起,然后消失。窗口的形状恢复了,但没有响应,在窗口上移动鼠标会显示应用程序正在“思考”或“挂起”,应用程序的任务栏上没有图标。 我的猜测是,这与线程/同步有关,但我不知道如何使它再次工作。我尝试了许多与线程相关的不同方法,但都失败了。我无法在UI线程中再次创建此窗口,因为据我所知,我可以编写C# 如何在主窗口关闭时重新创建windows窗体应用程序,c#,.net,multithreading,winforms,C#,.net,Multithreading,Winforms,我有windows窗体应用程序。当它关闭时,主窗口被释放,然后当用户点击托盘窗口被重新创建-它工作。然而,当我在使用FileSystemWatcher时尝试将应用程序恢复时,我遇到了一个问题。这个想法很简单,当文件被更改时,应用程序被带回来。但在这种情况下,应用程序会出现,但会挂起,然后消失。窗口的形状恢复了,但没有响应,在窗口上移动鼠标会显示应用程序正在“思考”或“挂起”,应用程序的任务栏上没有图标。 我的猜测是,这与线程/同步有关,但我不知道如何使它再次工作。我尝试了许多与线程相关的不同方法
\u mainWindow.BeginInvoke
,但在创建此表单之前,我不能这样做。
我创建了一个演示该问题的最小工作示例。可从以下网址获取:
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
事件的线程中创建的,该事件是一个后台线程,而不是应用程序.Run()
方法用于输出窗口消息的线程。因此,背景线程最终拥有该窗口
窗口的消息被发送到拥有它的线程,但该线程没有消息泵循环,因此窗口永远看不到消息。这些消息非常重要,因为它们处理与窗口交互的所有内容,包括用户输入和绘制窗口所涉及的所有内容(除了Windows桌面管理器处理的最小值)。这些消息甚至用于处理调用Control.Invoke()
和Control.BeginInvoke()
之类的事情。如果没有消息泵送循环,BeginInvoke()
委托将永远不会被处理,Invoke()
甚至永远不会返回
有多种方法可以解决这个问题。首先不处理窗口可能是一个选项(您可以覆盖OnFormClosing()
,取消事件并隐藏窗口)。然后,对\u main window.Invoke()
的调用将始终转到正确的线程,并按预期工作:
public partial class MainWindow : Form
{
// ...
protected override void OnFormClosing(FormClosingEventArgs e)
{
Visible = false;
e.Cancel = true;
base.OnFormClosing(e);
}
// ...
}
或者,您可以捕获主线程的SynchronizationContext
对象,该对象可用于执行与Control.Invoke()相同的操作。该技术的关键在于,在线程中创建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中的SynchronizationContext
。在随后的实例中更改了事件处理程序,因此也不需要它:
private void NewForm()
{
Console.WriteLine("Creating new MainWindow");
_mainWindow = new MainWindow();
_mainWindow.Show();
}
当你完全期待它回来的时候,你为什么要处理它?只需使用正常的系统托盘窗口行为,并将激活逻辑添加到FileWatcher处理。为了不使用系统资源,应用程序不需要。在窗体窗口上单击x使其释放。托盘是独立于表单的组件。但如果窗口不断返回,这只是暂时的节省,除非机器的资源非常有限,否则您可能会过度优化。用户手动重新打开窗口或触发文件监视程序的频率如何?感谢您的全面响应。它解决了我的问题,此外,我了解问题所在。实际上,我很接近SynchronizationContext的解决方案,但最终我失败了,可能是因为我在创建窗口之前尝试分配了它,此外,我在SynchronizationContext上“运行”代码时遇到了问题,现在我知道我可以使用“Post”来完成它。:)