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,123 +1,25 @@ | 
				
			|||
using System; | 
				
			|||
 | 
				
			|||
namespace Apewer.WinForm | 
				
			|||
namespace Apewer.WinForm | 
				
			|||
{ | 
				
			|||
 | 
				
			|||
    /// <summary>修饰键。</summary>
 | 
				
			|||
    public struct ModifierKey : IEquatable<ModifierKey> | 
				
			|||
    public enum ModifierKey : int | 
				
			|||
    { | 
				
			|||
 | 
				
			|||
        byte _value; | 
				
			|||
        bool _alt; | 
				
			|||
        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>表示未按下任何修饰键。</summary>
 | 
				
			|||
        None = 0, | 
				
			|||
 | 
				
			|||
        /// <summary>表示 ALT 修饰键。</summary>
 | 
				
			|||
        public static ModifierKey ALT { get; } = new ModifierKey(0x01); | 
				
			|||
        /// <summary>表示按下了 ALT 键。</summary>
 | 
				
			|||
        ALT = 0x0001, | 
				
			|||
 | 
				
			|||
        /// <summary>表示 CTRL 修饰键。</summary>
 | 
				
			|||
        public static ModifierKey CTRL { get; } = new ModifierKey(0x02); | 
				
			|||
        /// <summary>表示按下了 CTRL 键。</summary>
 | 
				
			|||
        CTRL = 0x0002, | 
				
			|||
 | 
				
			|||
        /// <summary>表示 SHIFT 修饰键。</summary>
 | 
				
			|||
        public static ModifierKey SHIFT { get; } = new ModifierKey(0x04); | 
				
			|||
        /// <summary>表示按下了 SHIFT 键。</summary>
 | 
				
			|||
        SHIFT = 0x0004, | 
				
			|||
 | 
				
			|||
        /// <summary>表示 WIN 修饰键。</summary>
 | 
				
			|||
        public static ModifierKey WIN { get; } = new ModifierKey(0x08); | 
				
			|||
        /// <summary>表示按下了 Windows 键。</summary>
 | 
				
			|||
        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