このサイトでは、分析、カスタマイズされたコンテンツ、および広告に Cookie を使用します。このサイトを引き続き閲覧すると、Cookie の使用に同意するものと見なされます。
Hi, Developers,
straightapps.com
作成 January 15, 2020
トップページ > Windows トップ > クリップボードにテキストや画像を入れる
line
Windows 開発
line

ここでは、クリップボードにプレーンテキストや画像を入れる方法について、書いています。

Ctrl + X はカット、 Ctrl + C はコピー、 そして Ctrl + V はペースト(貼り付け)です。 Windows でドキュメントを作成するのに、 コピー&ペースト ( コピペと言われます。 ) は欠かせません。

これを自分のプログラムで実現しなくても、特に困ることはありませんが、 ごくたまに、ツールなどで欲しくなることがあります。

欲しくなる頻度が低く、その手順を忘れてしまいがちでしたので、 とりあえずプレーンテキストをクリップボードに入れる方法と、 あわせて画像データをクリップボードに入れる方法をまとめました。

テキストについては、ペーストできることをメモ帳などで確認しているのみですが、 仕様的に正しい実装になっていれば、ペースト側のアプリは選ばないはずです。 テキストの取り出し方法は、ここでは扱っていません。

画像データについては、ペイントで確認していますが、確認のため、取り出しについても扱っています。

▼ セクション一覧

プレーンテキストをクリップボードにコピーする
画像データをクリップボードにコピーする
画像データをクリップボードから取り出す

なお、本サイトのご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。

プレーンテキストをクリップボードにコピーする

投稿 January 15, 2020

今回は、Visual C++ 2015、MFC 使用、マルチバイト文字セット使用という設定で、プログラムをビルドしています。

リストボックスに列挙したテキストが、クリックで選択されたときに、そのテキストをクリップボードにコピーする動作としています。 クリップボードにコピーしたいテキストは、CString 型の変数に入っている前提ですが、 クリップボードに入れるために LPTSTR ( マルチバイト文字セットなら1バイトの char です。 ) に変換していますので、特に気にする必要はありません。

コードはシンプルです。

まずは、何をクリップボードに入れるにしても、クリップボードを準備する必要があります。

まず、OpenClipboard 関数に、オーナーのウィンドウハンドルを渡して、クリップボードを開きます。

続けて、今はクリップボードにテキストを入れようとしていますので、 EmptyClipboard 関数で、クリップボードにあるデータをクリアします。

// クリップボードを開きます。
if (!::OpenClipboard(GetSafeHwnd())) {
	MessageBox(_T("クリップボードを開けません。"), NULL, MB_ICONSTOP);
	return;
}

// クリップボードを空にしてオーナーになります。
if (!::EmptyClipboard()) {
	::CloseClipboard();
	MessageBox(_T("クリップボードをクリアできません。"), NULL, MB_ICONSTOP);
	return;
}

クリップボードに渡すデータは、グローバルメモリに置く必要があります。

GlobalAlloc 関数で、クリップボードに入れたいテキストのぶんだけ、メモリを用意します。 ここで、GHND 指定GMEM_MOVEABLEGMEM_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 関数で、先頭の 14 バイト ( BITMAPFILEHEADER 構造体のサイズです。 ) を除いた部分を書き込んで、完了です。

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();
▲ページ先頭へ

画像データをクリップボードから取り出す

投稿 January 15, 2020

画像データをクリップボードにコピーでき、ペイントなどのアプリにペーストできたので、 今度は自分で取り出せるかを試しました。

まずは、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();
▲ページ先頭へ
line
関連トピックス
line

Windows 10 のクリップボード

クリップボード (Windows 10 バージョン 1809 の新機能)

Windows 開発トップ

Windows 開発関連の情報を、書いています。

line
その他のおすすめ
line

まだありません。



© 2017-2021 StraightApps.com 無断転載を禁じます。No reproduction without permission.