Skip to content

Commit

Permalink
More BREAKING CHANGES to NativeClipboard. (Sorry!) All atomic methods…
Browse files Browse the repository at this point in the history
… to set data have been removed due to inconsistencies and design challenges. New model forces all setting and getting to be done through IDataObject and it's methods and extensions. See documentation for class for example. Addresses #355.
  • Loading branch information
dahall committed Jan 29, 2023
1 parent 716b9e4 commit dd1af5f
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 452 deletions.
52 changes: 46 additions & 6 deletions PInvoke/Shell32/Clipboard.cs
Expand Up @@ -23,6 +23,8 @@ namespace Vanara.PInvoke
{
public static partial class Shell32
{
private static readonly Lazy<TYMED> AllTymed = new(() => Enum.GetValues(typeof(TYMED)).Cast<TYMED>().Aggregate((a, b) => a | b));

/// <summary>
/// <para>Values used with the DROPDESCRIPTION structure to specify the drop image.</para>
/// </summary>
Expand Down Expand Up @@ -390,8 +392,6 @@ public static object GetData(this IDataObject dataObj, uint formatId, DVASPECT a
}
}

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>
Expand Down Expand Up @@ -435,6 +435,21 @@ public static T GetData<T>(this IDataObject dataObj, uint formatId, int index =
return hmem.ToType<T>(charSet == CharSet.Auto ? (StringHelper.GetCharSize(charSet) == 1 ? CharSet.Ansi : CharSet.Unicode) : charSet);
}

/// <summary>
/// This is used when a group of files in CF_HDROP (FileDrop) format is being renamed as well as transferred. The data consists of an
/// array that contains a new name for each file, in the same order that the files are listed in the accompanying CF_HDROP format.
/// The format of the character array is the same as that used by CF_HDROP to list the transferred files.
/// </summary>
/// <returns>A list of strings containing a new name for each file.</returns>
public static string[] GetFileNameMap(this IDataObject dataObj)
{
if (dataObj.IsFormatAvailable(RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPW)))
return dataObj.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPW) as string[];
else if (dataObj.IsFormatAvailable(RegisterClipboardFormat(ShellClipboardFormat.CFSTR_FILENAMEMAPA)))
return dataObj.GetData(ShellClipboardFormat.CFSTR_FILENAMEMAPA) as string[];
return new string[0];
}

/// <summary>Gets an HTML string from bytes returned from the clipboard.</summary>
/// <param name="bytes">The bytes from the clipboard.</param>
/// <returns>The string representing the HTML.</returns>
Expand Down Expand Up @@ -467,6 +482,18 @@ public static string GetHtmlFromClipboard(byte[] bytes)
return Encoding.UTF8.GetString(bytes, startFrag, endFrag - startFrag);
}

/// <summary>Gets the text from the native Clipboard in the specified format.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="formatId">A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.</param>
/// <returns>The string value or <see langword="null"/> if the format is not available.</returns>
public static string GetText(this IDataObject dataObj, uint formatId) => GetData(dataObj, formatId)?.ToString();

/// <summary>Gets the text from the native Clipboard in the specified format.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="format">A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.</param>
/// <returns>The string value or <see langword="null"/> if the format is not available.</returns>
public static string GetText(this IDataObject dataObj, string format) => GetData(dataObj, format)?.ToString();

/// <summary>
/// Determines whether the data object is capable of rendering the data described in the parameters. Objects attempting a paste or
/// drop operation can call this method before calling GetData to get an indication of whether the operation may be successful.
Expand All @@ -487,8 +514,6 @@ public static bool IsFormatAvailable(this IDataObject dataObj, uint formatId)
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>
Expand Down Expand Up @@ -557,7 +582,7 @@ public static void SetData(this IDataObject dataObj, uint formatId, object obj,
//if (CLIPFORMAT.CF_TEXT.Equals(formatId))
// mbr = ClipboardBytesFormatter.Instance.Write(UnicodeToAnsiBytes(str));
//else
mbr = ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, GetEncoding(attr), true));
mbr = ClipboardBytesFormatter.Instance.Write(StringHelper.GetBytes(str, GetEncoding(attr), true));
break;

case IEnumerable<string> strlist:
Expand Down Expand Up @@ -652,6 +677,18 @@ public static void SetData(this IDataObject dataObj, uint formatId, object obj,
public static void SetData<T>(this IDataObject dataObj, uint formatId, T obj, int index = -1) where T : struct =>
SetData(dataObj, formatId, SafeMoveableHGlobalHandle.CreateFromStructure(obj), DVASPECT.DVASPECT_CONTENT, index);

/// <summary>Sets multiple text types to the data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="text">The Unicode Text value.</param>
/// <param name="htmlText">The HTML text value. If <see langword="null"/>, this format will not be set.</param>
/// <param name="rtfText">The Rich Text Format value. If <see langword="null"/>, this format will not be set.</param>
public static void SetText(this IDataObject dataObj, string text, string htmlText = null, string rtfText = null)
{
if (text is not null) dataObj.SetData(CLIPFORMAT.CF_UNICODETEXT, text);
if (htmlText is not null) dataObj.SetData(ShellClipboardFormat.CF_HTML, htmlText);
if (rtfText is not null) dataObj.SetData(ShellClipboardFormat.CF_RTF, rtfText);
}

/// <summary>Sets a URL with optional title to a data object.</summary>
/// <param name="dataObj">The data object.</param>
/// <param name="url">The URL.</param>
Expand Down Expand Up @@ -679,7 +716,8 @@ public static void SetUrl(this IDataObject dataObj, string url, string title = n
public static bool TryGetData<T>(this IDataObject dataObj, uint formatId, out T obj, int index = -1)
{
if (IsFormatAvailable(dataObj, formatId))
try {
try
{
var charSet = GetCharSet(ShellClipboardFormat.clipFmtIds.Value.TryGetValue(formatId, out (string name, ClipCorrespondingTypeAttribute attr) data) ? data.attr : null);
obj = GetData<T>(dataObj, formatId, index, charSet);
return true;
Expand Down Expand Up @@ -715,6 +753,8 @@ private static CharSet GetCharSet(ClipCorrespondingTypeAttribute attr)
return charSet;
}

private static Encoding GetEncoding(ClipCorrespondingTypeAttribute attr) => (Encoding)Activator.CreateInstance(attr.EncodingType ?? typeof(UnicodeEncoding));

/// <summary>
/// <para>
/// Used with the CFSTR_SHELLIDLIST clipboard format to transfer the pointer to an item identifier list (PIDL) of one or more Shell
Expand Down
41 changes: 23 additions & 18 deletions UnitTests/Windows.Shell/ClipboardTests.cs
@@ -1,5 +1,4 @@
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using NUnit.Framework;
using System;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -75,11 +74,10 @@ public void GetSetShellItems1()
[Test]
public void GetSetShellItems2()
{
Clipboard.Clear();
ShellItem[] items = Array.ConvertAll(files, f => new ShellItem(f));
Clipboard.SetShellItems(items);
var shArray = Clipboard.GetShellItemArray();
Assert.That(shArray.Count, Is.GreaterThan(0));
Clipboard.SetDataObject(Clipboard.CreateDataObjectFromShellItems(items));
var shArray = ShellItemArray.FromDataObject(Clipboard.CurrentDataObject);
Assert.That(shArray.Count, Is.EqualTo(items.Length));
CollectionAssert.AreEquivalent(files, shArray.Select(s => s.FileSystemPath));
}

Expand Down Expand Up @@ -242,31 +240,38 @@ public void GetSetDataTest()
public void SetNativeTextHtmlTest()
{
SHCreateDataObject(ppv: out var ido).ThrowIfFailed();
ido.SetData(ShellClipboardFormat.Register(ShellClipboardFormat.CF_HTML), html);
var outVal = ido.GetData(ShellClipboardFormat.Register(ShellClipboardFormat.CF_HTML));
ido.SetData(ShellClipboardFormat.CF_HTML, html);
var outVal = ido.GetData(ShellClipboardFormat.CF_HTML);
Assert.That(outVal, Is.EqualTo(html));
}

[Test]
public void SetNativeTextMultTest()
{
const string stxt = "112233";
Clipboard.SetText(stxt);
Assert.That(Clipboard.GetText(TextDataFormat.Text), Is.EqualTo(stxt));

Clipboard.SetText(txt, txt);
Assert.That(Clipboard.GetText(TextDataFormat.Text), Is.EqualTo(txt));
Assert.That(Clipboard.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
Assert.That(Clipboard.GetText(TextDataFormat.Html), Is.EqualTo(txt));
TestContext.WriteLine(Clipboard.GetText(TextDataFormat.Html));
var ido = Clipboard.CreateEmptyDataObject();
ido.SetData(CLIPFORMAT.CF_UNICODETEXT, stxt);
Clipboard.SetDataObject(ido);
Assert.That(Clipboard.CurrentDataObject.GetData(CLIPFORMAT.CF_UNICODETEXT), Is.EqualTo(stxt));

ido = Clipboard.CreateEmptyDataObject();
ido.SetText(txt, txt);
Clipboard.SetDataObject(ido);
Assert.That(Clipboard.CurrentDataObject.GetText(CLIPFORMAT.CF_TEXT), Is.EqualTo(txt));
Assert.That(Clipboard.CurrentDataObject.GetText(CLIPFORMAT.CF_UNICODETEXT), Is.EqualTo(txt));
Assert.That(Clipboard.CurrentDataObject.GetText(ShellClipboardFormat.CF_HTML), Is.EqualTo(txt));
TestContext.WriteLine(Clipboard.CurrentDataObject.GetText(ShellClipboardFormat.CF_HTML));
}

[Test]
public void SetNativeTextUnicodeTest()
{
const string txt = @"“0’0©0è0”";
Clipboard.SetText(txt, TextDataFormat.UnicodeText);
Assert.That(Clipboard.GetText(TextDataFormat.UnicodeText), Is.EqualTo(txt));
var ido = Clipboard.CreateEmptyDataObject();
ido.SetData(CLIPFORMAT.CF_UNICODETEXT, txt);
Clipboard.SetDataObject(ido);

Assert.That(Clipboard.CurrentDataObject.GetText(CLIPFORMAT.CF_UNICODETEXT), Is.EqualTo(txt));
}

//[Test]
Expand Down

0 comments on commit dd1af5f

Please sign in to comment.