Skip to content

Commit 2d02c0a

Browse files
committed
Still more work on NativeClipboard
1 parent f793f09 commit 2d02c0a

File tree

1 file changed

+59
-45
lines changed

1 file changed

+59
-45
lines changed

Windows.Shell.Common/NativeClipboard.cs

+59-45
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public static class NativeClipboard
4343
private const int stdRetryCnt = 5;
4444
private static readonly object objectLock = new();
4545
private static ListenerWindow listener;
46+
[ThreadStatic]
47+
private static bool oleInit = false;
4648
private static IComDataObject writableDataObj;
4749

4850
/// <summary>Occurs when whenever the contents of the Clipboard have changed.</summary>
@@ -82,36 +84,26 @@ public static IEnumerable<uint> CurrentlySupportedFormats
8284
}
8385
}
8486

85-
static bool TryMultThenThrowIfFailed(Func<HRESULT> func, int n = stdRetryCnt)
86-
{
87-
HRESULT hr = HRESULT.S_OK;
88-
for (int i = 1; i <= n; i++)
89-
{
90-
hr = func();
91-
if (hr.Failed && i == n)
92-
throw hr.GetException();
93-
}
94-
return hr == HRESULT.S_OK;
95-
}
96-
97-
static bool TryMultThenThrowIfFailed(Func<IComDataObject, HRESULT> func, IComDataObject o, int n = stdRetryCnt)
98-
{
99-
HRESULT hr = HRESULT.S_OK;
100-
for (int i = 1; i <= n; i++)
101-
{
102-
hr = func(o);
103-
if (hr.Failed && i == n)
104-
throw hr.GetException();
105-
}
106-
return hr == HRESULT.S_OK;
107-
}
87+
/// <summary>Retrieves the clipboard sequence number for the current window station.</summary>
88+
/// <returns>
89+
/// The clipboard sequence number. If you do not have <c>WINSTA_ACCESSCLIPBOARD</c> access to the window station, the function
90+
/// returns zero.
91+
/// </returns>
92+
/// <remarks>
93+
/// The system keeps a serial number for the clipboard for each window station. This number is incremented whenever the contents of
94+
/// the clipboard change or the clipboard is emptied. You can track this value to determine whether the clipboard contents have
95+
/// changed and optimize creating DataObjects. If clipboard rendering is delayed, the sequence number is not incremented until the
96+
/// changes are rendered.
97+
/// </remarks>
98+
public static uint SequenceNumber => GetClipboardSequenceNumber();
10899

109100
/// <summary>Gets or sets a <see cref="IComDataObject"/> instance from the Windows Clipboard.</summary>
110101
/// <value>A <see cref="IComDataObject"/> instance.</value>
111102
static IComDataObject ReadOnlyDataObject
112103
{
113104
get
114105
{
106+
Init();
115107
int n = stdRetryCnt;
116108
HRESULT hr = HRESULT.S_OK;
117109
for (int i = 1; i <= n; i++)
@@ -136,24 +128,12 @@ static IComDataObject WritableDataObj
136128
}
137129
set
138130
{
131+
Init();
139132
TryMultThenThrowIfFailed(OleSetClipboard, value);
140133
Flush();
141134
}
142135
}
143136

144-
/// <summary>Retrieves the clipboard sequence number for the current window station.</summary>
145-
/// <returns>
146-
/// The clipboard sequence number. If you do not have <c>WINSTA_ACCESSCLIPBOARD</c> access to the window station, the function
147-
/// returns zero.
148-
/// </returns>
149-
/// <remarks>
150-
/// The system keeps a serial number for the clipboard for each window station. This number is incremented whenever the contents of
151-
/// the clipboard change or the clipboard is emptied. You can track this value to determine whether the clipboard contents have
152-
/// changed and optimize creating DataObjects. If clipboard rendering is delayed, the sequence number is not incremented until the
153-
/// changes are rendered.
154-
/// </remarks>
155-
public static uint SequenceNumber => GetClipboardSequenceNumber();
156-
157137
/// <summary>Clears the clipboard of any data or formatting.</summary>
158138
public static void Clear() => WritableDataObj = null;
159139

@@ -174,7 +154,7 @@ static IComDataObject WritableDataObj
174154
public static IEnumerable<uint> EnumAvailableFormats() => ReadOnlyDataObject.EnumFormats().Select(f => unchecked((uint)f.cfFormat));
175155

176156
/// <summary>Carries out the clipboard shutdown sequence. It also releases any IDataObject instances that were placed on the clipboard.</summary>
177-
public static void Flush() { TryMultThenThrowIfFailed(OleFlushClipboard); writableDataObj = null; }
157+
public static void Flush() { Init(); TryMultThenThrowIfFailed(OleFlushClipboard); writableDataObj = null; }
178158

179159
/// <summary>Retrieves the window handle of the current owner of the clipboard.</summary>
180160
/// <returns>
@@ -274,13 +254,14 @@ public static string[] GetFileNameMap()
274254
/// <summary>Determines whether the clipboard contains data in the specified format.</summary>
275255
/// <param name="id">A standard or registered clipboard format.</param>
276256
/// <returns>If the clipboard format is available, the return value is <see langword="true"/>; otherwise <see langword="false"/>.</returns>
277-
public static bool IsFormatAvailable(uint id) => ReadOnlyDataObject.IsFormatAvailable(id); // EnumAvailableFormats().Contains(id);
257+
public static bool IsFormatAvailable(uint id) => ReadOnlyDataObject.IsFormatAvailable(id);
278258

279259
/// <summary>Determines whether the clipboard contains data in the specified format.</summary>
280260
/// <param name="id">A clipboard format string.</param>
281261
/// <returns>If the clipboard format is available, the return value is <see langword="true"/>; otherwise <see langword="false"/>.</returns>
282262
public static bool IsFormatAvailable(string id) => IsClipboardFormatAvailable(RegisterFormat(id));
283263

264+
// EnumAvailableFormats().Contains(id);
284265
/// <summary>Registers a new clipboard format. This format can then be used as a valid clipboard format.</summary>
285266
/// <param name="format">The name of the new format.</param>
286267
/// <returns>The registered clipboard format identifier.</returns>
@@ -296,12 +277,12 @@ public static string[] GetFileNameMap()
296277
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
297278
/// <param name="data">The binary data in the specified format.</param>
298279
/// <exception cref="System.ArgumentNullException">data</exception>
299-
public static void SetBinaryData(uint formatId, byte[] data) => WritableDataObj.SetData(formatId, data);
280+
public static void SetBinaryData(uint formatId, byte[] data) => Setter(i => i.SetData(formatId, data));
300281

301282
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
302283
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
303284
/// <param name="data">The data in the format dictated by <paramref name="formatId"/>.</param>
304-
public static void SetData<T>(uint formatId, T data) where T : struct => WritableDataObj.SetData(formatId, data);
285+
public static void SetData<T>(uint formatId, T data) where T : struct => Setter(i => i.SetData(formatId, data));
305286

306287
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
307288
/// <param name="formatId">The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.</param>
@@ -310,7 +291,7 @@ public static void SetData<T>(uint formatId, IEnumerable<T> values) where T : st
310291
{
311292
var pMem = SafeMoveableHGlobalHandle.CreateFromList(values);
312293
Win32Error.ThrowLastErrorIfInvalid(pMem);
313-
WritableDataObj.SetData(formatId, pMem);
294+
Setter(i => i.SetData(formatId, pMem));
314295
}
315296

316297
/// <summary>Places data on the clipboard in a specified clipboard format.</summary>
@@ -322,7 +303,7 @@ public static void SetData(uint formatId, IEnumerable<string> values, StringList
322303
{
323304
var pMem = SafeMoveableHGlobalHandle.CreateFromStringList(values, packing, charSet);
324305
Win32Error.ThrowLastErrorIfInvalid(pMem);
325-
WritableDataObj.SetData(formatId, pMem);
306+
Setter(i => i.SetData(formatId, pMem));
326307
}
327308

328309
/// <summary>Puts a list of shell items onto the clipboard.</summary>
@@ -337,7 +318,7 @@ public static void SetShellItems(ShellFolder parent, IEnumerable<ShellItem> rela
337318
if (parent is null) throw new ArgumentNullException(nameof(parent));
338319
if (relativeShellItems is null) throw new ArgumentNullException(nameof(relativeShellItems));
339320
SHCreateDataObject(parent.PIDL, relativeShellItems.Select(i => i.PIDL), default, out var dataObj).ThrowIfFailed();
340-
OleSetClipboard(dataObj).ThrowIfFailed();
321+
WritableDataObj = dataObj;
341322
}
342323

343324
/// <summary>Sets multiple text types to the Clipboard.</summary>
@@ -354,13 +335,13 @@ public static void SetText(string text, string htmlText = null, string rtfText =
354335
/// <summary>Sets a specific text type to the Clipboard.</summary>
355336
/// <param name="value">The text value.</param>
356337
/// <param name="format">The clipboard text format to set.</param>
357-
public static void SetText(string value, TextDataFormat format) => WritableDataObj.SetData(Txt2Id(format), value);
338+
public static void SetText(string value, TextDataFormat format) => Setter(i => i.SetData(Txt2Id(format), value));
358339

359340
/// <summary>Sets a URL with optional title to the clipboard.</summary>
360341
/// <param name="url">The URL.</param>
361342
/// <param name="title">The title. This value can be <see langword="null"/>.</param>
362343
/// <exception cref="ArgumentNullException">url</exception>
363-
public static void SetUrl(string url, string title = null) => WritableDataObj.SetUrl(url, title);
344+
public static void SetUrl(string url, string title = null) => Setter(i => i.SetUrl(url, title));
364345

365346
/// <summary>Obtains data from a source data object.</summary>
366347
/// <typeparam name="T">The type of the object being retrieved.</typeparam>
@@ -373,6 +354,39 @@ public static void SetText(string text, string htmlText = null, string rtfText =
373354
/// <returns><see langword="true"/> if data is available and retrieved; otherwise <see langword="false"/>.</returns>
374355
public static bool TryGetData<T>(uint formatId, out T obj, int index = -1) => ReadOnlyDataObject.TryGetData(formatId, out obj, index);
375356

357+
private static void Init() { if (!oleInit) { oleInit = OleInitialize().Succeeded; } }
358+
359+
private static void Setter(Action<IComDataObject> action)
360+
{
361+
var ido = WritableDataObj;
362+
action(ido);
363+
WritableDataObj = ido;
364+
}
365+
366+
private static bool TryMultThenThrowIfFailed(Func<HRESULT> func, int n = stdRetryCnt)
367+
{
368+
HRESULT hr = HRESULT.S_OK;
369+
for (int i = 1; i <= n; i++)
370+
{
371+
hr = func();
372+
if (hr.Failed && i == n)
373+
throw hr.GetException();
374+
}
375+
return hr == HRESULT.S_OK;
376+
}
377+
378+
private static bool TryMultThenThrowIfFailed(Func<IComDataObject, HRESULT> func, IComDataObject o, int n = stdRetryCnt)
379+
{
380+
HRESULT hr = HRESULT.S_OK;
381+
for (int i = 1; i <= n; i++)
382+
{
383+
hr = func(o);
384+
if (hr.Failed && i == n)
385+
throw hr.GetException();
386+
}
387+
return hr == HRESULT.S_OK;
388+
}
389+
376390
private static uint Txt2Id(TextDataFormat tf) => tf switch
377391
{
378392
TextDataFormat.Text => CLIPFORMAT.CF_TEXT,

0 commit comments

Comments
 (0)