# 流程

  • 创建一个 STA 线程
  • 调用 Application.Run 方法
  • 使 UI DPI感知
  • 创建 NotifyIcon 对象并使 Visible 为 true
  • 暴露 UI 线程调度器的 Invoke 方法

# 创建一个 STA 线程

1
2
3
4
5
UIThread = new Thread(() =>
{
});
UIThread.SetApartmentState(ApartmentState.STA);
UIThread.Start();

# 调用 Application.Run 方法

1
2
3
4
5
6
7
8
UIThread = new Thread(() =>
{
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run();
});
UIThread.SetApartmentState(ApartmentState.STA);
UIThread.Start();

# 使 UI DPI 感知

控制台程序是无法使用 manifest 文件来配置 DPI 感知的,我们使用 API 的方法来控制 DPI 感知

1
2
3
4
5
6
7
8
9
10
11
12
13
UIThread = new Thread(() =>
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
+ SetProcessDPIAware();
+
Application.Run();
});
UIThread.SetApartmentState(ApartmentState.STA);
UIThread.Start();

+[DllImport("user32.dll")]
+public static extern bool SetProcessDPIAware();

# 创建 NotifyIcon 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
UIThread = new Thread(() =>
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SetProcessDPIAware();
+ NotifyIcon = new NotifyIcon();
+ NotifyIcon.Icon = new Icon(new MemoryStream(Convert.FromBase64String(Resources.TaskBarIconResources.IconBase64)));
+ var menu = new ContextMenuStrip();
+ NotifyIcon.ContextMenuStrip = menu;
+ menu.Items.Add(new ToolStripMenuItem { Text = "框架版本: 1.0.0.0" });
+ ToolStripMenuItem reloadItem = new() { Text = "重载插件" };
+ reloadItem.Click += async (a, b) =>// 使用异步防止卡死托盘
+ {
+ await Task.Run(() =>
+ {
+ if (MessageBox.Show("确定要重载插件吗?", "嗯?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+ {
+ PluginManagerProxy.Instance.ReloadAllPlugins();
+ }
+ });
+ };
+ menu.Items.Add(reloadItem);
+
+ NotifyIcon.Visible = true; // 此时托盘将会出现
Application.Run();
});
UIThread.SetApartmentState(ApartmentState.STA);
UIThread.Start();

[DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();

# 暴露 UI 线程调度器的 Invoke 方法

当在线程外部需要更新托盘的内容时,需要切换线程至 UI 线程才可操作控件。我们将创建的 NotityIcon 对象暴露到属性即可通过调用控件的 Invoke 方法来切换线程至 UI 线程

1
2
3
4
5
6
7
8
9
10
11
private static void Invoke(Action action)
{
if (NotifyIcon.ContextMenuStrip != null && NotifyIcon.ContextMenuStrip.InvokeRequired)
{
NotifyIcon.ContextMenuStrip.BeginInvoke(action);
}
else
{
action.Invoke();
}
}