在调用Application.Restart之前在ApplicationContext中处理表单

时间:2016-01-11 22:54:11

标签: c# .net multithreading winforms user-interface

我有一个winforms应用程序,它使用自定义应用程序上下文,提供在系统托盘中运行应用程序的框架。

public partial class MyApplicationContext : ApplicationContext
{
    private IContainer components;
    private NotifyIcon notifyIcon = null;

    /// the main application form, which may never be shown
    private AppForm appForm = null;

    public MyApplicationContext() 
    {
        components = new System.ComponentModel.Container();
        notifyIcon = new NotifyIcon(components)
        {
            ContextMenuStrip = new ContextMenuStrip(),
            Icon = Properties.Resources.icon2a,
            Text = "Application",
            Visible = true
        };

        appForm = new AppForm();
    }
}

应用程序监视配置文件的更改,并根据需要重新启动。

private void requestRestart()
{
    /// dispose the application context
    this.Dispose(); 
    Application.Restart();
}

我必须在重新启动之前处置应用程序上下文,因为应用程序的新实例需要资源。但是,可以从另一个帖子调用requestRestart()所以我无法在appForm方法中或直接在重启方法中处置ApplicationContext.Dispose(),或者我会得到一个跨线程的例外。

如果表单在重新启动之前显示,当用户点击调用appForm.Show()的托盘图标时,将自动调用appForm.Dispose(true)。每MSDN

  

如果使用Show方法显示表单,将自动调用Dispose。

否则,如果表格从未显示过,GC终结器会调用Dispose(false)

如何确保在重新启动之前处理此表单?

1 个答案:

答案 0 :(得分:2)

我建议您从构造函数中删除appForm.Show(),并将其放入从托盘图标调用的方法中。

在托盘图标打开表单方法中,将appForm.Show()包装在使用块中:

using (appForm = new AppForm()) {
    appForm.Show();
}

这样,表格会在关闭时自行处理。

此外,请将以下内容放在requestRestart()方法中:

appForm?.Close();

这将确保在上下文强制重新启动时关闭表单。

如果您没有使用最新版本的.NET,以下内容将实现相同的目标:

if (appForm != null)
    appForm.Close();

编辑

这是一个完整的例子。这个答案对于你如何处理你的后台任务更加自以为是,但我认为你会发现它的效果非常好。

解释内容为注释。

在您的Program.cs中:

private static void Main()
{
    int restartCount = 0;
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    var applicationCtx = new MyApplicationContext();

    applicationCtx.RestartRequested += (o, e) =>
    {
        restartCount++; //this is just here so my program would stop restarting
        if (restartCount > 5) Application.Exit();

        Application.Restart();
    };

    Application.Run(applicationCtx);
}

然后在MyApplicationContext.cs中:

internal class MyApplicationContext : ApplicationContext
{
    private NotifyIcon notifyIcon;
    private IContainer components;
    private AppForm appForm;

    public event EventHandler RestartRequested;

    public MyApplicationContext()
    {
        notifyIcon = new NotifyIcon()
        {
            ContextMenuStrip = new ContextMenuStrip { Text = "Open Form" },
            Text = "Application",
            Visible = true,
            Icon = new Icon("icon.ico")
        };

        appForm = new AppForm();

        notifyIcon.DoubleClick += (o, e) =>
        {
            appForm.Show();
        };

        RestartRequested += (o, e) =>
        {
            appForm?.Close();  //Close() will dispose the form as well.
            notifyIcon?.Dispose();
        };

        BackgroundWork();
    }

    private void BackgroundWork()
    {
        Task.Run<bool>(() => //Here we are telling Task to run a background operation and return a bool
        {
            //this body will run in a separate thread
            Thread.Sleep(5000);  //this represents your background work
            var restart = true;  //whatever result the bg work yields
            return restart;
        }).ContinueWith((task) =>  //task is an instance of Task from above containing the result fromm the background work
        {
            var shouldRestart = task.Result;  // Result is the value you returned in the body Run body above
            if (shouldRestart) RestartRequested?.Invoke(this, EventArgs.Empty);
        },
        TaskScheduler.FromCurrentSynchronizationContext()); //This will return on the UI thread now, no need to worry about thread boundaries
    }
}