Tuesday 19 February 2013

Handling shortcuts in WPF application

This is a sketchy post, to be completed or discarded later on. The objectives was having inheritance and minimizing the instantiation code.

This is the base class.


public class KeyShortcutBase
{
    private readonly RoutedCommand _command;

    private readonly CommandBinding _commandBinding;

    protected KeyShortcutBase()
    {
        this._command = new RoutedCommand();

        this._commandBinding = new CommandBinding(
            this._command,
            OnExecuted,
            OnCanExecute);
    }

    public void ConnectShortcut(
        UIElement host, InputGesture gesture)
    {
        this.ConnectShortcut(host, new[] { gesture });
    }

    public void ConnectShortcut(
        UIElement host, IEnumerable<InputGesture> gestures)
    {
        foreach (var gesture in gestures)
        {
            this._command.InputGestures.Add(gesture);
        }

        if (host != null)
        {
            host.CommandBindings.Add(this._commandBinding);
        }
    }

    protected virtual void OnCanExecute(
        object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = false;
    }

    protected virtual void OnExecuted(
        object sender, ExecutedRoutedEventArgs e)
    {
    }
}

This is an extension class allowing shorter calls.

public static class KeyShortcutExtender
{
    public static void ConnectShortcut(
        this UIElement host, 
        KeyShortcutBase shortcut, 
        IEnumerable<InputGesture> gestures)
    {
        shortcut.ConnectShortcut(host, gestures);
    }

    public static void ConnectShortcut(
        this UIElement host, 
        KeyShortcutBase shortcut, 
        InputGesture gesture)
    {
        shortcut.ConnectShortcut(host, gesture);
    }
}

This is a subclass.

public class FindItemShortcut : KeyShortcutBase
{
    protected override void OnCanExecute(
        object sender,
        CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
    }

    protected override void OnExecuted(
        object sender,
        ExecutedRoutedEventArgs e)
    {
        MessageBox.Show(this.GetType().ToString());
    }
}

The subclass is instantiated in WPF Window with a single shortcut.

this.ConnectShortcut(
    new FindItemShortcut(), 
    new KeyGesture(Key.F, (ModifierKeys)6));

... and with multiple shortcuts.

this.ConnectShortcut(
    new FindItemShortcut(), 
    new InputGesture[]
        {
            new KeyGesture(Key.F, (ModifierKeys)6), 
            new KeyGesture(Key.S, ModifierKeys.Control), 
            new MouseGesture(MouseAction.RightDoubleClick)
        });

So far so good...


Tuesday 12 February 2013

Allowing only single instance of WPF application per login session

The intention is preventing user from starting multiple instances of the same application. Repeated run should activate (restore) the application's main window.

Having this task already resolved for Visual FoxPro through using semaphores, I chose same approach for WPF. Using direct calls to Semaphore API functions in .NET makes rather little sense, since the Semaphore Class provides all required functionality.

First step is removing StartupUri in App.xaml and overriding Application.OnStartup method. This allows instantiating application's main window only after checking that no other instances are running.


namespace WpfApplicationRunOnce
{
    using System.Windows;

    public partial class App
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            if (!AppLauncher.RunApp())
            {
                this.Shutdown(0);
            }
        }
    }
}

Static AppLauncher class either creates instance of the main window or activates the one that already exists.

namespace WpfApplicationRunOnce
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Threading;

    public static class AppLauncher
    {
        private const string ApplicationId = "MyAppUniqueId";

        private static MainWindow mainWindow;

        internal enum RegisterResult
        {
            Success,
            AlreadyRunning,
            Failed
        }

        private enum ShowWindowMode
        {
            Maximize = 3,
            Restore = 9
        }

        private enum WindowStyles
        {
            Maximize = 0x01000000
        }

        public static bool RunApp()
        {
            var result = RegisterApp(ApplicationId);

            switch (result)
            {
                case RegisterResult.Success:
                    mainWindow = new MainWindow();
                    mainWindow.Show();
                    return true;

                case RegisterResult.AlreadyRunning:
                    ActivateAppMainWindow();
                    break;

                case RegisterResult.Failed:
                    break;
            }

            return false;
        }

        private static RegisterResult RegisterApp(string appId)
        {
            try
            {
                Semaphore.OpenExisting(appId);
                return RegisterResult.AlreadyRunning;
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                try
                {
                    var semaphore = new Semaphore(1, 1, appId);
                    return RegisterResult.Success;
                }
                catch
                {
                    return RegisterResult.Failed;
                }
            }
        }

        private static void ActivateAppMainWindow()
        {
            var current = Process.GetCurrentProcess();

            var running = Process.GetProcesses()
                .FirstOrDefault(p => p.Id != current.Id
                    && p.ProcessName.Equals(
                        current.ProcessName,
                        StringComparison.CurrentCultureIgnoreCase));

            if (running == null)
            {
                return;
            }

            var handle = running.MainWindowHandle;
            var windowStyle = GetWindowLong(handle, -16);

            ShowWindow(
                handle,
                (windowStyle & (long)WindowStyles.Maximize) != 0
                ? ShowWindowMode.Maximize : ShowWindowMode.Restore);

            SetForegroundWindow(handle);
        }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern int GetWindowLong(
            IntPtr hWnd,
            int nIndex);

        [DllImport("user32.dll")]
        private static extern int SetForegroundWindow(
            IntPtr window);

        [DllImport("user32.dll")]
        private static extern int ShowWindow(
            IntPtr window,
            ShowWindowMode showMode);
    }
}

In the code above, replace ApplicationId value with something less generic and more descriptive for your application. This value must be sufficiently unique to be used as the name for a semaphore.

The RegisterApp method attempts to open existing named semaphore. The success indicates that an instance of the application is already running. Otherwise (WaitHandleCannotBeOpenedException), the named semaphore is created.

The ActivateAppMainWindow method uses the name of the current process to find its already running instance.  The main window of which gets activated -- either maximized or restored to its original size and position.

There are at least three possible scenarios this approach covers:

  1. single executable file
  2. two copies of the executable file having same names
  3. two copies of the executable file having different names

The executable files in cases 2) and 3) do not need to be byte to byte identical. What matters is the ApplicationId value shared by the two.

The 3) case behaves differently. While multiple instances are still prevented, the running instance's main window does not get activated since the executable files in this case have different names.

* * *
While adding this functionality to WPF PRISM application, I had to make small changes to the implementation. The testing of the named semaphore existence was moved from the RegisterApp method to a separate method called TestInstance.


internal static RegisterResult TestInstance(string appId)
{
    try
    {
        Semaphore.OpenExisting(appId, SemaphoreRights.Synchronize);
        return RegisterResult.AlreadyRunning;
    }
    catch (WaitHandleCannotBeOpenedException ex)
    {
        return RegisterResult.NotDetected;
    }
}

internal static RegisterResult RegisterApp(string appId)
{
    try
    {
        var semaphore = new Semaphore(1, 1, appId);
        return RegisterResult.Success;
    }
    catch
    {
        return RegisterResult.Failed;
    }
}

The OnStartup method in App.xaml.cs was changed as follows.

protected override void OnStartup(StartupEventArgs e)
{
    var applicationId = Assembly.GetExecutingAssembly().FullName;

    if (AppLauncher.TestInstance(applicationId) == AppLauncher.RegisterResult.AlreadyRunning)
    {
        AppLauncher.ActivateAppMainWindow();
        this.Shutdown(0);
    }

    base.OnStartup(e);

    var bootstrapper = new Bootstrapper();
    bootstrapper.Run();

    AppLauncher.RegisterApp(applicationId);
}

The named semaphore must be created only after PRISM bootstrapper runs (starts the main process). Otherwise the semaphore has a very short life.