Browse Source

增加 Tray,提供在托盘中启动程序的方式。

master
王厅 3 days ago
parent
commit
16e6f3c71b
  1. 10
      Apewer.Windows/Apewer.Windows.csproj
  2. 7
      Apewer.Windows/Internals/Interop/User32.cs
  3. 83
      Apewer.Windows/WinForm/Extensions.cs
  4. 148
      Apewer.Windows/WinForm/HotKey.cs
  5. 26
      Apewer.Windows/WinForm/MenuItem.cs
  6. 122
      Apewer.Windows/WinForm/ModifierKey.cs
  7. 360
      Apewer.Windows/WinForm/Tray.cs

10
Apewer.Windows/Apewer.Windows.csproj

@ -19,6 +19,8 @@
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'"> <ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<!-- <FrameworkReference Include="Microsoft.AspNetCore.App" /> --> <!-- <FrameworkReference Include="Microsoft.AspNetCore.App" /> -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App" /> <FrameworkReference Include="Microsoft.WindowsDesktop.App" />
<!--<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />-->
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<!-- .NET Framework 4.6.1 --> <!-- .NET Framework 4.6.1 -->
@ -33,6 +35,7 @@
<Reference Include="System.Management" /> <Reference Include="System.Management" />
<Reference Include="System.Runtime.Caching" /> <Reference Include="System.Runtime.Caching" />
<Reference Include="System.Security" /> <Reference Include="System.Security" />
<Reference Include="System.ServiceProcess " />
<Reference Include="System.Speech" /> <Reference Include="System.Speech" />
<Reference Include="System.Transactions" /> <Reference Include="System.Transactions" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
@ -52,6 +55,7 @@
<Reference Include="System.Management" /> <Reference Include="System.Management" />
<Reference Include="System.Runtime.Caching" /> <Reference Include="System.Runtime.Caching" />
<Reference Include="System.Security" /> <Reference Include="System.Security" />
<Reference Include="System.ServiceProcess " />
<Reference Include="System.Speech" /> <Reference Include="System.Speech" />
<Reference Include="System.Transactions" /> <Reference Include="System.Transactions" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
@ -66,14 +70,10 @@
<Reference Include="System.Design" /> <Reference Include="System.Design" />
<Reference Include="System.Management" /> <Reference Include="System.Management" />
<Reference Include="System.Security" /> <Reference Include="System.Security" />
<Reference Include="System.ServiceProcess " />
<Reference Include="System.Transactions" /> <Reference Include="System.Transactions" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Tray\MainForm.cs">
<SubType>Form</SubType>
</Compile>
</ItemGroup>
</Project> </Project>

7
Apewer.Windows/Internals/Interop/User32.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Windows.Forms;
namespace Apewer.Internals.Interop namespace Apewer.Internals.Interop
{ {
@ -212,6 +213,9 @@ namespace Apewer.Internals.Interop
[DllImport("user32.dll ", CharSet = CharSet.Unicode)] [DllImport("user32.dll ", CharSet = CharSet.Unicode)]
public static extern IntPtr PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); public static extern IntPtr PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
[DllImport("user32.dll")]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, Keys vk);
/// <summary></summary> /// <summary></summary>
/// <returns></returns> /// <returns></returns>
[DllImport("user32.dll", EntryPoint = "ReleaseCapture")] [DllImport("user32.dll", EntryPoint = "ReleaseCapture")]
@ -332,6 +336,9 @@ namespace Apewer.Internals.Interop
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook); public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll")]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
/// <summary></summary> /// <summary></summary>
/// <param name="hwnd"></param> /// <param name="hwnd"></param>
/// <param name="hdcdst"></param> /// <param name="hdcdst"></param>

83
Apewer.Windows/WinForm/Extensions.cs

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Apewer.WinForm
{
/// <summary>Windows 窗体工具。</summary>
public static class Extensions
{
static void MenuItemEventHandler(object sender, EventArgs e)
{
#if NETFRAMEWORK
var mi = sender as System.Windows.Forms.MenuItem;
if (mi != null)
{
var tag = mi.Tag as MenuItem;
if (tag != null && tag.Action != null) tag.Action.Invoke(tag);
}
#endif
var tsmi = sender as System.Windows.Forms.ToolStripMenuItem;
if (tsmi != null)
{
var tag = tsmi.Tag as MenuItem;
if (tag != null && tag.Action != null) tag.Action.Invoke(tag);
}
}
#if NETFRAMEWORK
/// <summary>生成 <see cref="System.Windows.Forms.ContextMenu" /> 实例。</summary>
/// <exception cref="ArgumentNullException"></exception>
public static ContextMenu ContextMenu(this IEnumerable<MenuItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
var cm = new ContextMenu();
foreach (var item in items)
{
var isLine = item == null || item.Text == null || item.Text == "" || item.Text == "-";
var text = isLine ? "-" : item?.Text;
var mi = new System.Windows.Forms.MenuItem(text, MenuItemEventHandler);
mi.Enabled = !isLine && item.Action != null;
mi.Tag = item;
cm.MenuItems.Add(mi);
}
return cm;
}
#endif
/// <summary>生成 <see cref="ContextMenuStrip" /> 实例。</summary>
/// <exception cref="ArgumentNullException"></exception>
public static ContextMenuStrip ContextMenuStrip(this IEnumerable<MenuItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
var cms = new ContextMenuStrip();
foreach (var item in items)
{
if (item == null) continue;
var isLine = item.Text == null || item.Text == "" || item.Text == "-";
var text = isLine ? "-" : item.Text;
var tsmi = new ToolStripMenuItem(text, item.Image, MenuItemEventHandler);
tsmi.AutoSize = true;
tsmi.Height = isLine ? 19 : 30;
tsmi.Enabled = item.Action != null && item.Action != null;
tsmi.Tag = item;
cms.Items.Add(tsmi);
}
return cms;
}
}
}

148
Apewer.Windows/WinForm/HotKey.cs

@ -0,0 +1,148 @@
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Apewer.WinForm
{
/// <summary>热键。</summary>
public sealed class HotKey : IMessageFilter
{
#region instance
IntPtr _pointer = IntPtr.Zero;
Keys _key = Keys.None;
ModifierKey _modifier = ModifierKey.None;
int _id = 0;
Action<HotKey> _callback = null;
/// <summary>热键的标识符。 </summary>
public int Id { get => _id; }
/// <summary>修饰键。</summary>
public ModifierKey ModifierKey { get => _modifier; }
/// <summary>按键。</summary>
public Keys Key { get => _key; }
private HotKey(IntPtr pointer, Keys key, ModifierKey modifier, Action<HotKey> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback), "回调函数不能为空。");
switch (key)
{
case Keys.KeyCode:
case Keys.Modifiers:
case Keys.None:
case Keys.Shift:
case Keys.Control:
case Keys.Alt:
throw new ArgumentException($"按键【{key}】无法注册为热键。");
}
// 应用程序必须在0x0000到0xBFFF的范围内指定 ID 值。
// 共享 DLL 必须通过 0xFFFF(GlobalAddAtom 函数返回的范围)指定0xC000区域中的值。
_id = ((int)modifier << 16) & (int)key;
_pointer = pointer;
_callback = callback;
_key = key;
_modifier = modifier;
}
/// <summary></summary>
public override string ToString()
{
var alt = (ModifierKey & ModifierKey.ALT) == ModifierKey.ALT ? "ALT + " : "";
var ctrl = (ModifierKey & ModifierKey.CTRL) == ModifierKey.CTRL ? "CTRL + " : "";
var shift = (ModifierKey & ModifierKey.SHIFT) == ModifierKey.SHIFT ? "SHIFT + " : "";
var win = (ModifierKey & ModifierKey.WIN) == ModifierKey.WIN ? "WIN + " : "";
var result = $"{alt}{ctrl}{shift}{win}{Key}";
return result;
}
#endregion
#region bind
const int WM_HOTKEY = 0x0312;
[DllImport("user32.dll")]
static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, Keys vk);
[DllImport("user32.dll")]
static extern bool UnregisterHotKey(IntPtr hWnd, int id);
bool IMessageFilter.PreFilterMessage(ref Message m)
{
if (m.Msg == WM_HOTKEY && (int)m.WParam == _id)
{
_callback?.Invoke(this);
return true;
}
return false;
}
void Bind()
{
var success = RegisterHotKey(_pointer, _id, (int)_modifier, _key);
if (!success)
{
var error = Marshal.GetLastWin32Error();
if (error == 1409) throw new Exception($"热键【{ToString()}】已被占用。");
throw new Exception($"错误 {error}:注册热键【{ToString()}】失败。");
}
Application.AddMessageFilter(this);
}
/// <summary>释放热键。</summary>
public void Release()
{
UnregisterHotKey(Application.OpenForms[0].Handle, _id);
}
#endregion
// Virtual-Key 代码
// https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
/// <summary>绑定热键。</summary>
/// <param name="form">将接收热键生成的 WM_HOTKEY(0x0312)消息的窗口句柄。</param>
/// <param name="key">热键的虚拟键代码。</param>
/// <param name="modifier">修饰键。</param>
/// <param name="callback">回调。</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentException" />
/// <exception cref="Exception" />
public static HotKey Bind(Form form, Keys key, ModifierKey modifier, Action<HotKey> callback)
{
if (form == null) throw new ArgumentNullException(nameof(form));
if (callback == null) throw new ArgumentNullException(nameof(callback));
var hotkey = new HotKey(form.Handle, key, modifier, callback);
hotkey.Bind();
return hotkey;
}
/// <summary>绑定热键。</summary>
/// <remarks>WM_HOTKEY 消息将发布到调用线程的消息队列,并且必须在消息循环中进行处理。</remarks>
/// <param name="key">热键的虚拟键代码。</param>
/// <param name="modifier">修饰键。</param>
/// <param name="callback">回调。</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentException" />
/// <exception cref="Exception" />
public static HotKey Bind(Keys key, ModifierKey modifier, Action<HotKey> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
var hotkey = new HotKey(IntPtr.Zero, key, modifier, callback);
hotkey.Bind();
return hotkey;
}
}
}

26
Apewer.Windows/WinForm/MenuItem.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace Apewer.WinForm
{
/// <summary>菜单项。</summary>
public class MenuItem
{
/// <summary>显示的文本,不设置此属性时菜单将显示未为分隔线。</summary>
public string Text { get; set; }
/// <summary>点击菜单时要执行的命令。</summary>
public Action<MenuItem> Action { get; set; }
/// <summary>菜单中显示的图片。</summary>
public Image Image { get; set; }
/// <summary>自定义数据。</summary>
public virtual object Data { get; set; }
}
}

122
Apewer.Windows/WinForm/ModifierKey.cs

@ -1,122 +1,24 @@
using System; namespace Apewer.WinForm
namespace Apewer.WinForm
{ {
/// <summary>修饰键。</summary> /// <summary>修饰键。</summary>
public struct ModifierKey : IEquatable<ModifierKey> public enum ModifierKey : int
{ {
byte _value; /// <summary>表示未按下任何修饰键。</summary>
bool _alt; None = 0,
bool _ctrl;
bool _shift;
bool _win;
/// <summary>包含 ALT 键。</summary>
public bool WithALT { get => _alt; }
/// <summary>包含 CTRL 键。</summary>
public bool WithCTRL { get => _ctrl; }
/// <summary>包含 SHIFT 键。</summary>
public bool WithSHIFT { get => _shift; }
/// <summary>包含 WIN 键。</summary>
public bool WithWIN { get => _win; }
/// <summary>修饰键组合的值。</summary>
public byte Value { get => _value; }
/// <exception cref="OverflowException"></exception>
private ModifierKey(byte value)
{
_alt = value << 7 >> 7 == 1;
_ctrl = value << 6 >> 7 == 1;
_shift = value << 5 >> 7 == 1;
_win = value << 4 >> 7 == 1;
_value = Convert.ToByte(value);
if (_value != value) throw new OverflowException($"参数【{value}】含有无效的键。");
}
private ModifierKey(bool alt, bool ctrl, bool shift, bool win)
{
_alt = alt;
_ctrl = ctrl;
_shift = shift;
_win = win;
var value = 0;
if (alt) value += 0x01;
if (ctrl) value += 0x02;
if (shift) value += 0x04;
if (win) value += 0x05;
_value = Convert.ToByte(value);
}
/// <summary></summary>
public override int GetHashCode() => _value;
/// <summary></summary>
public override bool Equals(object obj)
{
if (obj is ModifierKey b) return _value == b._value;
return false;
}
/// <summary></summary>
public bool Equals(ModifierKey another) => _value == another._value;
/// <summary></summary>
public static implicit operator byte(ModifierKey instance) => instance._value;
/// <summary></summary>
/// <exception cref="OverflowException"></exception>
public static implicit operator ModifierKey(byte value) => new ModifierKey(value);
/// <summary></summary>
/// <exception cref="ArgumentException" />
public static ModifierKey operator +(ModifierKey a, ModifierKey b)
{
if (a._alt && b._alt) throw new ArgumentException("相加的两个组合键共同包含了 ALT 键。");
if (a._ctrl && b._ctrl) throw new ArgumentException("相加的两个组合键共同包含了 CTRL 键。");
if (a._shift && b._shift) throw new ArgumentException("相加的两个组合键共同包含了 SHIFT 键。");
if (a._win && b._win) throw new ArgumentException("相加的两个组合键共同包含了 WIN 键。");
var alt = a._alt || b._alt;
var ctrl = a._ctrl || b._ctrl;
var shift = a._shift || b._shift;
var win = a._win || b._win;
return new ModifierKey(alt, ctrl, shift, win);
}
/// <summary></summary>
public static ModifierKey operator -(ModifierKey a, ModifierKey b)
{
var alt = a._alt;
var ctrl = a._ctrl;
var shift = a._shift;
var win = a._win;
if (b._alt) alt = false;
if (b._ctrl) ctrl = false;
if (b._shift) shift = false;
if (b._win) win = false;
return new ModifierKey(alt, ctrl, shift, win);
}
/// <summary>表示 ALT 修饰键。</summary> /// <summary>表示按下了 ALT 键。</summary>
public static ModifierKey ALT { get; } = new ModifierKey(0x01); ALT = 0x0001,
/// <summary>表示 CTRL 修饰键。</summary> /// <summary>表示按下了 CTRL 键。</summary>
public static ModifierKey CTRL { get; } = new ModifierKey(0x02); CTRL = 0x0002,
/// <summary>表示 SHIFT 修饰键。</summary> /// <summary>表示按下了 SHIFT 键。</summary>
public static ModifierKey SHIFT { get; } = new ModifierKey(0x04); SHIFT = 0x0004,
/// <summary>表示 WIN 修饰键。</summary> /// <summary>表示按下了 Windows 键。</summary>
public static ModifierKey WIN { get; } = new ModifierKey(0x08); WIN = 0x0008
} }

360
Apewer.Windows/WinForm/Tray.cs

@ -0,0 +1,360 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Security;
using System.Security.Permissions;
using System.ServiceProcess;
using System.Threading;
using System.Windows.Forms;
namespace Apewer.WinForm
{
/// <summary>通用托盘。</summary>
public sealed class Tray : IDisposable
{
#region icon
NotifyIcon _origin = null;
/// <summary>通知图标。</summary>
NotifyIcon Icon { get => _origin; }
/// <summary>鼠标点击通知图标时执行的程序。</summary>
public Action OnClick { get; set; }
/// <summary>鼠标点击通知图标时执行的程序。</summary>
public Action OnDoubleClick { get; set; }
/// <summary>设置图标。</summary>
public void SetIcon(Icon icon)
{
if (_disposed) throw new ObjectDisposedException(ToString());
ClearIcon();
SyncInvoke(() =>
{
if (icon == null)
{
_origin.Visible = false;
}
else
{
_origin.Icon = icon;
_origin.Visible = true;
}
});
}
/// <summary>设置标题。</summary>
public void SetTitle(string title)
{
if (_disposed) throw new ObjectDisposedException(ToString());
if (_origin != null)
{
SyncInvoke(() =>
{
_origin.Text = title;
});
}
}
void ClearIcon()
{
SyncInvoke(() =>
{
if (_origin != null)
{
var old = _origin.Icon;
_origin.Icon = null;
RuntimeUtility.Dispose(old);
}
});
}
/// <summary>获取 EXE 文件的图标。</summary>
public static Icon GetExeIcon() => System.Drawing.Icon.ExtractAssociatedIcon(RuntimeUtility.ExecutablePath);
/// <summary>获取 EXE 文件的图标。</summary>
/// <exception cref="ArgumentNullException" />
/// <exception cref="FileNotFoundException" />
public static Icon GetExeIcon(string filePath)
{
if (filePath.IsEmpty()) throw new ArgumentNullException(nameof(filePath));
if (!File.Exists(filePath)) throw new FileNotFoundException($"文件【{filePath}】不存在。");
var icon = System.Drawing.Icon.ExtractAssociatedIcon(filePath);
return icon;
}
#endregion
#region tip
/// <summary>鼠标点击弹出提示时执行的程序。</summary>
public Action OnTipClick { get; set; }
/// <summary>显示悬浮通知。</summary>
/// <exception cref="ObjectDisposedException"></exception>
public void ShowTip(string text, string title = null, ToolTipIcon icon = ToolTipIcon.None)
{
if (_disposed) throw new ObjectDisposedException(ToString());
if (text.IsEmpty()) return;
_origin?.ShowBalloonTip(5000, title, text, icon);
}
#endregion
#region menu
/// <summary>清除右键菜单。</summary>
public void ClearMenu()
{
if (_disposed) throw new ObjectDisposedException(ToString());
SyncInvoke(() =>
{
#if NETFRAMEWORK
if (_origin.ContextMenu != null)
{
var old = _origin.ContextMenu;
_origin.ContextMenu = null;
RuntimeUtility.Dispose(old);
}
#endif
if (_origin.ContextMenuStrip != null)
{
var old = _origin.ContextMenuStrip;
_origin.ContextMenuStrip = null;
RuntimeUtility.Dispose(old);
}
});
}
/// <summary>设置右键菜单。</summary>
public void SetMenu(MenuItem[] menu)
{
ClearMenu();
if (menu == null) return;
#if NETFRAMEWORK
SetMenu(menu.ContextMenu());
#else
SetMenu(menu.ContextMenuStrip());
#endif
}
#if NETFRAMEWORK
/// <summary>设置右键菜单。</summary>
public void SetMenu(ContextMenu menu)
{
if (_disposed) throw new ObjectDisposedException(ToString());
ClearMenu();
SyncInvoke(() =>
{
if (menu != null) _origin.ContextMenu = menu;
});
}
#endif
/// <summary>设置右键菜单。</summary>
public void SetMenu(ContextMenuStrip menu)
{
if (_disposed) throw new ObjectDisposedException(ToString());
ClearMenu();
SyncInvoke(() =>
{
if (menu != null) _origin.ContextMenuStrip = menu;
});
}
#endregion
#region instance
bool _disposed = false;
SynchronizationContext _sync = null;
Form _form = null;
Action<Tray> _action = null;
/// <summary></summary>
/// <exception cref="InvalidOperationException"></exception>
public Tray(Action<Tray> action)
{
_sync = SynchronizationContext.Current;
if (_sync == null) throw new InvalidOperationException($"当前线程无法启动 {nameof(Tray)} 实例。");
_origin = new NotifyIcon();
_origin.Click += (s, e) => OnClick?.Invoke();
_origin.DoubleClick += (s, e) => OnDoubleClick?.Invoke();
_origin.BalloonTipClicked += (s, e) => OnTipClick?.Invoke();
_form = new Form();
_form.FormBorderStyle = FormBorderStyle.None;
_form.Opacity = 0;
_form.ShowInTaskbar = false;
_form.Size = new Size(0, 0);
_form.StartPosition = FormStartPosition.CenterScreen;
_form.TopMost = true;
_action = action;
_form.Load += Load;
_form.Show();
_form.Visible = false;
}
/// <summary></summary>
public void Dispose()
{
if (_disposed) throw new ObjectDisposedException(ToString());
ClearMenu();
ClearIcon();
_origin.Dispose();
_origin = null;
_form.Dispose();
_form = null;
_disposed = true;
}
void Load(object sender, EventArgs e)
{
RuntimeUtility.InBackground(() =>
{
_action?.Invoke(this);
});
}
/// <summary></summary>
public void SyncInvoke(Action action)
{
if (action == null) return;
if (_sync == null) action.Invoke();
else _sync.Send(state => action.Invoke(), null);
}
/// <summary></summary>
public void FormInvoke(Action action)
{
if (action == null) return;
if (_form == null) return;
if (_form.InvokeRequired)
{
_form.Invoke(action);
return;
}
action.Invoke();
}
/// <summary>显示对话框,并获取结果。</summary>
/// <exception cref="ArgumentNullException" />
public DialogResult ShowDialog(CommonDialog dialog)
{
if (_disposed) throw new ObjectDisposedException(ToString());
if (dialog == null) throw new ArgumentNullException(nameof(dialog));
if (_form.InvokeRequired)
{
var result = default(DialogResult);
_form.Invoke(() =>
{
_form.Visible = true;
result = dialog.ShowDialog();
_form.Visible = false;
});
return result;
}
return dialog.ShowDialog();
}
/// <summary></summary>
public void Exit()
{
if (!_disposed) Dispose();
Application.Exit();
}
#endregion
#region run
/// <summary>已启动的服务名称。</summary>
public static string ServiceName { get; private set; }
/// <summary>在当前线程运行托盘程序,并启动消息循环。</summary>
/// <param name="action">启动托盘后执行的程序。</param>
/// <exception cref="ArgumentNullException" />
[STAThread]
public static void Run(Action<Tray> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
Control.CheckForIllegalCrossThreadCalls = false;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (SynchronizationContext.Current == null)
{
var sc = AsyncOperationManager.SynchronizationContext;
if (sc == null || sc.GetType() == typeof(SynchronizationContext))
{
new PermissionSet(PermissionState.Unrestricted).Assert();
try { AsyncOperationManager.SynchronizationContext = new WindowsFormsSynchronizationContext(); }
finally { CodeAccessPermission.RevertAssert(); }
}
}
var instance = new Tray(action);
// action.Invoke(instance);
Application.Run();
}
/// <summary>启动服务。</summary>
/// <param name="onStart">服务启动后执行的程序。</param>
/// <param name="onStop">停止服务时执行的程序。</param>
public static void Service(Action onStart, Action onStop = null)
{
var processName = Process.GetCurrentProcess().ProcessName;
var service = new TrayService(processName, onStart, onStop);
ServiceBase.Run(service);
}
class TrayService : ServiceBase
{
Action on_start;
Action on_stop;
public TrayService(string serviceName, Action onStart, Action onStop)
{
if (serviceName.IsEmpty()) throw new ArgumentNullException(nameof(serviceName));
if (onStart == null) throw new ArgumentNullException(nameof(onStart));
ServiceName = serviceName;
on_start = onStart;
on_stop = onStop;
}
protected override void OnStart(string[] args) => RuntimeUtility.InBackground(on_start);
protected override void OnStop() => on_stop?.Invoke();
}
#endregion
}
}
Loading…
Cancel
Save