【问题标题】:Run the current application as Single Instance and show the previous instance将当前应用程序作为单个实例运行并显示上一个实例
【发布时间】:2018-05-27 13:21:34
【问题描述】:

我刚刚实现了这段代码来保护应用程序的单个实例,以免应用程序运行两次。

现在我想知道如何显示已经在运行的原始应用程序进程。

这是我在程序类中的代码:

static class Program
{
    [STAThread]
    static void Main()
    {
        const string appName = "MyappName";
        bool createdNew;
        mutex = new Mutex(true, appName, out createdNew);

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Form form = new Form1();

        if (!createdNew)
        {
            form.Show();  <<=========================== NOT WORKING
            form.Visible = true; <<===================== None
            form.TopMost = true; <<===================== of
            form.BringToFront(); <<===================== these working!
            form.WindowState = FormWindowState.Maximized;
            return;
        }
        Application.Run(form);
    }        private static Mutex mutex = null;
}

【问题讨论】:

  • 我无法重现该问题。
  • 您要运行多个还是只运行一个?检查我的答案!
  • 您必须与程序的现有实例对话,并告诉它将其窗口移至前台。这需要更多的代码,以及坚韧不拔的代码,因为进程互操作总是很难做到正确。支持已经内置到框架中的支持的一个重要原因。谷歌“c# windowsformsapplicationbase startupnextinstance”获得不错的点击率。甚至可能显示this post

标签: c# winforms ui-automation single-instance


【解决方案1】:

我建议您使用不同的方法,结合使用 System.Threading.Mutex 类和 UIAutomation AutomationElement 类。

如您所知,Mutex 可以是一个简单的字符串。您可以以 GUID 的形式为应用程序分配 Mutex,但也可以是其他任何形式。
假设这是当前应用程序Mutex

string ApplicationMutex = "BcFFcd23-3456-6543-Fc44abcd1234";
//Or
string ApplicationMutex = "Global\BcFFcd23-3456-6543-Fc44abcd1234";

注意
使用 "Global\" 前缀来定义 Mutex 的范围。如果未指定前缀,则假定并使用 "Local\" 前缀。当多个桌面处于活动状态或终端服务在服务器上运行时,这将阻止进程的单个实例。

如果我们想验证另一个正在运行的进程是否已经注册了相同的Mutex,我们尝试注册我们的Mutex,如果失败,我们的应用程序的另一个实例已经在运行。
我们让用户知道Application只支持单个实例,然后切换到正在运行的进程,显示其界面,最后退出重复的Application,处理Mutex

激活前一个应用程序实例的方法可能会因应用程序的类型而异,但只有一些细节会发生变化。
我们可以使用Process..GetProcesses() 来检索正在运行的进程列表,并验证其中一个是否与我们的具有相同的详细信息。

在这里,您有一个窗口应用程序(它有一个 UI),因此已经可以过滤列表,不包括那些没有MainWindowHandle 的进程。

Process[] windowedProcesses = 
    Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero).ToArray();

为了确定正确的,我们可以测试Process.ProcessName是否相同。
但是这个 name 与可执行文件名相关联。如果文件名更改(有人出于某种原因更改它),我们将永远不会以这种方式识别进程。

识别正确进程的一种可能方法是测试Process.MainModule.FileVersionInfo.ProductName 并检查它是否相同。

找到后,可以使用已识别进程的MainWindowHandle 创建的AutomationElement 将原始应用程序置于最前面。
AutomationElement 可以自动化不同的模式(一种为 UI 元素提供自动化功能的控件)。
WindowPattern 允许控制基于窗口的控件(平台无关紧要,可以是 WinForms 的表单或 WPF 的窗口)。

AutomationElement element = AutomationElement.FromHandle(process.MainWindowHandle);
WindowPattern wPattern = element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
wPattern.SetWindowVisualState(WindowVisualState.Normal);

要使用UIAutomation 功能,您必须在您的项目中添加这些引用:
- UIAutomationClient
- UIAutomationTypes

更新:
由于应用程序的窗体可能被隐藏,Process.GetProcesses() 将找不到它的窗口句柄,因此AutomationElement.FromHandle() 不能用于识别Form 窗口。

一种可能的解决方法是在不关闭 UIAutomation“模式”的情况下注册一个自动化事件,使用 Automation.AddAutomationEventHandler,它允许在 UI 自动化事件发生时接收通知,例如即将显示新窗口(运行程序)。

仅当应用程序需要作为单实例运行时才注册该事件。引发事件时,会将新进程 AutomationElement 名称(Windows 标题文本)与当前进程进行比较,如果相同,隐藏的表单将取消隐藏并显示为正常状态。
作为故障安全措施,我们提供信息MessageBoxMessageBox 标题与应用程序 MainForm 具有相同的标题。
使用将 WindowsState 设置为 Minimized 并将其 Visible 属性设置为 false 的表单进行测试)。


在原始进程被带到前面之后,我们只需要关闭当前线程并释放我们创建的资源(主要是互斥体,在这种情况下)。

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;

static class Program
{
    static Mutex mutex = null;

    [STAThread]
    static void Main()
    {
        Application.ThreadExit += ThreadOnExit;
        string applicationMutex = @"Global\BcFFcd23-3456-6543-Fc44abcd1234";
        mutex = new Mutex(true, applicationMutex);
        bool singleInstance = mutex.WaitOne(0, false);
        if (!singleInstance)
        {
            string appProductName = Process.GetCurrentProcess().MainModule.FileVersionInfo.ProductName;
            Process[] windowedProcesses = 
                Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero).ToArray();

            foreach (Process process in windowedProcesses.Where(p => p.MainModule.FileVersionInfo.ProductName == appProductName))
            {
                if (process.Id != Process.GetCurrentProcess().Id)
                {
                    AutomationElement wElement = AutomationElement.FromHandle(process.MainWindowHandle);
                    if (wElement.Current.IsOffscreen)
                    {
                        WindowPattern wPattern = wElement.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
                        #if DEBUG
                        WindowInteractionState state = wPattern.Current.WindowInteractionState;
                        Debug.Assert(!(state == WindowInteractionState.NotResponding), "The application is not responding");
                        Debug.Assert(!(state == WindowInteractionState.BlockedByModalWindow), "Main Window blocked by a Modal Window");
                        #endif
                        wPattern.SetWindowVisualState(WindowVisualState.Normal);
                        break;
                    }
                }
            }
            Thread.Sleep(200);
            MessageBox.Show("Application already running", "MyApplicationName",
                            MessageBoxButtons.OK, MessageBoxIcon.Information, 
                            MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
        }

        if (SingleInstance) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MyAppMainForm());
        }
        else {
            Application.ExitThread();
        }
    }
    private static void ThreadOnExit(object s, EventArgs e)
    {
        mutex.Dispose();
        Application.ThreadExit -= ThreadOnExit;
        Application.Exit();
    }
}

在应用程序MainForm 构造函数中:
(这用于在运行新实例时隐藏应用程序的主窗口,因此Program.cs 中的过程无法找到它的句柄)

public partial class MyAppMainForm : Form
{
    public MyAppMainForm()
    {
        InitializeComponent();
        Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, 
                                             AutomationElement.RootElement, 
                                             TreeScope.Subtree, (uiElm, evt) =>
        {
            AutomationElement element = uiElm as AutomationElement;
            string windowText = element.Current.Name;
            if (element.Current.ProcessId != Process.GetCurrentProcess().Id && windowText == this.Text)
            {
                this.BeginInvoke(new MethodInvoker(() =>
                {
                    this.WindowState = FormWindowState.Normal;
                    this.Show();
                }));
            }
        });
    }    
}

【讨论】:

  • 谢谢,这在应用程序已经可见但隐藏时效果很好。当应用程序被隐藏但仍作为进程处于活动状态并最小化为通知图标时,我收到“应用程序不工作”错误。
  • 所以我使用 this.Hide();和 notifyIcon1.Visible = true;当应用程序最小化或点击交叉时
  • @Salihan Pamuk 好吧,您没有在问题中指定这一点。这是不同的情况。我会看看我能做些什么(我现在正在工作)。
  • 我已经找到了一篇帮助我摆脱困境的文章:sanity-free.org/143/… 这适用于所有情况(最小化、隐藏、不可见)
  • @Salihan Pamuk 好的,但请注意,使用您链接的方法(11 岁),您可能会看到应用程序的实例卡在内存中。无论如何,我将研究一种解决方案来查看隐藏的应用程序,它不涉及 P/Invoking。在这种情况下,我没有测试 Hans Passant 发布的链接。不过我会试试看的。
【解决方案2】:

只运行一次:

static class Program
{    
    [STAThread]
    static void Main()
    {
        bool createdNew = true;
        using (Mutex mutex = new Mutex(true, "samplename", out createdNew))
        {
            if (createdNew)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
                AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
                Application.Run(new Form1());
            }
            else
            {
                ProcessUtils.SetFocusToPreviousInstance("samplename");
            }
        }
    }

    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
    }

    private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
    }
}

ProcessUtils:

   public static class ProcessUtils
    {
        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        static extern bool IsIconic(IntPtr hWnd);

        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        const int SW_RESTORE = 9;

        [DllImport("user32.dll")]
        static extern IntPtr GetLastActivePopup(IntPtr hWnd);

        [DllImport("user32.dll")]
        static extern bool IsWindowEnabled(IntPtr hWnd);


        public static void SetFocusToPreviousInstance(string windowCaption)
        {

            IntPtr hWnd = FindWindow(null, windowCaption);


            if (hWnd != null)
            {

                IntPtr hPopupWnd = GetLastActivePopup(hWnd);



                if (hPopupWnd != null && IsWindowEnabled(hPopupWnd))
                {
                    hWnd = hPopupWnd;
                }

                SetForegroundWindow(hWnd);


                if (IsIconic(hWnd))
                {
                    ShowWindow(hWnd, SW_RESTORE);
                }
            }
        }
    }

正常运行:

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

【讨论】:

    【解决方案3】:

    如果您仍在寻找答案。有一个很好的例子here 使用 windows 消息来恢复以前的实例。即使第一个实例被最小化与 FindWindow 女巫在这种情况下不起作用,它也可以工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-10
      • 2014-05-11
      相关资源
      最近更新 更多