“When we paste a table copied from Excel, the formatting falls apart. We want it to paste as a table.” “Content we copy in our app turns into something weird when we paste it into Word.” “We want to be able to take files in by drag and drop.” — In consulting conversations about business-app changes, requests around copy-and-paste and drag and drop (D&D) are a staple.
Precisely because these are “features everyone takes for granted”, how they actually work is surprisingly little known. If you think of the clipboard as “a box you put one piece of data into”, you cannot explain why the same copy produces different results depending on where you paste, or why paste stops working after you close the source app. The real clipboard is a mechanism that places the same content in multiple formats at once, and lets the paste side pick a format it understands.
And drag and drop, at bottom, is OLE data transfer that hands across exactly the same data representation as the clipboard (IDataObject), through COM interfaces. In other words, copy-and-paste and D&D are siblings: understand one correctly and the other is right there.
This article is aimed at IT staff at small and midsize companies and at Windows app developers. It ties together, in a single picture, how clipboard formats work, the practices on the paste side and the copy side, the correct way to watch the clipboard, the administrative policies for clipboard history, cloud sync, and RDP, and the structure and pitfalls of OLE drag and drop.
1. The Bottom Line First
- The clipboard is a single area shared by the apps on the same desktop (window station), and what sits there is not “one piece of data” but the same content in multiple formats at once. A different session, such as RDP, originally has a different clipboard; the redirection feature is what bridges the two. Because the destination picks a format it understands, the same copy produces different results depending on where you paste.12
- For text, use CF_UNICODETEXT. CF_TEXT is ANSI and code-page dependent, and on Japanese systems it is a breeding ground for mojibake. The system converts between the two implicitly, but the canonical side is Unicode.3
- Files travel as CF_HDROP (a double-NUL-terminated array of paths), and formatted text uses the registered format “HTML Format”. HTML Format has an unusual structure: UTF-8 text with a header of byte offsets.45
- The real cause of “I closed the source app and could no longer paste” is delayed rendering. It is a mechanism that places not the payload but only a promise to “produce it when asked”; if you skip materializing it at exit (responding to WM_RENDERALLFORMATS, or OleFlushClipboard for OLE), paste stops working.26
- Treat pasted data as untrusted input from outside. Microsoft itself states plainly that “clipboard data is not trusted. Parse it carefully”.7
- For watching the clipboard, AddClipboardFormatListener + WM_CLIPBOARDUPDATE is the only option. Do not use polling, and do not use the old SetClipboardViewer (the viewer chain). Registered formats that keep secrets out of history and sync (ExcludeClipboardContentFromMonitorProcessing and friends) are also provided.81
- Clipboard history (Win+V) and cloud sync are an IT-management concern. You can control them with AllowClipboardHistory and AllowCrossDeviceClipboard via GPO / Intune (Policy CSP), and RDP clipboard redirection has a dedicated policy of its own.91011
- Drag and drop is COM. The same IDataObject as the clipboard is handed between IDropSource (the drag source) and IDropTarget (the drop target) through the DoDragDrop loop. RegisterDragDrop requires initialization with OleInitialize (STA).1213
- You cannot drop from ordinary-privilege File Explorer onto an elevated app. UIPI (message blocking by integrity level) is the cause, and it is a constraint you should know at design time.14
Below we walk through this from the clipboard foundations up.
2. What the Clipboard Really Is — Not “One Piece of Data” but “the Same Content in Multiple Formats”
The clipboard is a common data-sharing mechanism that every app sharing the same desktop can reach (more precisely, it is per window station: a different user session or RDP session each has its own clipboard. Copy-and-paste works over RDP because the redirection feature bridges the two — Chapter 7). The first principle is that it is user-driven: the official design stance is that you do not put data in or take data out behind the user’s back.1
The important point is that a copy does not place “one piece of data”. The window that copies empties the clipboard and then places several formats in a row, expressing the same content from the more capable format down to the less capable one.2 For example, when you copy a table in a spreadsheet, conceptually something like the following is on the clipboard at the same time.
| Priority | Format | Contents |
|---|---|---|
| 1 | App-private format | A complete internal representation, including formulas and formatting (for paste back into the same app) |
| 2 | HTML Format | An HTML fragment that keeps the table structure and formatting |
| 3 | CSV | Cell-delimited text |
| 4 | CF_UNICODETEXT | Tab-separated plain text |
| 5 | Image format | A bitmap of how the table looks |
The paste side picks a format it understands from this list and extracts it. Paste into Word and you get a formatted table; paste into Notepad and you get tab-separated text — because the two chose different formats. “The result depends on where you paste” is not a bug; it is the normal consequence of this design.
flowchart TB
accTitle: Why the same copy produces different results depending on where you paste
accDescr: The copy side places the same content on the clipboard in multiple formats, and the paste side picks a format it understands, so Word gets a formatted table and Notepad gets tab-separated text
copy["Copy: spreadsheet"] --> cb["Clipboard (many formats)"]
cb --> rich["Richer formats"]
cb --> plain["Plainer formats"]
rich --> f1["App-private"]
rich --> f2["HTML Format"]
plain --> f3["CSV"]
plain --> f4["CF_UNICODETEXT"]
f2 -->|"Word"| word["Formatted table"]
f4 -->|"Notepad"| notepad["Tab-separated text"]
Put the other way around, the opening complaints — “the formatting falls apart”, “something weird gets pasted” — almost all reduce to a problem of how one side chooses formats, or how the other side offers them. Chapter 4 covers the paste side; Chapter 5 covers the copy side.
3. Standard Formats and Registered Formats — CF_UNICODETEXT, CF_HDROP, HTML Format
3.1. Standard Formats — Use the Unicode Side for Text
Formats the OS defines up front are called standard formats. The ones that show up constantly in business apps are the following.3
| Format | Value | Contents |
|---|---|---|
| CF_TEXT | 1 | ANSI text (code-page dependent) |
| CF_UNICODETEXT | 13 | Unicode text. This is the canonical format for text |
| CF_HDROP | 15 | A list of file paths (an HDROP handle) |
| CF_DIB | 8 | A device-independent bitmap |
| CF_LOCALE | 16 | The locale identifier associated with the text |
CF_TEXT and CF_UNICODETEXT are converted into each other implicitly by the system (synthesized formats). The character-code conversion uses the code page associated with CF_LOCALE.3 Relying on that conversion drops characters that ANSI cannot represent (for example Unicode-only symbols and combining characters), so the rule is unify what the app reads and writes on CF_UNICODETEXT (DataFormats.UnicodeText in .NET).
flowchart TB
accTitle: Implicit conversion between CF_UNICODETEXT and CF_TEXT
accDescr: The app reads and writes only CF_UNICODETEXT; the system synthesizes CF_TEXT by implicit conversion with the CF_LOCALE code page. Characters that ANSI cannot represent are dropped in that conversion
apprw["App reads and writes"] --> uni["CF_UNICODETEXT"]
uni <-->|"CF_LOCALE conversion"| ansi["CF_TEXT (ANSI)"]
ansi -.-> loss["Unrepresentable chars are dropped"]
3.2. CF_HDROP — Files Travel as “a List of Paths”
CF_HDROP is what is used when you copy files in File Explorer, or when you drag and drop files. The payload is not the files themselves; it is a memory block that lays out a “double-NUL-terminated” array: after a DROPFILES structure header, full-path strings separated by NUL characters, and an empty string at the end. The header’s pFiles is the start offset of the path list, and fWide says whether the strings are Unicode.4
[DROPFILES header: pFiles=start offset of the path list, fWide=1(Unicode)]
C:\data\a.txt(NUL)C:\data\b.txt(NUL)(NUL)
In native code you pull them out one at a time with DragQueryFile; in .NET you receive them as a string[] via DataFormats.FileDrop. The fact that “what travels is only the paths, not the files themselves” will matter again in the D&D of Chapters 8 and 9.
flowchart TB
accTitle: Memory-block layout of CF_HDROP
accDescr: A DROPFILES structure sits at the start of the global memory; pFiles is the start offset of the path list and fWide says whether it is Unicode. Full paths then follow, NUL-separated, and the block ends with an empty string (double NUL). What travels is only the paths, not the files themselves
hdr["DROPFILES (pFiles / fWide)"] --> p1["C:\\data\\a.txt + NUL"]
p1 --> p2["C:\\data\\b.txt + NUL"]
p2 --> tail["Empty string (double NUL)"]
hdr -.-> note["Only paths travel, not files"]
3.3. Registered Formats — RegisterClipboardFormat and “HTML Format”
For data that standard formats cannot express, an app can pick a name and register its own format. Pass a name to RegisterClipboardFormat and you get a format ID back; registering under the same name from a different app returns the same ID, so once you agree on the name you can share data between apps.1 When you pass structured data among your own suite of apps, use a name that will not collide, such as KomuraSoft.Report.RowData.
The representative registered format is “HTML Format”, for formatted text (together with RTF, one of the two major rich-text formats). The payload is UTF-8 text, but it has an unusual structure: a header that lists byte offsets is attached at the front.5
Version:0.9
StartHTML:<byte offset of the start of the whole HTML>
EndHTML:<byte offset of the end of the whole HTML>
StartFragment:<byte offset of the start of the fragment>
EndFragment:<byte offset of the end of the fragment>
<html><body>
<!--StartFragment--><b>bold</b> fragment text<!--EndFragment-->
</body></html>
Each offset is a byte position from the start of the data, including the header itself; the usual practice is to reserve a fixed width (for example 10 digits) and write the measured values back after you have built the body. StartFragment/EndFragment mark the start and end of “the fragment the user actually selected” in bytes (not in characters). In UTF-8 that includes Japanese, the character count and the byte count diverge, so if you get this offset calculation wrong, paste into another app drops the beginning or the end. If you generate HTML Format yourself, you must fill the header with byte positions measured after encoding to UTF-8.5
flowchart TB
accTitle: How the HTML Format header relates to the offsets
accDescr: The header's StartHTML and EndHTML point at the whole HTML, and StartFragment and EndFragment point at the fragment the user selected, both as byte positions from the start of the data. Because character count and byte count diverge in UTF-8, fill the header with byte positions measured after encoding
header["Header (byte offsets)"] --> html["Whole HTML"]
html --> frag["Selected fragment"]
header -.-> byte["Offsets are bytes after UTF-8"]
CSV (DataFormats.CommaSeparatedValue in .NET) is also commonly used for tabular data. For interop with Excel, offering HTML Format (with formatting), CSV (values only), and CF_UNICODETEXT (tab-separated) together means you do not have to pick a single paste destination.
4. Practices on the Paste Side — Format Priority and Validation
4.1. Look from Rich Formats Down
The formats on the clipboard are lined up in the order the copy side placed them (that is, from more expressive to less). The paste side’s baseline is to look, among the formats you can handle, starting from the one with the most information. In Win32 you either enumerate with EnumClipboardFormats and use the first format you recognize, or you pass your own priority list to GetPriorityClipboardFormat and let it choose.2
In .NET the branch looks something like the following.
// Pasting a table: look from rich to plain
var data = Clipboard.GetDataObject();
if (data is null) return;
// Advertising a format does not guarantee the payload is a string. Use this
// branch only when the type also checks out; otherwise fall through to the next candidate
if (data.GetDataPresent(DataFormats.Html)
&& data.GetData(DataFormats.Html) is string html)
{
// Validate the HTML Format header, then import as a table
}
else if (data.GetDataPresent(DataFormats.CommaSeparatedValue))
{
// Import as CSV
}
else if (data.GetDataPresent(DataFormats.UnicodeText))
{
// Import as tab-separated text
}
That is the answer to the opening complaint, “pasting an Excel table falls apart”. An app that reads only plain text never receives the table structure. How far down the format list you accept is a design decision on the paste side.
flowchart TB
accTitle: Paste branching that looks from rich formats down
accDescr: If HTML Format is present and the payload is also a string, import as a table; otherwise try CSV; if that is missing too, fall through to tab-separated text. If none of the candidates is present, refuse
startsel["Start paste"] --> h{"HTML Format + string?"}
h -->|"yes"| useh["Validate header → table"]
h -->|"no"| c{"CSV present?"}
c -->|"yes"| usec["Import as CSV"]
c -->|"no"| t{"UnicodeText?"}
t -->|"yes"| uset["Tab-separated text"]
t -->|"no"| giveup["Refuse"]
4.2. Pasted Data Is External Input
It is easy to miss, but clipboard contents are data from outside, and you do not know which app placed them. Microsoft also warns, in the OLE clipboard documentation, that “clipboard data is not trusted. Parse it carefully before you use it in the app”.7
- Validate that HTML Format header offsets do not point outside the buffer (apps that emit broken headers do exist).
- Values you import as numbers, dates, or codes should go through the same validation as on-screen input.
- Put in a defense against huge data. Even if someone pastes a hundreds-of-megabytes image or millions of lines of text, do not block the UI, and refuse once a limit is exceeded. A caveat: .NET’s GetData, at the moment you call it, materializes the whole payload into a managed string (and delayed rendering runs as part of that), so placing a size check after GetData is not a defense. In Win32, checking GlobalSize on the HGLOBAL that GetClipboardData returns does give you a defense at the stage of “do not proceed into conversion and parsing as a managed string”, but for delayed-rendering formats GetClipboardData itself kicks off rendering, so you still cannot prevent materialization on the copy-source side. To keep the UI from freezing, move the fetch off the UI thread (and even then, because .NET’s Clipboard requires STA, do it on a dedicated thread set to STA, not on the Task.Run thread-pool thread (MTA) — Section 5.1).
The idea that “a value that arrives from outside, whatever the path, is validated before you use it” is the same one laid out in “Never Use a QR Code’s Decoded Value As-Is”. The assumption that paste is safe because it is a user action is how accidents start.
flowchart TB
accTitle: Validate pasted data before you use it
accDescr: Data taken from the clipboard goes through format-present, payload-type, size-limit, and content validation in that order; fail any of them and you refuse or fall through to the next candidate format
present["Format present?"] --> type["Payload type OK?"]
type --> size["Size within limit?"]
size --> content["Validate contents"]
content --> ok["Import"]
type -.->|"wrong type"| rej["Refuse / next format"]
size -.->|"too large"| rej
content -.->|"invalid"| rej
5. Practices on the Copy Side — Offering Multiple Formats at Once, and Delayed Rendering
5.1. Place Multiple Formats at Once
The copy side’s practice is the reverse of 4.1: offer a rich format and a plain format at the same time. With the WinForms/WPF DataObject you can write it in a few lines.15
// WinForms (System.Windows.Forms). WPF is the same shape with System.Windows DataObject/Clipboard
var data = new DataObject();
data.SetData(DataFormats.Html, htmlFormatText); // HTML Format string including the header
data.SetData(DataFormats.CommaSeparatedValue, csv); // CSV
data.SetData(DataFormats.UnicodeText, plainText); // Plain text
Clipboard.SetDataObject(data, copy: true); // copy:true = keep after the app exits
Two notes. First, .NET’s Clipboard class can only be used from an STA thread.15 The WinForms/WPF UI thread is STA because of [STAThread], so this is normally not a problem, but touching it from a background thread fails (the STA/MTA fundamentals are in “COM STA/MTA Fundamentals”). Second, what copy: true means is tied to delayed rendering in the next subsection.
5.2. Delayed Rendering — Why “Close the Source and You Cannot Paste”
Building a large payload in many formats every time is wasteful, so the clipboard has a mechanism called delayed rendering. Pass NULL as the data handle to SetClipboardData and, instead of the payload, only a promise to “produce it when asked” is registered; when someone requests that format, WM_RENDERFORMAT arrives at the copy source, and only then is the data generated.2
The consequence of this design is the opening “I closed the source app and could no longer paste”. Before it exits, the copy source receives WM_RENDERALLFORMATS and is responsible for materializing every format that has not yet been rendered; exit without doing that and the format is lost.2
flowchart TB
accTitle: Delayed rendering and why close-then-paste fails
accDescr: The copy source registers only a promise with a NULL handle, and materializes on demand via WM_RENDERFORMAT. At exit it is responsible for materializing every format with WM_RENDERALLFORMATS; skip that and the format is lost
promise["SetClipboardData NULL = promise"] --> req["Paste side requests it"]
req --> render["WM_RENDERFORMAT → build now"]
promise --> quit["Copy source about to exit"]
quit -->|"RENDERALLFORMATS"| ok["Paste works after exit"]
quit -->|"Skip materialize"| lost["Format lost after close"]
On the OLE clipboard (the style that places an IDataObject with OleSetClipboard), this relationship is even clearer. All the clipboard holds is a pointer to the data object, and calling OleFlushClipboard at app exit materializes the data onto the clipboard, so paste still works after exit.6 .NET’s Clipboard.SetDataObject(data, copy: true) is what specifies this “keep it after exit” behavior.
When you copy a large range in Excel and try to quit, the prompt “There is a large amount of information on the Clipboard. Do you want to be able to paste this information into another program later?” is exactly the confirmation of whether to run this materialization (the flush). If you use delayed rendering in your own app, remember that the exit-time materialization is part of the same set. Delayed rendering is a performance optimization, and because the render request runs synchronously inside message processing, data that takes a long time to generate has the trade-off of freezing the UI.2
6. Practices for Watching the Clipboard — Listener, Retry, and History Exclusion
6.1. Use AddClipboardFormatListener
Requirements such as “we want to detect a barcode-reader value or a copy from the line-of-business system and import it automatically” need you to watch clipboard changes. Historically there are three methods; today the right answer is one.8
| Method | Assessment |
|---|---|
| Read on a timer (polling) | Wasteful, and you can miss updates. Do not use |
| SetClipboardViewer (viewer chain) | A bug in one app in the chain breaks the whole chain. Kept only for backward compatibility |
| AddClipboardFormatListener | Recommended. WM_CLIPBOARDUPDATE arrives at the registered window |
flowchart TB
accTitle: Flow of clipboard watching
accDescr: Register with AddClipboardFormatListener when the handle is created, and WM_CLIPBOARDUPDATE arrives no matter which app copied. Read with a retry, and unregister symmetrically with RemoveClipboardFormatListener when the handle is destroyed
created["AddClipboardFormatListener"] --> wait["Wait"]
anyapp["Some app copies"] --> notify["WM_CLIPBOARDUPDATE"]
wait --> notify
notify --> readtry["Read with retry (6.2)"]
readtry --> wait
destroyed["RemoveClipboardFormatListener"] -.->|"unregister"| created
// Minimal WinForms implementation
public partial class MainForm : Form
{
[DllImport("user32.dll", SetLastError = true)]
static extern bool AddClipboardFormatListener(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
const int WM_CLIPBOARDUPDATE = 0x031D;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
AddClipboardFormatListener(Handle);
}
protected override void OnHandleDestroyed(EventArgs e)
{
// Unregister symmetrically to match handle destruction / recreation
RemoveClipboardFormatListener(Handle);
base.OnHandleDestroyed(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CLIPBOARDUPDATE)
{
// Read Clipboard.GetDataObject() here and import if the format is one you need
}
base.WndProc(ref m);
}
}
6.2. Retry When You Cannot Open It
Only one window at a time can open the clipboard; while another process has it open, OpenClipboard fails.2 Immediately after WM_CLIPBOARDUPDATE, the copy source or another watcher is often still operating, so a temporary read failure is a normal event. Always put in a few retries with a short wait (tens of milliseconds) in between. Note that the .NET Clipboard overloads that let you specify a retry count and interval exist only on the write side, SetDataObject. There is no equivalent on the read side (GetDataObject and friends), so you write the catch-wait-retry yourself — ExternalException on WinForms, COMException on WPF.
flowchart TB
accTitle: Clipboard-read retry flow
accDescr: Only one window at a time can open the clipboard, so a read right after the change notification can fail by racing with another process. On an exception, wait tens of milliseconds and retry; if you hit the limit, give up this time and pick it up on the next update
upd["WM_CLIPBOARDUPDATE"] --> tryread["Attempt a read"]
tryread -->|"success"| useok["Import (Ch. 4 checks)"]
tryread -->|"in use"| waitretry["Wait tens of ms"]
waitretry -->|"retry"| tryread
waitretry -->|"limit"| giveup2["Give up this time"]
6.3. Keep It Out of History and Sync — Care for Copy Features That Handle Secrets
Windows has clipboard history (Win+V) and cross-device sync (the cloud clipboard), and data an app places is in scope for both by default. An app that puts secrets such as passwords or account numbers on a copy feature also places a registered format that excludes the content from history and sync.1
- ExcludeClipboardContentFromMonitorProcessing: Place this and that copy’s contents are included in neither history nor sync.
- CanIncludeInClipboardHistory (DWORD 0): Suppress history only.
- CanUploadToCloudClipboard (DWORD 0): Suppress cross-device sync only.
The reason a password copied by a password manager does not remain on Win+V is this mechanism. You get a format ID by passing the name to RegisterClipboardFormat and set it alongside the ordinary data, so it is worth implementing in any business app that handles secrets.
7. The Clipboard from an IT Perspective — History, Cloud Sync, and RDP Controls
Stepping a little away from development, here are the points that matter to an administrator. Clipboard history accumulates recent copies, and the cloud clipboard syncs copies across devices signed in with the same Microsoft account / Microsoft Entra account.10 Convenient as that is, it also produces residue and spillover: personal information copied from a line-of-business system accumulates in history, and content copied on a work PC syncs to a personal PC.
The two policies you use to control this in an organization are the following.
| What you control | GPO (Computer Configuration > Administrative Templates > System > OS Policies) | Policy CSP (Intune) | Default |
|---|---|---|---|
| Clipboard history | Allow Clipboard History | Experience/AllowClipboardHistory | Allowed |
| Cross-device sync | Allow Clipboard synchronization across devices | Privacy/AllowCrossDeviceClipboard | Allowed |
Both are available from Windows 10 version 1809 onward; disable them and the corresponding items in the Settings app are grayed out, and the policy takes effect immediately.910
The other staple is RDP (Remote Desktop) clipboard redirection. By default, copy-and-paste works between the local PC and the remote session, so it can become a path for taking secrets off a server. The “Do not allow clipboard redirection” policy (registry value fDisableClip) can block both directions.11 Recent Windows Server / Windows 11 releases have also added finer-grained policies, such as restricting the server-to-client direction to text only. Whether you ban it outright or restrict it in stages is a balance of operations and security.
flowchart TB
accTitle: Paths clipboard contents can spread along, and the control points
accDescr: Copied contents are in scope for history and cloud sync by default, and on RDP they travel to another session via redirection. Each path can be controlled by policy, and the app side can exclude itself from history and sync with the exclusion formats
cb["Clipboard"] --> hist["History (Win+V)"]
cb --> cloud["Cloud sync"]
cb --> rdp["RDP redirection"]
hist -.-> p1["AllowClipboardHistory"]
cloud -.-> p2["AllowCrossDeviceClipboard"]
rdp -.-> p3["fDisableClip"]
cb -.-> p4["App exclude formats (6.3)"]
8. Drag and Drop Is COM — IDataObject + IDropSource + IDropTarget
8.1. The Same Data as the Clipboard, a Different Way of Carrying It
OLE drag and drop runs with the following three roles.12
| Role | Who implements it | Job |
|---|---|---|
| IDataObject | Drag source | The payload being carried. The same multi-format data object as the clipboard |
| IDropSource | Drag source | Deciding whether the drag continues or is cancelled, and cursor feedback |
| IDropTarget | Drop target | Declaring accept/reject in DragEnter/DragOver/DragLeave/Drop, and receiving the drop |
The drag source calls DoDragDrop, the drag loop starts, and when the mouse enters a drop-target window that IDropTarget is notified; on drop, the IDataObject is handed over. Official documentation also says that “D&D provides exactly the same functionality as clipboard copy-and-paste. If an app already implements copy-and-paste, the addition is small”.12 In other words, the multi-format DataObject you built in Chapters 2 through 5 becomes the D&D payload as-is.
flowchart TB
accTitle: Flow of OLE drag and drop
accDescr: The drag source puts an IDataObject in the payload and calls DoDragDrop to start the drag loop; the drop target's IDropTarget declares accept/reject in DragEnter and DragOver, and on Drop picks a format from the IDataObject and extracts it
src["IDataObject + IDropSource"] -->|"DoDragDrop"| loop["Drag loop"]
loop -->|"mouse enters"| enter["DragEnter/Over: Effect"]
enter -->|"button up"| drop["IDropTarget.Drop"]
drop --> data["Pick a format and extract"]
8.2. OleInitialize (STA) Is Required
A window that will be a drop target registers with RegisterDragDrop, and there is a classic pitfall here. If you initialized COM with CoInitialize/CoInitializeEx, RegisterDragDrop always fails with E_OUTOFMEMORY; you must initialize with OleInitialize.13 OleInitialize initializes COM as STA, because D&D is a feature rooted in the STA world of windows and a message pump. The calling thread must also be running a message pump; skip that and other apps hang during the drag.13 The background here is exactly the threading-model discussion in “COM STA/MTA Fundamentals”.
In a WinForms/WPF app the framework takes care of OLE initialization and the interface implementations, so the developer only has to write the events.
// WinForms: accept dropped files
listView1.AllowDrop = true;
listView1.DragEnter += (s, e) =>
{
// Also check that the source allows Copy (some sources only allow Move/Link)
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
&& (e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy
? DragDropEffects.Copy // Accept: receive as a copy
: DragDropEffects.None; // Do not accept
};
listView1.DragDrop += (s, e) =>
{
// Drag data is also untrusted input. Even if it advertises FileDrop, the payload
// can be null or a different type, and GetData itself can fail
object data;
try { data = e.Data.GetData(DataFormats.FileDrop); }
catch (COMException) { return; }
if (data is not string[] paths) return;
foreach (var path in paths)
{
// Validate the path before importing (Section 9.3)
}
};
The shape is the same in WPF: you receive with AllowDrop="True" and the DragOver/Drop events on the element, and you extract the path array with e.Data.GetData(DataFormats.FileDrop). Declaring accept/reject (Effect) on every DragEnter/DragOver is the IDropTarget convention; skip it and you get the bug where the cursor stays on “not allowed” and never changes.
9. D&D Pitfalls — Elevation, Move, and Path Validation
9.1. You Cannot Drop onto an App Elevated as Administrator
Drop a file from File Explorer onto an app launched with “Run as administrator” and nothing happens — this is not an implementation bug, it is OS behavior. UIPI (User Interface Privilege Isolation) blocks messages from a lower-integrity process to a higher-integrity window by default, so drop notifications from ordinary-privilege (medium-integrity) File Explorer never reach an elevated app.14
flowchart TB
accTitle: How UIPI blocks drops onto an elevated app
accDescr: Drop notifications from medium-integrity File Explorer to a high-integrity elevated app are blocked by UIPI by default and never arrive. Keep the UI at ordinary privilege and isolate privileged work, and the drop arrives
explorer["Explorer (medium)"] -->|"drop notify"| uipi{"UIPI"}
uipi -->|"blocked"| elevated["Elevated app: no drop"]
uipi -->|"passes"| normal["Ordinary UI: drop arrives"]
normal -.->|"delegate privileged work"| broker["Isolated elevated process"]
A workaround that individually allows specific messages such as WM_DROPFILES with ChangeWindowMessageFilterEx is well known,14 but what that lets through is the older (WM_DROPFILES) drop notification; it does not solve OLE D&D as a whole. The practical guidance is clear: stop designing the app to run elevated all the time. Isolate only the work that needs elevation into a separate process, and the UI itself can stay at ordinary privilege and receive D&D (the isolation design is covered in detail in “How to Concretely Isolate “Only the Operations That Need Administrator Privileges” in a Windows App”).
9.2. What DragDropEffects Means — Move Is a Contract That “the Original Goes Away”
Copy/Move/Link on DragDropEffects are not decoration; they are a contract between the drag source and the drop target. The drag source declares the set of effects it allows in DoDragDrop, the drop target picks the actual effect, and when Move succeeds, the drag source deletes the data (the file) — that is the convention. If the receiving side thoughtlessly returns Move, you get the accident “I dropped it and the original file disappeared”. For a business app’s import use, the receiving side stating Copy is the safe default.
flowchart TB
accTitle: The DragDropEffects contract — Move deletes the original
accDescr: The drag source declares the set of allowed effects in DoDragDrop, and the drop target picks the actual effect. When Move succeeds the drag source deletes the file, so for import the receiving side should state Copy
srcdecl["Source: allowed effects"] --> tgtsel["Target: pick Effect"]
tgtsel -->|"Copy"| copyok["Original remains (import)"]
tgtsel -->|"Move"| moveact["Source deletes the file"]
9.3. Validating a Dropped Path
What travels in CF_HDROP/FileDrop is only the path (Section 3.2). Before you import, put it through the same untrusted-input validation as paste.
- File or folder: Decide as a spec what happens when a whole folder is dropped (recurse and import, or refuse).
- OneDrive placeholders: The path may exist while the file body is not local — an on-demand file. The moment you open it a download starts, and offline it fails. Behavior and countermeasures are in “OneDrive “Files On-Demand” and Business Apps”.
- Long paths and unusual paths: Paths over MAX_PATH, network (UNC) paths, and paths on removable media should be accepted only after you have confirmed that the downstream processing can handle them.
- Count and total size: So that dropping thousands of files does not freeze the UI, make the import asynchronous and put in a limit and a progress display.
10. Summary
- The clipboard is a mechanism that places the same content in multiple formats at once in a single area shared within the same desktop (window station). The paste side picks the format, so the same copy produces different results.
- Text is CF_UNICODETEXT, files are CF_HDROP, and formatted text is the registered format HTML Format (a byte-offset header + UTF-8).
- The paste side looks from rich to plain and treats the payload as external input. The copy side offers multiple formats at once, and if it uses delayed rendering it implements exit-time materialization (WM_RENDERALLFORMATS / OleFlushClipboard) as well.
- Watching is AddClipboardFormatListener + WM_CLIPBOARDUPDATE. Prepare for OpenClipboard races with a retry, and keep secrets out of history and sync with ExcludeClipboardContentFromMonitorProcessing and friends.
- IT can control clipboard history, cloud sync, and RDP redirection with GPO / Intune. The default is allowed for all of them, so decide deliberately in environments that handle secrets.
- D&D is COM: IDropSource/IDropTarget hand across the same IDataObject as the clipboard. RegisterDragDrop requires OleInitialize (STA).
- Drops onto an elevated app are blocked by UIPI. Move on DragDropEffects is a contract that “the original goes away”; validate a dropped path before you import it.
Copy-and-paste and D&D are, to the user, features that should feel like air. That is exactly why “I cannot paste”, “it falls apart”, and “it disappeared” hurt the experience so much — and why an app that offers multiple formats and handles drops properly makes everyday operations smoother by itself. I hope this is useful material when you decide what to fix first.
Related Articles
- What Are COM / ActiveX / OCX? - The Differences and Relationships Explained
- COM STA/MTA Fundamentals - Threading Models and How to Avoid Hangs
- Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11
- Why EXCEL.EXE Processes Remain After C# Excel COM Automation — Reference Release Patterns and the Replacement Decision
- Windows App UX Design - Priorities by Usage Environment
- OneDrive “Files On-Demand” and Business Apps — The Assumptions Placeholders Break and How to Deal with Them
Related Consulting Areas
KomuraSoft LLC handles the design and implementation of copy-and-paste and drag-and-drop support in business apps (offering multiple formats, Excel interop, importing dropped files), root-cause investigation of problems such as “it falls apart when I paste” or “the copy disappears”, input automation that watches the clipboard, and implementations that keep confidential data out of history and sync. Cases that involve the lower layers of COM and OLE are welcome even if you start from isolating the symptom.
- Windows Application Development
- COM Component Development
- Technical Consulting & Design Review
- Contact Us
References
-
Microsoft Learn, Clipboard Formats. On a window being able to place the same information in multiple clipboard formats; registered formats via RegisterClipboardFormat (registering the same name returns the same value, so apps can share it); synthesized formats; and excluding content from clipboard history / cloud sync with ExcludeClipboardContentFromMonitorProcessing, CanIncludeInClipboardHistory, and CanUploadToCloudClipboard. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Clipboard Operations. On only one window at a time being able to open the clipboard; placing formats from more expressive to less expressive at copy time; format selection at paste time with EnumClipboardFormats / GetPriorityClipboardFormat; delayed rendering by passing NULL to SetClipboardData and the WM_RENDERFORMAT / WM_RENDERALLFORMATS responsibilities; and the trade-offs of delayed rendering. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Standard Clipboard Formats. On the definitions of the standard formats CF_TEXT (ANSI), CF_UNICODETEXT, CF_HDROP, CF_DIB, and CF_LOCALE, and on the system implicitly converting CF_TEXT and CF_UNICODETEXT using the code page associated with CF_LOCALE. ↩ ↩2 ↩3
-
Microsoft Learn, Shell Clipboard Formats. On CF_HDROP being composed of a DROPFILES structure plus a double-NUL-terminated array of full-path strings; retrieving individual paths with DragQueryFile; and CFSTR_ shell formats requiring registration via RegisterClipboardFormat. ↩ ↩2
-
Microsoft Learn, HTML Clipboard Format. On the registered name being “HTML Format”; the header structure with byte offsets such as Version, StartHTML, EndHTML, StartFragment, and EndFragment; the encoding always being UTF-8; and the StartFragment/EndFragment comment convention. ↩ ↩2 ↩3
-
Microsoft Learn, OleFlushClipboard function (ole2.h). On OleSetClipboard making the clipboard hold only a pointer to the data object; OleFlushClipboard materializing the data onto the clipboard so paste still works after the app exits; and emptying the clipboard with OleSetClipboard(NULL) when you do not need to keep it at exit. ↩ ↩2
-
Microsoft Learn, OleGetClipboard function (ole2.h). On how to obtain an IDataObject from the clipboard, and the warning that clipboard data is not trusted and should be parsed carefully before the app uses it. ↩ ↩2
-
Microsoft Learn, Using the clipboard. On comparing the three ways of watching the clipboard (viewer windows, sequence numbers, and format listeners); new programs being expected to use a listener via AddClipboardFormatListener; the viewer chain being fragile when chain maintenance is incomplete; and sequence numbers not being something you should poll. ↩ ↩2
-
Microsoft Learn, Policy CSP - Experience. On allowing or denying clipboard history with the Experience/AllowClipboardHistory policy; availability from Windows 10 version 1809 onward; the default being allowed; and the GPO mapping under “System > OS Policies” with changes taking effect immediately. ↩ ↩2
-
Microsoft Learn, Policy CSP - Privacy. On allowing or denying cross-device clipboard sync with the Privacy/AllowCrossDeviceClipboard policy; sync happening between devices signed in with the same Microsoft account / Microsoft Entra account; and the default being allowed. ↩ ↩2 ↩3
-
Microsoft Learn, Policy CSP - ADMX_TerminalServer. On TS_CLIENT_CLIPBOARD (“Do not allow clipboard redirection”, registry value fDisableClip) being able to forbid clipboard sharing between local and remote in a Remote Desktop session, and on redirection being allowed by default. ↩ ↩2
-
Microsoft Learn, Drag and Drop (COM). On OLE drag and drop running with the three of IDropSource (drag source), IDropTarget (drop target), and DoDragDrop (the loop OLE provides); providing the same functionality as clipboard copy-and-paste, so that an app which already implements copy-and-paste needs only a small addition; and the kinds of feedback. ↩ ↩2 ↩3
-
Microsoft Learn, RegisterDragDrop function (ole2.h). On registering a drop-target window with an IDropTarget; always failing with E_OUTOFMEMORY if COM was initialized with CoInitialize/CoInitializeEx, so that OleInitialize is required; and the drag-source app hanging if the calling thread is not running a message pump. ↩ ↩2 ↩3
-
Microsoft Learn, ChangeWindowMessageFilterEx function (winuser.h). On UIPI being a security mechanism that by default blocks receiving messages from a lower-integrity sender, and on allowing specific messages per window with a message filter (MSGFLT_ALLOW). ↩ ↩2 ↩3
-
Microsoft Learn, How to add data to the Clipboard (Windows Forms). On placing data in multiple formats at once with DataObject and Clipboard.SetDataObject; adding in multiple formats so that other apps can recognize it; and the Clipboard class being usable only from an STA thread, so that [STAThread] is required. ↩ ↩2
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Windows App Outsourcing and Contract Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or contract development, here is how to sort out existing software modification, device inte...
What "Not Responding" Really Is — How Windows Decides an App Has Hung, and How to Design Apps That Don't
Windows' "Not Responding" is a mechanism in which the OS judges that a window has not retrieved a message for 5 seconds and replaces it w...
An Introduction to Windows App Accessibility — Preparing for UI Automation and Reasonable-Accommodation Requirements
Against the backdrop of the amended Act for Eliminating Discrimination against Persons with Disabilities, which took effect in April 2024...
Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11
Why a Windows 11 context menu hides items behind "Show more options", explained from the extension → ProgID → verb association basics thr...
CI/CD for WinForms / WPF Apps in Practice — Automating from Build to Signing and Distribution with GitHub Actions
A practical guide to setting up CI/CD for WinForms / WPF apps with GitHub Actions. Covers a minimal YAML for build+test on windows-latest...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
UI Threading & Timers
Topic page for WPF / WinForms UI threading, async flow, Dispatcher usage, and timer decisions.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Legacy Asset Reuse & Migration Support
We help plan staged migration while continuing to reuse COM / ActiveX / OCX assets, native code, and 32-bit dependencies.
Frequently Asked Questions
Common questions about the topic of this article.
- Why does the formatting of a table copied from Excel fall apart when I paste it into my app?
- The clipboard does not hold "one piece of data". The same content is placed in several formats at once (the source app's private format, HTML Format, CSV, Unicode text, and so on), and the destination app picks a format it understands and extracts that. When formatting falls apart, the typical cause is that the destination is reading only plain text (CF_UNICODETEXT). If you want the table structure as well, implement the paste side so that it prefers HTML Format or CSV. Conversely, if you want other apps to paste correctly from a copy made in your own app, offer both a rich format and a plain format at copy time.
- Why can I no longer paste after I close the app I copied from?
- Because the source is using delayed rendering. Apps that handle large data do not place the payload at copy time; they register only a promise on the clipboard that they will "produce it when asked". If the source then exits without materializing the data in response to WM_RENDERALLFORMATS at shutdown, any format that has not yet been rendered is lost. An app that uses the OLE clipboard (IDataObject) can keep paste working after exit by calling OleFlushClipboard at shutdown to materialize the data.
- How can my own app watch the clipboard for changes?
- The currently recommended method is to register your window as a listener with AddClipboardFormatListener and handle the WM_CLIPBOARDUPDATE message that arrives every time the contents change. Polling the contents on a timer wastes work and can miss updates, and the old viewer chain based on SetClipboardViewer is kept only for backward compatibility, because a bug in one app in the chain breaks the whole chain. Note also that OpenClipboard on a read can fail because another process holds the clipboard, so implement a retry with a short wait if you want the read to be stable.
- Is there a way to keep secrets such as passwords out of clipboard history (Win+V)?
- There are two levers, one on the app side and one on the policy side. On the app side, if you also place the registered format ExcludeClipboardContentFromMonitorProcessing when you copy, that content is included in neither history nor cross-device sync. You can also control each independently with CanIncludeInClipboardHistory (history only) and CanUploadToCloudClipboard (sync only). This is the mechanism password managers use. If you want to turn it off for the whole organization, you can disable history and cloud sync themselves with AllowClipboardHistory and AllowCrossDeviceClipboard via Group Policy or Intune (Policy CSP).
- Why can I not drag and drop a file onto an app that is running as administrator?
- Because a security mechanism called UIPI (User Interface Privilege Isolation) blocks message delivery from a lower-integrity process to a higher-integrity window. File Explorer runs at ordinary privilege (medium integrity), so drag-and-drop notifications never reach the window of an elevated app. A workaround that individually allows messages such as WM_DROPFILES with ChangeWindowMessageFilterEx is well known, but it applies only to the older drop notification. The real fix is to stop designing the app to run elevated all the time, and to isolate only the work that needs elevation into a separate process.