クリップボードの操作

ウィンドウでデータの切り取り、コピー、貼り付けを行うときは、クリップボードを使う必要があります。 ウィンドウは、切り取りとコピーの操作ではクリップボードにデータを配置し、貼り付け操作ではクリップボードからデータを取得します。 以降のセクションでは、これらの操作とそれに関連する問題について説明します。

クリップボードにデータを配置したり、クリップボードからデータを取得したりするには、最初に OpenClipboard 関数を使ってクリップボードを開く必要があります。 クリップボードを開くことができるのは、一度に 1 つのウィンドウだけです。 クリップボードを開いているウィンドウを調べるには、GetOpenClipboardWindow 関数を呼び出します。 終わったら、ウィンドウで CloseClipboard 関数を呼び出してクリップボードを閉じる必要があります。

このセクションでは、次のトピックについて説明します。

切り取りとコピーの操作

ウィンドウでクリップボードに情報を配置するには、最初に EmptyClipboard 関数を使ってクリップボードの以前の内容をクリアします。 この関数は、WM_DESTROYCLIPBOARD メッセージを以前のクリップボード所有者に送信し、クリップボード上のデータに関連付けられているリソースを解放して、クリップボードを開いているウィンドウにクリップボードの所有権を割り当てます。 クリップボードを所有しているウィンドウを調べるには、GetClipboardOwner 関数を呼び出します。

クリップボードを空にした後、ウィンドウでは、説明が最も多いクリップボード形式から最も少ない形式の順に、可能な限り多くのクリップボード形式でデータをクリップボードに配置します。 形式ごとに、ウィンドウで SetClipboardData 関数を呼び出して、形式識別子とグローバル メモリ ハンドルを指定します。 メモリ ハンドルは NULL にすることができます。これは、要求されたらウィンドウでデータをレンダリングすることを示します。 詳しくは、「遅延レンダリング」をご覧ください。

貼り付け操作

ウィンドウでクリップボードから貼り付け情報を取得するには、最初に、取得するクリップボード形式を決定します。 通常、ウィンドウでは EnumClipboardFormats 関数を使って利用できるクリップボード形式を列挙し、認識する最初の形式を使います。 この方法では、データがクリップボードに配置されたときに設定された優先順位に従って、使用できる最適な形式が選択されます。

または、ウィンドウで GetPriorityClipboardFormat 関数を使うこともできます。 この関数は、指定された優先順位に従って、使用できる最適なクリップボード形式を識別します。 1 つのクリップボード形式のみを認識するウィンドウでは、IsClipboardFormatAvailable 関数を使うことで、その形式を利用できるかどうかを判断できます。

使うクリップボード形式を決定した後で、ウィンドウは GetClipboardData 関数を呼び出します。 この関数は、指定した形式のデータを含むグローバル メモリ オブジェクトへのハンドルを返します。 ウィンドウでは、データを調べたりコピーしたりするために、メモリ オブジェクトを短期間ロックできます。 ただし、ウィンドウでオブジェクトを解放したり、長期間ロックしたままにしたりしないでください。

クリップボードの所有権

"クリップボードの所有者" は、クリップボードの情報に関連付けられているウィンドウです。 ウィンドウは、クリップボードにデータを配置すると、具体的には EmptyClipboard 関数を呼び出すと、クリップボードの所有者になります。 ウィンドウは、閉じられるか、別のウィンドウがクリップボードを空にするまで、クリップボードの所有者でい続けます。

クリップボードが空にされると、クリップボードの所有者は WM_DESTROYCLIPBOARD メッセージを受け取ります。 ウィンドウは、次のようないくつかの理由でこのメッセージを処理する可能性があります。

  • ウィンドウが、1 つ以上のクリップボード形式を遅延レンダリングしました。 WM_DESTROYCLIPBOARD メッセージに応答して、ウィンドウは要求によりデータをレンダリングするために割り当てられたリソースを解放する可能性があります。 データのレンダリングについて詳しくは、「遅延レンダリング」をご覧ください。
  • ウィンドウが、プライベート クリップボード形式でクリップボードにデータを配置しました。 プライベート クリップボード形式のデータは、クリップボードが空にされるときにシステムによって解放されません。 そのため、クリップボードの所有者は、WM_DESTROYCLIPBOARD メッセージを受信したらデータを解放する必要があります。 プライベート クリップボード形式について詳しくは、「クリップボードの形式」をご覧ください。
  • ウィンドウが、CF_OWNERDISPLAY クリップボード形式を使ってクリップボードにデータを配置しました。 ウィンドウは、WM_DESTROYCLIPBOARD メッセージに応答して、クリップボード ビューアー ウィンドウに情報を表示するために使ったリソースを解放する可能性があります。 この代替形式について詳しくは、「所有者の表示形式」をご覧ください。

遅延レンダリング

ウィンドウは、クリップボードにクリップボード形式を配置するとき、データが必要になるまで、その形式のデータのレンダリングを遅らせることができます。 これを行うには、アプリケーションで SetClipboardData 関数の hData パラメーターに NULL を指定できます。 これは、アプリケーションが複数のクリップボード形式をサポートしていて、その一部またはすべてがレンダリングに時間がかかるものである場合に便利です。 ウィンドウは、NULL ハンドルを渡すことによって、複雑なクリップボード形式を必要な場合にのみレンダリングします。

ウィンドウは、クリップボード形式のレンダリングを遅らせる場合、クリップボードの所有者である限り、要求に応じてその形式をレンダリングできるようにしておく必要があります。 システムは、レンダリングされていない特定の形式の要求を受信すると、クリップボード所有者に WM_RENDERFORMAT メッセージを送信します。 ウィンドウは、このメッセージを受信したら、SetClipboardData 関数を呼び出して、要求された形式でクリップボードにグローバル メモリ ハンドルを配置する必要があります。

アプリケーションでは、WM_RENDERFORMAT メッセージに応答して SetClipboardData を呼び出す前に、クリップボードを開いてはなりません。 クリップボードを開く必要はなく、それを行おうとしても、現在はその形式のレンダリングを要求したアプリケーションによってクリップボードが開かれたままになっているため、失敗します。

破棄されようとしているクリップボード所有者が、一部またはすべてのクリップボード形式を遅延レンダリングしている場合は、WM_RENDERALLFORMATS メッセージを受け取ります。 このメッセージを受け取ったウィンドウは、クリップボードを開き、GetClipboardOwner 関数を使って自分がまだクリップボードの所有者であることを確認してから、自分が提供しているすべてのクリップボード形式の有効なメモリ ハンドルを、クリップボードに配置する必要があります。 これにより、クリップボードの所有者が破棄された後でも、これらの形式を確実に使用できます。

WM_RENDERFORMAT とは異なり、WM_RENDERALLFORMATS に応答するアプリケーションでは、SetClipboardData を呼び出してクリップボードにグローバル メモリ ハンドルを配置する前に、クリップボードを開く必要があります。

WM_RENDERALLFORMATS メッセージに応答してレンダリングされないすべてのクリップボード形式は、他のアプリケーションで使用できず、クリップボード関数によって列挙されなくなります。

遅延レンダリングのガイダンス

遅延レンダリングはパフォーマンス機能であり、アプリケーションは、要求されない可能性がある形式のクリップボード データをレンダリングする作業を回避できます。 ただし、遅延レンダリングの使用に伴う次のトレードオフについて考慮する必要があります。

  • 遅延レンダリングを使うと、アプリケーションが若干複雑になり、前に説明したように、2 つのレンダリング ウィンドウ メッセージを処理する必要があります。
  • 遅延レンダリングを使った場合、データのレンダリングにかかる時間がユーザーにわかるほど長くなると、アプリケーションは UI の応答性を維持することができなくなります。 遅延レンダリングでは、データが最終的に必要になった場合、前に説明したように、ウィンドウはレンダリング ウィンドウ メッセージを処理しながらデータをレンダリングする必要があります。 その結果、レンダリング ウィンドウ メッセージの処理中は他のウィンドウ メッセージを処理できないので、データのレンダリングに非常に時間がかかる場合は、レンダリングが行われている間、アプリケーションが目に見えて応答しなくなる (ハングする) 可能性があります。 遅延レンダリングを使わないアプリケーションでは、レンダリング実行中の UI の応答性を維持するため、代わりにバックグラウンド スレッドでデータをレンダリングすることを選択できます。場合によっては、遅延レンダリングの使用時には利用できない進行状況またはキャンセル オプションを提供します。
  • 遅延レンダリングを使用し、最終的にデータが必要になった場合は、わずかなオーバーヘッドが発生します。 前に説明したように、遅延レンダリングを使用するウィンドウでは、最初に NULL ハンドルを使って SetClipboardData 関数を呼び出し、後でデータが必要になったら、ウィンドウ メッセージに応答して、レンダリングされたデータへのハンドルを使って SetClipboardData 関数をもう一度呼び出す必要があります。 その結果、最終的にデータが必要になった場合、遅延レンダリングを使用すると、ウィンドウ メッセージを処理して、SetClipboardData 関数を 2 回目に呼び出すコストが増えます。 このコストは小さなものですが、ゼロではありません。 アプリケーションがサポートするクリップボード形式が 1 つだけで、常にデータが最終的に要求される場合、遅延レンダリングを使うことで追加されるのはこのわずかなオーバーヘッドだけです (コストはハードウェアによって異なり、10 から 100 マイクロ秒の間と推定されます)。 ただし、データが小さい場合は、遅延レンダリングを使うことによるオーバーヘッドがデータのレンダリング コストを超える可能性があり、遅延レンダリングを使ってパフォーマンスを向上させる目的が実現しなくなる可能性があります。 (テストでは、データが既に最終形式になっているときは、データが 100 KiB 以下の場合、遅延レンダリングを使用するオーバーヘッドが、データをクリップボードにコピーするコストを常に超えました。このテストには、データをレンダリングするコストは含まれず、レンダリング後にデータをコピーするコストだけです。)
  • 遅延レンダリングは、それによって増えるオーバーヘッドより多くの時間が節約される場合、最終的にパフォーマンス上の利点となります。 遅延レンダリングのオーバーヘッドを判断するには、測定するのが最善ですが、10 から 100 マイクロ秒と推定されます。 クリップボード形式ごとに遅延レンダリングを使うことで節約される量を計算するには、その形式でデータをレンダリングするコストを測定し、(上で説明したウィンドウ メッセージに基づいて) その形式が最終的に要求される頻度を決定します。 データのレンダリング コストに、(クリップボードが空にされるか、その内容が変更される前に) データが最終的に要求されない時間の割合を乗算して、各クリップボード形式に対する遅延レンダリングの節約量を決定します。 遅延レンダリングは、節約量がオーバーヘッド コストを超える場合、最終的にパフォーマンス上の利点となります。
  • 具体的なガイドラインとしては、データのレンダリングに大きなコストがかからない、テキストのようなクリップボード形式を 1 つだけサポートするアプリケーションでは、データのサイズが 4 KiB 以下の場合、データをクリップボードに直接配置することを検討します。

メモリとクリップボード

クリップボードに配置するメモリ オブジェクトは、GlobalAlloc 関数と GMEM_MOVEABLE フラグを使って割り当てる必要があります。

メモリ オブジェクトがクリップボードに配置された後、そのメモリ ハンドルの所有権はシステムに移されます。 クリップボードが空にされるとき、メモリ オブジェクトが次のいずれかのクリップボード形式である場合、システムは指定されている関数を呼び出してメモリ オブジェクトを解放します。

オブジェクトを解放する関数 クリップボードの形式
DeleteMetaFile
CF_DSPENHMETAFILE
CF_DSPMETAFILEPICT
CF_ENHMETAFILE
CF_METAFILEPICT
DeleteObject
CF_BITMAP
CF_DSPBITMAP
CF_PALETTE
GlobalFree
CF_DIB
CF_DIBV5
CF_DSPTEXT
CF_OEMTEXT
CF_TEXT
CF_UNICODETEXT
なし
CF_OWNERDISPLAY
クリップボードの CF_OWNERDISPLAY オブジェクトを空にするときは、アプリケーション自体がメモリ オブジェクトを解放する必要があります。