如何通过URI激活打开Winui Maui?

发布于 2025-02-07 00:15:47 字数 1607 浏览 3 评论 0原文

我创建了一个毛伊岛Windows应用程序。我希望通过URI激活应用程序,然后将查询参数传递给应用程序。

我添加了Windows协议,用于通过包装中的URI调用该应用程序:

  <Extensions>
            <uap:Extension Category="windows.protocol">
              <uap:Protocol Name="my-app">
                <uap:DisplayName>My App</uap:DisplayName>
              </uap:Protocol>
            </uap:Extension>
      </Extensions>

当我通过浏览器my-app://foo.com?user = 123456激活应用程序时,应用程序启动,但它会在冷启动下启动。在我的Win UI应用程序中,我已经覆盖了OnLaunched方法,但是无论我如何启动该应用程序,我都无法访问该协议。我正在尝试从UWP应用程序中重新创建以下代码:

protected override void OnActivated(IActivatedEventArgs args)
    {
        if (args.Kind == ActivationKind.Protocol)
        {

            ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
            var queryStr = eventArgs.Uri.Query;
            App.UserId = System.Web.HttpUtility.ParseQueryString(queryStr).Get("user");

            // Navigate to a view
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                Xamarin.Forms.Forms.Init(args);
                Window.Current.Content = rootFrame;
            }

            rootFrame.Navigate(typeof(MainPage), eventArgs);
        }

        Window.Current.Activate();
    }

因此,我从此开始,但是UwplaunchactivatedEventarg一直以启动而不是协议返回。

    protected override void OnLaunched(LaunchActivatedEventArgs args)
{
  var kind =   args.UWPLaunchActivatedEventArgs.Kind;
        base.OnLaunched(e)
}

I've created a Maui windows application. I'm looking to activate the application through a URI and pass query parameters to the app.

I've added the windows protocol for calling the app via uri in the package manifest:

  <Extensions>
            <uap:Extension Category="windows.protocol">
              <uap:Protocol Name="my-app">
                <uap:DisplayName>My App</uap:DisplayName>
              </uap:Protocol>
            </uap:Extension>
      </Extensions>

When I activate the application via the browser my-app://foo.com?user=123456 the app launches, but it launches as a cold start. Within my Win UI app I've overrode the onLaunched method, but regardless of how I've launched the app I cannot get access to the protocol. I'm trying to recreate the following code from my UWP Application:

protected override void OnActivated(IActivatedEventArgs args)
    {
        if (args.Kind == ActivationKind.Protocol)
        {

            ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
            var queryStr = eventArgs.Uri.Query;
            App.UserId = System.Web.HttpUtility.ParseQueryString(queryStr).Get("user");

            // Navigate to a view
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
            {
                rootFrame = new Frame();
                Xamarin.Forms.Forms.Init(args);
                Window.Current.Content = rootFrame;
            }

            rootFrame.Navigate(typeof(MainPage), eventArgs);
        }

        Window.Current.Activate();
    }

So I've started with this, but UWPLaunchActivatedEventArg is consistently being returned as Launch instead of protocol.

    protected override void OnLaunched(LaunchActivatedEventArgs args)
{
  var kind =   args.UWPLaunchActivatedEventArgs.Kind;
        base.OnLaunched(e)
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

诗笺 2025-02-14 00:15:47

这是我的解决方案:

var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();

使用参数运行URI,这是屏幕截图:

Here is my solution:

var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();

run your URI with parameters and here is the screenshot:
enter image description here

烟燃烟灭 2025-02-14 00:15:47

我花了一段时间才能理解其他答案的含义 - 要“铸造”对象。

var actEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if ( actEventArgs.Kind == ExtendedActivationKind.Protocol) {
    var d = actEventArgs.Data as IProtocolActivatedEventArgs;
    if (d != null) {
        var uri = d.Uri;
        var uriString = uri.AbsoluteUri;
        // do something...
    }
}

这给我带来了URI字符串。但是 - 我得到了一个新窗口。我想将URI派遣到已经创建的窗口(如果存在,否则可以创建)。

我已经通过捕获狂热的事件与Xamarin.forms UWP完成了。还没有想到毛伊岛。
鲍勃

It took me a while to understand what the other answer meant -- got to "cast" the object.

var actEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
if ( actEventArgs.Kind == ExtendedActivationKind.Protocol) {
    var d = actEventArgs.Data as IProtocolActivatedEventArgs;
    if (d != null) {
        var uri = d.Uri;
        var uriString = uri.AbsoluteUri;
        // do something...
    }
}

This gets me the URI string. BUT -- I get a new window created. I want to dispatch the URI to an already created window (if it exists, otherwise create).

I've done with with Xamarin.Forms UWP, by catching the OnActivated event. Haven't got that figured for MAUI yet.
Bob

丢了幸福的猪 2025-02-14 00:15:47
var args = Environment.GetCommandLineArgs();
var args = Environment.GetCommandLineArgs();
哀由 2025-02-14 00:15:47

好的,最后我找到了一个有用的文档来解决这个问题。我遇到了这些问题:

  • 我无法通过协议进行任何激活或肿瘤的事件调用。

  • 改为打开一个新窗口。

  • 旧实例(例如,登录窗口在等待)未收到任何信号,表明my-app:// login/result = xyz已被调用。

我发现应用程序生命周期功能迁移,我建议阅读它,至少我链接的部分,它将帮助您了解问题以及为什么我以这种方式解决它。请注意,尽管链接到单个实例应用程序,但我只使用它来解决问题,而解决方案并不迫使您的应用程序成为单一结构。

总而言之,Windows App SDK的流量(基于Winui3基于Winui3,又基于MAUI)更改了:

UWPWindows App SDK1。Appapplains
instance1 1。App启动(<强>实例1 )
2。用户打开my-app:// url2。用户打开my-app:// url
3。 instance11 带有协议

3。Applaight( instance2 )的接收 在Windows App SDK中,您以前的 实例1 实例没有激活信号。

要解决这个问题,我们使用 redirectactivation toasync

  1. onlaunch onlaunch中,是否in是 procest 。这只会发生在 实例2 (URI启动的实例)中。
  2. 如果是这样,则将这些参数重定向到所有其他实例(如果您拥有多个实体应用程序)或您可能想要的任何自定义逻辑。
    • 对于毛伊岛,我们也不调用base.onlaunched在此方法中,因此没有创建新窗口。
  3. 相反,如果这不是协议的启动,请登记激活事件,以收听由instance2转发的协议事件。 注意毛伊岛:您还必须为毛伊岛这样做。由于某些原因,anc活化生命周期未在此处调用。

这是解决方案,它适用于MAUI,但我相信它也适用于Winui3和Windows App SDK:

更新:对于毛伊岛,如果您不想触摸app.xaml.cs ,您可以使用builder.configurelifecycleevents

// In your MauiProgram.cs or write it in a Windows platform-specific code:

builder.ConfigureLifecycleEvents(lc =>
{
    lc.AddWindows(static win =>
    {
        win.OnLaunched(static async (app, args) =>
        {
            var appInstance = AppInstance.GetCurrent();
            var e = appInstance.GetActivatedEventArgs();

            if (e.Kind != ExtendedActivationKind.Protocol
                || e.Data is not ProtocolActivatedEventArgs protocol)
            {
                appInstance.Activated += AppInstance_Activated;
                return;
            }

            var instances = AppInstance.GetInstances();
            await Task.WhenAll(instances.Select(async q => await q.RedirectActivationToAsync(e)));

            app.Exit();
        });
    });
});


private static void AppInstance_Activated(object? sender, AppActivationArguments e)
{
    if (e.Kind != ExtendedActivationKind.Protocol ||
        e.Data is not ProtocolActivatedEventArgs protocol)
    {
        return;
    }

    // Process your activation here
    Debug.WriteLine("URI activated: " + protocol.Uri);
}

修改app.xaml.cs的旧代码:

// In your App.xaml.cs

public partial class App : MauiWinUIApplication
{
    // ...

    protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        var appInstance = AppInstance.GetCurrent();
        var e = appInstance.GetActivatedEventArgs();

        // If it's not a Protocol activation, just launch the app normally
        if (e.Kind != ExtendedActivationKind.Protocol ||
            e.Data is not ProtocolActivatedEventArgs protocol)
        {
            appInstance.Activated += AppInstance_Activated;

            base.OnLaunched(args);
            return;
        }

        // If it's a Protocol activation, redirect it to other instances
        var instances = AppInstance.GetInstances();
        await Task.WhenAll(instances
            .Select(async q => await q.RedirectActivationToAsync(e)));

        return;
    }

    private void AppInstance_Activated(object? sender, AppActivationArguments e)
    {
        if (e.Kind != ExtendedActivationKind.Protocol ||
            e.Data is not ProtocolActivatedEventArgs protocol)        
        {
            return;
        }

        // Process your activation here
        Debug.WriteLine("URI activated: " + protocol.Uri);
    }

}

Okay finally I found a useful doc to solve this problem. I faced these issues:

  • I couldn't get any Activated or OnActivated event call with the Protocol.

  • A new window is opened instead.

  • The old instance (where the login window is waiting for example) does not receive any signals that a URI like my-app://login/result=xyz has been called.

I found out Application lifecycle functionality migration, I recommend reading it, at least the section I linked, it will help you understand the issue and why I solved it this way. Note that despite linking to a Single Instance Apps, I only use it to solve the issue and the solution does not force your app to become a single-instance.

To summarize the article, the flow of Windows App SDK (which WinUI3 based on, which in turn MAUI based on) changed:

UWPWindows App SDK
1. App Launch (Instance1)1. App Launch (Instance1)
2. User opens my-app://url2. User opens my-app://url
3. Instance1 receives OnActivated with Protocol3. App Launch (Instance2) with Protocol

As you can see, in Windows App SDK, no Activated signal is given to your former Instance1 instance.

To fix that, we use RedirectActivationToAsync:

  1. In OnLaunch, check if the Kind is Protocol. This would only happen to Instance2 (the instances that are launched by URI).
  2. If it is, redirect those parameters to all other instances (in case you have multi-instance apps), or any custom logic you may want.
    • For MAUI, we also do not call base.OnLaunched in this method so no new window is created.
  3. Instead, if it's not a launch by Protocol, register Activated event to listen to Protocol event forwarded by Instance2. NOTE for MAUI: you also have to do this for MAUI. For some reason the OnActivated lifecycle is not called here.

Here's the solution, it's for MAUI but I believe it applies to WinUI3 and Windows App SDK as well:

Update: for MAUI, if you do not want to touch App.xaml.cs, you can use the builder.ConfigureLifecycleEvents:

// In your MauiProgram.cs or write it in a Windows platform-specific code:

builder.ConfigureLifecycleEvents(lc =>
{
    lc.AddWindows(static win =>
    {
        win.OnLaunched(static async (app, args) =>
        {
            var appInstance = AppInstance.GetCurrent();
            var e = appInstance.GetActivatedEventArgs();

            if (e.Kind != ExtendedActivationKind.Protocol
                || e.Data is not ProtocolActivatedEventArgs protocol)
            {
                appInstance.Activated += AppInstance_Activated;
                return;
            }

            var instances = AppInstance.GetInstances();
            await Task.WhenAll(instances.Select(async q => await q.RedirectActivationToAsync(e)));

            app.Exit();
        });
    });
});


private static void AppInstance_Activated(object? sender, AppActivationArguments e)
{
    if (e.Kind != ExtendedActivationKind.Protocol ||
        e.Data is not ProtocolActivatedEventArgs protocol)
    {
        return;
    }

    // Process your activation here
    Debug.WriteLine("URI activated: " + protocol.Uri);
}

Old code that modify App.xaml.cs:

// In your App.xaml.cs

public partial class App : MauiWinUIApplication
{
    // ...

    protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        var appInstance = AppInstance.GetCurrent();
        var e = appInstance.GetActivatedEventArgs();

        // If it's not a Protocol activation, just launch the app normally
        if (e.Kind != ExtendedActivationKind.Protocol ||
            e.Data is not ProtocolActivatedEventArgs protocol)
        {
            appInstance.Activated += AppInstance_Activated;

            base.OnLaunched(args);
            return;
        }

        // If it's a Protocol activation, redirect it to other instances
        var instances = AppInstance.GetInstances();
        await Task.WhenAll(instances
            .Select(async q => await q.RedirectActivationToAsync(e)));

        return;
    }

    private void AppInstance_Activated(object? sender, AppActivationArguments e)
    {
        if (e.Kind != ExtendedActivationKind.Protocol ||
            e.Data is not ProtocolActivatedEventArgs protocol)        
        {
            return;
        }

        // Process your activation here
        Debug.WriteLine("URI activated: " + protocol.Uri);
    }

}
狼亦尘 2025-02-14 00:15:47

我添加了这个新的,更完整的答案,尽管我还没有假装完全理解这一点,而且我并没有做出很好的工作跟踪我从哪里获得的示例代码。而且,我应该几个月前回到这里。

请注意,将我的logp()方法写入控制台,而mainpage.inst.setopeningstring()则通过URI。

Windows/program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Threading;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.AppLifecycle;
using Windows.ApplicationModel.Activation;
using Windows.Storage;
using System.Runtime.InteropServices;

using static OpenerAppMaui.Util;
namespace OpenerAppMaui.WinUI;

class Program {
    [STAThread]
    static void Main(string[] args) {
        logp($"Program.Main({args.Length} args)");
        WinRT.ComWrappersSupport.InitializeComWrappers();
        bool isRedirect = DecideRedirection();
        if (!isRedirect) {
            logp("Program.Main() NOT isRedir");
            Microsoft.UI.Xaml.Application.Start((p) =>
            {
                var context = new DispatcherQueueSynchronizationContext(
                    DispatcherQueue.GetForCurrentThread());
                SynchronizationContext.SetSynchronizationContext(context);

                // Important: has to go to the WINDOW APP "App" class (not the generic one)
                new App();
                logp("Program.Main() after new App()");
            });
        } else {
            logp("Program.Main() IS isRedir (should get handled with OnActivated() below)");
        }
    }

    private static bool DecideRedirection() {
        bool isRedirect = false;

        AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
        ExtendedActivationKind kind = args.Kind;
        logp($"Program.DecideRedirection() kind={kind}, ");
        if (args.Kind == ExtendedActivationKind.Protocol) {
            ProtocolActivatedEventArgs dataProtActEvArgs = args.Data as ProtocolActivatedEventArgs;
            logp($"Program.DecideRedirection()) data= {dataProtActEvArgs.Uri}");
        }
        try {
            AppInstance keyInstance = AppInstance.FindOrRegisterForKey("randomKey");

            if (keyInstance.IsCurrent) {
                keyInstance.Activated += OnActivated;
            } else {
                isRedirect = true;
                logp($"Program.DecideRedirection() Redirect to keyInstance {keyInstance}");
                RedirectActivationTo(args, keyInstance);
            }
        } catch (Exception ex) {
            logp($"Program.DecideRedirection() Exception, ex={ex.Message}");
        }

        return isRedirect;
    }


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateEvent(
        IntPtr lpEventAttributes, bool bManualReset,
        bool bInitialState, string lpName);

    [DllImport("kernel32.dll")]
    private static extern bool SetEvent(IntPtr hEvent);

    [DllImport("ole32.dll")]
    private static extern uint CoWaitForMultipleObjects(
        uint dwFlags, uint dwMilliseconds, ulong nHandles,
        IntPtr[] pHandles, out uint dwIndex);

    private static IntPtr redirectEventHandle = IntPtr.Zero;

    // Do the redirection on another thread, and use a non-blocking
    // wait method to wait for the redirection to complete.
    public static void RedirectActivationTo( AppActivationArguments args, AppInstance keyInstance) {
        redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
        Task.Run(() =>
        {
            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
            SetEvent(redirectEventHandle);
        });
        uint CWMO_DEFAULT = 0;
        uint INFINITE = 0xFFFFFFFF;
        _ = CoWaitForMultipleObjects(
           CWMO_DEFAULT, INFINITE, 1,
           new IntPtr[] { redirectEventHandle }, out uint handleIndex);
    }

    private static void OnActivated(object sender, AppActivationArguments args) {
        ExtendedActivationKind kind = args.Kind;

        // this for reassurance that I've got the right instance (original launch) doing the work.
        AppActivationArguments thisArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
        ExtendedActivationKind thisInstanceArgsKind = thisArgs.Kind;

        logp($"Program.OnActivated() kind={kind}, thisInstanceArgsKind={thisInstanceArgsKind}");    // s/b, protocol & launch
        if (args.Kind == ExtendedActivationKind.Protocol) {
            ProtocolActivatedEventArgs dataProtActEvArgs = args.Data as ProtocolActivatedEventArgs;
            logp($"Program.OnActivated() data= {dataProtActEvArgs.Uri}");

            MainThread.BeginInvokeOnMainThread( ()=> {  // hooray!  this works!  (12/30/22)
                var openStr = dataProtActEvArgs.Uri.ToString();
                logp($"Program.OnActivated() setting openStr: {openStr}");
                MainPage.Inst.setOpeningString(openStr );
                logp($"Program.OnActivated() openStr set");
            });
        }
    }

}

I'm adding this new, more complete answer, though I don't pretend to completely understand this yet, and I did not do great job keeping track of what sample code I got from where. And, I should've gotten back to here months ago.

Note my logp() method which writes to the console, and MainPage.Inst.setOpeningString() which passes the URI along.

Windows/Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Threading;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.AppLifecycle;
using Windows.ApplicationModel.Activation;
using Windows.Storage;
using System.Runtime.InteropServices;

using static OpenerAppMaui.Util;
namespace OpenerAppMaui.WinUI;

class Program {
    [STAThread]
    static void Main(string[] args) {
        logp(
quot;Program.Main({args.Length} args)");
        WinRT.ComWrappersSupport.InitializeComWrappers();
        bool isRedirect = DecideRedirection();
        if (!isRedirect) {
            logp("Program.Main() NOT isRedir");
            Microsoft.UI.Xaml.Application.Start((p) =>
            {
                var context = new DispatcherQueueSynchronizationContext(
                    DispatcherQueue.GetForCurrentThread());
                SynchronizationContext.SetSynchronizationContext(context);

                // Important: has to go to the WINDOW APP "App" class (not the generic one)
                new App();
                logp("Program.Main() after new App()");
            });
        } else {
            logp("Program.Main() IS isRedir (should get handled with OnActivated() below)");
        }
    }

    private static bool DecideRedirection() {
        bool isRedirect = false;

        AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
        ExtendedActivationKind kind = args.Kind;
        logp(
quot;Program.DecideRedirection() kind={kind}, ");
        if (args.Kind == ExtendedActivationKind.Protocol) {
            ProtocolActivatedEventArgs dataProtActEvArgs = args.Data as ProtocolActivatedEventArgs;
            logp(
quot;Program.DecideRedirection()) data= {dataProtActEvArgs.Uri}");
        }
        try {
            AppInstance keyInstance = AppInstance.FindOrRegisterForKey("randomKey");

            if (keyInstance.IsCurrent) {
                keyInstance.Activated += OnActivated;
            } else {
                isRedirect = true;
                logp(
quot;Program.DecideRedirection() Redirect to keyInstance {keyInstance}");
                RedirectActivationTo(args, keyInstance);
            }
        } catch (Exception ex) {
            logp(
quot;Program.DecideRedirection() Exception, ex={ex.Message}");
        }

        return isRedirect;
    }


    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateEvent(
        IntPtr lpEventAttributes, bool bManualReset,
        bool bInitialState, string lpName);

    [DllImport("kernel32.dll")]
    private static extern bool SetEvent(IntPtr hEvent);

    [DllImport("ole32.dll")]
    private static extern uint CoWaitForMultipleObjects(
        uint dwFlags, uint dwMilliseconds, ulong nHandles,
        IntPtr[] pHandles, out uint dwIndex);

    private static IntPtr redirectEventHandle = IntPtr.Zero;

    // Do the redirection on another thread, and use a non-blocking
    // wait method to wait for the redirection to complete.
    public static void RedirectActivationTo( AppActivationArguments args, AppInstance keyInstance) {
        redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
        Task.Run(() =>
        {
            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
            SetEvent(redirectEventHandle);
        });
        uint CWMO_DEFAULT = 0;
        uint INFINITE = 0xFFFFFFFF;
        _ = CoWaitForMultipleObjects(
           CWMO_DEFAULT, INFINITE, 1,
           new IntPtr[] { redirectEventHandle }, out uint handleIndex);
    }

    private static void OnActivated(object sender, AppActivationArguments args) {
        ExtendedActivationKind kind = args.Kind;

        // this for reassurance that I've got the right instance (original launch) doing the work.
        AppActivationArguments thisArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
        ExtendedActivationKind thisInstanceArgsKind = thisArgs.Kind;

        logp(
quot;Program.OnActivated() kind={kind}, thisInstanceArgsKind={thisInstanceArgsKind}");    // s/b, protocol & launch
        if (args.Kind == ExtendedActivationKind.Protocol) {
            ProtocolActivatedEventArgs dataProtActEvArgs = args.Data as ProtocolActivatedEventArgs;
            logp(
quot;Program.OnActivated() data= {dataProtActEvArgs.Uri}");

            MainThread.BeginInvokeOnMainThread( ()=> {  // hooray!  this works!  (12/30/22)
                var openStr = dataProtActEvArgs.Uri.ToString();
                logp(
quot;Program.OnActivated() setting openStr: {openStr}");
                MainPage.Inst.setOpeningString(openStr );
                logp(
quot;Program.OnActivated() openStr set");
            });
        }
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文