クリップボードの使用

このセクションには、次のタスクのコード サンプルがあります。

切り取り、コピー、貼り付けコマンドの実装

このセクションでは、標準 の 切り取りコピー貼 り付け コマンドをアプリケーションに実装する方法について説明します。 このセクションの例では、これらのメソッドを使用して、登録済みのクリップボード形式、 CF_OWNERDISPLAY 形式、および CF_TEXT 形式を使用してクリップボードにデータを 配置します。 登録された形式は、ラベルと呼ばれる長方形または楕円のテキスト ウィンドウを表すために使用されます。

データの選択

情報をクリップボードにコピーする前に、ユーザーはコピーまたは切り取る特定の情報を選択する必要があります。 アプリケーションでは、ユーザーがドキュメント内の情報を選択し、選択したデータを示す何らかの視覚的フィードバックを提供する必要があります。

編集メニューの作成

アプリケーションは、[編集] メニュー コマンドの標準キーボード アクセラレータを含むアクセラレータ テーブルを読み込む必要があります。 アクセラレータを有効にするには、 TranslateAccelerator 関数をアプリケーションのメッセージ ループに追加する必要があります。 キーボード アクセラレータの詳細については、「キーボード アクセラレータ」を参照してください。

WM_INITMENUPOPUP メッセージの処理

特定の時点ですべてのクリップボード コマンドをユーザーが使用できるわけではありません。 アプリケーションは WM_INITMENUPOPUP メッセージを処理して、使用可能なコマンドのメニュー項目を有効にし、使用できないコマンドを無効にする必要があります。

Label という名前の WM_INITMENUPOPUP アプリケーションの場合を次に示します。

case WM_INITMENUPOPUP:
    InitMenu((HMENU) wParam);
    break;

InitMenu 関数の定義は次のようになります。

void WINAPI InitMenu(HMENU hmenu) 
{ 
    int  cMenuItems = GetMenuItemCount(hmenu); 
    int  nPos; 
    UINT id; 
    UINT fuFlags; 
    PLABELBOX pbox = (hwndSelected == NULL) ? NULL : 
        (PLABELBOX) GetWindowLong(hwndSelected, 0); 
 
    for (nPos = 0; nPos < cMenuItems; nPos++) 
    { 
        id = GetMenuItemID(hmenu, nPos); 
 
        switch (id) 
        { 
            case IDM_CUT: 
            case IDM_COPY: 
            case IDM_DELETE: 
                if (pbox == NULL || !pbox->fSelected) 
                    fuFlags = MF_BYCOMMAND | MF_GRAYED; 
                else if (pbox->fEdit) 
                    fuFlags = (id != IDM_DELETE && pbox->ichSel 
                            == pbox->ichCaret) ? 
                        MF_BYCOMMAND | MF_GRAYED : 
                        MF_BYCOMMAND | MF_ENABLED; 
                else 
                    fuFlags = MF_BYCOMMAND | MF_ENABLED; 
 
                EnableMenuItem(hmenu, id, fuFlags); 
                break; 
 
            case IDM_PASTE: 
                if (pbox != NULL && pbox->fEdit) 
                    EnableMenuItem(hmenu, id, 
                        IsClipboardFormatAvailable(CF_TEXT) ? 
                            MF_BYCOMMAND | MF_ENABLED : 
                            MF_BYCOMMAND | MF_GRAYED 
                    ); 
                else 
                    EnableMenuItem(hmenu, id, 
                        IsClipboardFormatAvailable( 
                                uLabelFormat) ? 
                            MF_BYCOMMAND | MF_ENABLED : 
                            MF_BYCOMMAND | MF_GRAYED 
                    ); 
 
        } 
    } 
}

WM_COMMAND メッセージの処理

メニュー コマンドを処理するには、アプリケーションのメイン ウィンドウ プロシージャに WM_COMMAND ケースを追加します。 ラベル アプリケーションの ウィンドウ プロシージャの WM_COMMAND ケースを次に示します。

case WM_COMMAND: 
    switch (LOWORD(wParam)) 
    { 
        case IDM_CUT: 
            if (EditCopy()) 
                EditDelete(); 
            break; 
 
        case IDM_COPY: 
            EditCopy(); 
            break; 
 
        case IDM_PASTE: 
            EditPaste(); 
            break; 
 
        case IDM_DELETE: 
            EditDelete(); 
            break; 
 
        case IDM_EXIT: 
            DestroyWindow(hwnd); 
    } 
    break; 

Copy コマンドと Cut コマンドを実行するために、ウィンドウ プロシージャはアプリケーション定義 EditCopy 関数を呼び出します。 詳細については、「クリップボードへの情報のコピー」を参照してください。 Paste コマンドを実行するために、ウィンドウ プロシージャはアプリケーション定義 EditPaste 関数を呼び出します。 EditPaste 関数の詳細については、「クリップボードからの情報の貼り付け」を参照してください。

クリップボードへの情報のコピー

Label アプリケーションでは、アプリケーション定義の EditCopy 関数によって、現在の選択範囲がクリップボードにコピーされます。 この関数は、次のことを行います。

  1. OpenClipboard 関数を呼び出してクリップボードを開きます。
  2. EmptyClipboard 関数を呼び出してクリップボードを空にします。
  3. アプリケーションが提供するクリップボード形式ごとに SetClipboardData 関数を 1 回呼び出します。
  4. CloseClipboard 関数を呼び出してクリップボードを閉じます。

現在の選択内容に応じて、EditCopy 関数はテキストの範囲をコピーするか、ラベル全体を表すアプリケーション定義構造をコピーします。 LABELBOXと呼ばれる構造体は、次のように定義されています。

#define BOX_ELLIPSE  0 
#define BOX_RECT     1 
 
#define CCH_MAXLABEL 80 
#define CX_MARGIN    12 
 
typedef struct tagLABELBOX {  // box 
    RECT rcText;    // coordinates of rectangle containing text 
    BOOL fSelected; // TRUE if the label is selected 
    BOOL fEdit;     // TRUE if text is selected 
    int nType;      // rectangular or elliptical 
    int ichCaret;   // caret position 
    int ichSel;     // with ichCaret, delimits selection 
    int nXCaret;    // window position corresponding to ichCaret 
    int nXSel;      // window position corresponding to ichSel 
    int cchLabel;   // length of text in atchLabel 
    TCHAR atchLabel[CCH_MAXLABEL]; 
} LABELBOX, *PLABELBOX;

次は、 EditCopy 関数です。

BOOL WINAPI EditCopy(VOID) 
{ 
    PLABELBOX pbox; 
    LPTSTR  lptstrCopy; 
    HGLOBAL hglbCopy; 
    int ich1, ich2, cch; 
 
    if (hwndSelected == NULL) 
        return FALSE; 
 
    // Open the clipboard, and empty it. 
 
    if (!OpenClipboard(hwndMain)) 
        return FALSE; 
    EmptyClipboard(); 
 
    // Get a pointer to the structure for the selected label. 
 
    pbox = (PLABELBOX) GetWindowLong(hwndSelected, 0); 
 
    // If text is selected, copy it using the CF_TEXT format. 
 
    if (pbox->fEdit) 
    { 
        if (pbox->ichSel == pbox->ichCaret)     // zero length
        {   
            CloseClipboard();                   // selection 
            return FALSE; 
        } 
 
        if (pbox->ichSel < pbox->ichCaret) 
        { 
            ich1 = pbox->ichSel; 
            ich2 = pbox->ichCaret; 
        } 
        else 
        { 
            ich1 = pbox->ichCaret; 
            ich2 = pbox->ichSel; 
        } 
        cch = ich2 - ich1; 
 
        // Allocate a global memory object for the text. 
 
        hglbCopy = GlobalAlloc(GMEM_MOVEABLE, 
            (cch + 1) * sizeof(TCHAR)); 
        if (hglbCopy == NULL) 
        { 
            CloseClipboard(); 
            return FALSE; 
        } 
 
        // Lock the handle and copy the text to the buffer. 
 
        lptstrCopy = GlobalLock(hglbCopy); 
        memcpy(lptstrCopy, &pbox->atchLabel[ich1], 
            cch * sizeof(TCHAR)); 
        lptstrCopy[cch] = (TCHAR) 0;    // null character 
        GlobalUnlock(hglbCopy); 
 
        // Place the handle on the clipboard. 
 
        SetClipboardData(CF_TEXT, hglbCopy); 
    } 
 
    // If no text is selected, the label as a whole is copied. 
 
    else 
    { 
        // Save a copy of the selected label as a local memory 
        // object. This copy is used to render data on request. 
        // It is freed in response to the WM_DESTROYCLIPBOARD 
        // message. 
 
        pboxLocalClip = (PLABELBOX) LocalAlloc( 
            LMEM_FIXED, 
            sizeof(LABELBOX) 
        ); 
        if (pboxLocalClip == NULL) 
        { 
            CloseClipboard(); 
            return FALSE; 
        } 
        memcpy(pboxLocalClip, pbox, sizeof(LABELBOX)); 
        pboxLocalClip->fSelected = FALSE; 
        pboxLocalClip->fEdit = FALSE; 
 
        // Place a registered clipboard format, the owner-display 
        // format, and the CF_TEXT format on the clipboard using 
        // delayed rendering. 
 
        SetClipboardData(uLabelFormat, NULL); 
        SetClipboardData(CF_OWNERDISPLAY, NULL); 
        SetClipboardData(CF_TEXT, NULL); 
    } 
 
    // Close the clipboard. 
 
    CloseClipboard(); 
 
    return TRUE; 
}

クリップボードからの情報の貼り付け

Label アプリケーションでは、アプリケーション定義 EditPaste 関数によってクリップボードの内容が貼り付けられます。 この関数は、次のことを行います。

  1. OpenClipboard 関数を呼び出してクリップボードを開きます。
  2. 取得する使用可能なクリップボード形式を決定します。
  3. GetClipboardData 関数を呼び出して、選択した形式のデータへのハンドルを取得します。
  4. データのコピーをドキュメントに挿入します。 GetClipboardData によって返される ハンドルは引き続きクリップボードによって所有されるため、アプリケーションで解放したり、ロックしたままにしたりしてはなりません。
  5. CloseClipboard 関数を呼び出してクリップボードを閉じます。

ラベルが選択されていて、カーソルが含まれている場合、EditPaste 関数はクリップボードのテキストを挿入ポイントに挿入します。 選択されていない場合、またはラベルが選択されている場合、関数はクリップボードのアプリケーション定義 LABELBOX 構造を使用して新しいラベルを作成します。 LABELBOX 構造は、登録済みのクリップボード形式を使用してクリップボードに配置されます。

LABELBOXと呼ばれる構造体は、次のように定義されています。

#define BOX_ELLIPSE  0 
#define BOX_RECT     1 
 
#define CCH_MAXLABEL 80 
#define CX_MARGIN    12 
 
typedef struct tagLABELBOX {  // box 
    RECT rcText;    // coordinates of rectangle containing text 
    BOOL fSelected; // TRUE if the label is selected 
    BOOL fEdit;     // TRUE if text is selected 
    int nType;      // rectangular or elliptical 
    int ichCaret;   // caret position 
    int ichSel;     // with ichCaret, delimits selection 
    int nXCaret;    // window position corresponding to ichCaret 
    int nXSel;      // window position corresponding to ichSel 
    int cchLabel;   // length of text in atchLabel 
    TCHAR atchLabel[CCH_MAXLABEL]; 
} LABELBOX, *PLABELBOX;

次は、 EditPaste 関数です。

VOID WINAPI EditPaste(VOID) 
{ 
    PLABELBOX pbox; 
    HGLOBAL   hglb; 
    LPTSTR    lptstr; 
    PLABELBOX pboxCopy; 
    int cx, cy; 
    HWND hwnd; 
 
    pbox = hwndSelected == NULL ? NULL : 
        (PLABELBOX) GetWindowLong(hwndSelected, 0); 
 
    // If the application is in edit mode, 
    // get the clipboard text. 
 
    if (pbox != NULL && pbox->fEdit) 
    { 
        if (!IsClipboardFormatAvailable(CF_TEXT)) 
            return; 
        if (!OpenClipboard(hwndMain)) 
            return; 
 
        hglb = GetClipboardData(CF_TEXT); 
        if (hglb != NULL) 
        { 
            lptstr = GlobalLock(hglb); 
            if (lptstr != NULL) 
            { 
                // Call the application-defined ReplaceSelection 
                // function to insert the text and repaint the 
                // window. 
 
                ReplaceSelection(hwndSelected, pbox, lptstr); 
                GlobalUnlock(hglb); 
            } 
        } 
        CloseClipboard(); 
 
        return; 
    } 
 
    // If the application is not in edit mode, 
    // create a label window. 
 
    if (!IsClipboardFormatAvailable(uLabelFormat)) 
        return; 
    if (!OpenClipboard(hwndMain)) 
        return; 
 
    hglb = GetClipboardData(uLabelFormat); 
    if (hglb != NULL) 
    { 
        pboxCopy = GlobalLock(hglb); 
        if (pboxCopy != NULL) 
        { 
            cx = pboxCopy->rcText.right + CX_MARGIN; 
            cy = pboxCopy->rcText.top * 2 + cyText; 
 
            hwnd = CreateWindowEx( 
                WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT, 
                atchClassChild, NULL, WS_CHILD, 0, 0, cx, cy, 
                hwndMain, NULL, hinst, NULL 
            ); 
            if (hwnd != NULL) 
            { 
                pbox = (PLABELBOX) GetWindowLong(hwnd, 0); 
                memcpy(pbox, pboxCopy, sizeof(LABELBOX)); 
                ShowWindow(hwnd, SW_SHOWNORMAL); 
                SetFocus(hwnd); 
            } 
            GlobalUnlock(hglb); 
        } 
    } 
    CloseClipboard(); 
}

クリップボード形式の登録

クリップボード形式を登録するには、次のように、 RegisterClipboardFormat 関数の呼び出しをアプリケーションのインスタンス初期化関数に追加します。

// Register a clipboard format. 
 
// We assume that atchTemp can contain the format name and
// a null-terminator, otherwise it is truncated.
//
LoadString(hinstCurrent, IDS_FORMATNAME, atchTemp, 
    sizeof(atchTemp)/sizeof(TCHAR)); 
uLabelFormat = RegisterClipboardFormat(atchTemp); 
if (uLabelFormat == 0) 
    return FALSE;

WM_RENDERFORMATWM_RENDERALLFORMATS メッセージの 処理

ウィンドウが SetClipboardData 関数に NULL ハンドルを渡す場合は、要求に応じてデータをレンダリングする WM_RENDERFORMAT メッセージと WM_RENDERALLFORMATS メッセージを処理する必要があります。

ウィンドウが特定の形式のレンダリングを遅らせ、別のアプリケーションがその形式のデータを要求すると、 WM_RENDERFORMAT メッセージがウィンドウに送信されます。 さらに、ウィンドウが 1 つ以上の形式のレンダリングを遅らせ、それらの形式の一部が再びメインウィンドウが破棄されようとしたときに、そのウィンドウに WM_RENDERALLFORMATS メッセージが送信されてから破棄されます。

クリップボード形式をレンダリングするには、ウィンドウ プロシージャは、 SetClipboardData 関数を使用してクリップボードに非NULL データ ハンドルを配置する必要があります。 ウィンドウ プロシージャが WM_RENDERFORMAT メッセージに応答して形式をレンダリングする場合は、 SetClipboardDataを呼び出す前にクリップボードを開く必要があります。 ただし、 WM_RENDERALLFORMATS メッセージに応答して 1 つ以上の形式をレンダリングする場合は、クリップボードを開き、 SetClipboardData を呼び出す前にウィンドウがまだクリップボードを所有していることをチェックし、戻る前にクリップボードを閉じる必要があります。

Label アプリケーションは、次のように WM_RENDERFORMAT メッセージと WM_RENDERALLFORMATS メッセージを処理します。

case WM_RENDERFORMAT: 
    RenderFormat((UINT) wParam); 
    break; 
 
case WM_RENDERALLFORMATS:
    if (OpenClipboard(hwnd))
    {
        if (GetClipboardOwner() == hwnd)
        {
            RenderFormat(uLabelFormat);
            RenderFormat(CF_TEXT);
        }
        CloseClipboard();
    }
    break;

どちらの場合も、ウィンドウ プロシージャは、次のように定義されたアプリケーション定義 RenderFormat 関数を呼び出します。

LABELBOXと呼ばれる構造体は、次のように定義されています。

#define BOX_ELLIPSE  0 
#define BOX_RECT     1 
 
#define CCH_MAXLABEL 80 
#define CX_MARGIN    12 
 
typedef struct tagLABELBOX {  // box 
    RECT rcText;    // coordinates of rectangle containing text 
    BOOL fSelected; // TRUE if the label is selected 
    BOOL fEdit;     // TRUE if text is selected 
    int nType;      // rectangular or elliptical 
    int ichCaret;   // caret position 
    int ichSel;     // with ichCaret, delimits selection 
    int nXCaret;    // window position corresponding to ichCaret 
    int nXSel;      // window position corresponding to ichSel 
    int cchLabel;   // length of text in atchLabel 
    TCHAR atchLabel[CCH_MAXLABEL]; 
} LABELBOX, *PLABELBOX;
void WINAPI RenderFormat(UINT uFormat) 
{ 
    HGLOBAL hglb; 
    PLABELBOX pbox; 
    LPTSTR  lptstr; 
    int cch; 
 
    if (pboxLocalClip == NULL) 
        return; 
 
    if (uFormat == CF_TEXT) 
    { 
        // Allocate a buffer for the text. 
 
        cch = pboxLocalClip->cchLabel; 
        hglb = GlobalAlloc(GMEM_MOVEABLE, 
            (cch + 1) * sizeof(TCHAR)); 
        if (hglb == NULL) 
            return; 
 
        // Copy the text from pboxLocalClip. 
 
        lptstr = GlobalLock(hglb); 
        memcpy(lptstr, pboxLocalClip->atchLabel, 
            cch * sizeof(TCHAR)); 
        lptstr[cch] = (TCHAR) 0; 
        GlobalUnlock(hglb); 
 
        // Place the handle on the clipboard. 
 
        SetClipboardData(CF_TEXT, hglb); 
    } 
    else if (uFormat == uLabelFormat) 
    { 
        hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(LABELBOX)); 
        if (hglb == NULL) 
            return; 
        pbox = GlobalLock(hglb); 
        memcpy(pbox, pboxLocalClip, sizeof(LABELBOX)); 
        GlobalUnlock(hglb); 
 
        SetClipboardData(uLabelFormat, hglb); 
    } 
}

WM_DESTROYCLIPBOARD メッセージの処理

ウィンドウは、遅延レンダリングをサポートするために確保したリソースを解放するために WM_DESTROYCLIPBOARD メッセージを処理できます。 たとえば、Label アプリケーションは、クリップボードにラベルをコピーするときに、ローカル メモリ オブジェクトを割り当てます。 その後、次のように、 WM_DESTROYCLIPBOARD メッセージに応答してこのオブジェクトを解放します。

case WM_DESTROYCLIPBOARD: 
    if (pboxLocalClip != NULL) 
    { 
        LocalFree(pboxLocalClip); 
        pboxLocalClip = NULL; 
    } 
    break;

所有者表示クリップボード形式の使用

ウィンドウがクリップボード形式を使用して CF_OWNERDISPLAY クリップボードに情報を配置する場合は、次の操作を行う必要があります。

  • WM_PAINTCLIPBOARD メッセージを処理する。 このメッセージは、クリップボード ビューアー ウィンドウの一部を再描画する必要があるときに、クリップボードの所有者に送信されます。
  • WM_SIZECLIPBOARD メッセージを処理する。 このメッセージは、クリップボード ビューアー ウィンドウのサイズが変更されたか、コンテンツが変更されたときに、クリップボードの所有者に送信されます。 通常、ウィンドウはクリップボード ビューアー ウィンドウのスクロール位置と範囲を設定することで、このメッセージに応答します。 このメッセージに応答して、Label アプリケーションはクリップボード ビューアー ウィンドウの SIZE 構造も更新します。
  • WM_HSCROLLCLIPBOARDWM_VSCROLLCLIPBOARD メッセージの処理 これらのメッセージは、クリップボード ビューアー ウィンドウでスクロール バー イベントが発生すると、クリップボードの所有者に送信されます。
  • WM_ASKCBFORMATNAME メッセージを処理する。 クリップボード ビューアー ウィンドウは、このメッセージをアプリケーションに送信して、所有者表示形式の名前を取得します。

Label アプリケーションのウィンドウ プロシージャは、次のようにこれらのメッセージを処理します。

LRESULT CALLBACK MainWindowProc(hwnd, msg, wParam, lParam) 
HWND hwnd; 
UINT msg; 
WPARAM wParam; 
LPARAM lParam; 
{ 
    static RECT rcViewer; 
 
    RECT rc; 
    LPRECT lprc; 
    LPPAINTSTRUCT lpps; 
 
    switch (msg) 
    { 
        //
        // Handle other messages.
        //
        case WM_PAINTCLIPBOARD: 
            // Determine the dimensions of the label. 
 
            SetRect(&rc, 0, 0, 
                pboxLocalClip->rcText.right + CX_MARGIN, 
                pboxLocalClip->rcText.top * 2 + cyText 
            ); 
 
            // Center the image in the clipboard viewer window. 
 
            if (rc.right < rcViewer.right) 
            { 
                rc.left = (rcViewer.right - rc.right) / 2; 
                rc.right += rc.left; 
            } 
            if (rc.bottom < rcViewer.bottom) 
            { 
                rc.top = (rcViewer.bottom - rc.bottom) / 2; 
                rc.bottom += rc.top; 
            } 
 
            // Paint the image, using the specified PAINTSTRUCT 
            // structure, by calling the application-defined 
            // PaintLabel function. 
 
            lpps = (LPPAINTSTRUCT) GlobalLock((HGLOBAL) lParam); 
            PaintLabel(lpps, pboxLocalClip, &rc); 
            GlobalUnlock((HGLOBAL) lParam); 
            break; 
 
        case WM_SIZECLIPBOARD: 
            // Save the dimensions of the window in a static 
            // RECT structure. 
 
            lprc = (LPRECT) GlobalLock((HGLOBAL) lParam); 
            memcpy(&rcViewer, lprc, sizeof(RECT)); 
            GlobalUnlock((HGLOBAL) lParam); 
 
            // Set the scroll ranges to zero (thus eliminating 
            // the need to process the WM_HSCROLLCLIPBOARD and 
            // WM_VSCROLLCLIPBOARD messages). 
 
            SetScrollRange((HWND) wParam, SB_HORZ, 0, 0, TRUE); 
            SetScrollRange((HWND) wParam, SB_VERT, 0, 0, TRUE); 
 
            break; 
 
        case WM_ASKCBFORMATNAME: 
            LoadString(hinst, IDS_OWNERDISPLAY, 
                (LPSTR) lParam, wParam); 
            break; 
 
        default: 
            return DefWindowProc(hwnd, msg, wParam, lParam); 
    } 
    return 0; 
}

クリップボードの内容の監視

クリップボードへの変更を監視するには、3 つの方法があります。 最も古い方法は、クリップボード ビューアー ウィンドウを作成する方法です。 Windows 2000 では、クリップボードのシーケンス番号を照会する機能が追加され、Windows Vista ではクリップボード形式リスナーのサポートが追加されました。 クリップボード ビューアー ウィンドウは、以前のバージョンの Windows との下位互換性のためにサポートされています。 新しいプログラムでは、クリップボード形式リスナーまたはクリップボードシーケンス番号を使用する必要があります。

クリップボードシーケンス番号のクエリ

クリップボードの内容が変更されるたびに、クリップボードのシーケンス番号と呼ばれる 32 ビット値がインクリメントされます。 プログラムは、 GetClipboardSequenceNumber 関数を呼び出すことによって、現在のクリップボードシーケンス番号を取得できます。 プログラムは、前回の呼び出し GetClipboardSequenceNumberによって返された値と比較することで、クリップボードの内容が変更されたかどうかを判断できます。 この方法は、現在のクリップボードの内容に基づいて結果をキャッシュするプログラムに適しており、そのキャッシュの結果を使用する前に計算がまだ有効かどうかを知る必要があります。 これは通知メソッドではないため、ポーリング ループでは使用しないでください。 クリップボードの内容が変更されたときに通知を受け取る場合は、クリップボード形式リスナーまたはクリップボード ビューアーを使用します。

クリップボード形式リスナーの作成

クリップボード形式リスナーは、クリップボードの内容が変更されたときに通知されるように登録されたウィンドウです。 この方法は、クリップボード ビューアー ウィンドウの作成よりも推奨されます。実装が簡単で、プログラムがクリップボード ビューアー チェーンを適切にメインできない場合や、クリップボード ビューアー チェーン内のウィンドウがメッセージへの応答を停止した場合の問題を回避するためです。

ウィンドウは、 AddClipboardFormatListener 関数を呼び出してクリップボード形式リスナーとして登録します。 クリップボードの内容が変更されると、ウィンドウに WM_CLIPBOARDUPDATE メッセージが投稿されます。 登録は RemoveClipboardFormatListener 関数を呼び出してウィンドウ自体の登録を解除するまで有効のままです。

クリップボード ビューアー ウィンドウの作成

クリップボード ビューアー ウィンドウには、クリップボードの現在のコンテンツが表示され、クリップボードのコンテンツが変更されたときにメッセージが受信されます。 クリップボード ビューアー ウィンドウを作成するには、アプリケーションで次の操作を行う必要があります。

  • クリップボードビューアチェーンへのウィンドウの追加
  • WM_CHANGECBCHAIN メッセージを処理する。
  • WM_DRAWCLIPBOARD メッセージを処理する。
  • 破棄する前に、クリップボード ビューアー チェーンからウィンドウを削除します。

クリップボードビューアチェーンへのウィンドウの追加

SetClipboardViewer 関数を呼び出して、ウィンドウがクリップボード ビューアー チェーンに追加されます。 戻り値は、チェーン内の次のウィンドウへのハンドルです。 ウィンドウは、この値を追跡する必要があります。たとえば、 hwndNextViewer という名前の付いた静的変数に保存します。

次の例では、 WM_CREATE メッセージに応答して、クリップボード ビューアー チェーンにウィンドウを追加します。

case WM_CREATE: 
 
    // Add the window to the clipboard viewer chain. 
 
    hwndNextViewer = SetClipboardViewer(hwnd); 
    break;

次のタスクのコード スニペットが表示されます。

WM_CHANGECBCHAIN メッセージの処理

クリップボード ビューアー ウィンドウは、別のウィンドウがクリップボード ビューアー チェーンからそれ自体を削除しているときに WM_CHANGECBCHAIN メッセージを受信します。 削除されるウィンドウがチェーン内の次のウィンドウである場合、メッセージを受信するウィンドウは、チェーンから次のウィンドウのリンクを解除する必要があります。 それ以外の場合は、このメッセージをチェーン内の次のウィンドウに渡す必要があります。

次の例は、 WM_CHANGECBCHAIN メッセージの処理を示しています。

case WM_CHANGECBCHAIN: 
 
    // If the next window is closing, repair the chain. 
 
    if ((HWND) wParam == hwndNextViewer) 
        hwndNextViewer = (HWND) lParam; 
 
    // Otherwise, pass the message to the next link. 
 
    else if (hwndNextViewer != NULL) 
        SendMessage(hwndNextViewer, uMsg, wParam, lParam); 
 
    break;

クリップボード ビューアー チェーンからのウィンドウの削除

クリップボード ビューアー チェーンからそれ自体を削除するには、ウィンドウによって関数が ChangeClipboardChain 呼び出されます。 次の例では、 WM_DESTROY メッセージに応答して、クリップボード ビューアー チェーンからウィンドウを削除します。

case WM_DESTROY: 
    ChangeClipboardChain(hwnd, hwndNextViewer); 
    PostQuitMessage(0); 
    break;

WM_DRAWCLIPBOARD メッセージの処理

WM_DRAWCLIPBOARD メッセージは、クリップボードの内容が変更されたことをクリップボード ビューアー ウィンドウに通知します。 ウィンドウでは、 WM_DRAWCLIPBOARD メッセージを処理するときに次の操作を行う必要があります。

  1. 表示する使用可能なクリップボード形式を決定します。
  2. クリップボード データを取得し、ウィンドウに表示します。 または、クリップボードの形式が CF_OWNERDISPLAYの場合は 、クリップボードの所有者に WM_PAINTCLIPBOARD メッセージを送信します。
  3. クリップボード ビューアー チェーンの次のウィンドウにメッセージを送信します。

WM_DRAWCLIPBOARD メッセージの処理例については、「クリップボード ビューアーの例」の一覧の例を参照してください。

クリップボード ビューアーの例

次の例は、単純なクリップボード ビューアー アプリケーションを示しています。

HINSTANCE hinst; 
UINT uFormat = (UINT)(-1); 
BOOL fAuto = TRUE; 
 
LRESULT APIENTRY MainWndProc(hwnd, uMsg, wParam, lParam) 
HWND hwnd; 
UINT uMsg; 
WPARAM wParam; 
LPARAM lParam; 
{ 
    static HWND hwndNextViewer; 
 
    HDC hdc; 
    HDC hdcMem; 
    PAINTSTRUCT ps; 
    LPPAINTSTRUCT lpps; 
    RECT rc; 
    LPRECT lprc; 
    HGLOBAL hglb; 
    LPSTR lpstr; 
    HBITMAP hbm; 
    HENHMETAFILE hemf; 
    HWND hwndOwner; 
 
    switch (uMsg) 
    { 
        case WM_PAINT: 
            hdc = BeginPaint(hwnd, &ps); 
 
            // Branch depending on the clipboard format. 
 
            switch (uFormat) 
            { 
                case CF_OWNERDISPLAY: 
                    hwndOwner = GetClipboardOwner(); 
                    hglb = GlobalAlloc(GMEM_MOVEABLE, 
                        sizeof(PAINTSTRUCT)); 
                    lpps = GlobalLock(hglb);
                    memcpy(lpps, &ps, sizeof(PAINTSTRUCT)); 
                    GlobalUnlock(hglb); 
 
                    SendMessage(hwndOwner, WM_PAINTCLIPBOARD, 
                        (WPARAM) hwnd, (LPARAM) hglb); 
 
                    GlobalFree(hglb); 
                    break; 
 
                case CF_BITMAP: 
                    hdcMem = CreateCompatibleDC(hdc); 
                    if (hdcMem != NULL) 
                    { 
                        if (OpenClipboard(hwnd)) 
                        { 
                            hbm = (HBITMAP) 
                                GetClipboardData(uFormat); 
                            SelectObject(hdcMem, hbm); 
                            GetClientRect(hwnd, &rc); 
 
                            BitBlt(hdc, 0, 0, rc.right, rc.bottom, 
                                hdcMem, 0, 0, SRCCOPY); 
                            CloseClipboard(); 
                        } 
                        DeleteDC(hdcMem); 
                    } 
                    break; 
 
                case CF_TEXT: 
                    if (OpenClipboard(hwnd)) 
                    { 
                        hglb = GetClipboardData(uFormat); 
                        lpstr = GlobalLock(hglb); 
 
                        GetClientRect(hwnd, &rc); 
                        DrawText(hdc, lpstr, -1, &rc, DT_LEFT); 
 
                        GlobalUnlock(hglb); 
                        CloseClipboard(); 
                    } 
                    break; 
 
                case CF_ENHMETAFILE: 
                    if (OpenClipboard(hwnd)) 
                    { 
                        hemf = GetClipboardData(uFormat); 
                        GetClientRect(hwnd, &rc); 
                        PlayEnhMetaFile(hdc, hemf, &rc); 
                        CloseClipboard(); 
                    } 
                    break; 
 
                case 0: 
                    GetClientRect(hwnd, &rc); 
                    DrawText(hdc, "The clipboard is empty.", -1, 
                        &rc, DT_CENTER | DT_SINGLELINE | 
                        DT_VCENTER); 
                    break; 
 
                default: 
                    GetClientRect(hwnd, &rc); 
                    DrawText(hdc, "Unable to display format.", -1, 
                        &rc, DT_CENTER | DT_SINGLELINE | 
                        DT_VCENTER); 
            } 
            EndPaint(hwnd, &ps); 
            break; 
 
        case WM_SIZE: 
            if (uFormat == CF_OWNERDISPLAY) 
            { 
                hwndOwner = GetClipboardOwner(); 
                hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(RECT)); 
                lprc = GlobalLock(hglb); 
                GetClientRect(hwnd, lprc); 
                GlobalUnlock(hglb); 
 
                SendMessage(hwndOwner, WM_SIZECLIPBOARD, 
                    (WPARAM) hwnd, (LPARAM) hglb); 
 
                GlobalFree(hglb); 
            } 
            break; 
 
        case WM_CREATE: 
 
            // Add the window to the clipboard viewer chain. 
 
            hwndNextViewer = SetClipboardViewer(hwnd); 
            break; 
 
        case WM_CHANGECBCHAIN: 
 
            // If the next window is closing, repair the chain. 
 
            if ((HWND) wParam == hwndNextViewer) 
                hwndNextViewer = (HWND) lParam; 
 
            // Otherwise, pass the message to the next link. 
 
            else if (hwndNextViewer != NULL) 
                SendMessage(hwndNextViewer, uMsg, wParam, lParam); 
 
            break; 
 
        case WM_DESTROY: 
            ChangeClipboardChain(hwnd, hwndNextViewer); 
            PostQuitMessage(0); 
            break; 
 
        case WM_DRAWCLIPBOARD:  // clipboard contents changed. 
 
            // Update the window by using Auto clipboard format. 
 
            SetAutoView(hwnd); 
 
            // Pass the message to the next window in clipboard 
            // viewer chain. 
 
            SendMessage(hwndNextViewer, uMsg, wParam, lParam); 
            break; 
 
        case WM_INITMENUPOPUP: 
            if (!HIWORD(lParam)) 
                InitMenu(hwnd, (HMENU) wParam); 
            break; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                case IDM_EXIT: 
                    DestroyWindow(hwnd); 
                    break; 
 
                case IDM_AUTO: 
                    SetAutoView(hwnd); 
                    break; 
 
                default: 
                    fAuto = FALSE; 
                    uFormat = LOWORD(wParam); 
                    InvalidateRect(hwnd, NULL, TRUE); 
            } 
            break; 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return (LRESULT) NULL; 
} 
 
void WINAPI SetAutoView(HWND hwnd) 
{ 
    static UINT auPriorityList[] = { 
        CF_OWNERDISPLAY, 
        CF_TEXT, 
        CF_ENHMETAFILE, 
        CF_BITMAP 
    }; 
 
    uFormat = GetPriorityClipboardFormat(auPriorityList, 4); 
    fAuto = TRUE; 
 
    InvalidateRect(hwnd, NULL, TRUE); 
    UpdateWindow(hwnd); 
} 
 
void WINAPI InitMenu(HWND hwnd, HMENU hmenu) 
{ 
    UINT uFormat; 
    char szFormatName[80]; 
    LPCSTR lpFormatName; 
    UINT fuFlags; 
    UINT idMenuItem; 
 
    // If a menu is not the display menu, no initialization is necessary. 
 
    if (GetMenuItemID(hmenu, 0) != IDM_AUTO) 
        return; 
 
    // Delete all menu items except the first. 
 
    while (GetMenuItemCount(hmenu) > 1) 
        DeleteMenu(hmenu, 1, MF_BYPOSITION); 
 
    // Check or uncheck the Auto menu item. 
 
    fuFlags = fAuto ? MF_BYCOMMAND | MF_CHECKED : 
        MF_BYCOMMAND | MF_UNCHECKED; 
    CheckMenuItem(hmenu, IDM_AUTO, fuFlags); 
 
    // If there are no clipboard formats, return. 
 
    if (CountClipboardFormats() == 0) 
        return; 
 
    // Open the clipboard. 
 
    if (!OpenClipboard(hwnd)) 
        return; 
 
    // Add a separator and then a menu item for each format. 
 
    AppendMenu(hmenu, MF_SEPARATOR, 0, NULL); 
    uFormat = EnumClipboardFormats(0); 
 
    while (uFormat) 
    { 
        // Call an application-defined function to get the name 
        // of the clipboard format. 
 
        lpFormatName = GetPredefinedClipboardFormatName(uFormat); 
 
        // For registered formats, get the registered name. 
 
        if (lpFormatName == NULL) 
        {

        // Note that, if the format name is larger than the
        // buffer, it is truncated. 
            if (GetClipboardFormatName(uFormat, szFormatName, 
                    sizeof(szFormatName))) 
                lpFormatName = szFormatName; 
            else 
                lpFormatName = "(unknown)"; 
        } 
 
        // Add a menu item for the format. For displayable 
        // formats, use the format ID for the menu ID. 
 
        if (IsDisplayableFormat(uFormat)) 
        { 
            fuFlags = MF_STRING; 
            idMenuItem = uFormat; 
        } 
        else 
        { 
            fuFlags = MF_STRING | MF_GRAYED; 
            idMenuItem = 0; 
        } 
        AppendMenu(hmenu, fuFlags, idMenuItem, lpFormatName); 
 
        uFormat = EnumClipboardFormats(uFormat); 
    } 
    CloseClipboard(); 
 
} 
 
BOOL WINAPI IsDisplayableFormat(UINT uFormat) 
{ 
    switch (uFormat) 
    { 
        case CF_OWNERDISPLAY: 
        case CF_TEXT: 
        case CF_ENHMETAFILE: 
        case CF_BITMAP: 
            return TRUE; 
    } 
    return FALSE; 
}