Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Single instance WinUI desktop apps #4780

Open
JaiganeshKumaran opened this issue Apr 10, 2021 · 4 comments
Open

Proposal: Single instance WinUI desktop apps #4780

JaiganeshKumaran opened this issue Apr 10, 2021 · 4 comments
Labels
appModel-win32 Exclusive to WinUI 3 Win32 Desktop apps area-AppWindow feature proposal New feature proposal product-winui3 WinUI 3 issues team-Reach Issue for the Reach team

Comments

@JaiganeshKumaran
Copy link
Contributor

JaiganeshKumaran commented Apr 10, 2021

Proposal: Single instance WinUI desktop apps

Unlike UWP apps which are single instance and for multiple instances need to be enabled manually, Win32 apps are multi instance by default. While this is fine for most apps, some apps particularly need single instancing and today WinUI desktop doesn't provide a way to do that easily.

Summary

Add a property named SingleInstance in the App class. If it's true then new instances of the process will bring the existing window on to the foreground instead of showing a message that existing instance is running like some old desktop apps and not bother to switch and the newly created process should be terminated. One problem here is if the same instance has multiple windows open, you need rightly open the main window and not a secondary window created by the app. A shared mutex could be used to know whether an existing instance is there or not however there's no way to know which window to switch in case of multiple windows unless the developer does it manually themselves as WinUI doesn't know.

Rationale

  • More flexibility for developers
  • Brings WinUI desktop on par with UWP XAML

Scope

Capability Priority
This proposal will allow developers to make their WinUI desktop single instance Must

Important Notes

I added this code under App's constructor in my app to make it single instance, but it probably won't work with apps that have multiple windows in the same instance.

auto existingWindow = FindWindow(L"WinUIDesktopWin32WindowClass", L"Develop");
if (existingWindow != nullptr)
{
    ShowWindow(existingWindow, SW_RESTORE);
    SetForegroundWindow(existingWindow);
    ExitProcess(0);
}
@JaiganeshKumaran JaiganeshKumaran added the feature proposal New feature proposal label Apr 10, 2021
@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Apr 10, 2021
@JaiganeshKumaran
Copy link
Contributor Author

See also microsoft/WindowsAppSDK#111

@JaiganeshKumaran
Copy link
Contributor Author

JaiganeshKumaran commented Apr 11, 2021

The problem with this is that the main process doesn't know it's window has been activated through a new process. It should be possible but then you can't use the same OnLaunched method and instead a custom one. You can't use SendMessage for example since WinUI doesn't provide a way to let us handle the window messages.

@ghost ghost removed the needs-triage Issue needs to be triaged by the area owners label Apr 11, 2021
@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Apr 11, 2021
@StephenLPeters StephenLPeters added area-AppWindow team-Reach Issue for the Reach team product-winui3 WinUI 3 issues appModel-win32 Exclusive to WinUI 3 Win32 Desktop apps labels Apr 12, 2021
@bpulliam bpulliam removed the needs-triage Issue needs to be triaged by the area owners label Oct 7, 2021
@sjb-sjb
Copy link

sjb-sjb commented Nov 27, 2023

For those of you who land here looking for how to single-instance your WinUI 3 app, here is what I put together after cobbling together bits and pieces from other posts.

public static class ApplicationInstancing
{
    #region TryRedirection

    /// <summary>
    /// If there is already an instance registered with the given key, then redirect 
    /// execution to that instance and return true. 
    /// Otherwise, register this instance with the key and return false. 
    /// If a redirection occurs, then <c>Application.Current</c> is searched for an 
    /// method <c>void OnRedirection( AppActivationArguments e)</c>, and if found then 
    /// it will be called. The args used will be the arguments from this instance's activation.
    /// Typically, <c>OnRedirection</c> should activate the window and set it as foreground. 
    /// </summary>
    ///
    /// <remarks>
    /// In order to achieve single-instancing of your WinUI 3 app, 
    /// define DISABLE_XAML_GENERATED_MAIN in your application's 
    /// Project > Properties > Build > General > Conditional 
    /// compilation symbols. Then create a class in your app as follows: 
    /// <code>
    /// static class Program { 
    ///     [STAThread]
    ///     static void Main(string[] args) { 
    ///         string key = ...;
    ///         if (ApplicationInstancing.TryRedirection( key)) { return; }
    ///         ApplicationInstancing.Main&lt;App&gt;();
    ///     }
    /// }
    /// </code>
    /// 
    /// Define the <c>key</c> to be the string that uniquely identifies the 
    /// running instance of your app to redirect to. For example, this could 
    /// simply be the name of the application. Or it could include a file 
    /// name if you want to have one instance per filename, for example. 
    /// 
    /// Here, <c>App</c> is the <c>Application</c>-derived class that defines 
    /// your application.
    /// It must have a constructor that takes no arguments. 
    /// 
    /// Note, one cannot declare Main to be async, because in WinUI this prevents
    /// Narrator from reading XAML elements. 
    /// </remarks>
    /// <param name="key">The string that uniquely identifies the instance.</param>
    /// <returns></returns>
    public static bool TryRedirection( string key)
    {
        AppInstance registeredInstance = AppInstance.FindOrRegisterForKey( key);
        if (registeredInstance.IsCurrent) {
            registeredInstance.Activated += RegisteredInstance_Activated;
            return false;
        } else { 
            AppActivationArguments activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs(); // was: registerdInstance.GetActivatedEventArgs()
            var redirectSemaphore = new Semaphore(0, 1);
            Action redirectAndRelease = () => {
                registeredInstance.RedirectActivationToAsync(activationArgs).AsTask().Wait();
                redirectSemaphore.Release();
            };
            Task.Run(redirectAndRelease); // on thread pool.
            redirectSemaphore.WaitOne();
            return true;
        }
    }

    private static void RegisteredInstance_Activated(object? sender, AppActivationArguments e)
    {
        dynamic app = Application.Current;
        try {
            app.OnRedirection(e);
        } catch (Exception) {
        }
    }

    #endregion

    #region Main

    [global::System.Runtime.InteropServices.DllImport("Microsoft.ui.xaml.dll")]
    private static extern void XamlCheckProcessRequirements();

    /// <summary>
    /// The main function  of a WinUI application. 
    /// 
    /// This does the same thing as the <c>Main( string[] args)</c> method 
    /// generated by Visual Studio for a WinUI 3 application. 
    /// In order to suppress the automatic generation by Visual Studio,
    /// define DISABLE_XAML_GENERATED_MAIN in your application's 
    /// Project > Properties > Build > General > Conditional 
    /// compilation symbols.      
    /// </summary>
    public static void Main<TApp>() where TApp: Application, new()
    {
        XamlCheckProcessRequirements();
        ComWrappersSupport.InitializeComWrappers();
        ApplicationInitializationCallback initializer = (p) => {
            var context = new DispatcherQueueSynchronizationContext( DispatcherQueue.GetForCurrentThread());
            SynchronizationContext.SetSynchronizationContext(context);
            new TApp();
        };
        Application.Start(initializer);
    }
}

#endregion

A related task is raising the window of the currently-running instance of your app so that it is on top of other windows. Just calling Window.Activate will not do it, as discussed in #7595 . Here's how I did it. This method must be called on the UI thread.

    static internal void ActivateAndSetAsForeground(this Window window)
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);

        window.Activate();
        bool didSet = SetForegroundWindow(hWnd);
        Debug.Assert(didSet);
    }

@sjb-sjb
Copy link

sjb-sjb commented Nov 28, 2023

Comment, about the question of which window to bring to the top, this could be addressed simply by adding a virtual OnRedirection method to Application which does nothing and which the app developer can override to brong to top whichever window they desire.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
appModel-win32 Exclusive to WinUI 3 Win32 Desktop apps area-AppWindow feature proposal New feature proposal product-winui3 WinUI 3 issues team-Reach Issue for the Reach team
Projects
None yet
Development

No branches or pull requests

4 participants