固定在任务栏上的链接过程"

时间:2018-01-31 10:23:16

标签: c# winapi process app-id taskbar

我们说我有两个名为launcher.exelaunchee.exe的程序。启动器显示一个按钮 - 当点击 - 启动launchee.exe时,launchee.exe是一个简单的hello world程序。

如果我没有采取任何措施阻止它,那么当用户将"固定到任务栏时#34;在hello world程序中,它将固定launchee.exe并且不会通过启动器来启动应用程序。

告诉Windows固定launcher.exe而不是launchee.exe的最佳方法是什么?

为了使具体内容成为现实,在C#中实现launcher.exe的一个例子:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;

public class Launcher : Form
{
    static public void Main ()
    {
        Application.Run (new Launcher ());
    }

    public Launcher ()
    {
        Button b = new Button ();
        b.Text = "Launch";
        b.Click += new EventHandler (Button_Click);
        Controls.Add (b);
    }

    private void Button_Click (object sender, EventArgs e)
    {
        Process.Start("launchee.exe");
        System.Environment.Exit(0);
    }
}

launchee.exe

using System;
using System.Drawing;
using System.Windows.Forms;

public class Launchee : Form
{
    static public void Main ()
    {
        Application.Run (new Launchee ());
    }

    public Launchee ()
    {
        Label b = new Label();
        b.Text = "Hello World !";
        Controls.Add (b);
    }
}

2 个答案:

答案 0 :(得分:2)

我建议基于绑定低级API来访问AppUserModelID的anwser。我觉得这个解决方案很脆弱而且很混乱。它主要受到Windows Api CodePack的启发,似乎已被微软停止使用。我希望有人能提出更清洁的解决方案。

其目的是将AppUserId设置为" Stackoverflow.chain.process.pinning"并手动设置RelaunchCommand以及DisplayName属性(它们必须根据AppUserModelID一起设置)。

要在示例实现中使用它,需要分别在TaskBar.SetupLauncher(this)TaskBar.SetupLaunchee(this)构造函数中调用LauncherLaunchee

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

internal struct PropertyKey
{
  Guid formatId;
  int propertyId;

  internal PropertyKey(Guid guid, int propertyId)
  {
      this.formatId = guid;
      this.propertyId = propertyId;
  }
}

[StructLayout(LayoutKind.Explicit)]
internal struct PropVariant
{
  [FieldOffset(0)] internal ushort vt;
  [FieldOffset(8)] internal IntPtr pv;
  [FieldOffset(8)] internal UInt64 padding;

  [DllImport("Ole32.dll", PreserveSig = false)]
  internal static extern void PropVariantClear(ref PropVariant pvar);

  internal PropVariant(string value)
  {
      this.vt = (ushort)VarEnum.VT_LPWSTR;
      this.padding = 0;
      this.pv = Marshal.StringToCoTaskMemUni(value);
  }

  internal void Clear()
  {
    PropVariantClear (ref this);
  }
}

[ComImport,
Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore
{
  int GetCount([Out] out uint propertyCount);
  void GetAt([In] uint propertyIndex, [Out, MarshalAs(UnmanagedType.Struct)] out PropertyKey key);
  void GetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey key, [Out, MarshalAs(UnmanagedType.Struct)] out PropVariant pv);
  void SetValue([In, MarshalAs(UnmanagedType.Struct)] ref PropertyKey key, [In, MarshalAs(UnmanagedType.Struct)] ref PropVariant pv);
  void Commit();
}

internal static class TaskBar {
  [DllImport("shell32.dll")]
  static extern int SHGetPropertyStoreForWindow(
      IntPtr hwnd,
      ref Guid iid /*IID_IPropertyStore*/,
      [Out(), MarshalAs(UnmanagedType.Interface)]out IPropertyStore propertyStore);

  internal static class Key {
    private static Guid propGuid = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3");
    internal static PropertyKey AppId = new PropertyKey(propGuid, 5);
    internal static PropertyKey RelaunchCommand = new PropertyKey(propGuid, 2);
    internal static PropertyKey DisplayName = new PropertyKey(propGuid, 4);
  }

  private static void ClearValue(IPropertyStore store, PropertyKey key) {
      var prop = new PropVariant();
      prop.vt = (ushort)VarEnum.VT_EMPTY;
      store.SetValue(ref key, ref prop);
  }

  private static void SetValue(IPropertyStore store, PropertyKey key, string value) {
    var prop = new PropVariant(value);
    store.SetValue(ref key, ref prop);
    prop.Clear();
  }

  internal static IPropertyStore Store(IntPtr handle) {
    IPropertyStore store;
    var store_guid = new Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99");
    int rc = SHGetPropertyStoreForWindow(handle, ref store_guid, out store);
    if (rc != 0) throw Marshal.GetExceptionForHR(rc);
    return store;
  }

  internal static void SetupLauncher(Form form) {
    IntPtr handle = form.Handle;
    var store = Store(handle);
    SetValue (store, Key.AppId, "Stackoverflow.chain.process.pinning");
    form.FormClosed += delegate { Cleanup(handle); };
  } 

  internal static void SetupLaunchee(Form form) {
    IntPtr handle = form.Handle;
    var store = Store(handle);
    SetValue (store, Key.AppId, "Stackoverflow.chain.process.pinning");
    string exePath = System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "launcher.exe");
    SetValue (store, Key.RelaunchCommand, exePath);
    SetValue (store, Key.DisplayName, "Test");
    form.FormClosed += delegate { Cleanup(handle); };
  }

  internal static void Cleanup(IntPtr handle) {
    var store = Store(handle);
    ClearValue (store, Key.AppId);
    ClearValue (store, Key.RelaunchCommand);
    ClearValue (store, Key.DisplayName);
  }
}

答案 1 :(得分:1)

我遇到了同样的问题,最终设法找到了一个不错的解决方案。在最新的Windows 10更新中,设置RelaunchCommand对我不再有用。

为简单起见,我使用“ App”代替“ Launchee”作为名称,因为它很容易与Launcher混淆。

简短版本:

Launcher.exe和App.exe在任务栏中分组在一起。 Laucher.exe执行更新部分,并照常启动App.exe。如果在启动器运行时选择“固定到任务栏”,它将把启动器固定到任务栏。如果该应用程序已经启动,并且您将此应用程序固定在任务栏上,则在启动器组合在一起时,它仍将启动程序固定在任务栏上。

长版:

两个应用程序都在任务栏中分组在一起,它们共享相同的AppID。可以按照此处的描述进行操作:How to group different apps in Windows task bar?

入门程序应该具有一个任务栏上显示图标的UI。就我而言,它是一个SplashScreen作为UI。它会启动App.exe,Laucher会等待两秒钟,直到启动App.exe,它们会在任务栏中共享相同时间的少量符号。然后启动器可能会关闭,如果您之后将应用程序固定,它将启动器固定到任务栏。

在这里您可以找到启动的示例应用程序,它是WPF应用程序:

using System.Runtime.InteropServices;
using System.Windows;

namespace TestApp
{
    public partial class MainWindow : Window
    {
        [DllImport("shell32.dll", SetLastError = true)]
        private static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);

        private const string AppID = "73660a02-a7ec-4f9a-ba25-c55ddbf60225"; // generate your own with: Guid.NewGuid();

        public MainWindow()
        {
            SetCurrentProcessExplicitAppUserModelID(AppID);
            InitializeComponent();
            Topmost = true; // to make sure UI is in front once
            Topmost = false;
        }
    }
}

第二个WPF应用程序,即启动器/启动器:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;

namespace TestStarter
{
    public partial class MainWindow : Window
    {
        [DllImport("shell32.dll", SetLastError = true)]
        private static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);

        private const string AppID = "73660a02-a7ec-4f9a-ba25-c55ddbf60225"; // generate your own with: Guid.NewGuid();

        public MainWindow()
        {
            SetCurrentProcessExplicitAppUserModelID(AppID);
            InitializeComponent();
            Process.Start(@"C:\Test\TestApp.exe");
            ExitAfterDelay();
        }

        private async void ExitAfterDelay()
        {
            await Task.Delay(2000);
            Environment.Exit(0);
        }
    }
}