到目前为止,这是我为创建单个实例WPF应用程序而实现的代码:

#region Using Directives
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
#endregion

namespace MyWPF
{
    public partial class MainApplication : Application, IDisposable
    {
        #region Members
        private Int32 m_Message;
        private Mutex m_Mutex;
        #endregion

        #region Methods: Functions
        private IntPtr HandleMessages(IntPtr handle, Int32 message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
        {
            if (message == m_Message)
            {
                if (MainWindow.WindowState == WindowState.Minimized)
                    MainWindow.WindowState = WindowState.Normal;

                Boolean topmost = MainWindow.Topmost;

                MainWindow.Topmost = true;
                MainWindow.Topmost = topmost;
            }

            return IntPtr.Zero;
        }

        private void Dispose(Boolean disposing)
        {
            if (disposing && (m_Mutex != null))
            {
                m_Mutex.ReleaseMutex();
                m_Mutex.Close();
                m_Mutex = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region Methods: Overrides
        protected override void OnStartup(StartupEventArgs e)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            Boolean mutexCreated;
            String mutexName = String.Format(CultureInfo.InvariantCulture, "Local\{{{0}}}{{{1}}}", assembly.GetType().GUID, assembly.GetName().Name);

            m_Mutex = new Mutex(true, mutexName, out mutexCreated);
            m_Message = NativeMethods.RegisterWindowMessage(mutexName);

            if (!mutexCreated)
            {
                m_Mutex = null;

                NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, m_Message, IntPtr.Zero, IntPtr.Zero);

                Current.Shutdown();

                return;
            }

            base.OnStartup(e);

            MainWindow window = new MainWindow();
            MainWindow = window;
            window.Show(); 

            HwndSource.FromHwnd((new WindowInteropHelper(window)).Handle).AddHook(new HwndSourceHook(HandleMessages));
        }

        protected override void OnExit(ExitEventArgs e)
        {
            Dispose();
            base.OnExit(e);
        }
        #endregion
    }
}


一切正常……但是我对此有一些疑问,我想来收到您有关如何改进我的方法的建议。


代码分析要求我实现IDisposable接口,因为我使用的是IDisposable成员(Mutex)。我的Dispose()实现足够好吗?我应该避免使用它,因为它永远不会被调用吗?
最好使用m_Mutex = new Mutex(true, mutexName, out mutexCreated);并检查结果,或者先使用m_Mutex = new Mutex(false, mutexName);然后再检查m_Mutex.WaitOne(TimeSpan.Zero, false);?如果是多线程,我的意思是...
RegisterWindowMessage API调用应该返回UInt32 ...但是HwndSourceHook只接受Int32作为消息值...我应该担心意外行为(例如结果大于Int32.MaxValue)吗?
OnStartup中覆盖...即使在另一个实例已经在运行并且要关闭应用程序的情况下,我也应该执行base.OnStartup(e);吗?
是否有更好的方法将现有实例带到顶部不需要设置Topmost值吗?也许是Activate()
您能看到我的方法有什么缺陷吗?关于多线程,不良异常处理之类的东西?例如...如果我的应用程序在OnStartupOnExit之间崩溃会发生什么?


#1 楼

这可能违反了Code Review的精神,但是您不需要为WPF编写自己的单实例管理器! Microsoft已经编写了代码来完成此任务,但是它的广告宣传不佳。

Microsoft的单实例管理器非常全面,我还没有发现任何问题。 (而且,如果您不想使用它,那么它至少可以为您自己的实现提供一个很好的参考。)

如何在WPF中管理单个实例

步骤1:将System.Runtime.Remoting引用添加到您的项目中。

步骤2:将此单个实例类添加到您的项目中。

步骤3:在主应用程序类中实现ISingleInstanceApp接口在App.xaml.cs(此接口由SingleInstance.cs文件提供)中。

例如:

public partial class App : Application, ISingleInstanceApp

步骤4:通过以下步骤更改启动对象在Visual Studio中转到Project-> <projectname> Properties,单击Application选项卡。找到Startup object:并选择<projectname>.App选项。



步骤5:在Main中定义App.xaml.cs方法,并在Unique变量中为其指定唯一的字符串。

// TODO: Make this unique!
private const string Unique = "Change this to something that uniquely identifies your program.";

[STAThread]
public static void Main()
{   
    if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
    {
        var application = new App();
        application.InitializeComponent();
        application.Run();

        // Allow single instance code to perform cleanup operations
        SingleInstance<App>.Cleanup();
    }
}

#region ISingleInstanceApp Members
public bool SignalExternalCommandLineArgs(IList<string> args)
{
    // Handle command line arguments of second instance
    return true;
}
#endregion


步骤6:在解决方案资源管理器中右键单击App.xaml,选择Properties,然后将Build Action更改为Page

单实例技术

打开新实例时不执行任何操作

如果您不希望在启动单个实例时发生任何事情,则无需修改上面的示例代码。 br />
在打开新实例时激活原始窗口

而不是在用户尝试打开该程序的第二个实例时不执行任何操作,激活原始窗口并进行更改通常是有益的它的窗口状态为WindowState.Normal,如果原始窗口最小化,则可以提供更好的用户体验。

要激活并使窗口可见,请修改SignalExternalCommandLineArgs方法,如图所示。 (请注意,MainWindowApp类的属性,请勿与任意命名的MainWindow.xaml.cs类混淆。)

public bool SignalExternalCommandLineArgs(IList<string> args)
{
    // Bring window to foreground
    if (this.MainWindow.WindowState == WindowState.Minimized)
    {
        this.MainWindow.WindowState = WindowState.Normal;
    }

    this.MainWindow.Activate();

    return true;
}


评论


\ $ \ begingroup \ $
使用此设置,例如,您将如何显示,激活和最小化属于已运行应用程序的窗口?
\ $ \ endgroup \ $
–朱利安
2014年5月8日19:06

\ $ \ begingroup \ $
@Julien我刚刚在答案中增加了一个名为“单实例技术”的部分,它回答了这个问题。简短版:在App的MainWindow属性上调用Activate()方法,并将其WindowState设置为WindowState.Normal。
\ $ \ endgroup \ $
–埃文·旺德拉塞克(Evan Wondrasek)
2014-09-10 23:34



\ $ \ begingroup \ $
@EvanWondrasek,我已经尝试过使用clickone wpf的解决方案,该解决方案以前使用WindowsFormApplictionBase。但是,这两种方法都存在着无法捕获第二个或以后的实例启动args的问题。它仅适用于初审,之后不适用。我有一个单独的帖子,描述了有关此问题的更多信息。有什么帮助吗? stackoverflow.com/questions/30463690/…
\ $ \ endgroup \ $
–奈尔
15年5月26日在22:12



\ $ \ begingroup \ $
可在此处获得更新的版本(具有Apache许可证):code.msdn.microsoft.com/Windows-7-Taskbar-Single-4120eafd(原始博客所有者在这篇文章中引用了该博客:blogs.microsoft.co.il / arik / 2011/04/04 /…)。
\ $ \ endgroup \ $
– Mike Pelley
16年8月17日在23:50



\ $ \ begingroup \ $
App.xaml的构建动作是否为ApplicationDefinition并不重要?您能否评论一下更改的含义?
\ $ \ endgroup \ $
–凯尔·德莱尼(Kyle Delaney)
18 Mar 29 '18在15:58