More BREAKING CHANGES to NativeClipboard. (Sorry!) All atomic methods to set data have been removed due to inconsistencies and design challenges. New model forces all setting and getting to be done through IDataObject and it's methods and extensions. See documentation for class for example. Addresses #355.

pull/375/head
David Hall 2023-01-29 10:20:13 -07:00
parent 716b9e4036
commit dd1af5f67c
3 changed files with 123 additions and 453 deletions

View File

@ -23,6 +23,8 @@ namespace Vanara.PInvoke
{
public static partial class Shell32
{
private static readonly Lazy<TYMED> AllTymed = new(() => Enum.GetValues(typeof(TYMED)).Cast<TYMED>().Aggregate((a, b) => a | b));
/// <summary>
/// <para>Values used with the DROPDESCRIPTION structure to specify the drop image.</para>
/// </summary>
@ -390,8 +392,6 @@ namespace Vanara.PInvoke
}
}
private static Encoding GetEncoding(ClipCorrespondingTypeAttribute attr) => (Encoding)Activator.CreateInstance(attr.EncodingType ?? typeof(UnicodeEncoding));
/// <summary>Obtains data from a source data object.</summary>
/// <typeparam name="T">The type of the object being retrieved.</typeparam>
/// <param name="dataObj">The data object.</param>
@ -435,6 +435,21 @@ namespace Vanara.PInvoke
return hmem.ToType<T>(charSet == CharSet.Auto ? (StringHelper.GetCharSize(charSet) == 1 ? CharSet.Ansi : CharSet.Unicode) : charSet);
}
/// <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(this IDataObject dataObj)
{
if (dataObj.IsFormatAvailable(RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPW)))
return dataObj.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPW) as string[];
else if (dataObj.IsFormatAvailable(RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPA)))
return dataObj.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPA) as string[];
return new string[0];
}
/// <summary>Gets an HTML string from bytes returned from the clipboard.</summary>
/// <param name="bytes">The bytes from the clipboard.</param>
/// <returns>The string representing the HTML.</returns>
@ -467,6 +482,18 @@ namespace Vanara.PInvoke
return Encoding.UTF8.GetString(bytes, startFrag, endFrag - startFrag);
}
/// <summary>Gets the text from the native Clipboard in the specified format.</summary>
/// <param name="dataObj">The data object.</param>
/// <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>
public static string GetText(this IDataObject dataObj, uint formatId) => GetData(dataObj, formatId)?.ToString();
/// <summary>Gets the text from the native Clipboard in the specified format.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="format">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>
public static string GetText(this IDataObject dataObj, string format) => GetData(dataObj, format)?.ToString();
/// <summary>
/// Determines whether the data object is capable of rendering the data described in the parameters. Objects attempting a paste or
/// drop operation can call this method before calling GetData to get an indication of whether the operation may be successful.
@ -487,8 +514,6 @@ namespace Vanara.PInvoke
return dataObj.QueryGetData(ref formatetc) == HRESULT.S_OK;
}
private static readonly Lazy<TYMED> AllTymed = new(() => Enum.GetValues(typeof(TYMED)).Cast<TYMED>().Aggregate((a, b) => a | b));
/// <summary>Transfer a data stream to an object that contains a data source.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="format">Specifies the particular clipboard format of interest.</param>
@ -557,7 +582,7 @@ namespace Vanara.PInvoke
//if (CLIPFORMAT.CF_TEXT.Equals(formatId))
// mbr = ClipboardBytesFormatter.Instance.Write(UnicodeToAnsiBytes(str));
//else
mbr = ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, GetEncoding(attr), true));
mbr = ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, GetEncoding(attr), true));
break;
case IEnumerable<string> strlist:
@ -652,6 +677,18 @@ namespace Vanara.PInvoke
public static void SetData<T>(this IDataObject dataObj, uint formatId, T obj, int index = -1) where T : struct =>
SetData(dataObj, formatId, SafeMoveableHGlobalHandle.CreateFromStructure(obj), DVASPECT.DVASPECT_CONTENT, index);
/// <summary>Sets multiple text types to the data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <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>
public static void SetText(this IDataObject dataObj, string text, string htmlText = null, string rtfText = null)
{
if (text is not null) dataObj.SetData(CLIPFORMAT.CF_UNICODETEXT, text);
if (htmlText is not null) dataObj.SetData(ShellClipboardFormat.CF_HTML, htmlText);
if (rtfText is not null) dataObj.SetData(ShellClipboardFormat.CF_RTF, rtfText);
}
/// <summary>Sets a URL with optional title to a data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="url">The URL.</param>
@ -679,7 +716,8 @@ namespace Vanara.PInvoke
public static bool TryGetData<T>(this IDataObject dataObj, uint formatId, out T obj, int index = -1)
{
if (IsFormatAvailable(dataObj, formatId))
try {
try
{
var charSet = GetCharSet(ShellClipboardFormat.clipFmtIds.Value.TryGetValue(formatId, out (string name, ClipCorrespondingTypeAttribute attr) data) ? data.attr : null);
obj = GetData<T>(dataObj, formatId, index, charSet);
return true;
@ -715,6 +753,8 @@ namespace Vanara.PInvoke
return charSet;
}
private static Encoding GetEncoding(ClipCorrespondingTypeAttribute attr) => (Encoding)Activator.CreateInstance(attr.EncodingType ?? typeof(UnicodeEncoding));
/// <summary>
/// <para>
/// Used with the CFSTR_SHELLIDLIST clipboard format to transfer the pointer to an item identifier list (PIDL) of one or more Shell

View File

@ -1,5 +1,4 @@
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using NUnit.Framework;
using System;
using System.IO;
using System.Linq;
@ -75,11 +74,10 @@ namespace Vanara.Windows.Shell.Tests
[Test]
public void GetSetShellItems2()
{
Clipboard.Clear();
ShellItem[] items = Array.ConvertAll(files, f => new ShellItem(f));
Clipboard.SetShellItems(items);
var shArray = Clipboard.GetShellItemArray();
Assert.That(shArray.Count, Is.GreaterThan(0));
Clipboard.SetDataObject(Clipboard.CreateDataObjectFromShellItems(items));
var shArray = ShellItemArray.FromDataObject(Clipboard.CurrentDataObject);
Assert.That(shArray.Count, Is.EqualTo(items.Length));
CollectionAssert.AreEquivalent(files, shArray.Select(s => s.FileSystemPath));
}
@ -242,8 +240,8 @@ namespace Vanara.Windows.Shell.Tests
public void SetNativeTextHtmlTest()
{
SHCreateDataObject(ppv: out var ido).ThrowIfFailed();
ido.SetData(ShellClipboardFormat.Register(ShellClipboardFormat.CF_HTML), html);
var outVal = ido.GetData(ShellClipboardFormat.Register(ShellClipboardFormat.CF_HTML));
ido.SetData(ShellClipboardFormat.CF_HTML, html);
var outVal = ido.GetData(ShellClipboardFormat.CF_HTML);
Assert.That(outVal, Is.EqualTo(html));
}
@ -251,22 +249,29 @@ namespace Vanara.Windows.Shell.Tests
public void SetNativeTextMultTest()
{
const string stxt = "112233";
Clipboard.SetText(stxt);
Assert.That(Clipboard.GetText(TextDataFormat.Text), Is.EqualTo(stxt));
var ido = Clipboard.CreateEmptyDataObject();
ido.SetData(CLIPFORMAT.CF_UNICODETEXT, stxt);
Clipboard.SetDataObject(ido);
Assert.That(Clipboard.CurrentDataObject.GetData(CLIPFORMAT.CF_UNICODETEXT), Is.EqualTo(stxt));
Clipboard.SetText(txt, txt);
Assert.That(Clipboard.GetText(TextDataFormat.Text), Is.EqualTo(txt));
Assert.That(Clipboard.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
Assert.That(Clipboard.GetText(TextDataFormat.Html), Is.EqualTo(txt));
TestContext.WriteLine(Clipboard.GetText(TextDataFormat.Html));
ido = Clipboard.CreateEmptyDataObject();
ido.SetText(txt, txt);
Clipboard.SetDataObject(ido);
Assert.That(Clipboard.CurrentDataObject.GetText(CLIPFORMAT.CF_TEXT), Is.EqualTo(txt));
Assert.That(Clipboard.CurrentDataObject.GetText(CLIPFORMAT.CF_UNICODETEXT), Is.EqualTo(txt));
Assert.That(Clipboard.CurrentDataObject.GetText(ShellClipboardFormat.CF_HTML), Is.EqualTo(txt));
TestContext.WriteLine(Clipboard.CurrentDataObject.GetText(ShellClipboardFormat.CF_HTML));
}
[Test]
public void SetNativeTextUnicodeTest()
{
const string txt = @"“00©0è0”";
Clipboard.SetText(txt, TextDataFormat.UnicodeText);
Assert.That(Clipboard.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
var ido = Clipboard.CreateEmptyDataObject();
ido.SetData(CLIPFORMAT.CF_UNICODETEXT, txt);
Clipboard.SetDataObject(ido);
Assert.That(Clipboard.CurrentDataObject.GetText(CLIPFORMAT.CF_UNICODETEXT), Is.EqualTo(txt));
}
//[Test]

View File

@ -1,12 +1,10 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.Shell32;
using static Vanara.PInvoke.User32;
@ -14,70 +12,20 @@ using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
namespace Vanara.Windows.Shell
{
/// <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,
}
/// <summary>
/// Static class with methods to interact with the Clipboard. This implementation relies exclusively on COM clipboard methods and does not use those from USER32.
/// </summary>
/// <summary>Static class with methods to interact with the Clipboard.</summary>
/// <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&lt;RECT&gt;("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" />
public static class NativeClipboard
{
private const int stdRetryCnt = 5;
private const int stdRetryDelay = 100;
private static readonly object objectLock = new();
private static ListenerWindow listener;
private static ListenerWindow? listener;
[ThreadStatic]
private static bool oleInit = false;
@ -103,7 +51,28 @@ namespace Vanara.Windows.Shell
}
}
private static event EventHandler InternalClipboardUpdate;
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();
}
}
/// <summary>Retrieves the currently supported clipboard formats.</summary>
/// <value>A sequence of the currently supported formats.</value>
@ -131,66 +100,31 @@ namespace Vanara.Windows.Shell
/// </remarks>
public static uint SequenceNumber => GetClipboardSequenceNumber();
/// <summary>Gets or sets a <see cref="IComDataObject"/> instance from the Windows Clipboard.</summary>
/// <value>A <see cref="IComDataObject"/> instance.</value>
static IComDataObject ReadOnlyDataObject
{
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();
}
}
static IComDataObject WritableDataObj
{
get
{
SHCreateDataObject(ppv: out var writableDataObj).ThrowIfFailed();
return writableDataObj;
}
set
{
Init();
TryMultThenThrowIfFailed(OleSetClipboard, value);
if (value is not null)
Marshal.ReleaseComObject(value);
Flush();
}
}
/// <summary>Clears the clipboard of any data or formatting.</summary>
public static void Clear() => WritableDataObj = null;
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();
/// <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)
{
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;
}
/// <summary>Creates an empty, writable data object.</summary>
/// <value>The data object.</value>
public static IComDataObject CreateEmptyDataObject() => WritableDataObj;
/// <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>
public static IEnumerable<uint> EnumAvailableFormats() => ReadOnlyDataObject.EnumFormats().Select(f => unchecked((uint)f.cfFormat));
public static IComDataObject CreateEmptyDataObject()
{
SHCreateDataObject(ppv: out var writableDataObj).ThrowIfFailed();
return writableDataObj;
}
/// <summary>Carries out the clipboard shutdown sequence. It also releases any IDataObject instances that were placed on the clipboard.</summary>
public static void Flush() { Init(); TryMultThenThrowIfFailed(OleFlushClipboard); }
@ -206,164 +140,6 @@ namespace Vanara.Windows.Shell
/// </remarks>
public static HWND GetClipboardOwner() => User32.GetClipboardOwner();
/// <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>
/// <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>
/// <exception cref="System.InvalidOperationException">Unrecognized TYMED value.</exception>
public static object GetData(uint formatId, DVASPECT aspect = DVASPECT.DVASPECT_CONTENT, int index = -1) =>
ReadOnlyDataObject.GetData(formatId, aspect, index);
/// <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>
/// <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>
/// <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);
/// <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>
public static T GetData<T>(uint formatId, int index = -1) => ReadOnlyDataObject.GetData<T>(formatId, index);
/// <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);
/// <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))
return ReadOnlyDataObject.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPW) as string[];
else if (IsFormatAvailable(ShellClipboardFormat.CFSTR_FILENAMEMAPA))
return ReadOnlyDataObject.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPA) as string[];
return new string[0];
}
/// <summary>Retrieves the first available clipboard format in the specified list.</summary>
/// <param name="idList">The clipboard formats, in priority order.</param>
/// <returns>
@ -389,15 +165,6 @@ namespace Vanara.Windows.Shell
/// </remarks>
public static HWND GetOpenClipboardWindow() => User32.GetOpenClipboardWindow();
/// <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>
public static ShellItemArray GetShellItemArray() => IsFormatAvailable(ShellClipboardFormat.CFSTR_SHELLIDLIST) ? ShellItemArray.FromDataObject(ReadOnlyDataObject) : null;
/// <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>
public static string GetText(TextDataFormat formatId = TextDataFormat.UnicodeText) => GetData(Txt2Id(formatId)) as string;
/// <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.
@ -408,7 +175,7 @@ namespace Vanara.Windows.Shell
/// <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>
public static bool IsFormatAvailable(uint id) => ReadOnlyDataObject.IsFormatAvailable(id);
public static bool IsFormatAvailable(uint id) => IsClipboardFormatAvailable(id);
/// <summary>Determines whether the clipboard contains data in the specified format.</summary>
/// <param name="id">A clipboard format string.</param>
@ -427,154 +194,23 @@ namespace Vanara.Windows.Shell
/// </remarks>
public static uint RegisterFormat(string format) => ShellClipboardFormat.Register(format);
/// <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>
public static void SetBinaryData(uint formatId, byte[] data) => Setter(i => i.SetData(formatId, data));
/// <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);
/// <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>
public static void SetData<T>(uint formatId, T data) where T : struct => Setter(i => i.SetData(formatId, data));
/// <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);
/// <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>
public static void SetData<T>(uint formatId, IEnumerable<T> values) where T : struct
{
var pMem = SafeMoveableHGlobalHandle.CreateFromList(values);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Setter(i => i.SetData(formatId, pMem));
}
/// <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);
/// <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>
public static void SetData(uint formatId, IEnumerable<string> values, StringListPackMethod packing = StringListPackMethod.Concatenated, CharSet charSet = CharSet.Auto)
{
var pMem = SafeMoveableHGlobalHandle.CreateFromStringList(values, packing, charSet);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Setter(i => i.SetData(formatId, pMem));
}
/// <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);
/// <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));
/// <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 void SetShellItems(IEnumerable<ShellItem> shellItems) => WritableDataObj = (shellItems is ShellItemArray shia ? shia : new ShellItemArray(shellItems)).ToDataObject();
/// <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)
public static void SetDataObject(IComDataObject? dataObj)
{
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();
WritableDataObj = dataObj;
Init();
TryMultThenThrowIfFailed(OleSetClipboard, dataObj);
if (dataObj is not null)
Marshal.ReleaseComObject(dataObj);
Flush();
}
/// <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>
public static void SetText(string text, string htmlText = null, string rtfText = null) => Setter(ido =>
{
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);
});
/// <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>
public static void SetText(string value, TextDataFormat format) => Setter(i => i.SetData(Txt2Id(format), value));
/// <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>
public static void SetUrl(string url, string title = null) => Setter(i => i.SetUrl(url, title));
/// <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>
public static bool TryGetData<T>(uint formatId, out T obj, int index = -1) => ReadOnlyDataObject.TryGetData(formatId, out obj, index);
private static void Init() { if (!oleInit) { oleInit = CoInitialize().Succeeded; } }
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;
@ -589,7 +225,7 @@ namespace Vanara.Windows.Shell
throw hr.GetException();
}
private static bool TryMultThenThrowIfFailed(Func<IComDataObject, HRESULT> func, IComDataObject o, int n = stdRetryCnt)
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++)
@ -602,17 +238,6 @@ namespace Vanara.Windows.Shell
}
throw hr.GetException();
}
private static uint Txt2Id(TextDataFormat tf) => tf switch
{
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)),
};
private class ListenerWindow : SystemEventHandler
{
protected override bool MessageFilter(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam, out IntPtr lReturn)