2023-01-29 12:20:13 -05:00
|
|
|
|
#nullable enable
|
|
|
|
|
using System;
|
2019-01-21 11:42:39 -05:00
|
|
|
|
using System.Collections.Generic;
|
2020-07-26 15:54:57 -04:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.InteropServices;
|
2019-01-21 11:42:39 -05:00
|
|
|
|
using Vanara.Extensions;
|
2020-07-26 15:54:57 -04:00
|
|
|
|
using Vanara.PInvoke;
|
2019-01-21 11:42:39 -05:00
|
|
|
|
using static Vanara.PInvoke.Ole32;
|
|
|
|
|
using static Vanara.PInvoke.Shell32;
|
2020-07-26 15:54:57 -04:00
|
|
|
|
using static Vanara.PInvoke.User32;
|
2019-01-21 11:42:39 -05:00
|
|
|
|
using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
|
|
|
|
|
|
|
|
|
|
namespace Vanara.Windows.Shell
|
|
|
|
|
{
|
2023-01-29 12:20:13 -05:00
|
|
|
|
/// <summary>Static class with methods to interact with the Clipboard.</summary>
|
2023-01-15 11:38:17 -05:00
|
|
|
|
/// <example>
|
|
|
|
|
/// <code title="Indirect manipulation">// This model let's you place multiple formats at once on the clipboard
|
|
|
|
|
/// IDataObject ido = NativeClipboard.CreateEmptyDataObject();
|
|
|
|
|
/// ido.SetData(CLIPFORMAT.CF_UNICODETEXT, txt);
|
|
|
|
|
/// ido.SetData(Shell32.ShellClipboardFormat.CF_HTML, htmlFragment);
|
|
|
|
|
/// ido.SetData("MyRectFormat", new RECT(1, 2, 3, 4));
|
|
|
|
|
/// NativeClipboard.SetDataObject(ido);</code></example>
|
2023-01-05 12:28:15 -05:00
|
|
|
|
public static class NativeClipboard
|
2019-01-21 11:42:39 -05:00
|
|
|
|
{
|
2023-01-10 17:51:13 -05:00
|
|
|
|
private const int stdRetryCnt = 5;
|
2023-01-14 13:36:08 -05:00
|
|
|
|
private const int stdRetryDelay = 100;
|
2021-08-17 15:04:22 -04:00
|
|
|
|
private static readonly object objectLock = new();
|
2023-01-29 12:20:13 -05:00
|
|
|
|
private static ListenerWindow? listener;
|
2023-01-11 14:26:40 -05:00
|
|
|
|
[ThreadStatic]
|
|
|
|
|
private static bool oleInit = false;
|
2022-01-06 17:11:41 -05:00
|
|
|
|
|
2020-07-26 15:54:57 -04:00
|
|
|
|
/// <summary>Occurs when whenever the contents of the Clipboard have changed.</summary>
|
2021-08-17 15:04:22 -04:00
|
|
|
|
public static event EventHandler ClipboardUpdate
|
|
|
|
|
{
|
|
|
|
|
add
|
|
|
|
|
{
|
|
|
|
|
lock (objectLock)
|
|
|
|
|
{
|
2021-08-26 19:28:15 -04:00
|
|
|
|
listener ??= new ListenerWindow();
|
2021-08-17 15:04:22 -04:00
|
|
|
|
InternalClipboardUpdate += value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
remove
|
|
|
|
|
{
|
|
|
|
|
lock (objectLock)
|
|
|
|
|
{
|
|
|
|
|
InternalClipboardUpdate -= value;
|
2021-08-26 19:28:15 -04:00
|
|
|
|
if (InternalClipboardUpdate is null || InternalClipboardUpdate.GetInvocationList().Length == 0)
|
2021-08-17 15:04:22 -04:00
|
|
|
|
listener = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 12:20:13 -05:00
|
|
|
|
private static event EventHandler? InternalClipboardUpdate;
|
|
|
|
|
|
|
|
|
|
/// <summary>Gets the <see cref="IComDataObject"/> instance from the Windows Clipboard.</summary>
|
|
|
|
|
/// <value>A <see cref="IComDataObject"/> instance.</value>
|
|
|
|
|
public static IComDataObject CurrentDataObject
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
Init();
|
|
|
|
|
int n = stdRetryCnt;
|
|
|
|
|
HRESULT hr = HRESULT.S_OK;
|
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
|
|
{
|
|
|
|
|
hr = OleGetClipboard(out var idata);
|
|
|
|
|
if (hr.Succeeded)
|
|
|
|
|
return idata;
|
|
|
|
|
if (i < n)
|
|
|
|
|
System.Threading.Thread.Sleep(stdRetryDelay);
|
|
|
|
|
}
|
|
|
|
|
throw hr.GetException();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-21 11:42:39 -05:00
|
|
|
|
|
2020-07-26 15:54:57 -04:00
|
|
|
|
/// <summary>Retrieves the currently supported clipboard formats.</summary>
|
|
|
|
|
/// <value>A sequence of the currently supported formats.</value>
|
2022-01-06 17:11:41 -05:00
|
|
|
|
public static IEnumerable<uint> CurrentlySupportedFormats
|
2020-07-26 15:54:57 -04:00
|
|
|
|
{
|
|
|
|
|
get
|
2019-01-21 11:42:39 -05:00
|
|
|
|
{
|
2020-07-26 15:54:57 -04:00
|
|
|
|
GetUpdatedClipboardFormats(null, 0, out var cnt);
|
|
|
|
|
var fmts = new uint[cnt];
|
|
|
|
|
Win32Error.ThrowLastErrorIfFalse(GetUpdatedClipboardFormats(fmts, (uint)fmts.Length, out cnt));
|
2022-01-06 17:11:41 -05:00
|
|
|
|
return fmts.Take((int)cnt).ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-11 14:26:40 -05:00
|
|
|
|
/// <summary>Retrieves the clipboard sequence number for the current window station.</summary>
|
|
|
|
|
/// <returns>
|
|
|
|
|
/// The clipboard sequence number. If you do not have <c>WINSTA_ACCESSCLIPBOARD</c> access to the window station, the function
|
|
|
|
|
/// returns zero.
|
|
|
|
|
/// </returns>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// The system keeps a serial number for the clipboard for each window station. This number is incremented whenever the contents of
|
|
|
|
|
/// the clipboard change or the clipboard is emptied. You can track this value to determine whether the clipboard contents have
|
|
|
|
|
/// changed and optimize creating DataObjects. If clipboard rendering is delayed, the sequence number is not incremented until the
|
|
|
|
|
/// changes are rendered.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public static uint SequenceNumber => GetClipboardSequenceNumber();
|
2023-01-10 17:51:13 -05:00
|
|
|
|
|
2023-01-29 12:20:13 -05:00
|
|
|
|
/// <summary>Clears the clipboard of any data or formatting.</summary>
|
|
|
|
|
public static void Clear() => SetDataObject(null);
|
|
|
|
|
|
|
|
|
|
/// <summary>Puts a list of shell items onto the clipboard.</summary>
|
|
|
|
|
/// <param name="shellItems">The sequence of shell items. The PIDL of each shell item must be absolute.</param>
|
|
|
|
|
public static IComDataObject CreateDataObjectFromShellItems(params ShellItem[] shellItems) => shellItems.Length == 0 ? CreateEmptyDataObject() : new ShellItemArray(shellItems).ToDataObject();
|
2023-01-10 17:51:13 -05:00
|
|
|
|
|
2023-01-29 12:20:13 -05:00
|
|
|
|
/// <summary>Puts a list of shell items onto the clipboard.</summary>
|
|
|
|
|
/// <param name="parent">The parent folder instance.</param>
|
|
|
|
|
/// <param name="relativeShellItems">The sequence of shell items relative to <paramref name="parent"/>.</param>
|
|
|
|
|
public static IComDataObject CreateDataObjectFromShellItems(ShellFolder parent, params ShellItem[] relativeShellItems)
|
2022-01-06 17:11:41 -05:00
|
|
|
|
{
|
2023-01-29 12:20:13 -05:00
|
|
|
|
if (parent is null) throw new ArgumentNullException(nameof(parent));
|
|
|
|
|
if (relativeShellItems.Length == 0) return CreateEmptyDataObject();
|
|
|
|
|
SHCreateDataObject(parent.PIDL, relativeShellItems.Select(i => i.PIDL), default, out var dataObj).ThrowIfFailed();
|
|
|
|
|
return dataObj;
|
2019-01-21 11:42:39 -05:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-15 11:38:17 -05:00
|
|
|
|
/// <summary>Creates an empty, writable data object.</summary>
|
|
|
|
|
/// <value>The data object.</value>
|
2023-01-29 12:20:13 -05:00
|
|
|
|
public static IComDataObject CreateEmptyDataObject()
|
|
|
|
|
{
|
|
|
|
|
SHCreateDataObject(ppv: out var writableDataObj).ThrowIfFailed();
|
|
|
|
|
return writableDataObj;
|
|
|
|
|
}
|
2023-01-05 12:28:15 -05:00
|
|
|
|
|
2022-01-06 17:11:41 -05:00
|
|
|
|
/// <summary>Carries out the clipboard shutdown sequence. It also releases any IDataObject instances that were placed on the clipboard.</summary>
|
2023-01-15 11:38:17 -05:00
|
|
|
|
public static void Flush() { Init(); TryMultThenThrowIfFailed(OleFlushClipboard); }
|
2022-01-06 17:11:41 -05:00
|
|
|
|
|
2020-07-26 15:54:57 -04:00
|
|
|
|
/// <summary>Retrieves the window handle of the current owner of the clipboard.</summary>
|
|
|
|
|
/// <returns>
|
|
|
|
|
/// <para>If the function succeeds, the return value is the handle to the window that owns the clipboard.</para>
|
|
|
|
|
/// <para>If the clipboard is not owned, the return value is <c>IntPtr.Zero</c>.</para>
|
|
|
|
|
/// </returns>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// <para>The clipboard can still contain data even if the clipboard is not currently owned.</para>
|
|
|
|
|
/// <para>In general, the clipboard owner is the window that last placed data in clipboard.</para>
|
|
|
|
|
/// </remarks>
|
2022-01-06 17:11:41 -05:00
|
|
|
|
public static HWND GetClipboardOwner() => User32.GetClipboardOwner();
|
2020-07-26 15:54:57 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>Retrieves the first available clipboard format in the specified list.</summary>
|
|
|
|
|
/// <param name="idList">The clipboard formats, in priority order.</param>
|
|
|
|
|
/// <returns>
|
|
|
|
|
/// If the function succeeds, the return value is the first clipboard format in the list for which data is available. If the
|
|
|
|
|
/// clipboard is empty, the return value is 0. If the clipboard contains data, but not in any of the specified formats, the return
|
|
|
|
|
/// value is –1.
|
|
|
|
|
/// </returns>
|
2022-01-11 20:13:44 -05:00
|
|
|
|
public static int GetFirstFormatAvailable(params uint[] idList) => GetPriorityClipboardFormat(idList, idList.Length);
|
2020-07-26 15:54:57 -04:00
|
|
|
|
|
2022-01-06 17:11:41 -05:00
|
|
|
|
/// <summary>Retrieves from the clipboard the name of the specified registered format.</summary>
|
|
|
|
|
/// <param name="formatId">The type of format to be retrieved.</param>
|
|
|
|
|
/// <returns>The format name.</returns>
|
2023-01-05 12:28:15 -05:00
|
|
|
|
public static string GetFormatName(uint formatId) => ShellClipboardFormat.GetName(formatId);
|
2022-01-06 17:11:41 -05:00
|
|
|
|
|
2020-07-26 15:54:57 -04:00
|
|
|
|
/// <summary>Retrieves the handle to the window that currently has the clipboard open.</summary>
|
|
|
|
|
/// <returns>
|
|
|
|
|
/// If the function succeeds, the return value is the handle to the window that has the clipboard open. If no window has the
|
|
|
|
|
/// clipboard open, the return value is <c>IntPtr.Zero</c>.
|
|
|
|
|
/// </returns>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// If an application or DLL specifies a <c>NULL</c> window handle when calling the OpenClipboard function, the clipboard is opened
|
|
|
|
|
/// but is not associated with a window. In such a case, <c>GetOpenClipboardWindow</c> returns <c>IntPtr.Zero</c>.
|
|
|
|
|
/// </remarks>
|
2022-01-06 17:11:41 -05:00
|
|
|
|
public static HWND GetOpenClipboardWindow() => User32.GetOpenClipboardWindow();
|
2020-07-26 15:54:57 -04:00
|
|
|
|
|
2022-01-06 17:11:41 -05:00
|
|
|
|
/// <summary>Determines whether the data object pointer previously placed on the clipboard is still on the clipboard.</summary>
|
|
|
|
|
/// <param name="dataObject">
|
|
|
|
|
/// The IDataObject interface on the data object containing clipboard data of interest, which the caller previously placed on the clipboard.
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <returns><see langword="true"/> on success; otherwise, <see langword="false"/>.</returns>
|
|
|
|
|
public static bool IsCurrentDataObject(IComDataObject dataObject) => OleIsCurrentClipboard(dataObject) == HRESULT.S_OK;
|
2020-07-26 15:54:57 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>Determines whether the clipboard contains data in the specified format.</summary>
|
|
|
|
|
/// <param name="id">A standard or registered clipboard format.</param>
|
|
|
|
|
/// <returns>If the clipboard format is available, the return value is <see langword="true"/>; otherwise <see langword="false"/>.</returns>
|
2023-01-29 12:20:13 -05:00
|
|
|
|
public static bool IsFormatAvailable(uint id) => IsClipboardFormatAvailable(id);
|
2020-07-26 15:54:57 -04:00
|
|
|
|
|
2022-12-20 17:06:37 -05:00
|
|
|
|
/// <summary>Determines whether the clipboard contains data in the specified format.</summary>
|
|
|
|
|
/// <param name="id">A clipboard format string.</param>
|
|
|
|
|
/// <returns>If the clipboard format is available, the return value is <see langword="true"/>; otherwise <see langword="false"/>.</returns>
|
|
|
|
|
public static bool IsFormatAvailable(string id) => IsClipboardFormatAvailable(RegisterFormat(id));
|
|
|
|
|
|
2023-01-11 14:26:40 -05:00
|
|
|
|
// EnumAvailableFormats().Contains(id);
|
2022-01-06 17:11:41 -05:00
|
|
|
|
/// <summary>Registers a new clipboard format. This format can then be used as a valid clipboard format.</summary>
|
|
|
|
|
/// <param name="format">The name of the new format.</param>
|
|
|
|
|
/// <returns>The registered clipboard format identifier.</returns>
|
|
|
|
|
/// <exception cref="System.ArgumentNullException">format</exception>
|
2020-07-26 15:54:57 -04:00
|
|
|
|
/// <remarks>
|
2022-01-06 17:11:41 -05:00
|
|
|
|
/// If a registered format with the specified name already exists, a new format is not registered and the return value identifies the
|
|
|
|
|
/// existing format. This enables more than one application to copy and paste data using the same registered clipboard format. Note
|
|
|
|
|
/// that the format name comparison is case-insensitive.
|
2020-07-26 15:54:57 -04:00
|
|
|
|
/// </remarks>
|
2023-01-05 12:28:15 -05:00
|
|
|
|
public static uint RegisterFormat(string format) => ShellClipboardFormat.Register(format);
|
2020-07-26 15:54:57 -04:00
|
|
|
|
|
2023-01-15 11:38:17 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Places a specific data object onto the clipboard. This makes the data object accessible to the OleGetClipboard function.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="dataObj">
|
|
|
|
|
/// The IDataObject interface on the data object from which the data to be placed on the clipboard can be obtained.
|
|
|
|
|
/// </param>
|
2023-01-29 12:20:13 -05:00
|
|
|
|
public static void SetDataObject(IComDataObject? dataObj)
|
2023-01-05 12:28:15 -05:00
|
|
|
|
{
|
2023-01-29 12:20:13 -05:00
|
|
|
|
Init();
|
|
|
|
|
TryMultThenThrowIfFailed(OleSetClipboard, dataObj);
|
|
|
|
|
if (dataObj is not null)
|
|
|
|
|
Marshal.ReleaseComObject(dataObj);
|
|
|
|
|
Flush();
|
2020-07-26 15:54:57 -04:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-14 18:12:40 -05:00
|
|
|
|
private static void Init() { if (!oleInit) { oleInit = CoInitialize().Succeeded; } }
|
2023-01-11 14:26:40 -05:00
|
|
|
|
|
|
|
|
|
private static bool TryMultThenThrowIfFailed(Func<HRESULT> func, int n = stdRetryCnt)
|
|
|
|
|
{
|
|
|
|
|
HRESULT hr = HRESULT.S_OK;
|
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
|
|
{
|
|
|
|
|
hr = func();
|
2023-01-14 13:36:08 -05:00
|
|
|
|
if (hr.Succeeded)
|
|
|
|
|
return hr == HRESULT.S_OK;
|
|
|
|
|
if (i < n)
|
|
|
|
|
System.Threading.Thread.Sleep(stdRetryDelay);
|
2023-01-11 14:26:40 -05:00
|
|
|
|
}
|
2023-01-14 13:36:08 -05:00
|
|
|
|
throw hr.GetException();
|
2023-01-11 14:26:40 -05:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-29 12:20:13 -05:00
|
|
|
|
private static bool TryMultThenThrowIfFailed(Func<IComDataObject?, HRESULT> func, IComDataObject? o, int n = stdRetryCnt)
|
2023-01-11 14:26:40 -05:00
|
|
|
|
{
|
|
|
|
|
HRESULT hr = HRESULT.S_OK;
|
|
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
|
|
{
|
|
|
|
|
hr = func(o);
|
2023-01-14 13:36:08 -05:00
|
|
|
|
if (hr.Succeeded)
|
|
|
|
|
return hr == HRESULT.S_OK;
|
|
|
|
|
if (i < n)
|
|
|
|
|
System.Threading.Thread.Sleep(stdRetryDelay);
|
2023-01-11 14:26:40 -05:00
|
|
|
|
}
|
2023-01-14 13:36:08 -05:00
|
|
|
|
throw hr.GetException();
|
2023-01-11 14:26:40 -05:00
|
|
|
|
}
|
2022-01-06 17:11:41 -05:00
|
|
|
|
private class ListenerWindow : SystemEventHandler
|
2020-07-26 15:54:57 -04:00
|
|
|
|
{
|
2022-01-06 17:11:41 -05:00
|
|
|
|
protected override bool MessageFilter(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam, out IntPtr lReturn)
|
2020-07-26 15:54:57 -04:00
|
|
|
|
{
|
2021-08-17 15:04:22 -04:00
|
|
|
|
lReturn = default;
|
2021-08-26 19:28:15 -04:00
|
|
|
|
switch (msg)
|
2020-07-26 15:54:57 -04:00
|
|
|
|
{
|
2021-08-26 19:28:15 -04:00
|
|
|
|
case (uint)WindowMessage.WM_DESTROY:
|
2022-01-06 17:11:41 -05:00
|
|
|
|
RemoveClipboardFormatListener(MessageWindowHandle);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case (uint)ClipboardNotificationMessage.WM_CLIPBOARDUPDATE:
|
|
|
|
|
InternalClipboardUpdate?.Invoke(this, EventArgs.Empty);
|
2021-08-26 19:28:15 -04:00
|
|
|
|
break;
|
2020-07-26 15:54:57 -04:00
|
|
|
|
}
|
2021-08-17 15:04:22 -04:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-08-29 17:08:08 -04:00
|
|
|
|
|
2022-01-06 17:11:41 -05:00
|
|
|
|
protected override void OnMessageWindowHandleCreated()
|
2021-08-29 17:08:08 -04:00
|
|
|
|
{
|
2022-01-06 17:11:41 -05:00
|
|
|
|
base.OnMessageWindowHandleCreated();
|
|
|
|
|
AddClipboardFormatListener(MessageWindowHandle);
|
2021-08-29 17:08:08 -04:00
|
|
|
|
}
|
2020-07-26 15:54:57 -04:00
|
|
|
|
}
|
2019-01-21 11:42:39 -05:00
|
|
|
|
}
|
|
|
|
|
}
|