diff --git a/PInvoke/Shared/WinUser/CLIPFORMAT.cs b/PInvoke/Shared/WinUser/CLIPFORMAT.cs
index 03c0bb17..75757f97 100644
--- a/PInvoke/Shared/WinUser/CLIPFORMAT.cs
+++ b/PInvoke/Shared/WinUser/CLIPFORMAT.cs
@@ -144,7 +144,7 @@ namespace Vanara.PInvoke
public static implicit operator int(CLIPFORMAT value) => value._value;
/// A handle to a bitmap (HBITMAP).
- [ClipCorrespondingType(typeof(HBITMAP))]
+ [ClipCorrespondingType(typeof(HBITMAP), TYMED.TYMED_GDI)]
public static readonly CLIPFORMAT CF_BITMAP = 2;
/// A memory object containing a BITMAPINFO structure followed by the bitmap bits.
@@ -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.
///
+ [ClipCorrespondingType(typeof(string[]))]
public static readonly CLIPFORMAT CF_HDROP = 15;
///
@@ -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.
///
- [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;
///
@@ -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.
///
- [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;
/// Tagged-image file format.
@@ -297,7 +298,7 @@ namespace Vanara.PInvoke
///
/// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data.
///
- [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;
/// Represents audio data in one of the standard wave formats, such as 11 kHz or 22 kHz PCM.
@@ -312,10 +313,7 @@ namespace Vanara.PInvoke
/// Initializes a new instance of the class.
/// The type that corresponds to this entity.
/// The medium type used to store the payload.
- 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;
/// Gets the medium type used to store the payload.
/// The medium type.
diff --git a/PInvoke/Shell32/Clipboard.cs b/PInvoke/Shell32/Clipboard.cs
index ef4e8fcf..153d6de2 100644
--- a/PInvoke/Shell32/Clipboard.cs
+++ b/PInvoke/Shell32/Clipboard.cs
@@ -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> clipFmtIds = new(() =>
- {
- Type type = typeof(CLIPFORMAT);
- Dictionary 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().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().FirstOrDefault()));
- }
- return knownIds;
- });
-
///
/// Values used with the DROPDESCRIPTION structure to specify the drop image.
///
@@ -102,6 +92,35 @@ namespace Vanara.PInvoke
FD_UNICODE = 0x80000000,
}
+ /// Converts an ANSI string to Unicode.
+ /// The ANSI string value.
+ /// The Unicode string value.
+ 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);
+ }
+
+ /// Enumerates the structures that define the formats and media supported by a given data object.
+ /// The data object.
+ /// A sequence of structures.
+ public static IEnumerable 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];
+ }
+
/// Takes an HTML fragment and wraps it in the HTML format specification for the clipboard.
///
/// 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());
}
+ /// Obtains data from a source data object.
+ /// The data object.
+ /// Specifies the particular clipboard format of interest.
+ ///
+ /// 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.
+ ///
+ ///
+ /// 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.
+ ///
+ /// The object associated with the request. If no object can be determined, a [] is returned.
+ /// Unrecognized TYMED value.
+ public static object GetData(this IDataObject dataObj, string format, DVASPECT aspect = DVASPECT.DVASPECT_CONTENT, int index = -1) =>
+ GetData(dataObj, RegisterClipboardFormat(format), aspect, index);
+
/// Obtains data from a source data object.
/// The data object.
/// Specifies the particular clipboard format of interest.
@@ -195,8 +233,8 @@ namespace Vanara.PInvoke
/// Unrecognized TYMED value.
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));
+
+ /// Obtains data from a source data object.
+ /// The type of the object being retrieved.
+ /// The data object.
+ /// Specifies the particular clipboard format of interest.
+ ///
+ /// 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.
+ ///
+ /// The character set to use for string types.
+ /// The object associated with the request. If no object can be determined, default(T) is returned.
+ /// This format does not support direct type access. - formatId
+ public static T GetData(this IDataObject dataObj, string format, int index = -1, CharSet charSet = CharSet.Auto) =>
+ GetData(dataObj, RegisterClipboardFormat(format), index, charSet);
+
/// Obtains data from a source data object.
/// The type of the object being retrieved.
/// The data object.
@@ -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(charSet == CharSet.Auto ? (StringHelper.GetCharSize(charSet) == 1 ? CharSet.Ansi : CharSet.Unicode) : charSet);
- }
- finally
- {
- hmem.Unlock();
- }
+ return hmem.ToType(charSet == CharSet.Auto ? (StringHelper.GetCharSize(charSet) == 1 ? CharSet.Ansi : CharSet.Unicode) : charSet);
}
/// Gets an HTML string from bytes returned from the clipboard.
@@ -346,6 +388,46 @@ namespace Vanara.PInvoke
return Encoding.UTF8.GetString(bytes, startFrag, endFrag - startFrag);
}
+ ///
+ /// 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.
+ ///
+ /// The data object.
+ /// Specifies the particular clipboard format of interest.
+ /// if is available; otherwise, .
+ 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 AllTymed = new(() => Enum.GetValues(typeof(TYMED)).Cast().Aggregate((a, b) => a | b));
+
+ /// Transfer a data stream to an object that contains a data source.
+ /// The data object.
+ /// Specifies the particular clipboard format of interest.
+ /// The object to add.
+ ///
+ /// 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.
+ ///
+ ///
+ /// 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.
+ ///
+ 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);
+
/// Transfer a data stream to an object that contains a data source.
/// The data object.
/// Specifies the particular clipboard format of interest.
@@ -363,104 +445,98 @@ namespace Vanara.PInvoke
///
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 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 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);
}
+ /// Transfer a data stream to an object that contains a data source.
+ /// The type of the object being passed.
+ /// The data object.
+ /// Specifies the particular clipboard format of interest.
+ /// The object to add.
+ ///
+ /// 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.
+ ///
+ public static void SetData(this IDataObject dataObj, string format, T obj, int index = -1) where T : struct =>
+ SetData(dataObj, RegisterClipboardFormat(format), SafeMoveableHGlobalHandle.CreateFromStructure(obj), DVASPECT.DVASPECT_CONTENT, index);
+
/// Transfer a data stream to an object that contains a data source.
/// The type of the object being passed.
/// The data object.
@@ -485,6 +573,20 @@ namespace Vanara.PInvoke
public static void SetData(this IDataObject dataObj, uint formatId, T obj, int index = -1) where T : struct =>
SetData(dataObj, formatId, SafeMoveableHGlobalHandle.CreateFromStructure(obj), DVASPECT.DVASPECT_CONTENT, index);
+ /// Sets a URL with optional title to a data object.
+ /// The data object.
+ /// The URL.
+ /// The title. This value can be .
+ /// url
+ 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, $"{System.Net.WebUtility.HtmlEncode(title ?? url)}");
+ dataObj.SetData(ShellClipboardFormat.CFSTR_INETURLA, url);
+ dataObj.SetData(ShellClipboardFormat.CFSTR_INETURLW, url);
+ }
+
/// Obtains data from a source data object.
/// The type of the object being retrieved.
/// The data object.
@@ -497,46 +599,17 @@ namespace Vanara.PInvoke
/// if data is available and retrieved; otherwise .
public static bool TryGetData(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();
+ if (IsFormatAvailable(dataObj, formatId))
+ try {
+ var charSet = GetCharSet(ShellClipboardFormat.clipFmtIds.Value.TryGetValue(formatId, out (string name, ClipCorrespondingTypeAttribute attr) data) ? data.attr : null);
+ obj = GetData(dataObj, formatId, index, charSet);
return true;
}
- }
- catch
- {
- }
+ catch { }
+ obj = default;
return false;
}
- /// Converts an ANSI string to Unicode.
- /// The ANSI string value.
- /// The Unicode string value.
- 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);
- }
-
/// Converts an Unicode string to ANSI.
/// The Unicode string value.
/// The ANSI string value.
@@ -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;
+ }
+
///
///
/// 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);
}
}
+
+ /// Performs an implicit conversion from to .
+ /// The instance.
+ /// The result of the conversion.
+ 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)
+ };
}
/// Defines the CF_FILEGROUPDESCRIPTOR clipboard format.
@@ -1039,20 +1139,20 @@ namespace Vanara.PInvoke
///
/// If dwScope is not set to RESOURCE_CONNECTED, this field is undefined.
///
- public IntPtr lpLocalName;
+ public StrPtrAuto lpLocalName;
///
/// 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 dwUsage is set to RESOURCEUSAGE_CONNECTABLE. If the enumerated item is
/// a current connection, this field will refer to the remote network name that lpLocalName is connected to.
///
- public IntPtr lpRemoteName;
+ public StrPtrAuto lpRemoteName;
/// May be any provider-supplied comment associated with the enumerated item.
- public IntPtr lpComment;
+ public StrPtrAuto lpComment;
/// Specifies the name of the provider that owns this enumerated item.
- public IntPtr lpProvider;
+ public StrPtrAuto lpProvider;
}
/// Defines the clipboard format.
@@ -1165,22 +1265,22 @@ namespace Vanara.PInvoke
public static class ShellClipboardFormat
{
/// Comma Separated Value
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CF_CSV = "Csv";
/// HTML Format
- [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";
/// RichEdit Text and Objects
public const string CF_RETEXTOBJ = "RichEdit Text and Objects";
/// Rich Text Format
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CF_RTF = "Rich Text Format";
/// Rich Text Format Without Objects
- [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";
/// Undocumented.
@@ -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.
///
///
- [ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_FILEDESCRIPTORA = "FileGroupDescriptor";
///
@@ -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.
///
///
- [ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
+ [ClipCorrespondingType(typeof(FILEGROUPDESCRIPTOR), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_FILEDESCRIPTORW = "FileGroupDescriptorW";
///
@@ -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.
///
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_FILENAMEA = "FileName";
///
@@ -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.
///
- [ClipCorrespondingType(typeof(string[]), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string[]), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_FILENAMEMAPA = "FileNameMap";
///
@@ -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.
///
- [ClipCorrespondingType(typeof(string[]), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
+ [ClipCorrespondingType(typeof(string[]), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_FILENAMEMAPW = "FileNameMapW";
///
@@ -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.
///
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_FILENAMEW = "FileNameW";
///
@@ -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.
///
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_INETURLA = CFSTR_SHELLURL;
///
@@ -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.
///
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_INETURLW = "UniformResourceLocatorW";
/// Undocumented.
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(UnicodeEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UnicodeEncoding))]
public const string CFSTR_INVOKECOMMAND_DROPPARAM = "InvokeCommand DropParam";
///
@@ -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.
///
///
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_MOUNTEDVOLUME = "MountedVolume";
///
@@ -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.
///
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_PRINTERGROUP = "PrinterFriendlyName";
/// Undocumented.
@@ -1480,7 +1580,7 @@ namespace Vanara.PInvoke
public const string CFSTR_SHELLIDLISTOFFSET = "Shell Object Offsets";
/// This format identifier has been deprecated; use instead.
- [ClipCorrespondingType(typeof(string), TYMED.TYMED_HGLOBAL, EncodingType = typeof(ASCIIEncoding))]
+ [ClipCorrespondingType(typeof(string), EncodingType = typeof(UTF8Encoding))]
public const string CFSTR_SHELLURL = "UniformResourceLocator";
///
@@ -1516,6 +1616,66 @@ namespace Vanara.PInvoke
/// Undocumented.
[ClipCorrespondingType(typeof(uint))]
public const string CFSTR_ZONEIDENTIFIER = "ZoneIdentifier";
+
+ internal static readonly Lazy> 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().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().FirstOrDefault()));
+ }
+ return knownIds;
+ });
+
+ /// Retrieves from the clipboard the name of the specified registered format.
+ /// The type of format to be retrieved.
+ /// The format name.
+ 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);
+ }
+
+ /// Registers a new clipboard format. This format can then be used as a valid clipboard format.
+ /// The name of the new format.
+ /// The registered clipboard format identifier.
+ /// format
+ ///
+ /// 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.
+ ///
+ 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;
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());
}
diff --git a/UnitTests/Windows.Shell/ClipboardTests.cs b/UnitTests/Windows.Shell/ClipboardTests.cs
index 582e7aaa..e4df2af5 100644
--- a/UnitTests/Windows.Shell/ClipboardTests.cs
+++ b/UnitTests/Windows.Shell/ClipboardTests.cs
@@ -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 = "“We’ve been here”
";
-
- [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 = @"“0’0©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"), r);
+
+ var lcid = Kernel32.GetUserDefaultLCID();
+ ido.SetData(CLIPFORMAT.CF_LOCALE, lcid);
+ //Assert.AreEqual(ido.GetData(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());
+
+ //ido.SetData("StringArray", files);
+ //CollectionAssert.AreEquivalent(files, ido.GetData("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 = @"“0’0©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 = @"“0’0©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;
diff --git a/Windows.Shell.Common/NativeClipboard.cs b/Windows.Shell.Common/NativeClipboard.cs
index 9fca0bfb..ed1b7eb9 100644
--- a/Windows.Shell.Common/NativeClipboard.cs
+++ b/Windows.Shell.Common/NativeClipboard.cs
@@ -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.
///
///
- public class NativeClipboard : IDisposable
+ public static class NativeClipboard
{
private static readonly object objectLock = new();
- private static Dictionary knownIds;
private static ListenerWindow listener;
- [ThreadStatic]
- private static bool open = false;
-
- private readonly bool dontClose = false;
-
- /// Initializes a new instance of the class.
- /// If set to , is called to clear the Clipboard.
- ///
- /// A handle to the window to be associated with the open clipboard. If this parameter is HWND.NULL, the open clipboard is
- /// associated with the current task.
- ///
- 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();
- }
-
/// Occurs when whenever the contents of the Clipboard have changed.
public static event EventHandler ClipboardUpdate
{
@@ -139,6 +105,22 @@ namespace Vanara.Windows.Shell
///
public static uint SequenceNumber => GetClipboardSequenceNumber();
+ /// Enumerates the data formats currently available on the clipboard.
+ /// An enumeration of the data formats currently available on the clipboard.
+ ///
+ ///
+ /// The EnumFormats 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.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ public static IEnumerable EnumAvailableFormats() => DataObject.EnumFormats().Select(f => unchecked((uint)f.cfFormat));
+
/// Carries out the clipboard shutdown sequence. It also releases any IDataObject instances that were placed on the clipboard.
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
/// Retrieves from the clipboard the name of the specified registered format.
/// The type of format to be retrieved.
/// The format name.
- 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);
/// Retrieves the handle to the window that currently has the clipboard open.
///
@@ -246,6 +207,11 @@ namespace Vanara.Windows.Shell
/// The associated with the data object, if set. Otherwise, .
public static ShellItemArray GetShellItemArray() => IsFormatAvailable(ShellClipboardFormat.CFSTR_SHELLIDLIST) ? ShellItemArray.FromDataObject(DataObject) : null;
+ /// Gets the text from the native Clipboard in the specified format.
+ /// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.
+ /// The string value or if the format is not available.
+ public static string GetText(TextDataFormat formatId) => GetData(Txt2Id(formatId)) as string;
+
/// Determines whether the data object pointer previously placed on the clipboard is still on the clipboard.
///
/// 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
/// Determines whether the clipboard contains data in the specified format.
/// A standard or registered clipboard format.
/// If the clipboard format is available, the return value is ; otherwise .
- public static bool IsFormatAvailable(uint id) => IsClipboardFormatAvailable(id);
+ public static bool IsFormatAvailable(uint id) => DataObject.IsFormatAvailable(id); // EnumAvailableFormats().Contains(id);
/// Determines whether the clipboard contains data in the specified format.
/// A clipboard format string.
@@ -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.
///
- public static uint RegisterFormat(string format)
+ public static uint RegisterFormat(string format) => ShellClipboardFormat.Register(format);
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The binary data in the specified format.
+ /// data
+ public static void SetBinaryData(uint formatId, byte[] data) => DataObject.SetData(formatId, data);
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The data in the format dictated by .
+ public static void SetData(uint formatId, T data) where T : struct => DataObject.SetData(formatId, data);
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The data in the format dictated by .
+ public static void SetData(uint formatId, IEnumerable 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;
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The list of strings.
+ /// The packing type for the strings.
+ /// The character set to use for the strings.
+ public static void SetData(uint formatId, IEnumerable values, StringListPackMethod packing = StringListPackMethod.Concatenated, CharSet charSet = CharSet.Auto)
+ {
+ var pMem = SafeMoveableHGlobalHandle.CreateFromStringList(values, packing, charSet);
+ Win32Error.ThrowLastErrorIfInvalid(pMem);
+ DataObject.SetData(formatId, pMem);
}
/// Puts a list of shell items onto the clipboard.
/// The sequence of shell items. The PIDL of each shell item must be absolute.
- public static void SetShellItems(IEnumerable shellItems)
- {
- DataObject = (shellItems is ShellItemArray shia ? shia : new ShellItemArray(shellItems)).ToDataObject();
- }
+ public static void SetShellItems(IEnumerable shellItems) => DataObject = (shellItems is ShellItemArray shia ? shia : new ShellItemArray(shellItems)).ToDataObject();
/// Puts a list of shell items onto the clipboard.
/// The parent folder instance.
@@ -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));
}
+ /// Sets multiple text types to the Clipboard.
+ /// The Unicode Text value.
+ /// The HTML text value. If , this format will not be set.
+ /// The Rich Text Format value. If , this format will not be set.
+ 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);
+ }
+
+ /// Sets a specific text type to the Clipboard.
+ /// The text value.
+ /// The clipboard text format to set.
+ public static void SetText(string value, TextDataFormat format) => DataObject.SetData(Txt2Id(format), value);
+
+ /// Sets a URL with optional title to the clipboard.
+ /// The URL.
+ /// The title. This value can be .
+ /// url
+ public static void SetUrl(string url, string title = null) => DataObject.SetUrl(url, title);
+
/// Obtains data from a source data object.
/// The type of the object being retrieved.
/// Specifies the particular clipboard format of interest.
@@ -325,169 +322,16 @@ namespace Vanara.Windows.Shell
/// if data is available and retrieved; otherwise .
public static bool TryGetData(uint formatId, out T obj, int index = -1) => DataObject.TryGetData(formatId, out obj, index);
- ///
- /// Retrieves data from the clipboard in a specified format. The clipboard must have been opened previously and this pointer cannot
- /// be used once goes out of scope.
- ///
- /// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.
- ///
- /// If the function succeeds, the return value is the handle to a clipboard object in the specified format.
- /// If the function fails, the return value is IntPtr.Zero. To get extended error information, call GetLastError.
- ///
- ///
- /// Caution Clipboard data is not trusted. Parse the data carefully before using it in your application.
- /// An application can enumerate the available formats in advance by using the EnumClipboardFormats function.
- ///
- /// The clipboard controls the handle that the GetClipboardData 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 method is called, after is disposed, or after any of the
- /// Set... methods are called with the same clipboard format.
- ///
- ///
- /// The system performs implicit data format conversions between certain clipboard formats when an application calls the
- /// GetClipboardData 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.
- ///
- ///
- public SafeMoveableHGlobalHandle DanagerousGetData(uint formatId) => new(GetClipboardData(formatId), false);
-
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- public void Dispose()
+ private static uint Txt2Id(TextDataFormat tf) => tf switch
{
- if (dontClose) return;
- CloseClipboard();
- open = false;
- }
-
- ///
- /// 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.
- ///
- public void Empty() => Win32Error.ThrowLastErrorIfFalse(EmptyClipboard());
-
- /// Enumerates the data formats currently available on the clipboard.
- /// An enumeration of the data formats currently available on the clipboard.
- ///
- ///
- /// The EnumFormats 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.
- ///
- ///
- /// 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.
- ///
- ///
- public IEnumerable EnumAvailableFormats() => EnumClipboardFormats();
-
- /// Gets the text from the native Clipboard in the specified format.
- /// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.
- /// The string value or if the format is not available.
- 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)),
};
- /// Places data on the clipboard in a specified clipboard format.
- /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
- /// The binary data in the specified format.
- /// data
- public void SetBinaryData(uint formatId, byte[] data)
- {
- SafeMoveableHGlobalHandle pMem = new(data);
- Win32Error.ThrowLastErrorIfInvalid(pMem);
- Win32Error.ThrowLastErrorIfNull(SetClipboardData(formatId, pMem.TakeOwnership()));
- }
-
- /// Places data on the clipboard in a specified clipboard format.
- /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
- /// The data in the format dictated by .
- public void SetData(uint formatId, T data)
- {
- var pMem = SafeMoveableHGlobalHandle.CreateFromStructure(data);
- Win32Error.ThrowLastErrorIfInvalid(pMem);
- Win32Error.ThrowLastErrorIfNull(SetClipboardData(formatId, pMem.TakeOwnership()));
- }
-
- /// Places data on the clipboard in a specified clipboard format.
- /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
- /// The data in the format dictated by .
- public void SetData(uint formatId, IEnumerable values) where T : struct
- {
- var pMem = SafeMoveableHGlobalHandle.CreateFromList(values);
- Win32Error.ThrowLastErrorIfInvalid(pMem);
- Win32Error.ThrowLastErrorIfNull(SetClipboardData(formatId, pMem.TakeOwnership()));
- }
-
- /// Places data on the clipboard in a specified clipboard format.
- /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
- /// The list of strings.
- /// The packing type for the strings.
- /// The character set to use for the strings.
- public void SetData(uint formatId, IEnumerable 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()));
- }
-
- /// Sets multiple text types to the Clipboard.
- /// The Unicode Text value.
- /// The HTML text value. If , this format will not be set.
- /// The Rich Text Format value. If , this format will not be set.
- 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);
- }
-
- /// Sets a specific text type to the Clipboard.
- /// The text value.
- /// The clipboard text format to set.
- 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);
- }
-
- /// Sets a URL with optional title to the clipboard.
- /// The URL.
- /// The title. This value can be .
- /// url
- public void SetUrl(string url, string title = null)
- {
- if (url is null) throw new ArgumentNullException(nameof(url));
- SetText(url, $"{System.Net.WebUtility.HtmlEncode(title ?? url)}", 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)
diff --git a/Windows.Shell/ShellDataObject.cs b/Windows.Shell/ShellDataObject.cs
index f9ff8581..efac6aef 100644
--- a/Windows.Shell/ShellDataObject.cs
+++ b/Windows.Shell/ShellDataObject.cs
@@ -34,10 +34,7 @@ namespace Vanara.Windows.Shell
///
/// The format of the specified data. See for predefined formats.
/// The data to store.
- public ShellDataObject(string format, object data) : base()
- {
- SetData(format, data);
- }
+ public ShellDataObject(string format, object data) : base() => SetData(format, data);
/// Initializes a new instance of the class.
/// A list of ShellItem instances.
@@ -80,7 +77,7 @@ namespace Vanara.Windows.Shell
/// if the data object is within a drag-and-drop loop; otherwise, .
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);
}