Ctrl + X はカット、
Ctrl + C はコピー、
そして Ctrl + V はペースト(貼り付け)です。
Windows でドキュメントを作成するのに、
これを自分のプログラムで実現しなくても、特に困ることはありませんが、 ごくたまに、ツールなどで欲しくなることがあります。
欲しくなる頻度が低く、その手順を忘れてしまいがちでしたので、 とりあえずプレーンテキストをクリップボードに入れる方法と、 あわせて画像データをクリップボードに入れる方法をまとめました。
テキストについては、ペーストできることをメモ帳などで確認しているのみですが、 仕様的に正しい実装になっていれば、ペースト側のアプリは選ばないはずです。 テキストの取り出し方法は、ここでは扱っていません。
画像データについては、ペイントで確認していますが、確認のため、取り出しについても扱っています。
なお、本サイトのご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
投稿 January 15, 2020
今回は、Visual C++ 2015、MFC 使用、マルチバイト文字セット使用という設定で、プログラムをビルドしています。
リストボックスに列挙したテキストが、クリックで選択されたときに、そのテキストをクリップボードにコピーする動作としています。
クリップボードにコピーしたいテキストは、CString 型の変数に入っている前提ですが、
クリップボードに入れるために
コードはシンプルです。
まずは、何をクリップボードに入れるにしても、クリップボードを準備する必要があります。
まず、OpenClipboard 関数に、オーナーのウィンドウハンドルを渡して、クリップボードを開きます。
続けて、今はクリップボードにテキストを入れようとしていますので、 EmptyClipboard 関数で、クリップボードにあるデータをクリアします。
// クリップボードを開きます。 if (!::OpenClipboard(GetSafeHwnd())) { MessageBox(_T("クリップボードを開けません。"), NULL, MB_ICONSTOP); return; } // クリップボードを空にしてオーナーになります。 if (!::EmptyClipboard()) { ::CloseClipboard(); MessageBox(_T("クリップボードをクリアできません。"), NULL, MB_ICONSTOP); return; }
クリップボードに渡すデータは、グローバルメモリに置く必要があります。
GlobalAlloc 関数で、クリップボードに入れたいテキストのぶんだけ、メモリを用意します。 ここで、GHND 指定は GMEM_MOVEABLE と GMEM_ZEROINIT の組み合わせで、 確保するメモリを移動可能で、0クリアしてもらう、というものです。普通の指定です。 サイズは、CString 型の strWord に入っているテキストのサイズに、終端 NULL を含めたサイズを指定しています。
確保したメモリを GlobalLock 関数で固定してそのポインタを取得、 wsprintf 関数でテキストを書き込み、GlobalUnlock 関数で固定を解除します。
そして、SetClipboardData 関数に hGlobal を渡すわけですが、その型を CF_TEXT とします。 これは、ただのテキストの意味です。 その他のクリップボード・フォーマットは、Standard Clipboard Formats にまとめられています。
最後は CloseClipboard 関数でクリップボードをクローズして、完了です。
// グローバルメモリにテキストを用意します。 HGLOBAL hGlobal = GlobalAlloc(GHND, strWord.GetLength() + 1); if (!hGlobal) { ::CloseClipboard(); MessageBox(_T("メモリを用意できません。"), NULL, MB_ICONSTOP); return; } LPTSTR p = (LPTSTR)GlobalLock(hGlobal); wsprintf(p, strWord); GlobalUnlock(hGlobal); ::SetClipboardData(CF_TEXT, hGlobal); ::CloseClipboard();
投稿 January 15, 2020
ダイアログに画像ファイルがドロップされたら、その画像をクリップボードにコピーする、という実装にしています。 ここではドロップの受け入れについては省略し、TCHAR 型の szBuffer[ MAX_PATH ] に、ファイル名がフルパスで入っているものとします。 ドロップについては「フォルダのドロップを受け付ける」をご参照ください。
まずは、クリップボードに直接関係ありませんが、MFC の CImage クラスを利用して、画像データを読み込みます。 これにより、BMP でも JPEG でも PNG でも関係なく、画像の処理が可能です。
CImage クラスが使えない場合でも、BMP ファイル形式でメモリにデータを用意できれば、同じことができます。
CImage img; if (FAILED(img.Load(szBuffer))) { MessageBox(_T("ドロップされたのは画像ファイルではありません。"), NULL, MB_ICONSTOP); return; }
クリップボードのオーナーになって、空にする、というのは、テキストの場合と同じです。
// クリップボードを開きます。 if (!::OpenClipboard(GetSafeHwnd())) { MessageBox(_T("クリップボードを開けません。"), NULL, MB_ICONSTOP); return; } // クリップボードを空にしてオーナーになります。 if (!::EmptyClipboard()) { ::CloseClipboard(); MessageBox(_T("クリップボードをクリアできません。"), NULL, MB_ICONSTOP); return; }
以下、いろいろ試したのでわかりにくいかも知れませんが、まずはグローバルメモリにストリームを作成して、そこに画像データを送ろうとしています。 まずは、CreateStreamOnHGlobal 関数で、ストリームを作成しています。
続けて、CImage クラスの Save 関数にストリームを指定し、 BMP 形式を指定して、書き込んでいます。 なんと便利なんでしょう!
IStream* stream = NULL; HRESULT hr = CreateStreamOnHGlobal(0, TRUE, &stream); if (!SUCCEEDED(hr)) { MessageBox(_T("ストリームを作成できませんでした。"), NULL, MB_ICONSTOP); return; } img.Save(stream, Gdiplus::ImageFormatBMP);
では、クリップボードに渡すグローバルメモリを用意し、そこにデータを書き込みます。
IStream_Size 関数で、liSize にストリームのサイズを得ます。 つまり、BMP ファイルのサイズと同じになります。 常識的なサイズであれば、DWORD に収まるのでその部分を取得し、 IStream_Reset 関数でストリーム内のポインタをストリームの先頭に戻しておきます。
用意するグローバルメモリのサイズは、ストリームのサイズから、ファイルの先頭にある BITMAPFILEHEADER 構造体のサイズを引いたものとします。
用意できたら、IStream_Read 関数で、先頭の
stream は不要になったので、削除します。
そして、テキストの場合と同じように、グローバルメモリにセットされたデータを、SetClipboardData 関数で、クリップボードに渡します。 指定するクリップボード・フォーマットは、CF_BITMAP ではなく、CF_DIB を指定しないといけないようです。
ULARGE_INTEGER liSize; IStream_Size(stream, &liSize); DWORD len = liSize.LowPart; IStream_Reset(stream); HGLOBAL hGlobal = GlobalAlloc(GHND, len - 14); PBYTE p = (PBYTE)GlobalLock(hGlobal); IStream_Read(stream, p, 14); // BITMAPFILEHEADER を読み飛ばします IStream_Read(stream, p, len - 14); GlobalUnlock(hGlobal); stream->Release(); ::SetClipboardData(CF_DIB, hGlobal);
下記のブロックは、必要ではないのですが、参考のために残しています。 書き込まれたデータの種別を調べるコードです。
EnumClipboardFormats 関数に0を渡すと、クリップボードに設定されているデータの、最初のクリップボード・フォーマット値が列挙されます。 以下、得られたクリップボード・フォーマット値を指定することで、その次のクリップボード・フォーマット値を得られます。
UINT unFormat; unFormat = ::EnumClipboardFormats(0); while (unFormat != 0) { if (unFormat == CF_TEXT) { MessageBox(_T("CF_TEXT")); } if (unFormat == CF_BITMAP) { MessageBox(_T("CF_BITMAP")); } if (unFormat == CF_DIB) { MessageBox(_T("CF_DIB")); } unFormat = ::EnumClipboardFormats(unFormat); }
最後にクリップボードをクローズして終わります。 CImage の img も、削除するほうが安心です。
::CloseClipboard(); img.Destroy();
画像データをクリップボードにコピーでき、ペイントなどのアプリにペーストできたので、 今度は自分で取り出せるかを試しました。
まずは、IsClipboardFormatAvailable 関数でフォーマットを調べます。 CF_DIB フォーマットがなければ、処理を続行できません。 なお、CF_DIB で保存しても、CF_BITMAP でも同様に取り出せるようです。
if (!::IsClipboardFormatAvailable(CF_DIB)) { return; }
読み出しのために、OpenClipboard 関数で、クリップボードを開きます。 設定するときとは異なり、エンプティにはしません。
続けて、GetClipboardData 関数で取得したデータを HBITMAP に変換します。
// クリップボードを開きます。 if (!::OpenClipboard(GetSafeHwnd())) { MessageBox(_T("クリップボードを開けません。"), NULL, MB_ICONSTOP); return; } HBITMAP hBmp = (HBITMAP)::GetClipboardData(CF_DIB); if (!hBmp) { ::CloseClipboard(); return; }
CImage の img に、画像イメージを展開します。
GlobalSize 関数で、クリップボードにあるデータのサイズを取得します。 CImage の Create 関数で、同じサイズのイメージ領域を用意し、 そのビットデータへのポインタを pBits に取得しておきます。 また、1ラインのバイト数を nWidthBytes に計算しておきます。
PBYTE pData = (PBYTE)::GlobalLock((HANDLE)hBmp); // BITMAP FILEHEADER 用に BITMAP INFOHEADER 以降のファイルサイズ取得します。 SIZE_T dwBitSize = GlobalSize(hBmp); BITMAPINFOHEADER* pBif = (BITMAPINFOHEADER*)pData; img.Create(pBif->biWidth, pBif->biHeight, pBif->biBitCount); // Height に負の値を指定するとトップダウン DIB に PBYTE pBits = (PBYTE)img.GetBits(); SIZE_T dwCopySize = dwBitSize - sizeof(BITMAPINFOHEADER); int nWidthBytes = dwCopySize / pBif->biHeight;
CImage の作成時に正の値の高さを指定すると、ボトム・アップ DIB になるようです。 ライン単位でデータをコピーしています。
トップ・ダウン DIB である場合にも動作するよう、コードを入れています。
// ボトム・アップ DIB の場合 if (img.GetPitch() < 0) { nWidthBytes = -img.GetPitch(); PBYTE pTo = pBits; PBYTE pFrom = pData + sizeof(BITMAPINFOHEADER) + dwCopySize - nWidthBytes; for (int i = 0; i < pBif->biHeight; i++) { memcpy(pTo, pFrom, nWidthBytes); pTo -= nWidthBytes; pFrom -= nWidthBytes; } } // トップ・ダウン DIB の場合 else { nWidthBytes = img.GetPitch(); PBYTE pFrom = pData + dwBitSize - nWidthBytes; for (int i = 0; i < pBif->biHeight; i++) { memcpy(pBits, pFrom, nWidthBytes); pBits += nWidthBytes; pFrom -= nWidthBytes; } } ::GlobalUnlock(pData);
あとは img を Draw 関数で描画するなり、Save 関数で保存するなり、好きなように使えます。
利用後は、CloseClipboard 関数でクリップボードを閉じることと、img を破棄することをお忘れなく。
img.Destroy(); ::CloseClipboard();
クリップボード (Windows 10 バージョン 1809 の新機能)
まだありません。