2019-01-21 11:42:39 -05:00
using System ;
using System.Collections.Generic ;
2020-07-26 15:54:57 -04:00
using System.Linq ;
using System.Runtime.InteropServices ;
using System.Runtime.InteropServices.ComTypes ;
2019-01-21 11:42:39 -05:00
using Vanara.Extensions ;
2020-07-26 15:54:57 -04:00
using Vanara.InteropServices ;
using Vanara.PInvoke ;
2022-01-16 19:24:00 -05:00
using static Vanara . PInvoke . Kernel32 ;
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
{
2022-01-06 17:11:41 -05:00
/// <summary>Specifies the text data formats that can be used to query, get and set text data format with Clipboard.</summary>
public enum TextDataFormat
{
/// <summary>Specifies the standard ANSI text format.</summary>
Text ,
/// <summary>Specifies the standard Windows Unicode text format.</summary>
UnicodeText ,
/// <summary>Specifies text consisting of Rich Text Format (RTF) data.</summary>
Rtf ,
/// <summary>Specifies text consisting of HTML data.</summary>
Html ,
/// <summary>Specifies a comma-separated value (CSV) format, which is a common interchange format used by spreadsheets.</summary>
CommaSeparatedValue ,
}
2020-07-26 15:54:57 -04:00
/// <summary>
2023-01-15 11:38:17 -05:00
/// Static class with methods to interact with the Clipboard. This implementation relies exclusively on COM clipboard methods and does not use those from USER32.
2020-07-26 15:54:57 -04:00
/// </summary>
2023-01-15 11:38:17 -05:00
/// <example>
/// Below are two examples of a direct and indirect way to manipulate the clipboard.
/// <code title="Using the NativeClipboard class to set single items.">// Set/get simple text
/// NativeClipboard.SetText(txt, Vanara.Windows.Shell.TextDataFormat.UnicodeText);
/// string getText = NativeClipboard.GetText(Vanara.Windows.Shell.TextDataFormat.UnicodeText);
///
/// // Set/get format as text
/// NativeClipboard.SetData(Shell32.ShellClipboardFormat.CFSTR_FILENAMEW, @"C:\file1.txt");
/// string getFile = (string)NativeClipboard.GetData(Shell32.ShellClipboardFormat.CFSTR_FILENAMEW);
///
/// // Set/get text, html and rtf formats
/// NativeClipboard.SetText("Test", htmlFragment, rtfText); // sets text, html and rtf formats
/// string html = NativeClipboard.GetText(Vanara.Windows.Shell.TextDataFormat.Html);
///
/// // Set/get text, url and html formats for a url
/// NativeClipboard.SetUrl("https://microsoft.com", "Microsoft Home"); // sets text, url and html formats
/// string url = (string)NativeClipboard.GetData(Shell32.ShellClipboardFormat.CFSTR_INETURLW);
///
/// // Set/get string arrays
/// NativeClipboard.SetData(CLIPFORMAT.CF_HDROP, new[] { @"C:\file1.txt", @"C:\file2.txt" });
/// string[] getFiles = (string[])NativeClipboard.GetData(CLIPFORMAT.CF_HDROP);
///
/// // Set/get structures
/// NativeClipboard.SetData("MyRect", new RECT(1, 2, 3, 4));
/// var rect = NativeClipboard.GetData<RECT>("MyRect");
///
/// // Set/get shell items
/// NativeClipboard.SetShellItems(new[] { @"C:\file1.txt", @"C:\file2.txt" }.Select(ShellItem.Open));
/// ShellItemArray getArray = NativeClipboard.GetShellItemArray();</code>
/// <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>
/// <seealso cref="System.IDisposable" />
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 ( ) ;
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 ;
}
}
}
private static event EventHandler InternalClipboardUpdate ;
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
2022-01-06 17:11:41 -05:00
/// <summary>Gets or sets a <see cref="IComDataObject"/> instance from the Windows Clipboard.</summary>
/// <value>A <see cref="IComDataObject"/> instance.</value>
2023-01-10 17:51:13 -05:00
static IComDataObject ReadOnlyDataObject
{
get
{
2023-01-11 14:26:40 -05:00
Init ( ) ;
2023-01-10 17:51:13 -05:00
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 ;
2023-01-14 13:36:08 -05:00
if ( i < n )
System . Threading . Thread . Sleep ( stdRetryDelay ) ;
2023-01-10 17:51:13 -05:00
}
throw hr . GetException ( ) ;
}
}
static IComDataObject WritableDataObj
2022-01-06 17:11:41 -05:00
{
get
{
2023-01-15 11:38:17 -05:00
SHCreateDataObject ( ppv : out var writableDataObj ) . ThrowIfFailed ( ) ;
2023-01-10 17:51:13 -05:00
return writableDataObj ;
2019-01-21 11:42:39 -05:00
}
2023-01-07 23:46:50 -05:00
set
{
2023-01-11 14:26:40 -05:00
Init ( ) ;
2023-01-10 17:51:13 -05:00
TryMultThenThrowIfFailed ( OleSetClipboard , value ) ;
2023-01-24 18:11:52 -05:00
if ( value is not null )
Marshal . ReleaseComObject ( value ) ;
2023-01-07 23:46:50 -05:00
Flush ( ) ;
}
2019-01-21 11:42:39 -05:00
}
2023-01-07 23:46:50 -05:00
/// <summary>Clears the clipboard of any data or formatting.</summary>
2023-01-10 17:51:13 -05:00
public static void Clear ( ) = > WritableDataObj = null ;
2023-01-07 23:46:50 -05:00
2023-01-15 11:38:17 -05:00
/// <summary>Creates an empty, writable data object.</summary>
/// <value>The data object.</value>
public static IComDataObject CreateEmptyDataObject ( ) = > WritableDataObj ;
2023-01-05 12:28:15 -05:00
/// <summary>Enumerates the data formats currently available on the clipboard.</summary>
/// <returns>An enumeration of the data formats currently available on the clipboard.</returns>
/// <remarks>
/// <para>
/// The <c>EnumFormats</c> function enumerates formats in the order that they were placed on the clipboard. If you are copying
/// information to the clipboard, add clipboard objects in order from the most descriptive clipboard format to the least descriptive
/// clipboard format. If you are pasting information from the clipboard, retrieve the first clipboard format that you can handle.
/// That will be the most descriptive clipboard format that you can handle.
/// </para>
/// <para>
/// The system provides automatic type conversions for certain clipboard formats. In the case of such a format, this function
/// enumerates the specified format, then enumerates the formats to which it can be converted.
/// </para>
/// </remarks>
2023-01-10 17:51:13 -05:00
public static IEnumerable < uint > EnumAvailableFormats ( ) = > ReadOnlyDataObject . EnumFormats ( ) . Select ( f = > unchecked ( ( uint ) f . cfFormat ) ) ;
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
2022-01-22 17:35:37 -05:00
/// <summary>Obtains data from the clipboard.</summary>
/// <param name="formatId">Specifies the particular clipboard format of interest.</param>
/// <param name="aspect">
/// Indicates how much detail should be contained in the rendering. This parameter should be one of the DVASPECT enumeration values.
/// A single clipboard format can support multiple aspects or views of the object. Most data and presentation transfer and caching
/// methods pass aspect information. For example, a caller might request an object's iconic picture, using the metafile clipboard
/// format to retrieve it. Note that only one DVASPECT value can be used in dwAspect. That is, dwAspect cannot be the result of a
/// Boolean OR operation on several DVASPECT values.
/// </param>
/// <param name="index">
/// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the
/// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored.
/// </param>
2023-01-14 18:12:40 -05:00
/// <returns>
/// <para>The object associated with the request. If no object can be determined, a <see cref="byte"/>[] is returned.</para>
/// <para>Conversion for different clipboard formats is as follows:</para>
/// <list type="table">
/// <listheader>
/// <term>Format</term>
/// <term>Return Type</term>
/// </listheader>
/// <item>
/// <description><see cref="CLIPFORMAT.CF_HDROP"/>, <see cref="ShellClipboardFormat.CFSTR_FILENAMEMAPA"/>, <see cref="ShellClipboardFormat.CFSTR_FILENAMEMAPW"/></description>
/// <description><see cref="string"/>[]</description>
/// </item>
/// <item>
/// <description><see cref="CLIPFORMAT.CF_BITMAP"/></description>
/// <description><see cref="HBITMAP"/></description>
/// </item>
/// <item>
/// <description><see cref="CLIPFORMAT.CF_LOCALE"/></description>
/// <description><see cref="LCID"/></description>
/// </item>
/// <item>
/// <description>
/// <see cref="CLIPFORMAT.CF_OEMTEXT"/>, <see cref="CLIPFORMAT.CF_TEXT"/>, <see cref="CLIPFORMAT.CF_UNICODETEXT"/>, <see
/// cref="ShellClipboardFormat.CF_CSV"/>, <see cref="ShellClipboardFormat.CF_HTML"/>, <see cref="ShellClipboardFormat.CF_RTF"/>, <see
/// cref="ShellClipboardFormat.CF_RTFNOOBJS"/>, <see cref="ShellClipboardFormat.CFSTR_FILENAMEA"/>, <see
/// cref="ShellClipboardFormat.CFSTR_FILENAMEW"/>, <see cref="ShellClipboardFormat.CFSTR_INETURLA"/>, <see
/// cref="ShellClipboardFormat.CFSTR_INETURLW"/>, <see cref="ShellClipboardFormat.CFSTR_INVOKECOMMAND_DROPPARAM"/>, <see
/// cref="ShellClipboardFormat.CFSTR_MOUNTEDVOLUME"/>, <see cref="ShellClipboardFormat.CFSTR_PRINTERGROUP"/>, <see cref="ShellClipboardFormat.CFSTR_SHELLURL"/>
/// </description>
/// <description><see cref="string"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_DROPDESCRIPTION"/></description>
/// <description><see cref="DROPDESCRIPTION"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_FILE_ATTRIBUTES_ARRAY"/></description>
/// <description><see cref="FILE_ATTRIBUTES_ARRAY"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_FILECONTENTS"/></description>
/// <description><see cref="IStream"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_FILEDESCRIPTORA"/>, <see cref="ShellClipboardFormat.CFSTR_FILEDESCRIPTORW"/></description>
/// <description><see cref="FILEGROUPDESCRIPTOR"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_INDRAGLOOP"/></description>
/// <description><see cref="BOOL"/></description>
/// </item>
/// <item>
/// <description>
/// <see cref="ShellClipboardFormat.CFSTR_LOGICALPERFORMEDDROPEFFECT"/>, <see cref="ShellClipboardFormat.CFSTR_PASTESUCCEEDED"/>,
/// <see cref="ShellClipboardFormat.CFSTR_PERFORMEDDROPEFFECT"/>, <see cref="ShellClipboardFormat.CFSTR_PREFERREDDROPEFFECT"/>
/// </description>
/// <description><see cref="DROPEFFECT"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_NETRESOURCES"/></description>
/// <description><see cref="NRESARRAY"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_SHELLDROPHANDLER"/>, <see cref="ShellClipboardFormat.CFSTR_TARGETCLSID"/></description>
/// <description><see cref="Guid"/></description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_SHELLIDLIST"/></description>
/// <description><see cref="CIDA"/>
/// <para>
/// <note type="note">It is prefered to use the <see cref="SHCreateShellItemArrayFromDataObject(IDataObject)"/> method to get a list
/// of shell items from an <see cref="IDataObject"/>.</note>
/// </para>
/// </description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_SHELLIDLISTOFFSET"/></description>
/// <description><see cref="POINT"/>[]</description>
/// </item>
/// <item>
/// <description><see cref="ShellClipboardFormat.CFSTR_UNTRUSTEDDRAGDROP"/>, <see cref="ShellClipboardFormat.CFSTR_ZONEIDENTIFIER"/></description>
/// <description><see cref="uint"/></description>
/// </item>
/// </list>
/// </returns>
2022-01-22 17:35:37 -05:00
/// <exception cref="System.InvalidOperationException">Unrecognized TYMED value.</exception>
public static object GetData ( uint formatId , DVASPECT aspect = DVASPECT . DVASPECT_CONTENT , int index = - 1 ) = >
2023-01-10 17:51:13 -05:00
ReadOnlyDataObject . GetData ( formatId , aspect , index ) ;
2022-01-22 17:35:37 -05:00
2023-01-14 13:36:08 -05:00
/// <summary>Obtains data from the clipboard.</summary>
/// <param name="format">Specifies the particular clipboard format of interest.</param>
/// <param name="aspect">
/// Indicates how much detail should be contained in the rendering. This parameter should be one of the DVASPECT enumeration values.
/// A single clipboard format can support multiple aspects or views of the object. Most data and presentation transfer and caching
/// methods pass aspect information. For example, a caller might request an object's iconic picture, using the metafile clipboard
/// format to retrieve it. Note that only one DVASPECT value can be used in dwAspect. That is, dwAspect cannot be the result of a
/// Boolean OR operation on several DVASPECT values.
/// </param>
/// <param name="index">
/// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the
/// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored.
/// </param>
2023-01-14 18:12:40 -05:00
/// <returns>
/// The object associated with the request. If no object can be determined, a <see cref="byte"/>[] is returned. See the return
/// section of <see cref="NativeClipboard.GetData(uint, DVASPECT, int)"/> for more details.
/// </returns>
2023-01-14 13:36:08 -05:00
/// <exception cref="System.InvalidOperationException">Unrecognized TYMED value.</exception>
public static object GetData ( string format , DVASPECT aspect = DVASPECT . DVASPECT_CONTENT , int index = - 1 ) = >
ReadOnlyDataObject . GetData ( format , aspect , index ) ;
2022-01-22 17:35:37 -05:00
/// <summary>Obtains data from the clipboard.</summary>
/// <typeparam name="T">The type of the object being retrieved.</typeparam>
/// <param name="formatId">Specifies the particular clipboard format of interest.</param>
/// <param name="index">
/// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the
/// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored.
/// </param>
/// <returns>The object associated with the request. If no object can be determined, <c>default(T)</c> is returned.</returns>
2023-01-10 17:51:13 -05:00
public static T GetData < T > ( uint formatId , int index = - 1 ) = > ReadOnlyDataObject . GetData < T > ( formatId , index ) ;
2022-01-22 17:35:37 -05:00
2023-01-14 13:36:08 -05:00
/// <summary>Obtains data from the clipboard.</summary>
/// <typeparam name="T">The type of the object being retrieved.</typeparam>
/// <param name="format">Specifies the particular clipboard format of interest.</param>
/// <param name="index">
/// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the
/// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored.
/// </param>
/// <returns>The object associated with the request. If no object can be determined, <c>default(T)</c> is returned.</returns>
public static T GetData < T > ( string format , int index = - 1 ) = > ReadOnlyDataObject . GetData < T > ( format , index ) ;
2022-12-20 17:06:37 -05:00
/// <summary>
/// This is used when a group of files in CF_HDROP (FileDrop) format is being renamed as well as transferred. The data consists of an
/// array that contains a new name for each file, in the same order that the files are listed in the accompanying CF_HDROP format.
/// The format of the character array is the same as that used by CF_HDROP to list the transferred files.
/// </summary>
/// <returns>A list of strings containing a new name for each file.</returns>
public static string [ ] GetFileNameMap ( )
{
if ( IsFormatAvailable ( ShellClipboardFormat . CFSTR_FILENAMEMAPW ) )
2023-01-10 17:51:13 -05:00
return ReadOnlyDataObject . GetData ( ShellClipboardFormat . CFSTR_FILENAMEMAPW ) as string [ ] ;
2022-12-20 17:06:37 -05:00
else if ( IsFormatAvailable ( ShellClipboardFormat . CFSTR_FILENAMEMAPA ) )
2023-01-10 17:51:13 -05:00
return ReadOnlyDataObject . GetData ( ShellClipboardFormat . CFSTR_FILENAMEMAPA ) as string [ ] ;
2022-12-20 17:06:37 -05:00
return new string [ 0 ] ;
}
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-12-20 17:06:37 -05:00
/// <summary>Gets the shell item array associated with the data object, if possible.</summary>
/// <returns>The <see cref="ShellItemArray"/> associated with the data object, if set. Otherwise, <see langword="null"/>.</returns>
2023-01-10 17:51:13 -05:00
public static ShellItemArray GetShellItemArray ( ) = > IsFormatAvailable ( ShellClipboardFormat . CFSTR_SHELLIDLIST ) ? ShellItemArray . FromDataObject ( ReadOnlyDataObject ) : null ;
2022-12-20 17:06:37 -05:00
2023-01-05 12:28:15 -05:00
/// <summary>Gets the text from the native Clipboard in the specified format.</summary>
/// <param name="formatId">A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.</param>
/// <returns>The string value or <see langword="null"/> if the format is not available.</returns>
2023-01-14 18:12:40 -05:00
public static string GetText ( TextDataFormat formatId = TextDataFormat . UnicodeText ) = > GetData ( Txt2Id ( formatId ) ) as string ;
2023-01-05 12:28:15 -05: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-11 14:26:40 -05:00
public static bool IsFormatAvailable ( uint id ) = > ReadOnlyDataObject . IsFormatAvailable ( 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
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
/// <param name="data">The binary data in the specified format.</param>
/// <exception cref="System.ArgumentNullException">data</exception>
2023-01-11 14:26:40 -05:00
public static void SetBinaryData ( uint formatId , byte [ ] data ) = > Setter ( i = > i . SetData ( formatId , data ) ) ;
2020-07-26 15:54:57 -04:00
2023-01-11 19:28:51 -05:00
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
/// <param name="data">The data in the format dictated by <paramref name="formatId"/>.</param>
/// <param name="index">
/// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the
/// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored.
/// </param>
/// <param name="aspect">
/// Indicates how much detail should be contained in the rendering. This parameter should be one of the DVASPECT enumeration values.
/// A single clipboard format can support multiple aspects or views of the object. Most data and presentation transfer and caching
/// methods pass aspect information. For example, a caller might request an object's iconic picture, using the metafile clipboard
/// format to retrieve it. Note that only one DVASPECT value can be used in dwAspect. That is, dwAspect cannot be the result of a
/// Boolean OR operation on several DVASPECT values.
/// </param>
public static void SetData ( uint formatId , object data , int index = - 1 , DVASPECT aspect = DVASPECT . DVASPECT_CONTENT ) = > Setter ( i = > i . SetData ( formatId , data , aspect , index ) ) ;
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="format">The clipboard format.</param>
/// <param name="data">The data in the format dictated by <paramref name="format"/>.</param>
/// <param name="index">
/// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the
/// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored.
/// </param>
/// <param name="aspect">
/// Indicates how much detail should be contained in the rendering. This parameter should be one of the DVASPECT enumeration values.
/// A single clipboard format can support multiple aspects or views of the object. Most data and presentation transfer and caching
/// methods pass aspect information. For example, a caller might request an object's iconic picture, using the metafile clipboard
/// format to retrieve it. Note that only one DVASPECT value can be used in dwAspect. That is, dwAspect cannot be the result of a
/// Boolean OR operation on several DVASPECT values.
/// </param>
public static void SetData ( string format , object data , int index = - 1 , DVASPECT aspect = DVASPECT . DVASPECT_CONTENT ) = > SetData ( RegisterFormat ( format ) , data , index , aspect ) ;
2020-07-26 15:54:57 -04:00
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
/// <param name="data">The data in the format dictated by <paramref name="formatId"/>.</param>
2023-01-11 14:26:40 -05:00
public static void SetData < T > ( uint formatId , T data ) where T : struct = > Setter ( i = > i . SetData ( formatId , data ) ) ;
2020-07-26 15:54:57 -04:00
2023-01-11 19:28:51 -05:00
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="format">The clipboard format.</param>
/// <param name="data">The data in the format dictated by <paramref name="format"/>.</param>
public static void SetData < T > ( string format , T data ) where T : struct = > SetData ( RegisterFormat ( format ) , data ) ;
2020-07-26 15:54:57 -04:00
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
/// <param name="values">The data in the format dictated by <paramref name="formatId"/>.</param>
2023-01-05 12:28:15 -05:00
public static void SetData < T > ( uint formatId , IEnumerable < T > values ) where T : struct
2020-07-26 15:54:57 -04:00
{
2022-12-30 21:39:34 -05:00
var pMem = SafeMoveableHGlobalHandle . CreateFromList ( values ) ;
2020-07-26 15:54:57 -04:00
Win32Error . ThrowLastErrorIfInvalid ( pMem ) ;
2023-01-11 14:26:40 -05:00
Setter ( i = > i . SetData ( formatId , pMem ) ) ;
2020-07-26 15:54:57 -04:00
}
2023-01-11 19:28:51 -05:00
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="format">The clipboard format.</param>
/// <param name="values">The data in the format dictated by <paramref name="format"/>.</param>
public static void SetData < T > ( string format , IEnumerable < T > values ) where T : struct = > SetData ( RegisterFormat ( format ) , values ) ;
2020-07-26 15:54:57 -04:00
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
/// <param name="values">The list of strings.</param>
/// <param name="packing">The packing type for the strings.</param>
/// <param name="charSet">The character set to use for the strings.</param>
2023-01-05 12:28:15 -05:00
public static void SetData ( uint formatId , IEnumerable < string > values , StringListPackMethod packing = StringListPackMethod . Concatenated , CharSet charSet = CharSet . Auto )
2020-07-26 15:54:57 -04:00
{
2022-12-30 21:39:34 -05:00
var pMem = SafeMoveableHGlobalHandle . CreateFromStringList ( values , packing , charSet ) ;
2020-07-26 15:54:57 -04:00
Win32Error . ThrowLastErrorIfInvalid ( pMem ) ;
2023-01-11 14:26:40 -05:00
Setter ( i = > i . SetData ( formatId , pMem ) ) ;
2023-01-05 12:28:15 -05:00
}
2023-01-11 19:28:51 -05:00
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
/// <param name="format">The clipboard format.</param>
/// <param name="values">The data in the format dictated by <paramref name="format"/>.</param>
/// <param name="packing">The packing type for the strings.</param>
/// <param name="charSet">The character set to use for the strings.</param>
public static void SetData ( string format , IEnumerable < string > values , StringListPackMethod packing = StringListPackMethod . Concatenated , CharSet charSet = CharSet . Auto ) = >
SetData ( RegisterFormat ( format ) , values , packing , charSet ) ;
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>
public static void SetDataObject ( IComDataObject dataObj ) = > WritableDataObj = dataObj ? ? throw new ArgumentNullException ( nameof ( dataObj ) ) ;
2023-01-05 12:28:15 -05:00
/// <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>
2023-01-10 17:51:13 -05:00
public static void SetShellItems ( IEnumerable < ShellItem > shellItems ) = > WritableDataObj = ( shellItems is ShellItemArray shia ? shia : new ShellItemArray ( shellItems ) ) . ToDataObject ( ) ;
2023-01-05 12:28:15 -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 void SetShellItems ( ShellFolder parent , IEnumerable < ShellItem > relativeShellItems )
{
if ( parent is null ) throw new ArgumentNullException ( nameof ( parent ) ) ;
if ( relativeShellItems is null ) throw new ArgumentNullException ( nameof ( relativeShellItems ) ) ;
SHCreateDataObject ( parent . PIDL , relativeShellItems . Select ( i = > i . PIDL ) , default , out var dataObj ) . ThrowIfFailed ( ) ;
2023-01-11 14:26:40 -05:00
WritableDataObj = dataObj ;
2020-07-26 15:54:57 -04:00
}
/// <summary>Sets multiple text types to the Clipboard.</summary>
/// <param name="text">The Unicode Text value.</param>
/// <param name="htmlText">The HTML text value. If <see langword="null"/>, this format will not be set.</param>
/// <param name="rtfText">The Rich Text Format value. If <see langword="null"/>, this format will not be set.</param>
2023-01-24 09:50:56 -05:00
public static void SetText ( string text , string htmlText = null , string rtfText = null ) = > Setter ( ido = >
2020-07-26 15:54:57 -04:00
{
2023-01-24 09:50:56 -05:00
if ( text is not null ) ido . SetData ( CLIPFORMAT . CF_UNICODETEXT , text ) ;
if ( htmlText is not null ) ido . SetData ( ShellClipboardFormat . CF_HTML , htmlText ) ;
if ( rtfText is not null ) ido . SetData ( ShellClipboardFormat . CF_RTF , rtfText ) ;
} ) ;
2020-07-26 15:54:57 -04:00
/// <summary>Sets a specific text type to the Clipboard.</summary>
/// <param name="value">The text value.</param>
/// <param name="format">The clipboard text format to set.</param>
2023-01-11 14:26:40 -05:00
public static void SetText ( string value , TextDataFormat format ) = > Setter ( i = > i . SetData ( Txt2Id ( format ) , value ) ) ;
2020-07-26 15:54:57 -04:00
/// <summary>Sets a URL with optional title to the clipboard.</summary>
/// <param name="url">The URL.</param>
/// <param name="title">The title. This value can be <see langword="null"/>.</param>
/// <exception cref="ArgumentNullException">url</exception>
2023-01-11 14:26:40 -05:00
public static void SetUrl ( string url , string title = null ) = > Setter ( i = > i . SetUrl ( url , title ) ) ;
2020-07-26 15:54:57 -04:00
2023-01-05 12:28:15 -05:00
/// <summary>Obtains data from a source data object.</summary>
/// <typeparam name="T">The type of the object being retrieved.</typeparam>
/// <param name="formatId">Specifies the particular clipboard format of interest.</param>
/// <param name="obj">The object associated with the request. If no object can be determined, <c>default(T)</c> is returned.</param>
/// <param name="index">
/// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the
/// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored.
/// </param>
/// <returns><see langword="true"/> if data is available and retrieved; otherwise <see langword="false"/>.</returns>
2023-01-10 17:51:13 -05:00
public static bool TryGetData < T > ( uint formatId , out T obj , int index = - 1 ) = > ReadOnlyDataObject . TryGetData ( formatId , out obj , index ) ;
2023-01-05 12:28:15 -05: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 void Setter ( Action < IComDataObject > action )
{
var ido = WritableDataObj ;
action ( ido ) ;
WritableDataObj = ido ;
}
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
}
private static bool TryMultThenThrowIfFailed ( Func < IComDataObject , HRESULT > func , IComDataObject o , int n = stdRetryCnt )
{
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
}
2023-01-14 13:36:08 -05:00
2023-01-05 12:28:15 -05:00
private static uint Txt2Id ( TextDataFormat tf ) = > tf switch
2022-01-06 17:11:41 -05:00
{
2023-01-05 12:28:15 -05:00
TextDataFormat . Text = > CLIPFORMAT . CF_TEXT ,
TextDataFormat . UnicodeText = > CLIPFORMAT . CF_UNICODETEXT ,
TextDataFormat . Rtf = > ShellClipboardFormat . Register ( ShellClipboardFormat . CF_RTF ) ,
TextDataFormat . Html = > ShellClipboardFormat . Register ( ShellClipboardFormat . CF_HTML ) ,
TextDataFormat . CommaSeparatedValue = > ShellClipboardFormat . Register ( ShellClipboardFormat . CF_CSV ) ,
_ = > throw new ArgumentOutOfRangeException ( nameof ( tf ) ) ,
} ;
2020-07-26 15:54:57 -04: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
}
}