7 changed files with 642 additions and 116 deletions
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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; } |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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 |
||||
|
|
||||
} |
} |
||||
|
|
||||
|
@ -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…
Reference in new issue