Tons of fixes and updates to IDataObject and Clipboard methods and wrapper classes.

pull/363/head
David Hall 2023-01-05 10:28:15 -07:00
parent 03ef05b415
commit 55512c732e
5 changed files with 650 additions and 529 deletions

View File

@ -144,7 +144,7 @@ namespace Vanara.PInvoke
public static implicit operator int(CLIPFORMAT value) => value._value;
/// <summary>A handle to a bitmap (HBITMAP).</summary>
[ClipCorrespondingType(typeof(HBITMAP))]
[ClipCorrespondingType(typeof(HBITMAP), TYMED.TYMED_GDI)]
public static readonly CLIPFORMAT CF_BITMAP = 2;
/// <summary>A memory object containing a BITMAPINFO structure followed by the bitmap bits.</summary>
@ -205,6 +205,7 @@ namespace Vanara.PInvoke
/// A handle to type HDROP that identifies a list of files. An application can retrieve information about the files by passing the
/// handle to the DragQueryFile function.
/// </summary>
[ClipCorrespondingType(typeof(string[]))]
public static readonly CLIPFORMAT CF_HDROP = 15;
/// <summary>
@ -238,7 +239,7 @@ namespace Vanara.PInvoke
/// Text format containing characters in the OEM character set. Each line ends with a carriage return/linefeed (CR-LF) combination. A
/// null character signals the end of the data.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(System.Text.ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(System.Text.ASCIIEncoding))]
public static readonly CLIPFORMAT CF_OEMTEXT = 7;
/// <summary>
@ -288,7 +289,7 @@ namespace Vanara.PInvoke
/// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data.
/// Use this format for ANSI text.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(System.Text.ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(System.Text.UTF8Encoding))]
public static readonly CLIPFORMAT CF_TEXT = 1;
/// <summary>Tagged-image file format.</summary>
@ -297,7 +298,7 @@ namespace Vanara.PInvoke
/// <summary>
/// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(System.Text.UnicodeEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(System.Text.UnicodeEncoding))]
public static readonly CLIPFORMAT CF_UNICODETEXT = 13;
/// <summary>Represents audio data in one of the standard wave formats, such as 11 kHz or 22 kHz PCM.</summary>
@ -312,10 +313,7 @@ namespace Vanara.PInvoke
/// <summary>Initializes a new instance of the <see cref="ClipCorrespondingTypeAttribute"/> class.</summary>
/// <param name="typeRef">The type that corresponds to this entity.</param>
/// <param name="medium">The medium type used to store the payload.</param>
public ClipCorrespondingTypeAttribute(Type typeRef, TYMED medium = TYMED.TYMED_HGLOBAL) : base(typeRef)
{
Medium = medium;
}
public ClipCorrespondingTypeAttribute(Type typeRef, TYMED medium = TYMED.TYMED_HGLOBAL) : base(typeRef) => Medium = medium;
/// <summary>Gets the medium type used to store the payload.</summary>
/// <value>The medium type.</value>

View File

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@ -10,6 +11,7 @@ using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using Vanara.Extensions;
using Vanara.Extensions.Reflection;
using Vanara.InteropServices;
using static Vanara.PInvoke.Gdi32;
using static Vanara.PInvoke.Kernel32;
@ -21,18 +23,6 @@ namespace Vanara.PInvoke
{
public static partial class Shell32
{
private static readonly Lazy<Dictionary<uint, (string name, ClipCorrespondingTypeAttribute attr)>> clipFmtIds = new(() =>
{
Type type = typeof(CLIPFORMAT);
Dictionary<uint, (string Name, ClipCorrespondingTypeAttribute)> knownIds = type.GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.FieldType == type && f.IsInitOnly).ToDictionary(f => (uint)(CLIPFORMAT)f.GetValue(null), f => (f.Name, f.GetCustomAttributes<ClipCorrespondingTypeAttribute>().FirstOrDefault()));
foreach (FieldInfo f in typeof(ShellClipboardFormat).GetFields(BindingFlags.Public).Where(f => f.FieldType == typeof(string)))
{
string s = (string)f.GetValue(null);
knownIds.Add(RegisterClipboardFormat(s), (s, f.GetCustomAttributes<ClipCorrespondingTypeAttribute>().FirstOrDefault()));
}
return knownIds;
});
/// <summary>
/// <para>Values used with the DROPDESCRIPTION structure to specify the drop image.</para>
/// </summary>
@ -102,6 +92,35 @@ namespace Vanara.PInvoke
FD_UNICODE = 0x80000000,
}
/// <summary>Converts an ANSI string to Unicode.</summary>
/// <param name="value">The ANSI string value.</param>
/// <returns>The Unicode string value.</returns>
public static string AnsiToUnicode(string value)
{
if (string.IsNullOrEmpty(value)) return value;
byte[] ret = null;
var sz = MultiByteToWideChar(0, 0, value, value.Length, ret, 0);
ret = new byte[(int)sz];
MultiByteToWideChar(0, 0, value, value.Length, ret, sz);
return Encoding.Unicode.GetString(ret);
}
/// <summary>Enumerates the <see cref="FORMATETC"/> structures that define the formats and media supported by a given data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <returns>A sequence of <see cref="FORMATETC"/> structures.</returns>
public static IEnumerable<FORMATETC> EnumFormats(this IDataObject dataObj)
{
IEnumFORMATETC e = null;
try { e = dataObj.EnumFormatEtc(DATADIR.DATADIR_GET); e.Reset(); }
catch { }
if (e is null) yield break;
FORMATETC[] etc = new FORMATETC[1];
int[] f = new[] { 1 };
while (((HRESULT)e.Next(1, etc, f)).Succeeded && f[0] > 0)
yield return etc[0];
}
/// <summary>Takes an HTML fragment and wraps it in the HTML format specification for the clipboard.</summary>
/// <param name="htmlFragment">
/// The fragment contains pure, valid HTML representing the area the user has selected (to Copy, for example). This contains the
@ -177,6 +196,25 @@ namespace Vanara.PInvoke
return Encoding.UTF8.GetBytes(sb.ToString());
}
/// <summary>Obtains data from a source data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <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.</returns>
/// <exception cref="System.InvalidOperationException">Unrecognized TYMED value.</exception>
public static object GetData(this IDataObject dataObj, string format, DVASPECT aspect = DVASPECT.DVASPECT_CONTENT, int index = -1) =>
GetData(dataObj, RegisterClipboardFormat(format), aspect, index);
/// <summary>Obtains data from a source data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="formatId">Specifies the particular clipboard format of interest.</param>
@ -195,8 +233,8 @@ namespace Vanara.PInvoke
/// <exception cref="System.InvalidOperationException">Unrecognized TYMED value.</exception>
public static object GetData(this IDataObject dataObj, uint formatId, DVASPECT aspect = DVASPECT.DVASPECT_CONTENT, int index = -1)
{
ClipCorrespondingTypeAttribute attr = clipFmtIds.Value.TryGetValue(formatId, out (string name, ClipCorrespondingTypeAttribute attr) data) ? data.attr : null;
TYMED tymed = attr?.Medium ?? TYMED.TYMED_FILE | TYMED.TYMED_HGLOBAL | TYMED.TYMED_ISTREAM | TYMED.TYMED_ISTORAGE | TYMED.TYMED_GDI | TYMED.TYMED_MFPICT | TYMED.TYMED_ENHMF;
ClipCorrespondingTypeAttribute attr = ShellClipboardFormat.clipFmtIds.Value.TryGetValue(formatId, out (string name, ClipCorrespondingTypeAttribute attr) data) ? data.attr : null;
TYMED tymed = attr?.Medium ?? AllTymed.Value;
FORMATETC formatetc = new()
{
cfFormat = unchecked((short)(ushort)formatId),
@ -204,7 +242,6 @@ namespace Vanara.PInvoke
lindex = index,
tymed = tymed
};
//STGMEDIUM medium = new() { tymed = attr?.Medium ?? TYMED.TYMED_HGLOBAL };
dataObj.GetData(ref formatetc, out var medium);
// Handle TYMED values, passing through HGLOBAL
@ -212,7 +249,7 @@ namespace Vanara.PInvoke
if (medium.tymed != TYMED.TYMED_HGLOBAL)
return medium.tymed switch
{
TYMED.TYMED_FILE => new SafeMoveableHGlobalHandle(medium.unionmember, userFree).ToString(-1, CharSet.Ansi),
TYMED.TYMED_FILE => Marshal.PtrToStringBSTR(medium.unionmember),
TYMED.TYMED_ISTREAM => Marshal.GetObjectForIUnknown(medium.unionmember) as IStream,
TYMED.TYMED_ISTORAGE => Marshal.GetObjectForIUnknown(medium.unionmember) as IStorage,
TYMED.TYMED_GDI when userFree => new SafeHBITMAP(medium.unionmember),
@ -225,6 +262,12 @@ namespace Vanara.PInvoke
_ => throw new InvalidOperationException(),
};
// Handle list of shell items
if (formatId == ShellClipboardFormat.Register(ShellClipboardFormat.CFSTR_SHELLIDLIST))
{
return SHCreateShellItemArrayFromDataObject(dataObj);
}
using SafeMoveableHGlobalHandle hmem = new(medium.unionmember, userFree);
try
{
@ -233,13 +276,13 @@ namespace Vanara.PInvoke
// Handle CF_HDROP since it can't indicate specialty
if (CLIPFORMAT.CF_HDROP.Equals(formatId))
{
return ClipboardHDROPFormatter.Instance.Read(hmem);
return new ClipboardHDROPFormatter().Read(hmem);
}
// If there's no hint, return bytes
// If there's no hint, return bytes or ISerialized value
if (attr is null)
{
return hmem.GetBytes();
return ClipboardSerializedFormatter.IsSerialized(hmem) ? new ClipboardSerializedFormatter().Read(hmem) : hmem.GetBytes();
}
// Use clipboard formatter if available
@ -248,27 +291,19 @@ namespace Vanara.PInvoke
return ((IClipboardFormatter)Activator.CreateInstance(attr.Formatter)).Read(hmem);
}
// Handle strings
if (attr.TypeRef == typeof(string))
CharSet charSet = GetCharSet(attr);
switch (attr.TypeRef)
{
Encoding enc = (Encoding)Activator.CreateInstance(attr.EncodingType ?? typeof(UnicodeEncoding));
return enc.GetString(hmem.GetBytes());
// Handle strings
case Type t when t == typeof(string):
return GetEncoding(attr).GetString(hmem.GetBytes()).TrimEnd('\0');
// Handle string[]
case Type t when t == typeof(string[]):
return hmem.ToStringEnum(charSet).ToArray();
// Handle other types
default:
return hmem.CallLocked(p => p.Convert(hmem.Size, attr.TypeRef, charSet));
}
CharSet charSet = CharSet.Auto;
if (attr.EncodingType == typeof(ASCIIEncoding))
charSet = CharSet.Ansi;
else if (attr.EncodingType == typeof(UnicodeEncoding))
charSet = CharSet.Unicode;
// Handle string[]
if (attr.TypeRef == typeof(string[]))
{
return hmem.ToStringEnum(charSet).ToArray();
}
// Handle other types
return hmem.DangerousGetHandle().Convert(hmem.Size, attr.TypeRef, charSet);
}
finally
{
@ -276,6 +311,22 @@ 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>
/// <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>
/// <param name="charSet">The character set to use for string types.</param>
/// <returns>The object associated with the request. If no object can be determined, <c>default(T)</c> is returned.</returns>
/// <exception cref="System.ArgumentException">This format does not support direct type access. - formatId</exception>
public static T GetData<T>(this IDataObject dataObj, string format, int index = -1, CharSet charSet = CharSet.Auto) =>
GetData<T>(dataObj, RegisterClipboardFormat(format), index, charSet);
/// <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>
@ -296,22 +347,13 @@ namespace Vanara.PInvoke
lindex = index,
tymed = TYMED.TYMED_HGLOBAL
};
//STGMEDIUM medium = new() { tymed = TYMED.TYMED_HGLOBAL };
dataObj.GetData(ref formatetc, out var medium);
if (medium.tymed != TYMED.TYMED_HGLOBAL)
throw new ArgumentException("This format does not support direct type access.", nameof(formatId));
if (medium.unionmember == default)
return default;
using SafeMoveableHGlobalHandle hmem = new(medium.unionmember, medium.pUnkForRelease is null);
try
{
hmem.Lock();
return hmem.ToType<T>(charSet == CharSet.Auto ? (StringHelper.GetCharSize(charSet) == 1 ? CharSet.Ansi : CharSet.Unicode) : charSet);
}
finally
{
hmem.Unlock();
}
return hmem.ToType<T>(charSet == CharSet.Auto ? (StringHelper.GetCharSize(charSet) == 1 ? CharSet.Ansi : CharSet.Unicode) : charSet);
}
/// <summary>Gets an HTML string from bytes returned from the clipboard.</summary>
@ -346,6 +388,46 @@ namespace Vanara.PInvoke
return Encoding.UTF8.GetString(bytes, startFrag, endFrag - startFrag);
}
/// <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.
/// </summary>
/// <param name="dataObj">The data object.</param>
/// <param name="formatId">Specifies the particular clipboard format of interest.</param>
/// <returns><see langword="true"/> if <paramref name="formatId"/> is available; otherwise, <see langword="false"/>.</returns>
public static bool IsFormatAvailable(this IDataObject dataObj, uint formatId)
{
FORMATETC formatetc = new()
{
cfFormat = unchecked((short)(ushort)formatId),
dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = -1,
tymed = AllTymed.Value
};
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>
/// <param name="obj">The object to add.</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>
public static void SetData(this IDataObject dataObj, string format, object obj, DVASPECT aspect = DVASPECT.DVASPECT_CONTENT, int index = -1) =>
SetData(dataObj, RegisterClipboardFormat(format), obj, aspect, index);
/// <summary>Transfer a data stream to an object that contains a data source.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="formatId">Specifies the particular clipboard format of interest.</param>
@ -363,104 +445,98 @@ namespace Vanara.PInvoke
/// </param>
public static void SetData(this IDataObject dataObj, uint formatId, object obj, DVASPECT aspect = DVASPECT.DVASPECT_CONTENT, int index = -1)
{
TYMED tymed = TYMED.TYMED_HGLOBAL;
IntPtr mbr = default;
switch (obj)
ClipCorrespondingTypeAttribute attr = ShellClipboardFormat.clipFmtIds.Value.TryGetValue(formatId, out (string name, ClipCorrespondingTypeAttribute attr) data) ? data.attr : null;
TYMED tymed = attr?.Medium ?? TYMED.TYMED_HGLOBAL;
CharSet charSet = GetCharSet(attr);
IntPtr mbr = attr?.Formatter is null ? default : ((IClipboardFormatter)Activator.CreateInstance(attr.Formatter)).Write(obj);
if (mbr == default)
{
case null:
tymed = TYMED.TYMED_NULL;
break;
switch (obj)
{
case null:
tymed = TYMED.TYMED_NULL;
break;
case byte[] bytes:
ClipboardBytesFormatter.Instance.Write(bytes);
break;
case byte[] bytes:
mbr = ClipboardBytesFormatter.Instance.Write(bytes);
break;
case MemoryStream mstream:
ClipboardBytesFormatter.Instance.Write(mstream.GetBuffer());
break;
// TODO
//case MemoryStream mstream:
// mbr = ClipboardBytesFormatter.Instance.Write(mstream.GetBuffer());
// break;
case Stream stream:
byte[] sbytes = new byte[stream.Length];
stream.Position = 0;
stream.Read(sbytes, 0, sbytes.Length);
ClipboardBytesFormatter.Instance.Write(sbytes);
break;
// TODO
//case Stream stream:
// ComStream cstream = new(stream);
// tymed = TYMED.TYMED_ISTREAM;
// mbr = Marshal.GetIUnknownForObject((IStream)cstream);
// break;
case string str:
if (CLIPFORMAT.CF_UNICODETEXT.Equals(formatId) ||
RegisterClipboardFormat(ShellClipboardFormat.CFSTR_INETURLW) == formatId ||
RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEW) == formatId)
{
ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, true, CharSet.Unicode));
}
else if (CLIPFORMAT.CF_TEXT.Equals(formatId) || CLIPFORMAT.CF_OEMTEXT.Equals(formatId) ||
RegisterClipboardFormat(ShellClipboardFormat.CF_RTF) == formatId ||
RegisterClipboardFormat(ShellClipboardFormat.CFSTR_INETURLA) == formatId ||
RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEA) == formatId ||
RegisterClipboardFormat(ShellClipboardFormat.CF_CSV) == formatId)
{
ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, true, CharSet.Ansi));
}
else if (RegisterClipboardFormat(ShellClipboardFormat.CF_HTML) == formatId)
{
ClipboardHtmlFormatter.Instance.Write(str);
}
else
{
ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, true, CharSet.Auto));
}
case string str:
//if (CLIPFORMAT.CF_TEXT.Equals(formatId))
// mbr = ClipboardBytesFormatter.Instance.Write(UnicodeToAnsiBytes(str));
//else
mbr = ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, GetEncoding(attr), true));
break;
break;
case IEnumerable<string> strlist:
// Handle HDROP specifically since its formatter cannot be specified.
if (CLIPFORMAT.CF_HDROP.Equals(formatId))
mbr = new ClipboardHDROPFormatter().Write(strlist, charSet != CharSet.Ansi);
else
mbr = strlist.MarshalToPtr(StringListPackMethod.Concatenated, MoveableHGlobalMemoryMethods.Instance.AllocMem, out _, charSet, 0,
MoveableHGlobalMemoryMethods.Instance.LockMem, MoveableHGlobalMemoryMethods.Instance.UnlockMem);
break;
case IEnumerable<string> strlist:
if (CLIPFORMAT.CF_HDROP.Equals(formatId) ||
formatId == RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPA) ||
formatId == RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPW))
{
mbr = ClipboardHDROPFormatter.Instance.Write(strlist);
}
case IStream str:
tymed = TYMED.TYMED_ISTREAM;
mbr = Marshal.GetIUnknownForObject(str);
break;
break;
case IStorage store:
tymed = TYMED.TYMED_ISTORAGE;
mbr = Marshal.GetIUnknownForObject(store);
break;
case IStream str:
tymed = TYMED.TYMED_ISTREAM;
mbr = Marshal.GetIUnknownForObject(str);
break;
// TODO
//case FileInfo fileInfo:
// tymed = TYMED.TYMED_FILE;
// mbr = Marshal.StringToBSTR(fileInfo.FullName);
// break;
case IStorage store:
tymed = TYMED.TYMED_ISTORAGE;
mbr = Marshal.GetIUnknownForObject(store);
break;
case System.Runtime.Serialization.ISerializable ser:
mbr = new ClipboardSerializedFormatter().Write(ser);
break;
case System.Runtime.Serialization.ISerializable ser:
mbr = ClipboardSerializedFormatter.Instance.Write(ser);
break;
case SafeMoveableHGlobalHandle hg:
mbr = hg.TakeOwnership();
break;
case SafeMoveableHGlobalHandle hg:
mbr = hg.TakeOwnership();
break;
// TODO
//case SafeAllocatedMemoryHandle h:
// mbr = new SafeMoveableHGlobalHandle(h).TakeOwnership();
// break;
case SafeAllocatedMemoryHandle h:
mbr = new SafeMoveableHGlobalHandle(h).TakeOwnership();
break;
case HBITMAP:
case SafeHBITMAP:
tymed = TYMED.TYMED_GDI;
mbr = ((IHandle)obj).DangerousGetHandle();
break;
case HBITMAP:
case SafeHBITMAP:
tymed = TYMED.TYMED_GDI;
mbr = ((IHandle)obj).DangerousGetHandle();
break;
case HMETAFILE:
case SafeHMETAFILE:
tymed = TYMED.TYMED_MFPICT;
mbr = ((IHandle)obj).DangerousGetHandle();
break;
case HMETAFILE:
case SafeHMETAFILE:
tymed = TYMED.TYMED_MFPICT;
mbr = ((IHandle)obj).DangerousGetHandle();
break;
case HENHMETAFILE:
case SafeHENHMETAFILE:
tymed = TYMED.TYMED_ENHMF;
mbr = ((IHandle)obj).DangerousGetHandle();
break;
case HENHMETAFILE:
case SafeHENHMETAFILE:
tymed = TYMED.TYMED_ENHMF;
mbr = ((IHandle)obj).DangerousGetHandle();
break;
}
}
FORMATETC formatetc = new()
{
@ -473,6 +549,18 @@ namespace Vanara.PInvoke
dataObj.SetData(ref formatetc, ref medium, true);
}
/// <summary>Transfer a data stream to an object that contains a data source.</summary>
/// <typeparam name="T">The type of the object being passed.</typeparam>
/// <param name="dataObj">The data object.</param>
/// <param name="format">Specifies the particular clipboard format of interest.</param>
/// <param name="obj">The object to add.</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>
public static void SetData<T>(this IDataObject dataObj, string format, T obj, int index = -1) where T : struct =>
SetData(dataObj, RegisterClipboardFormat(format), SafeMoveableHGlobalHandle.CreateFromStructure(obj), DVASPECT.DVASPECT_CONTENT, index);
/// <summary>Transfer a data stream to an object that contains a data source.</summary>
/// <typeparam name="T">The type of the object being passed.</typeparam>
/// <param name="dataObj">The data object.</param>
@ -485,6 +573,20 @@ 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 a URL with optional title to a data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <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(this IDataObject dataObj, string url, string title = null)
{
if (url is null) throw new ArgumentNullException(nameof(url));
dataObj.SetData(CLIPFORMAT.CF_UNICODETEXT, url);
dataObj.SetData(ShellClipboardFormat.CF_HTML, $"<a href=\"{System.Net.WebUtility.UrlEncode(url)}\">{System.Net.WebUtility.HtmlEncode(title ?? url)}</a>");
dataObj.SetData(ShellClipboardFormat.CFSTR_INETURLA, url);
dataObj.SetData(ShellClipboardFormat.CFSTR_INETURLW, url);
}
/// <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>
@ -497,46 +599,17 @@ namespace Vanara.PInvoke
/// <returns><see langword="true"/> if data is available and retrieved; otherwise <see langword="false"/>.</returns>
public static bool TryGetData<T>(this IDataObject dataObj, uint formatId, out T obj, int index = -1)
{
FORMATETC formatetc = new()
{
cfFormat = unchecked((short)(ushort)formatId),
dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = index,
tymed = TYMED.TYMED_HGLOBAL
};
obj = default;
if (dataObj.QueryGetData(ref formatetc) != HRESULT.S_OK)
return false;
try
{
STGMEDIUM medium = new() { tymed = TYMED.TYMED_HGLOBAL };
dataObj.GetData(ref formatetc, out medium);
if (medium.tymed == TYMED.TYMED_HGLOBAL && medium.unionmember != default)
{
using SafeMoveableHGlobalHandle hmem = new(medium.unionmember, medium.pUnkForRelease is null);
obj = hmem.ToType<T>();
if (IsFormatAvailable(dataObj, formatId))
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;
}
}
catch
{
}
catch { }
obj = default;
return false;
}
/// <summary>Converts an ANSI string to Unicode.</summary>
/// <param name="value">The ANSI string value.</param>
/// <returns>The Unicode string value.</returns>
public static string AnsiToUnicode(string value)
{
if (string.IsNullOrEmpty(value)) return value;
byte[] ret = null;
var sz = MultiByteToWideChar(0, 0, value, value.Length, ret, 0);
ret = new byte[(int)sz];
MultiByteToWideChar(0, 0, value, value.Length, ret, sz);
return Encoding.Unicode.GetString(ret);
}
/// <summary>Converts an Unicode string to ANSI.</summary>
/// <param name="value">The Unicode string value.</param>
/// <returns>The ANSI string value.</returns>
@ -550,6 +623,19 @@ namespace Vanara.PInvoke
return ret;
}
private static CharSet GetCharSet(ClipCorrespondingTypeAttribute attr)
{
CharSet charSet = CharSet.Auto;
if (attr is not null)
{
if (attr.EncodingType == typeof(UTF8Encoding) || attr.EncodingType == typeof(ASCIIEncoding))
charSet = CharSet.Ansi;
else if (attr.EncodingType == typeof(UnicodeEncoding))
charSet = CharSet.Unicode;
}
return charSet;
}
/// <summary>
/// <para>
/// Used with the CFSTR_SHELLIDLIST clipboard format to transfer the pointer to an item identifier list (PIDL) of one or more Shell
@ -864,6 +950,20 @@ namespace Vanara.PInvoke
nFileSizeLow = (uint)(value & 0xFFFFFFFF);
}
}
/// <summary>Performs an implicit conversion from <see cref="System.IO.FileInfo"/> to <see cref="FILEDESCRIPTOR"/>.</summary>
/// <param name="fi">The <see cref="System.IO.FileInfo"/> instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator FILEDESCRIPTOR(System.IO.FileInfo fi) => new FILEDESCRIPTOR()
{
dwFlags = FD_FLAGS.FD_ATTRIBUTES | FD_FLAGS.FD_ACCESSTIME | FD_FLAGS.FD_CREATETIME | FD_FLAGS.FD_WRITESTIME | FD_FLAGS.FD_FILESIZE | (Marshal.SystemDefaultCharSize > 1 ? FD_FLAGS.FD_UNICODE : 0),
dwFileAttributes = (FileFlagsAndAttributes)fi.Attributes,
cFileName = fi.FullName,
ftCreationTime = fi.CreationTime.ToFileTimeStruct(),
ftLastAccessTime = fi.LastAccessTime.ToFileTimeStruct(),
ftLastWriteTime = fi.LastWriteTime.ToFileTimeStruct(),
nFileSize = unchecked((ulong)fi.Length)
};
}
/// <summary>Defines the CF_FILEGROUPDESCRIPTOR clipboard format.</summary>
@ -1039,20 +1139,20 @@ namespace Vanara.PInvoke
/// </para>
/// <para>If <c>dwScope</c> is not set to RESOURCE_CONNECTED, this field is undefined.</para>
/// </summary>
public IntPtr lpLocalName;
public StrPtrAuto lpLocalName;
/// <summary>
/// If the enumerated item is a network resource, this field contains a remote network name. This name may be then passed to
/// NPAddConnection to make a network connection if <c>dwUsage</c> is set to RESOURCEUSAGE_CONNECTABLE. If the enumerated item is
/// a current connection, this field will refer to the remote network name that <c>lpLocalName</c> is connected to.
/// </summary>
public IntPtr lpRemoteName;
public StrPtrAuto lpRemoteName;
/// <summary>May be any provider-supplied comment associated with the enumerated item.</summary>
public IntPtr lpComment;
public StrPtrAuto lpComment;
/// <summary>Specifies the name of the provider that owns this enumerated item.</summary>
public IntPtr lpProvider;
public StrPtrAuto lpProvider;
}
/// <summary>Defines the <see cref="ShellClipboardFormat.CFSTR_NETRESOURCES"/> clipboard format.</summary>
@ -1165,22 +1265,22 @@ namespace Vanara.PInvoke
public static class ShellClipboardFormat
{
/// <summary>Comma Separated Value</summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CF_CSV = "Csv";
/// <summary>HTML Format</summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UTF8Encoding), Formatter = typeof(ClipboardHtmlFormatter))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding), Formatter = typeof(ClipboardHtmlFormatter))]
public const string CF_HTML = "HTML Format";
/// <summary>RichEdit Text and Objects</summary>
public const string CF_RETEXTOBJ = "RichEdit Text and Objects";
/// <summary>Rich Text Format</summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CF_RTF = "Rich Text Format";
/// <summary>Rich Text Format Without Objects</summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CF_RTFNOOBJS = "Rich Text Format Without Objects";
/// <summary>Undocumented.</summary>
@ -1227,7 +1327,7 @@ namespace Vanara.PInvoke
/// to a single file, it could also, for example, represent data extracted by the source from a database or text document.
/// </para>
/// </summary>
[ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_FILEDESCRIPTORA = "FileGroupDescriptor";
/// <summary>
@ -1246,7 +1346,7 @@ namespace Vanara.PInvoke
/// to a single file, it could also, for example, represent data extracted by the source from a database or text document.
/// </para>
/// </summary>
[ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
[ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_FILEDESCRIPTORW = "FileGroupDescriptorW";
/// <summary>
@ -1254,7 +1354,7 @@ namespace Vanara.PInvoke
/// memory object. The structure's hGlobal member points to a single null-terminated string containing the file's fully qualified
/// file path. This format has been superseded by CF_HDROP, but it is supported for backward compatibility with Windows 3.1 applications.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_FILENAMEA = "FileName";
/// <summary>
@ -1264,7 +1364,7 @@ namespace Vanara.PInvoke
/// 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>
[ClipCorrespondingType(typeof(string[]), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string[]), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_FILENAMEMAPA = "FileNameMap";
/// <summary>
@ -1274,7 +1374,7 @@ namespace Vanara.PInvoke
/// 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>
[ClipCorrespondingType(typeof(string[]), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
[ClipCorrespondingType(typeof(string[]), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_FILENAMEMAPW = "FileNameMapW";
/// <summary>
@ -1282,7 +1382,7 @@ namespace Vanara.PInvoke
/// memory object. The structure's hGlobal member points to a single null-terminated string containing the file's fully qualified
/// file path. This format has been superseded by CF_HDROP, but it is supported for backward compatibility with Windows 3.1 applications.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_FILENAMEW = "FileNameW";
/// <summary>
@ -1308,7 +1408,7 @@ namespace Vanara.PInvoke
/// UNICODE is not defined, the application retrieves the CF_TEXT/CFSTR_SHELLURL version of the URL. If UNICODE is defined, the
/// application retrieves the CF_UNICODE version of the URL.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_INETURLA = CFSTR_SHELLURL;
/// <summary>
@ -1317,11 +1417,11 @@ namespace Vanara.PInvoke
/// UNICODE is not defined, the application retrieves the CF_TEXT/CFSTR_SHELLURL version of the URL. If UNICODE is defined, the
/// application retrieves the CF_UNICODE version of the URL.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_INETURLW = "UniformResourceLocatorW";
/// <summary>Undocumented.</summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_INVOKECOMMAND_DROPPARAM = "InvokeCommand DropParam";
/// <summary>
@ -1371,7 +1471,7 @@ namespace Vanara.PInvoke
/// folders as well as drive letters, the handler must be able to understand both the CSFTR_MOUNTEDVOLUME and CF_HDROP formats.
/// </para>
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_MOUNTEDVOLUME = "MountedVolume";
/// <summary>
@ -1449,7 +1549,7 @@ namespace Vanara.PInvoke
/// CF_HDROP. However, the pFiles member of the DROPFILES structure contains one or more friendly names of printers instead of
/// file paths.
/// </summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_PRINTERGROUP = "PrinterFriendlyName";
/// <summary>Undocumented.</summary>
@ -1480,7 +1580,7 @@ namespace Vanara.PInvoke
public const string CFSTR_SHELLIDLISTOFFSET = "Shell Object Offsets";
/// <summary><note type="note">This format identifier has been deprecated; use <see cref="CFSTR_INETURLA"/> instead.</note></summary>
[ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
[ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_SHELLURL = "UniformResourceLocator";
/// <summary>
@ -1516,6 +1616,66 @@ namespace Vanara.PInvoke
/// <summary>Undocumented.</summary>
[ClipCorrespondingType(typeof(uint))]
public const string CFSTR_ZONEIDENTIFIER = "ZoneIdentifier";
internal static readonly Lazy<Dictionary<uint, (string name, ClipCorrespondingTypeAttribute attr)>> clipFmtIds = new(() =>
{
Type cftype = typeof(CLIPFORMAT);
var knownIds = cftype.GetFields(BindingFlags.Static | BindingFlags.Public).
Where(f => f.FieldType == cftype && f.IsInitOnly).
ToDictionary(f => (uint)(CLIPFORMAT)f.GetValue(null), f => (f.Name, f.GetCustomAttributes<ClipCorrespondingTypeAttribute>().FirstOrDefault()));
foreach (FieldInfo f in typeof(ShellClipboardFormat).GetConstants().Where(f => f.FieldType == typeof(string)))
{
string s = (string)f.GetValue(null);
uint id = RegisterClipboardFormat(s);
if (!knownIds.ContainsKey(id))
knownIds.Add(id, (s, f.GetCustomAttributes<ClipCorrespondingTypeAttribute>().FirstOrDefault()));
}
return knownIds;
});
/// <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>
public static string GetName(uint formatId)
{
if (clipFmtIds.Value.TryGetValue(formatId, out var value))
return value.name;
// Ask sysetm for the registered name
StringBuilder sb = new(80);
int ret;
while (0 != (ret = GetClipboardFormatName(formatId, sb, sb.Capacity)))
{
if (ret < sb.Capacity - 1)
{
clipFmtIds.Value.Add(formatId, (sb.ToString(), null));
return sb.ToString();
}
sb.Capacity *= 2;
}
// Failing all elsewhere, return value as hex string
return string.Format(CultureInfo.InvariantCulture, "0x{0:X4}", formatId);
}
/// <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>
/// <remarks>
/// 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.
/// </remarks>
public static uint Register(string format)
{
if (format is null) throw new ArgumentNullException(nameof(format));
var id = Win32Error.ThrowLastErrorIf(RegisterClipboardFormat(format), v => v == 0);
if (!clipFmtIds.Value.ContainsKey(id))
clipFmtIds.Value.Add(id, (format, null));
return id;
}
}
internal class ClipboardBytesFormatter : IClipboardFormatter
@ -1529,8 +1689,6 @@ namespace Vanara.PInvoke
internal class ClipboardHDROPFormatter : IClipboardFormatter
{
public static ClipboardHDROPFormatter Instance { get; } = new();
public object Read(IntPtr hGlobal)
{
SafeMoveableHGlobalHandle h = new(hGlobal, false);
@ -1542,15 +1700,18 @@ namespace Vanara.PInvoke
public IntPtr Write(object value, bool wideChar)
{
SafeMoveableHGlobalHandle dfmem = SafeMoveableHGlobalHandle.CreateFromStructure(new DROPFILES { pFiles = (uint)Marshal.SizeOf(typeof(DROPFILES)), fWide = wideChar });
new NativeMemoryStream(dfmem) { CharSet = wideChar ? CharSet.Unicode : CharSet.Ansi }.Write((string[])value);
if (value is not string[] vals) throw new ArgumentException(null, nameof(value));
DROPFILES drop = new() { pFiles = (uint)Marshal.SizeOf(typeof(DROPFILES)), fWide = wideChar };
SafeMoveableHGlobalHandle dfmem = new(drop.pFiles + vals.Sum(v => (v.Length + 1) * (wideChar ? 2 : 1)) + 2);
dfmem.Write(drop);
dfmem.CallLocked(p => p.Write(vals, StringListPackMethod.Concatenated, wideChar ? CharSet.Unicode : CharSet.Ansi, (int)drop.pFiles, dfmem.Size));
return dfmem.TakeOwnership();
}
}
internal class ClipboardHtmlFormatter : ClipboardBytesFormatter
{
public new static ClipboardHtmlFormatter Instance { get; } = new();
public new static IClipboardFormatter Instance { get; } = new ClipboardHtmlFormatter();
public override object Read(IntPtr hGlobal) => GetHtmlFromClipboard((byte[])base.Read(hGlobal));
@ -1559,30 +1720,27 @@ namespace Vanara.PInvoke
internal class ClipboardSerializedFormatter : IClipboardFormatter
{
private static readonly byte[] guid = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray();
public static ClipboardSerializedFormatter Instance { get; } = new();
private static readonly Guid guid = new(0xFD9EA796, 0x3B13, 0x4370, 0xA6, 0x79, 0x56, 0x10, 0x6B, 0xB2, 0x88, 0xFB);
private static readonly byte[] guidBytes = guid.ToByteArray();
public static bool IsSerialized(SafeMoveableHGlobalHandle h) => h.Size >= 16 && h.ToStructure<Guid>() == guid;
public object Read(IntPtr hGlobal)
{
byte[] bytes = (byte[])ClipboardBytesFormatter.Instance.Read(hGlobal);
if (bytes.Length <= guid.Length || !Equals(bytes, guid, guid.Length))
if (bytes.Length <= guidBytes.Length || !Equals(bytes, guidBytes, guidBytes.Length))
{
throw new InvalidDataException();
}
using MemoryStream mem = new(bytes, false) { Position = guid.Length };
using MemoryStream mem = new(bytes, false) { Position = guidBytes.Length };
return new BinaryFormatter() { AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple }.Deserialize(mem);
static bool Equals(byte[] bytes, byte[] guid, int length)
{
for (int i = 0; i < length; i++)
{
if (bytes[i] != guid[i])
{
return false;
}
}
return true;
}
}
@ -1591,7 +1749,7 @@ namespace Vanara.PInvoke
{
System.Runtime.Serialization.ISerializable ser = (System.Runtime.Serialization.ISerializable)value;
MemoryStream ms = new();
new BinaryWriter(ms).Write(guid);
new BinaryWriter(ms).Write(guidBytes);
new BinaryFormatter().Serialize(ms, ser);
return ClipboardBytesFormatter.Instance.Write(ms.GetBuffer());
}

View File

@ -1,8 +1,13 @@
using NUnit.Framework;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;
using System.Windows.Forms;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
using Vanara.PInvoke.Tests;
using static Vanara.PInvoke.Shell32;
@ -11,28 +16,13 @@ using WFClipboard = System.Windows.Forms.Clipboard;
namespace Vanara.Windows.Shell.Tests
{
[TestFixture/*, SingleThreaded*/]
[TestFixture, SingleThreaded]
public class ClipboardTests
{
private const string html = "<pre style=\"font-family:Consolas;font-size:13px;color:#dadada;\"><span style=\"color:#dcdcaa;\">“Weve been here”</span></pre>";
[Test]
public void CtorTest()
{
const string txt = "Test";
using (var cb = new Clipboard(true, User32.GetDesktopWindow()))
cb.SetText(txt, txt, txt);
using (var cb = new Clipboard(false, User32.GetDesktopWindow()))
Assert.That(cb.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
using (var cb = new Clipboard())
Assert.That(cb.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
using (var cb = new Clipboard(true))
cb.SetText(txt, txt, txt);
using (var cb = new Clipboard())
Assert.That(cb.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
using (var cb = new Clipboard(false, User32.GetDesktopWindow()))
Assert.That(cb.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
}
readonly string[] files = { TestCaseSources.SmallFile, TestCaseSources.ImageFile, TestCaseSources.LogFile };
const string txt = @"“00©0è0”";
const string ptxt = "ABC123";
[Test]
public void DumpWFClipboardTest()
@ -53,21 +43,15 @@ namespace Vanara.Windows.Shell.Tests
[Test]
public void EnumFormatsTest()
{
using var cb = new Clipboard();
var fmts = cb.EnumAvailableFormats();
SHCreateDataObject(ppv: out var ido).ThrowIfFailed();
ido.SetData(CLIPFORMAT.CF_UNICODETEXT, "Test");
var fmts = ido.EnumFormats().ToArray();
Assert.That(fmts, Is.Not.Empty);
TestContext.Write(string.Join(", ", fmts.Select(f => Clipboard.GetFormatName(f))));
TestContext.Write(string.Join(", ", fmts.Select(f => Clipboard.GetFormatName((uint)f.cfFormat))));
var fmt = fmts.First();
Assert.IsTrue(Clipboard.IsFormatAvailable(fmt));
}
[Test]
public void GetNativeTextTest()
{
using var cb = new Clipboard();
foreach (TextDataFormat e in Enum.GetValues(typeof(TextDataFormat)))
TestContext.WriteLine($"{e}: {cb.GetText(e)}");
Assert.IsTrue(ido.IsFormatAvailable((uint)fmt.cfFormat));
}
[Test]
@ -77,83 +61,223 @@ namespace Vanara.Windows.Shell.Tests
Assert.That(Clipboard.GetFirstFormatAvailable(fmts), Is.GreaterThan(0));
}
[Test, Apartment(ApartmentState.STA)]
[Test]
public void GetSetShellItems1()
{
//Ole32.CoInitializeEx(default, Ole32.COINIT.COINIT_APARTMENTTHREADED).ThrowIfFailed();
//Clipboard.DataObject = null;
string[] files = { TestCaseSources.SmallFile, TestCaseSources.ImageFile, TestCaseSources.LogFile };
ShellItemArray items = new(Array.ConvertAll(files, f => new ShellItem(f)));
Clipboard.SetShellItems(items);
var shArray = Clipboard.GetShellItemArray();
var ido = items.ToDataObject();
var shArray = ShellItemArray.FromDataObject(ido);
Assert.That(shArray.Count, Is.GreaterThan(0));
Assert.IsTrue(files.SequenceEqual(shArray.Select(s => s.FileSystemPath)));
CollectionAssert.AreEquivalent(files, shArray.Select(s => s.FileSystemPath));
}
[Test]
public void GetSetShellItems2()
{
Clipboard.DataObject = null;
string[] files = { TestCaseSources.SmallFile, TestCaseSources.ImageFile, TestCaseSources.LogFile };
ShellItem[] items = Array.ConvertAll(files, f => new ShellItem(f));
Clipboard.SetShellItems(items);
var shArray = Clipboard.GetShellItemArray();
Assert.That(shArray.Count, Is.GreaterThan(0));
Assert.IsTrue(files.SequenceEqual(shArray.Select(s => s.FileSystemPath)));
CollectionAssert.AreEquivalent(files, shArray.Select(s => s.FileSystemPath));
}
[Test]
public void GetSetDataTest()
{
SHCreateDataObject(ppv: out var ido).ThrowIfFailed();
//using var hPal = Gdi32.CreatePalette(new LOGPALETTE() { palNumEntries = 256, palVersion = 0x0300, palPalEntry = new PALETTEENTRY[256] });
//ido.SetData(CLIPFORMAT.CF_PALETTE, hPal);
//Assert.That((HPALETTE)ido.GetData(CLIPFORMAT.CF_PALETTE), Is.EqualTo((HPALETTE)hPal));
using System.Drawing.Bitmap bmp = new(TestCaseSources.BmpFile);
using Gdi32.SafeHBITMAP hBmp = new(bmp.GetHbitmap());
ido.SetData(CLIPFORMAT.CF_BITMAP, hBmp);
Assert.AreEqual((HBITMAP)ido.GetData(CLIPFORMAT.CF_BITMAP), (HBITMAP)hBmp);
//using System.Drawing.Imaging.Metafile enhMeta = new System.Drawing.Imaging.Metafile(TestCaseSources.TempChildDirWhack + "test.wmf");
//using Gdi32.SafeHENHMETAFILE hEnh = new(enhMeta.GetHenhmetafile());
//ido.SetData(CLIPFORMAT.CF_ENHMETAFILE, hEnh);
//Assert.That((HENHMETAFILE)ido.GetData(CLIPFORMAT.CF_ENHMETAFILE), Is.EqualTo((HENHMETAFILE)hEnh));
ido.SetData(CLIPFORMAT.CF_HDROP, files);
ido.SetData(ShellClipboardFormat.CFSTR_FILENAMEMAPA, files);
ido.SetData(ShellClipboardFormat.CFSTR_FILENAMEMAPW, files);
CollectionAssert.AreEquivalent(files, (string[])ido.GetData(CLIPFORMAT.CF_HDROP));
CollectionAssert.AreEquivalent(files, (string[])ido.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPA));
CollectionAssert.AreEquivalent(files, (string[])ido.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPW));
ido.SetData(CLIPFORMAT.CF_OEMTEXT, ptxt);
Assert.AreEqual(ido.GetData(CLIPFORMAT.CF_OEMTEXT), ptxt);
ido.SetData(CLIPFORMAT.CF_TEXT, txt);
Assert.AreEqual(ido.GetData(CLIPFORMAT.CF_TEXT), txt);
ido.SetData(CLIPFORMAT.CF_UNICODETEXT, txt);
Assert.AreEqual(ido.GetData(CLIPFORMAT.CF_UNICODETEXT), txt);
var r = new RECT(0, 8, 16, 32);
ido.SetData("RECT", r);
Assert.AreEqual(ido.GetData<RECT>("RECT"), r);
var lcid = Kernel32.GetUserDefaultLCID();
ido.SetData(CLIPFORMAT.CF_LOCALE, lcid);
//Assert.AreEqual(ido.GetData<LCID>(CLIPFORMAT.CF_LOCALE), lcid);
//Assert.That(ido.GetData(CLIPFORMAT.CF_LOCALE), lcid);
const string csv = "a,b,c,d\n1,2,3,4";
ido.SetData(ShellClipboardFormat.CF_CSV, csv);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CF_CSV), csv);
ido.SetData(ShellClipboardFormat.CF_HTML, html);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CF_HTML), html);
var rtf = System.IO.File.ReadAllText(TestCaseSources.TempDirWhack + "Test.rtf");
ido.SetData(ShellClipboardFormat.CF_RTF, rtf);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CF_RTF), rtf);
ido.SetData(ShellClipboardFormat.CF_RTFNOOBJS, rtf);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CF_RTFNOOBJS), rtf);
DROPDESCRIPTION dropDesc = new() { type = DROPIMAGETYPE.DROPIMAGE_COPY, szMessage = "Move this" };
ido.SetData(ShellClipboardFormat.CFSTR_DROPDESCRIPTION, dropDesc);
Assert.AreEqual(((DROPDESCRIPTION)ido.GetData(ShellClipboardFormat.CFSTR_DROPDESCRIPTION)).szMessage, dropDesc.szMessage);
FILE_ATTRIBUTES_ARRAY faa = new() { cItems = 1, rgdwFileAttributes = new[] { 4U } };
ido.SetData(ShellClipboardFormat.CFSTR_FILE_ATTRIBUTES_ARRAY, faa);
Assert.AreEqual(((FILE_ATTRIBUTES_ARRAY)ido.GetData(ShellClipboardFormat.CFSTR_FILE_ATTRIBUTES_ARRAY)).cItems, faa.cItems);
FILEGROUPDESCRIPTOR fgd = new() { cItems = (uint)files.Length, fgd = new FILEDESCRIPTOR[files.Length] };
for (int i = 0; i < files.Length; i++)
{
if (i == 0) { ido.SetData(ShellClipboardFormat.CFSTR_FILENAMEA, files[i]); ido.SetData(ShellClipboardFormat.CFSTR_FILENAMEW, files[i]); }
fgd.fgd[i] = new FileInfo(files[i]);
ShlwApi.SHCreateStreamOnFileEx(fgd.fgd[i].cFileName, STGM.STGM_READ | STGM.STGM_SHARE_DENY_WRITE, 0, false, null, out var istream).ThrowIfFailed();
ido.SetData(ShellClipboardFormat.CFSTR_FILECONTENTS, istream, System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT, i);
}
ido.SetData(ShellClipboardFormat.CFSTR_FILEDESCRIPTORW, fgd);
Assert.AreEqual(((FILEGROUPDESCRIPTOR)ido.GetData(ShellClipboardFormat.CFSTR_FILEDESCRIPTORW)).cItems, fgd.cItems);
Assert.IsNotNull(ido.GetData(ShellClipboardFormat.CFSTR_FILECONTENTS, index: 1));
Assert.IsNotNull(ido.GetData(ShellClipboardFormat.CFSTR_FILENAMEA));
Assert.IsNotNull(ido.GetData(ShellClipboardFormat.CFSTR_FILENAMEW));
ido.SetUrl("https://microsoft.com", "Microsoft");
Assert.That(ido.GetData(ShellClipboardFormat.CFSTR_INETURLA), Does.StartWith("https://microsoft.com"));
Assert.That(ido.GetData(ShellClipboardFormat.CFSTR_INETURLW), Does.StartWith("https://microsoft.com"));
ido.SetData(ShellClipboardFormat.CFSTR_INDRAGLOOP, true);
Assert.AreEqual((BOOL)ido.GetData(ShellClipboardFormat.CFSTR_INDRAGLOOP), true);
ido.SetData(ShellClipboardFormat.CFSTR_INVOKECOMMAND_DROPPARAM, ptxt);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_INVOKECOMMAND_DROPPARAM), ptxt);
ido.SetData(ShellClipboardFormat.CFSTR_LOGICALPERFORMEDDROPEFFECT, Ole32.DROPEFFECT.DROPEFFECT_COPY);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_LOGICALPERFORMEDDROPEFFECT), Ole32.DROPEFFECT.DROPEFFECT_COPY);
ido.SetData(ShellClipboardFormat.CFSTR_MOUNTEDVOLUME, ptxt);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_MOUNTEDVOLUME), ptxt);
SafeLPTSTR remName = new("WINSTATION");
NRESARRAY nres = new() { cItems = 1, nr = new NETRESOURCE[] { new() { lpRemoteName = remName } } };
ido.SetData(ShellClipboardFormat.CFSTR_NETRESOURCES, nres);
Assert.AreEqual(((NRESARRAY)ido.GetData(ShellClipboardFormat.CFSTR_NETRESOURCES)).cItems, nres.cItems);
ido.SetData(ShellClipboardFormat.CFSTR_PASTESUCCEEDED, Ole32.DROPEFFECT.DROPEFFECT_COPY);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_PASTESUCCEEDED), Ole32.DROPEFFECT.DROPEFFECT_COPY);
ido.SetData(ShellClipboardFormat.CFSTR_PERFORMEDDROPEFFECT, Ole32.DROPEFFECT.DROPEFFECT_COPY);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_PERFORMEDDROPEFFECT), Ole32.DROPEFFECT.DROPEFFECT_COPY);
ido.SetData(ShellClipboardFormat.CFSTR_PREFERREDDROPEFFECT, Ole32.DROPEFFECT.DROPEFFECT_COPY);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_PREFERREDDROPEFFECT), Ole32.DROPEFFECT.DROPEFFECT_COPY);
ido.SetData(ShellClipboardFormat.CFSTR_PRINTERGROUP, ptxt);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_PRINTERGROUP), ptxt);
var guid = Guid.NewGuid();
ido.SetData(ShellClipboardFormat.CFSTR_SHELLDROPHANDLER, guid);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_SHELLDROPHANDLER), guid);
ido.SetData(ShellClipboardFormat.CFSTR_TARGETCLSID, guid);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_TARGETCLSID), guid);
ido.SetData(ShellClipboardFormat.CFSTR_UNTRUSTEDDRAGDROP, 16U);
Assert.AreEqual(ido.GetData(ShellClipboardFormat.CFSTR_UNTRUSTEDDRAGDROP), 16U);
ido.SetData("ByteArray", new byte[] { 1,2,3,4,5,6,7,8 });
Assert.AreEqual(((byte[])ido.GetData("ByteArray")).Length, 8);
//using var fs = File.OpenRead(files[0]);
//ido.SetData("Stream", fs);
//Assert.That(ido.GetData("Stream"), Is.TypeOf<IStream>());
//ido.SetData("StringArray", files);
//CollectionAssert.AreEquivalent(files, ido.GetData<string[]>("StringArray"));
// CFSTR_SHELLIDLIST
ShlwApi.SHCreateStreamOnFileEx(files[0], STGM.STGM_READ | STGM.STGM_SHARE_DENY_WRITE, 0, false, null, out var istream2).ThrowIfFailed();
ido.SetData("IStream", istream2);
Assert.That(ido.GetData("IStream") as IStream, Is.Not.Null);
// IStorage
//var fi = new FileInfo(files[0]);
//ido.SetData("File", fi);
//Assert.AreEqual(ido.GetData("File"), files[0]);
// ISerializable
System.Uri uri = new("https://microsoft.com");
ido.SetData("Uri", uri);
Assert.AreEqual(ido.GetData("Uri"), uri);
// SafeAllocated
//SafeCoTaskMemHandle h = SafeCoTaskMemHandle.CreateFromStringList(files);
//ido.SetData("hMem", h);
//Assert.AreEqual(ido.GetData("hMem") as byte[], h.GetBytes());
}
[Test]
public void SetNativeTextHtmlTest()
{
using (var cb = new Clipboard(true))
cb.SetText(html, TextDataFormat.Html);
using (var cb = new Clipboard())
{
var outVal = cb.GetText(TextDataFormat.Html);
Assert.That(outVal, Is.EqualTo(html));
}
SHCreateDataObject(ppv: out var ido).ThrowIfFailed();
ido.SetData(ShellClipboardFormat.Register(ShellClipboardFormat.CF_HTML), html);
var outVal = ido.GetData(ShellClipboardFormat.Register(ShellClipboardFormat.CF_HTML));
Assert.That(outVal, Is.EqualTo(html));
}
[Test]
public void SetNativeTextMultTest()
{
const string stxt = "112233";
using (var cb = new Clipboard(true))
cb.SetText(stxt);
using (var cb = new Clipboard())
Assert.That(cb.GetText(TextDataFormat.Text), Is.EqualTo(stxt));
Clipboard.SetText(stxt);
Assert.That(Clipboard.GetText(TextDataFormat.Text), Is.EqualTo(stxt));
const string txt = @"“00©0è0”";
using (var cb = new Clipboard(true))
cb.SetText(txt, txt);
using (var cb = new Clipboard())
{
Assert.That(cb.GetText(TextDataFormat.Text), Is.EqualTo(txt));
Assert.That(cb.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
Assert.That(cb.GetText(TextDataFormat.Html), Is.EqualTo(txt));
TestContext.WriteLine(cb.GetText(TextDataFormat.Html));
}
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));
}
[Test]
public void SetNativeTextUnicodeTest()
{
const string txt = @"“00©0è0”";
using (var cb = new Clipboard(true))
cb.SetText(txt, TextDataFormat.UnicodeText);
using (var cb = new Clipboard())
Assert.That(cb.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
Clipboard.SetText(txt, TextDataFormat.UnicodeText);
Assert.That(Clipboard.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
}
//[Test]
public void ChangeEventTest()
{
var sawChange = new System.Threading.ManualResetEvent(false);
var sawChange = new ManualResetEvent(false);
Clipboard.ClipboardUpdate += OnUpdate;
System.Threading.Thread.SpinWait(1000);
Thread.SpinWait(1000);
WFClipboard.SetText("Hello");
//using var cb = new Clipboard();
//cb.SetText("Hello");
//using var Clipboard = new Clipboard();
//Clipboard.SetText("Hello");
Assert.IsTrue(sawChange.WaitOne(5000));
Clipboard.ClipboardUpdate -= OnUpdate;

View File

@ -1,15 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
@ -45,38 +38,11 @@ namespace Vanara.Windows.Shell
/// disposal. This can be called multiple times in nested calls and will ensure the Clipboard is only opened and closed at the highest scope.
/// </summary>
/// <seealso cref="System.IDisposable"/>
public class NativeClipboard : IDisposable
public static class NativeClipboard
{
private static readonly object objectLock = new();
private static Dictionary<uint, string> knownIds;
private static ListenerWindow listener;
[ThreadStatic]
private static bool open = false;
private readonly bool dontClose = false;
/// <summary>Initializes a new instance of the <see cref="NativeClipboard"/> class.</summary>
/// <param name="empty">If set to <see langword="true"/>, <see cref="EmptyClipboard"/> is called to clear the Clipboard.</param>
/// <param name="hWndNewOwner">
/// A handle to the window to be associated with the open clipboard. If this parameter is <c>HWND.NULL</c>, the open clipboard is
/// associated with the current task.
/// </param>
public NativeClipboard(bool empty = false, HWND hWndNewOwner = default)
{
if (open)
{
dontClose = true;
return;
}
if (hWndNewOwner == default)
hWndNewOwner = GetDesktopWindow();
Win32Error.ThrowLastErrorIfFalse(OpenClipboard(hWndNewOwner));
open = true;
if (empty)
Empty();
}
/// <summary>Occurs when whenever the contents of the Clipboard have changed.</summary>
public static event EventHandler ClipboardUpdate
{
@ -139,6 +105,22 @@ namespace Vanara.Windows.Shell
/// </remarks>
public static uint SequenceNumber => GetClipboardSequenceNumber();
/// <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() => DataObject.EnumFormats().Select(f => unchecked((uint)f.cfFormat));
/// <summary>Carries out the clipboard shutdown sequence. It also releases any IDataObject instances that were placed on the clipboard.</summary>
public static void Flush() => OleFlushClipboard().ThrowIfFailed();
@ -190,9 +172,9 @@ namespace Vanara.Windows.Shell
public static string[] GetFileNameMap()
{
if (IsFormatAvailable(ShellClipboardFormat.CFSTR_FILENAMEMAPW))
return DataObject.GetData(RegisterFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPW)) as string[];
return DataObject.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPW) as string[];
else if (IsFormatAvailable(ShellClipboardFormat.CFSTR_FILENAMEMAPA))
return DataObject.GetData(RegisterFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPA)) as string[];
return DataObject.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPA) as string[];
return new string[0];
}
@ -208,28 +190,7 @@ namespace Vanara.Windows.Shell
/// <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>
public static string GetFormatName(uint formatId)
{
EnsureKnownIds();
if (knownIds.TryGetValue(formatId, out var value))
return value;
// Ask sysetm for the registered name
StringBuilder sb = new(80);
int ret;
while (0 != (ret = GetClipboardFormatName(formatId, sb, sb.Capacity)))
{
if (ret < sb.Capacity - 1)
{
knownIds.Add(formatId, sb.ToString());
return sb.ToString();
}
sb.Capacity *= 2;
}
// Failing all elsewhere, return value as hex string
return string.Format(CultureInfo.InvariantCulture, "0x{0:X4}", formatId);
}
public static string GetFormatName(uint formatId) => ShellClipboardFormat.GetName(formatId);
/// <summary>Retrieves the handle to the window that currently has the clipboard open.</summary>
/// <returns>
@ -246,6 +207,11 @@ namespace Vanara.Windows.Shell
/// <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(DataObject) : 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) => 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.
@ -256,7 +222,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) => IsClipboardFormatAvailable(id);
public static bool IsFormatAvailable(uint id) => DataObject.IsFormatAvailable(id); // EnumAvailableFormats().Contains(id);
/// <summary>Determines whether the clipboard contains data in the specified format.</summary>
/// <param name="id">A clipboard format string.</param>
@ -272,26 +238,44 @@ namespace Vanara.Windows.Shell
/// 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.
/// </remarks>
public static uint RegisterFormat(string format)
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) => DataObject.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>
public static void SetData<T>(uint formatId, T data) where T : struct => DataObject.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="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
{
if (format is null) throw new ArgumentNullException(nameof(format));
var pMem = SafeMoveableHGlobalHandle.CreateFromList(values);
Win32Error.ThrowLastErrorIfInvalid(pMem);
DataObject.SetData(formatId, pMem);
}
EnsureKnownIds();
var id = knownIds.FirstOrDefault(p => p.Value == format).Key;
if (id != 0)
return id;
id = Win32Error.ThrowLastErrorIf(RegisterClipboardFormat(format), v => v == 0);
knownIds.Add(id, format);
return id;
/// <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);
DataObject.SetData(formatId, pMem);
}
/// <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)
{
DataObject = (shellItems is ShellItemArray shia ? shia : new ShellItemArray(shellItems)).ToDataObject();
}
public static void SetShellItems(IEnumerable<ShellItem> shellItems) => DataObject = (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>
@ -300,20 +284,33 @@ namespace Vanara.Windows.Shell
{
if (parent is null) throw new ArgumentNullException(nameof(parent));
if (relativeShellItems is null) throw new ArgumentNullException(nameof(relativeShellItems));
var pidls = relativeShellItems.Select(i => i.PIDL.DangerousGetHandle()).ToArray();
SHCreateDataObject(parent.PIDL, (uint)pidls.Length, pidls, default, typeof(IComDataObject).GUID, out var dataObj).ThrowIfFailed();
SHCreateDataObject(parent.PIDL, relativeShellItems.Select(i => i.PIDL), default, out var dataObj).ThrowIfFailed();
OleSetClipboard(dataObj).ThrowIfFailed();
//DataObject = dataObj = shellItems is ShellItemArray shia ? shia.ToDataObject() : new ShellItemArray(shellItems).ToDataObject();
//if (!setAllFormats) return;
//var files = shellItems.Where(i => i.IsFileSystem).Select(i => i.FileSystemPath).ToArray();
//if (files.Length == 0) return;
//dataObj.SetData(CLIPFORMAT.CF_HDROP, files);
//dataObj.SetData(RegisterFormat(ShellClipboardFormat.CFSTR_FILENAMEA), files[0]);
//dataObj.SetData(RegisterFormat(ShellClipboardFormat.CFSTR_FILENAMEW), files[0]);
//dataObj.SetData(RegisterFormat(ShellClipboardFormat.CFSTR_FILEDESCRIPTORA));
}
/// <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)
{
if (text is null && htmlText is null && rtfText is null) return;
SetText(text, TextDataFormat.UnicodeText);
if (htmlText != null) SetText(htmlText, TextDataFormat.Html);
if (rtfText != null) SetText(rtfText, TextDataFormat.Rtf);
}
/// <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) => DataObject.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) => DataObject.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>
@ -325,169 +322,16 @@ namespace Vanara.Windows.Shell
/// <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) => DataObject.TryGetData(formatId, out obj, index);
/// <summary>
/// Retrieves data from the clipboard in a specified format. The clipboard must have been opened previously and this pointer cannot
/// be used once <see cref="NativeClipboard"/> goes out of scope.
/// </summary>
/// <param name="formatId">A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.</param>
/// <returns>
/// <para>If the function succeeds, the return value is the handle to a clipboard object in the specified format.</para>
/// <para>If the function fails, the return value is <c>IntPtr.Zero</c>. To get extended error information, call GetLastError.</para>
/// </returns>
/// <remarks>
/// <para><c>Caution</c> Clipboard data is not trusted. Parse the data carefully before using it in your application.</para>
/// <para>An application can enumerate the available formats in advance by using the EnumClipboardFormats function.</para>
/// <para>
/// The clipboard controls the handle that the <c>GetClipboardData</c> function returns, not the application. The application should
/// copy the data immediately. The application must not free the handle nor leave it locked. The application must not use the handle
/// after the <see cref="Empty"/> method is called, after <see cref="NativeClipboard"/> is disposed, or after any of the
/// <c>Set...</c> methods are called with the same clipboard format.
/// </para>
/// <para>
/// The system performs implicit data format conversions between certain clipboard formats when an application calls the
/// <c>GetClipboardData</c> function. For example, if the CF_OEMTEXT format is on the clipboard, a window can retrieve data in the
/// CF_TEXT format. The format on the clipboard is converted to the requested format on demand. For more information, see Synthesized
/// Clipboard Formats.
/// </para>
/// </remarks>
public SafeMoveableHGlobalHandle DanagerousGetData(uint formatId) => new(GetClipboardData(formatId), false);
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
private static uint Txt2Id(TextDataFormat tf) => tf switch
{
if (dontClose) return;
CloseClipboard();
open = false;
}
/// <summary>
/// Empties the clipboard and frees handles to data in the clipboard. The function then assigns ownership of the clipboard to the
/// window that currently has the clipboard open.
/// </summary>
public void Empty() => Win32Error.ThrowLastErrorIfFalse(EmptyClipboard());
/// <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 IEnumerable<uint> EnumAvailableFormats() => EnumClipboardFormats();
/// <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 string GetText(TextDataFormat formatId) => formatId switch
{
TextDataFormat.Text => DanagerousGetData(CLIPFORMAT.CF_TEXT).CallLocked(Marshal.PtrToStringAnsi),
TextDataFormat.UnicodeText => DanagerousGetData(CLIPFORMAT.CF_UNICODETEXT).CallLocked(Marshal.PtrToStringUni),
TextDataFormat.Rtf => DanagerousGetData(RegisterFormat(ShellClipboardFormat.CF_RTF)).CallLocked(Marshal.PtrToStringAnsi),
TextDataFormat.Html => Utils.GetHtml(GetClipboardData(RegisterFormat(ShellClipboardFormat.CF_HTML))),
TextDataFormat.CommaSeparatedValue => DanagerousGetData(RegisterFormat(ShellClipboardFormat.CF_CSV)).CallLocked(Marshal.PtrToStringAnsi),
_ => throw new ArgumentOutOfRangeException(nameof(formatId)),
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)),
};
/// <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 void SetBinaryData(uint formatId, byte[] data)
{
SafeMoveableHGlobalHandle pMem = new(data);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Win32Error.ThrowLastErrorIfNull(SetClipboardData(formatId, pMem.TakeOwnership()));
}
/// <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 void SetData<T>(uint formatId, T data)
{
var pMem = SafeMoveableHGlobalHandle.CreateFromStructure(data);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Win32Error.ThrowLastErrorIfNull(SetClipboardData(formatId, pMem.TakeOwnership()));
}
/// <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 void SetData<T>(uint formatId, IEnumerable<T> values) where T : struct
{
var pMem = SafeMoveableHGlobalHandle.CreateFromList(values);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Win32Error.ThrowLastErrorIfNull(SetClipboardData(formatId, pMem.TakeOwnership()));
}
/// <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 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);
Win32Error.ThrowLastErrorIfNull(SetClipboardData(formatId, pMem.TakeOwnership()));
}
/// <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 void SetText(string text, string htmlText = null, string rtfText = null)
{
if (text is null && htmlText is null && rtfText is null) return;
SetText(text, TextDataFormat.UnicodeText);
if (htmlText != null) SetText(htmlText, TextDataFormat.Html);
if (rtfText != null) SetText(rtfText, TextDataFormat.Rtf);
}
/// <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 void SetText(string value, TextDataFormat format)
{
(byte[] bytes, uint fmt) = format switch
{
TextDataFormat.Text => (UnicodeToAnsiBytes(value), (uint)CLIPFORMAT.CF_TEXT),
TextDataFormat.UnicodeText => (Encoding.Unicode.GetBytes(value + '\0'), (uint)CLIPFORMAT.CF_UNICODETEXT),
TextDataFormat.Rtf => (Encoding.ASCII.GetBytes(value + '\0'), RegisterFormat(ShellClipboardFormat.CF_RTF)),
TextDataFormat.Html => (FormatHtmlForClipboard(value), RegisterFormat(ShellClipboardFormat.CF_HTML)),
TextDataFormat.CommaSeparatedValue => (Encoding.ASCII.GetBytes(value + '\0'), RegisterFormat(ShellClipboardFormat.CF_CSV)),
_ => throw new ArgumentOutOfRangeException(nameof(format)),
};
SetBinaryData(fmt, bytes);
}
/// <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 void SetUrl(string url, string title = null)
{
if (url is null) throw new ArgumentNullException(nameof(url));
SetText(url, $"<a href=\"{System.Net.WebUtility.UrlEncode(url)}\">{System.Net.WebUtility.HtmlEncode(title ?? url)}</a>", null);
var textUrl = System.Net.WebUtility.UrlEncode(url + (title is null ? "" : ('\n' + title))) + '\0';
SetBinaryData(RegisterFormat(ShellClipboardFormat.CFSTR_INETURLA), Encoding.ASCII.GetBytes(textUrl));
SetBinaryData(RegisterFormat(ShellClipboardFormat.CFSTR_INETURLW), Encoding.Unicode.GetBytes(textUrl));
}
private static void EnsureKnownIds()
{
if (knownIds is not null)
return;
var type = typeof(CLIPFORMAT);
knownIds = type.GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.FieldType == type && f.IsInitOnly).ToDictionary(f => (uint)(CLIPFORMAT)f.GetValue(null), f => f.Name);
}
private class ListenerWindow : SystemEventHandler
{
protected override bool MessageFilter(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam, out IntPtr lReturn)

View File

@ -34,10 +34,7 @@ namespace Vanara.Windows.Shell
/// </summary>
/// <param name="format">The format of the specified data. See <see cref="T:System.Windows.Forms.DataFormats"/> for predefined formats.</param>
/// <param name="data">The data to store.</param>
public ShellDataObject(string format, object data) : base()
{
SetData(format, data);
}
public ShellDataObject(string format, object data) : base() => SetData(format, data);
/// <summary>Initializes a new instance of the <see cref="ShellDataObject"/> class.</summary>
/// <param name="items">A list of ShellItem instances.</param>
@ -80,7 +77,7 @@ namespace Vanara.Windows.Shell
/// <value><see langword="true"/> if the data object is within a drag-and-drop loop; otherwise, <see langword="false"/>.</value>
public bool InDragLoop
{
get => base.GetDataPresent(ShellClipboardFormat.CFSTR_INDRAGLOOP) ? (int)base.GetData(ShellClipboardFormat.CFSTR_INDRAGLOOP, false) != 0 : false;
get => base.GetDataPresent(ShellClipboardFormat.CFSTR_INDRAGLOOP) && (int)base.GetData(ShellClipboardFormat.CFSTR_INDRAGLOOP, false) != 0;
set => base.SetData(ShellClipboardFormat.CFSTR_INDRAGLOOP, false, value ? 1 : 0);
}