using System; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using SysClipboard = System.Windows.Clipboard; /// /// 剪贴板帮助类(WinForms / WPF 通用) /// 解决的问题: /// 1. CLIPBRD_E_CANT_OPEN (0x800401D0) 等 COMException / ExternalException /// (剪贴板被其他进程占用,如远程桌面、剪贴板管理器、杀毒软件、微信/QQ 等) /// 2. 非 STA 线程调用导致的 ThreadStateException /// 3. 空字符串 / null 导致的 ArgumentNullException /// public static class ClipboardHelper { /// 默认重试次数 public static int DefaultRetryCount { get; set; } = 10; /// 默认重试间隔(毫秒) public static int DefaultRetryDelayMs { get; set; } = 100; #region 同步 API /// 复制文本,失败返回 false,不抛异常 public static bool TrySetText(string text, int retryCount = -1, int retryDelayMs = -1) { return TryExecute(() => { if (string.IsNullOrEmpty(text)) SysClipboard.Clear(); else SetDataObjectCore(text); return true; }, out _, retryCount, retryDelayMs); } /// 复制任意数据(文本、文件列表、图片、DataObject 等) public static bool TrySetData(object data, int retryCount = -1, int retryDelayMs = -1) { if (data == null) return false; return TryExecute(() => { SetDataObjectCore(data); return true; }, out _, retryCount, retryDelayMs); } /// 读取文本,失败或无文本返回 null public static string TryGetText(int retryCount = -1, int retryDelayMs = -1) { TryExecute(() => SysClipboard.ContainsText() ? SysClipboard.GetText() : null, out string result, retryCount, retryDelayMs); return result; } /// 清空剪贴板 public static bool TryClear(int retryCount = -1, int retryDelayMs = -1) { return TryExecute(() => { SysClipboard.Clear(); return true; }, out _, retryCount, retryDelayMs); } /// 复制文本,失败时抛出最后一次的异常 public static void SetText(string text, int retryCount = -1, int retryDelayMs = -1) { Execute(() => { if (string.IsNullOrEmpty(text)) SysClipboard.Clear(); else SetDataObjectCore(text); return true; }, retryCount, retryDelayMs); } #endregion #region 异步 API(不阻塞 UI 线程,建议在 UI 线程 await 调用) public static async Task TrySetTextAsync(string text, int retryCount = -1, int retryDelayMs = -1) { retryCount = retryCount < 0 ? DefaultRetryCount : retryCount; retryDelayMs = retryDelayMs < 0 ? DefaultRetryDelayMs : retryDelayMs; for (int i = 0; i < retryCount; i++) { try { RunSta(() => { if (string.IsNullOrEmpty(text)) SysClipboard.Clear(); else SetDataObjectCore(text); return true; }); return true; } catch (Exception ex) when (IsRetryable(ex)) { if (i < retryCount - 1) await Task.Delay(retryDelayMs).ConfigureAwait(true); // 回到 UI 线程 } catch { return false; } } return false; } public static async Task TryGetTextAsync(int retryCount = -1, int retryDelayMs = -1) { retryCount = retryCount < 0 ? DefaultRetryCount : retryCount; retryDelayMs = retryDelayMs < 0 ? DefaultRetryDelayMs : retryDelayMs; for (int i = 0; i < retryCount; i++) { try { return RunSta(() => SysClipboard.ContainsText() ? SysClipboard.GetText() : null); } catch (Exception ex) when (IsRetryable(ex)) { if (i < retryCount - 1) await Task.Delay(retryDelayMs).ConfigureAwait(true); } catch { return null; } } return null; } #endregion #region 内部实现 private static void SetDataObjectCore(object data) { SysClipboard.SetDataObject(data, true); } private static T Execute(Func action, int retryCount, int retryDelayMs) { retryCount = retryCount < 0 ? DefaultRetryCount : retryCount; retryDelayMs = retryDelayMs < 0 ? DefaultRetryDelayMs : retryDelayMs; if (retryCount < 1) retryCount = 1; for (int i = 0; ; i++) { try { return RunSta(action); } catch (Exception ex) when (IsRetryable(ex) && i < retryCount - 1) { Thread.Sleep(retryDelayMs); } } } private static bool TryExecute(Func action, out T result, int retryCount, int retryDelayMs) { try { result = Execute(action, retryCount, retryDelayMs); return true; } catch { result = default(T); return false; } } /// 剪贴板必须在 STA 线程访问;当前不是 STA 就新建一个 STA 线程执行 private static T RunSta(Func action) { if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) return action(); T result = default(T); Exception error = null; var thread = new Thread(() => { try { result = action(); } catch (Exception ex) { error = ex; } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); if (error != null) { // 保留原始异常类型,便于上层判断 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(error).Throw(); } return result; } /// /// 判断是否为"剪贴板暂时被占用"类的可重试异常 /// COMException 继承自 ExternalException,所以一起覆盖 /// private static bool IsRetryable(Exception ex) { if (ex is ExternalException) { // 0x800401D0 CLIPBRD_E_CANT_OPEN // 0x800401D1 CLIPBRD_E_CANT_EMPTY // 0x800401D2 CLIPBRD_E_CANT_SET // 0x800401D3 CLIPBRD_E_BAD_DATA // 0x800401D4 CLIPBRD_E_CANT_CLOSE // WinForms 的 "Requested Clipboard operation did not succeed" 也是 ExternalException return true; } return false; } #endregion }