Excel 2016で選択したアクティブセルが太枠表示されない原因調査ツールのソースコードをGitHubにアップしました。

 

※使い方、仕様は「Excel 2016で選択したアクティブセルが太枠表示されない原因調査ツールリリース」をご覧ください。

 

■GitHubのリポジトリ(URL)は下記になります。

https://github.com/hiro-753/ClipBoardInspector

 

実行環境は、Windows10 Home 1903 、.NET Framework 4.0 で動作確認しています。

※お約束ですが、重要なファイルは保存した後、実行は自己責任でお願いします。(^^)
 

 

[動作概要]

起動してフック開始後、グローバルフックを使用して、下記メッセージのログを表示します。
・WM_CLIPBOARDUPDATE
・WM_PASTE
・WM_RENDERFORMAT

 

Excel2016で選択したアクティブセルが太枠表示されない現象は
WM_PASTEメッセージが発生しないのにWM_RENDERFORMATメッセージが発生した時に起きることから、
条件がマッチしたら自動的にフックが停止するようになっています。

 

[プロジェクト構成]

下記2プロジェクトで構成されます。
・ClipBoardInspector.exe (MFCダイアログアプリプロジェクト)
 ClipBoardInspector.cpp メイン
 ClipBoardInspectorDlg.cpp ダイアログ表示、フック操作、メッセージハンドリング
・DllHookMessage.dll (Win32 DLLプロジェクト)
 dllmain.cpp グローバルフック処理

 

[ClipBoardInspectorDlg.cpp]

アプリのメインソースファイルです。

起動されると、ダイアログを開いて、フック開始ボタン押下でDllHookMessage.dllのフック開始処理HookStart()をコールします。

DllHookMessage.dllからフックメッセージWM_HOOKを受信するまで待ち状態になります。

WM_HOOKはカスタムメッセージです。

 

WM_HOOKハンドラのソースは下記になります。

※下記ソースコード部分は横スクロールをかけている(SHIFT+マウスホイールスクロール可能)のでPCブラウザモードでご覧ください。


afx_msg LRESULT CClipBoardInspectorDlg::OnHook(WPARAM wParam, LPARAM lParam)
{
    CString sMsg;
    HWND hwnd = (HWND)lParam;
    BOOL bFound = false;
    static bool bPasteOccered = false;

    switch (wParam)
    {
    case WM_RBUTTONDOWN:
        sMsg.Format(_T("WM_RBUTTONDOWN メッセージ発生"));
        break;
    case WM_CLIPBOARDUPDATE:
        sMsg.Format(_T("WM_CLIPBOARDUPDATE メッセージ発生"));
        break;
    case WM_RENDERFORMAT:
        sMsg.Format(_T("WM_RENDERFORMAT メッセージ発生"));
        bFound = true;
        break;
    case WM_PASTE:
        sMsg.Format(_T("WM_PASTE メッセージ発生"));
        bPasteOccered = true;
        break;
    default:
        sMsg.Format(_T("%dメッセージ発生"), wParam);
    }
    
    //プロセス名を取得
    SetProcessName(hwnd, &sMsg);
    //リッチエディットコントロールへ出力
    sMsg += "\r\n";
    SetTextWithFormat(sMsg, bFound);

    //WM_PASTE発生してないのに、WM_RENDERFORMAT発生したらフックをやめる
    if (!bPasteOccered && bFound) {
        //フック解除
        HookStop();
        CString sMsg;
        sMsg = _T("フックが解除されました\r\n");
        //リッチエディットコントロールへ出力
        SetTextWithFormat(sMsg, false);

    } else if (bPasteOccered && bFound) {
        //WM_PASTE発生発生後にWM_RENDERFORMAT発生したら、
        //bPasteOcceredをクリア
        bPasteOccered = false;
    }
        
    return 0;
}

 

WM_CLIPBOARDUPDATE、WM_PASTE、WM_RENDERFORMATをハンドリングして、リッチエディットコントロールに表示しています。

リッチエディットコントロールを使用しているのは、選択した部分の文字色を変更したいためです。

CClipBoardInspectorDlg::SetProcessName()は、メッセージを送ってきたウィンドウハンドルからプロセス名を取得します。

CClipBoardInspectorDlg::SetTextWithFormat()で、リッチエディットコントロールへプロセス名と文字色を渡して表示しています。


最後に、WM_PASTE発生してないのに、WM_RENDERFORMAT発生したらフックをやめるため、DllHookMessage.dllのフック解除処理HookStop()をコールします。

bPasteOcceredはWM_PASTEが発生したらtrue、bFoundはWM_RENDERFORMATが発生したらtrueです。

これらの情報を元に判定します。

 

[dllmain.cpp]

DllHookMessage.dllのメインソースファイルです。

フック開始処理HookStart()のソースは下記になります。


// フック開始
DllExport void HookStart(HWND hWnd)
{
    g_hWnd = hWnd;  //呼び出し元のハンドル
        
    //PostMessageのみ
    g_hHookByGetMsgProc = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hInst, 0);
    //SendMessageのみ
    g_hHookByWndProc = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)WndProc, g_hInst, 0);

}

 

PostMessageをフックするのにWH_GETMESSAGE、SendMessageをフックするのにWH_CALLWNDPROCを指定してフックセットAPIを2回コールします。

 

フックプロシジャも2つ存在します。

PostMessageが発生したらGetMsgProc()がコールされ、とSendMessageが発生したらWndProc()がコールされることになります。

 

ソースは下記になります。


// フック処理
DllExport LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
    HWND hwnd;

    if (code < 0) {
        return CallNextHookEx(g_hHookByGetMsgProc, code, wParam, lParam);
    }
    
    MSG* getMsg;
    getMsg = (MSG*)lParam;

    //メッセージ発生のプロセスを取り出すため
    hwnd = getMsg->hwnd;

    if (getMsg->message == WM_RBUTTONDOWN) {

    }
    else if (getMsg->message == WM_CLIPBOARDUPDATE) {
        //ここでLPALAMでhwndを渡す(メッセージ発生のプロセスを取り出すため)
        SendMessage(g_hWnd, WM_HOOK, (WPARAM)WM_CLIPBOARDUPDATE, (LPARAM)hwnd);
    }

    // 最後は次のフックへ渡します
    return CallNextHookEx(g_hHookByGetMsgProc, code, wParam, lParam);
}

// フック処理
DllExport LRESULT CALLBACK WndProc(int code, WPARAM wParam, LPARAM lParam)
{
    HWND hwnd;

    if (code < 0) {
        return CallNextHookEx(g_hHookByWndProc, code, wParam, lParam);
    }

    CWPSTRUCT* winMsg;
    winMsg = (CWPSTRUCT*)lParam;

    //メッセージ発生のプロセスを取り出すため
    hwnd = winMsg->hwnd;

    if (winMsg->message == WM_RENDERFORMAT) {
        // WM_RENDERFORMAT
        SendMessage(g_hWnd, WM_HOOK, (WPARAM)WM_RENDERFORMAT, (LPARAM)hwnd);
    }
    else if (winMsg->message == WM_PASTE) {
        // WM_PASTE
        SendMessage(g_hWnd, WM_HOOK, (WPARAM)WM_PASTE, (LPARAM)hwnd);
    }

    // 最後は次のフックへ渡します
    return CallNextHookEx(g_hHookByWndProc, code, wParam, lParam);
}

 

いずれも、フックが発生したらWM_HOOKをHookStart(HWND hWnd)で渡された呼び出し元のダイアログのウィンドウハンドルg_hWndへ送信します。

 

ソースの説明は以上です。

 

-------
#編集後記#
今回試作したツールは、.NETのWindows Formsのようなフレームワークが入るとメッセージを直接処理できないことや、グローバルフックdll側はWin32なのでMFC/C++で実装しました。

Win32は最近余り使われないので、CRichEditCtrl(リッチエディットコントロール)のバグが残っており、これを特定するのに時間がかかりました。
※MSDN記事:CRichEditCtrl と CEdit の違いを吸収したい

このツールは目的が限定的なので活用・流用用途は少ないです。
・キーボードやマウス操作の制限を加えるなど
・全操作履歴を取る→行動分析などDBやAIプログラムとリンクするなど

後、ソースをいじることでWindows内部の勉強にはなるかな。
但し、ちょっと変更しただけでメッセージの無限ループが簡単に発生するので、流用する方は気を付けてください。
このツール自体を作るのにWindowsの再起動50回くらいしています。
もちろんbusy状態のためマウス操作で再起動できないので電源ボタンで再起動です。(^^)