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

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
}
}