• WPF实现只打开一个窗口,并且重复打开时已经打开的窗口置顶


    内容来自:https://codereview.stackexchange.com/questions/20871/single-instance-wpf-application

    第一步:添加System.RunTime.Remoting引用

    第二步:新建一个类class1.cs(按自己想法命名)

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Ipc;
    using System.Runtime.Serialization.Formatters;
    using System.Security;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Threading;
    
    namespace SingleWPF
    {
        internal enum WM
        {
            NULL = 0x0000,
            CREATE = 0x0001,
            DESTROY = 0x0002,
            MOVE = 0x0003,
            SIZE = 0x0005,
            ACTIVATE = 0x0006,
            SETFOCUS = 0x0007,
            KILLFOCUS = 0x0008,
            ENABLE = 0x000A,
            SETREDRAW = 0x000B,
            SETTEXT = 0x000C,
            GETTEXT = 0x000D,
            GETTEXTLENGTH = 0x000E,
            PAINT = 0x000F,
            CLOSE = 0x0010,
            QUERYENDSESSION = 0x0011,
            QUIT = 0x0012,
            QUERYOPEN = 0x0013,
            ERASEBKGND = 0x0014,
            SYSCOLORCHANGE = 0x0015,
            SHOWWINDOW = 0x0018,
            ACTIVATEAPP = 0x001C,
            SETCURSOR = 0x0020,
            MOUSEACTIVATE = 0x0021,
            CHILDACTIVATE = 0x0022,
            QUEUESYNC = 0x0023,
            GETMINMAXINFO = 0x0024,
    
            WINDOWPOSCHANGING = 0x0046,
            WINDOWPOSCHANGED = 0x0047,
    
            CONTEXTMENU = 0x007B,
            STYLECHANGING = 0x007C,
            STYLECHANGED = 0x007D,
            DISPLAYCHANGE = 0x007E,
            GETICON = 0x007F,
            SETICON = 0x0080,
            NCCREATE = 0x0081,
            NCDESTROY = 0x0082,
            NCCALCSIZE = 0x0083,
            NCHITTEST = 0x0084,
            NCPAINT = 0x0085,
            NCACTIVATE = 0x0086,
            GETDLGCODE = 0x0087,
            SYNCPAINT = 0x0088,
            NCMOUSEMOVE = 0x00A0,
            NCLBUTTONDOWN = 0x00A1,
            NCLBUTTONUP = 0x00A2,
            NCLBUTTONDBLCLK = 0x00A3,
            NCRBUTTONDOWN = 0x00A4,
            NCRBUTTONUP = 0x00A5,
            NCRBUTTONDBLCLK = 0x00A6,
            NCMBUTTONDOWN = 0x00A7,
            NCMBUTTONUP = 0x00A8,
            NCMBUTTONDBLCLK = 0x00A9,
    
            SYSKEYDOWN = 0x0104,
            SYSKEYUP = 0x0105,
            SYSCHAR = 0x0106,
            SYSDEADCHAR = 0x0107,
            COMMAND = 0x0111,
            SYSCOMMAND = 0x0112,
    
            MOUSEMOVE = 0x0200,
            LBUTTONDOWN = 0x0201,
            LBUTTONUP = 0x0202,
            LBUTTONDBLCLK = 0x0203,
            RBUTTONDOWN = 0x0204,
            RBUTTONUP = 0x0205,
            RBUTTONDBLCLK = 0x0206,
            MBUTTONDOWN = 0x0207,
            MBUTTONUP = 0x0208,
            MBUTTONDBLCLK = 0x0209,
            MOUSEWHEEL = 0x020A,
            XBUTTONDOWN = 0x020B,
            XBUTTONUP = 0x020C,
            XBUTTONDBLCLK = 0x020D,
            MOUSEHWHEEL = 0x020E,
    
    
            CAPTURECHANGED = 0x0215,
    
            ENTERSIZEMOVE = 0x0231,
            EXITSIZEMOVE = 0x0232,
    
            IME_SETCONTEXT = 0x0281,
            IME_NOTIFY = 0x0282,
            IME_CONTROL = 0x0283,
            IME_COMPOSITIONFULL = 0x0284,
            IME_SELECT = 0x0285,
            IME_CHAR = 0x0286,
            IME_REQUEST = 0x0288,
            IME_KEYDOWN = 0x0290,
            IME_KEYUP = 0x0291,
    
            NCMOUSELEAVE = 0x02A2,
    
            DWMCOMPOSITIONCHANGED = 0x031E,
            DWMNCRENDERINGCHANGED = 0x031F,
            DWMCOLORIZATIONCOLORCHANGED = 0x0320,
            DWMWINDOWMAXIMIZEDCHANGE = 0x0321,
    
            #region Windows 7
            DWMSENDICONICTHUMBNAIL = 0x0323,
            DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326,
            #endregion
    
            USER = 0x0400,
    
            // This is the hard-coded message value used by WinForms for Shell_NotifyIcon.
            // It's relatively safe to reuse.
            TRAYMOUSEMESSAGE = 0x800, //WM_USER + 1024
            APP = 0x8000,
        }
    
        [SuppressUnmanagedCodeSecurity]
        internal static class NativeMethods
        {
            /// <summary>
            /// Delegate declaration that matches WndProc signatures.
            /// </summary>
            public delegate IntPtr MessageHandler(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled);
    
            [DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
            private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
    
    
            [DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)]
            private static extern IntPtr _LocalFree(IntPtr hMem);
    
    
            public static string[] CommandLineToArgvW(string cmdLine)
            {
                IntPtr argv = IntPtr.Zero;
                try
                {
                    int numArgs = 0;
    
                    argv = _CommandLineToArgvW(cmdLine, out numArgs);
                    if (argv == IntPtr.Zero)
                    {
                        throw new Win32Exception();
                    }
                    var result = new string[numArgs];
    
                    for (int i = 0; i < numArgs; i++)
                    {
                        IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
                        result[i] = Marshal.PtrToStringUni(currArg);
                    }
    
                    return result;
                }
                finally
                {
    
                    IntPtr p = _LocalFree(argv);
                    // Otherwise LocalFree failed.
                    // Assert.AreEqual(IntPtr.Zero, p);
                }
            }
    
        }
    
        public interface ISingleInstanceApp
        {
            bool SignalExternalCommandLineArgs(IList<string> args);
        }
    
        /// <summary>
        /// This class checks to make sure that only one instance of 
        /// this application is running at a time.
        /// </summary>
        /// <remarks>
        /// Note: this class should be used with some caution, because it does no
        /// security checking. For example, if one instance of an app that uses this class
        /// is running as Administrator, any other instance, even if it is not
        /// running as Administrator, can activate it with command line arguments.
        /// For most apps, this will not be much of an issue.
        /// </remarks>
        public static class SingleInstance<TApplication>
                    where TApplication : Application, ISingleInstanceApp
    
        {
            #region Private Fields
    
            /// <summary>
            /// String delimiter used in channel names.
            /// </summary>
            private const string Delimiter = ":";
    
            /// <summary>
            /// Suffix to the channel name.
            /// </summary>
            private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
    
            /// <summary>
            /// Remote service name.
            /// </summary>
            private const string RemoteServiceName = "SingleInstanceApplicationService";
    
            /// <summary>
            /// IPC protocol used (string).
            /// </summary>
            private const string IpcProtocol = "ipc://";
    
            /// <summary>
            /// Application mutex.
            /// </summary>
            private static Mutex singleInstanceMutex;
    
            /// <summary>
            /// IPC channel for communications.
            /// </summary>
            private static IpcServerChannel channel;
    
            /// <summary>
            /// List of command line arguments for the application.
            /// </summary>
            private static IList<string> commandLineArgs;
    
            #endregion
    
            #region Public Properties
    
            /// <summary>
            /// Gets list of command line arguments for the application.
            /// </summary>
            public static IList<string> CommandLineArgs
            {
                get { return commandLineArgs; }
            }
    
            #endregion
    
            #region Public Methods
    
            /// <summary>
            /// Checks if the instance of the application attempting to start is the first instance. 
            /// If not, activates the first instance.
            /// </summary>
            /// <returns>True if this is the first instance of the application.</returns>
            public static bool InitializeAsFirstInstance(string uniqueName)
            {
                commandLineArgs = GetCommandLineArgs(uniqueName);
    
                // Build unique application Id and the IPC channel name.
                string applicationIdentifier = uniqueName + Environment.UserName;
    
                string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
    
                // Create mutex based on unique application Id to check if this is the first instance of the application. 
                bool firstInstance;
                singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
                if (firstInstance)
                {
                    CreateRemoteService(channelName);
                }
                else
                {
                    SignalFirstInstance(channelName, commandLineArgs);
                }
    
                return firstInstance;
            }
    
            /// <summary>
            /// Cleans up single-instance code, clearing shared resources, mutexes, etc.
            /// </summary>
            public static void Cleanup()
            {
                if (singleInstanceMutex != null)
                {
                    singleInstanceMutex.Close();
                    singleInstanceMutex = null;
                }
    
                if (channel != null)
                {
                    ChannelServices.UnregisterChannel(channel);
                    channel = null;
                }
            }
    
            #endregion
    
            #region Private Methods
    
            /// <summary>
            /// Gets command line args - for ClickOnce deployed applications, command line args may not be passed directly, they have to be retrieved.
            /// </summary>
            /// <returns>List of command line arg strings.</returns>
            private static IList<string> GetCommandLineArgs(string uniqueApplicationName)
            {
                string[] args = null;
                if (AppDomain.CurrentDomain.ActivationContext == null)
                {
                    // The application was not clickonce deployed, get args from standard API's
                    args = Environment.GetCommandLineArgs();
                }
                else
                {
                    // The application was clickonce deployed
                    // Clickonce deployed apps cannot recieve traditional commandline arguments
                    // As a workaround commandline arguments can be written to a shared location before 
                    // the app is launched and the app can obtain its commandline arguments from the 
                    // shared location               
                    string appFolderPath = Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), uniqueApplicationName);
    
                    string cmdLinePath = Path.Combine(appFolderPath, "cmdline.txt");
                    if (File.Exists(cmdLinePath))
                    {
                        try
                        {
                            using (TextReader reader = new StreamReader(cmdLinePath, System.Text.Encoding.Unicode))
                            {
                                args = NativeMethods.CommandLineToArgvW(reader.ReadToEnd());
                            }
    
                            File.Delete(cmdLinePath);
                        }
                        catch (IOException)
                        {
                        }
                    }
                }
    
                if (args == null)
                {
                    args = new string[] { };
                }
    
                return new List<string>(args);
            }
    
            /// <summary>
            /// Creates a remote service for communication.
            /// </summary>
            /// <param name="channelName">Application's IPC channel name.</param>
            private static void CreateRemoteService(string channelName)
            {
                BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
                serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
                IDictionary props = new Dictionary<string, string>();
    
                props["name"] = channelName;
                props["portName"] = channelName;
                props["exclusiveAddressUse"] = "false";
    
                // Create the IPC Server channel with the channel properties
                channel = new IpcServerChannel(props, serverProvider);
    
                // Register the channel with the channel services
                ChannelServices.RegisterChannel(channel, true);
    
                // Expose the remote service with the REMOTE_SERVICE_NAME
                IPCRemoteService remoteService = new IPCRemoteService();
                RemotingServices.Marshal(remoteService, RemoteServiceName);
            }
    
            /// <summary>
            /// Creates a client channel and obtains a reference to the remoting service exposed by the server - 
            /// in this case, the remoting service exposed by the first instance. Calls a function of the remoting service 
            /// class to pass on command line arguments from the second instance to the first and cause it to activate itself.
            /// </summary>
            /// <param name="channelName">Application's IPC channel name.</param>
            /// <param name="args">
            /// Command line arguments for the second instance, passed to the first instance to take appropriate action.
            /// </param>
            private static void SignalFirstInstance(string channelName, IList<string> args)
            {
                IpcClientChannel secondInstanceChannel = new IpcClientChannel();
                ChannelServices.RegisterChannel(secondInstanceChannel, true);
    
                string remotingServiceUrl = IpcProtocol + channelName + "/" + RemoteServiceName;
    
                // Obtain a reference to the remoting service exposed by the server i.e the first instance of the application
                IPCRemoteService firstInstanceRemoteServiceReference = (IPCRemoteService)RemotingServices.Connect(typeof(IPCRemoteService), remotingServiceUrl);
    
                // Check that the remote service exists, in some cases the first instance may not yet have created one, in which case
                // the second instance should just exit
                if (firstInstanceRemoteServiceReference != null)
                {
                    // Invoke a method of the remote service exposed by the first instance passing on the command line
                    // arguments and causing the first instance to activate itself
                    firstInstanceRemoteServiceReference.InvokeFirstInstance(args);
                }
            }
    
            /// <summary>
            /// Callback for activating first instance of the application.
            /// </summary>
            /// <param name="arg">Callback argument.</param>
            /// <returns>Always null.</returns>
            private static object ActivateFirstInstanceCallback(object arg)
            {
                // Get command line args to be passed to first instance
                IList<string> args = arg as IList<string>;
                ActivateFirstInstance(args);
                return null;
            }
    
            /// <summary>
            /// Activates the first instance of the application with arguments from a second instance.
            /// </summary>
            /// <param name="args">List of arguments to supply the first instance of the application.</param>
            private static void ActivateFirstInstance(IList<string> args)
            {
                // Set main window state and process command line args
                if (Application.Current == null)
                {
                    return;
                }
    
                ((TApplication)Application.Current).SignalExternalCommandLineArgs(args);
            }
    
            #endregion
    
            #region Private Classes
    
            /// <summary>
            /// Remoting service class which is exposed by the server i.e the first instance and called by the second instance
            /// to pass on the command line arguments to the first instance and cause it to activate itself.
            /// </summary>
            private class IPCRemoteService : MarshalByRefObject
            {
                /// <summary>
                /// Activates the first instance of the application.
                /// </summary>
                /// <param name="args">List of arguments to pass to the first instance.</param>
                public void InvokeFirstInstance(IList<string> args)
                {
                    if (Application.Current != null)
                    {
                        // Do an asynchronous call to ActivateFirstInstance function
                        Application.Current.Dispatcher.BeginInvoke(
                            DispatcherPriority.Normal, new DispatcherOperationCallback(SingleInstance<TApplication>.ActivateFirstInstanceCallback), args);
                    }
                }
    
                /// <summary>
                /// Remoting Object's ease expires after every 5 minutes by default. We need to override the InitializeLifetimeService class
                /// to ensure that lease never expires.
                /// </summary>
                /// <returns>Always null.</returns>
                public override object InitializeLifetimeService()
                {
                    return null;
                }
            }
    
            #endregion
        }
    }

    第三步:在App.xml.cs中操作:

    1:实现接口:ISingleInstanceApp

    2:添加以下代码

     // 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)
            {
                // Bring window to foreground
                if (this.MainWindow.WindowState == WindowState.Minimized)
                {
                    this.MainWindow.WindowState = WindowState.Normal;
                }
    
                this.MainWindow.Activate();
                return true;
            }
            #endregion

    注:如果只是打开一个窗口没有其他的操作,实现

    SignalExternalCommandLineArgs方法为:
    public bool SignalExternalCommandLineArgs(IList<string> args)
            {
                
                return true;
            }

    如果是打开一个窗口,重复打开置顶窗口,实现方法为:

    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;
            }

    第四步:将App.xaml的生成方式修改为Page

    第五步:将项目的属性->应用程序->启动对象,选择为自己项目名称.App

  • 相关阅读:
    编程路上有你们陪着值了
    我是屌丝程序猿,我为自己代言.
    JSON转换类
    深入理解requestAnimationFrame
    CentOS部署yapi
    前端三种路由方式
    yarn安装使用
    三大框架对比
    es6异步编程
    JS原型链
  • 原文地址:https://www.cnblogs.com/joyandjoys/p/10253670.html
Copyright © 2020-2023  润新知