You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
360 lines
11 KiB
360 lines
11 KiB
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
|
|
|
|
}
|
|
|
|
}
|
|
|