mirror of https://github.com/dahall/Vanara.git
Tons of fixes and updates to IDataObject and Clipboard methods and wrapper classes.
parent
03ef05b415
commit
55512c732e
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;\">“We’ve 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 = @"“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>("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 = @"“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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue