我们说我有两个名为launcher.exe
和launchee.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);
}
}
答案 0 :(得分:2)
我建议基于绑定低级API来访问AppUserModelID的anwser。我觉得这个解决方案很脆弱而且很混乱。它主要受到Windows Api CodePack的启发,似乎已被微软停止使用。我希望有人能提出更清洁的解决方案。
其目的是将AppUserId设置为" Stackoverflow.chain.process.pinning"并手动设置RelaunchCommand以及DisplayName属性(它们必须根据AppUserModelID一起设置)。
要在示例实现中使用它,需要分别在TaskBar.SetupLauncher(this)
和TaskBar.SetupLaunchee(this)
构造函数中调用Launcher
和Launchee
。
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);
}
}
}