CListCtrlの各カラムでToolTipを表示

CListCtrlのレポート形式時でのツールチップの表示は拡張スタイルの設定で可能です。
しかし、表示できるのは、アイテム部分のみでサブアイテム部分の表示はできません。

それを可能にする方法です。

動作確認はWinME/Win2K

やるべきこと

まず、ToolTipを有効にしなければなりません。
有効にするには EnableToolTips()関数を利用します。

ツールチップを有効にする
int CHogeListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) {

    if (CListCtrl::OnCreate(lpCreateStruct) == -1)
        return -1;

    //ツールチップを有効にします。
    EnableToolTips(TRUE);
    return 0;
}

この関数はツールチップがある場合のみ有効となります。
ツールチップがある場合はTRUEを返し、無い場合はFALSEを返してきます。
引数はTRUEでツールチップを有効、FALSEで無効にします。

次にいくつかメッセージをマップします。
マップ刷るメッセージはTTN_NEEDTEXTWとTTN_NEEDTEXTAです。
TTN_NEEDTEXTAはツール ヒント コントロールは ASCII テキスト(Win95)を使用しているメッセージです。
TTN_NEEDTEXTWはツール ヒント コントロールは UNICODE テキスト(WinNT)を使用しているメッセージです。
このメッセージはOSによって来たりこなかったりするようです。
TTN_NEEDTEXT もありますが、文字コードに気をつける方が良いようです。(多分)

メッセージのマップ(.h)
    //{{AFX_MSG(CHogeListCtrl)
    ・・・
    afx_msg BOOL OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult );
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()

メッセージのマップ(.cpp)
BEGIN_MESSAGE_MAP(CHogeListCtrl, CListCtrl)
    //{{AFX_MSG_MAP(CTreeListCtrl)
    ・・・
    //}}AFX_MSG_MAP
    //ON_NOTIFY_EX( TTN_NEEDTEXT, 0, OnToolTipText )
    ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnToolTipText)
    ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
    ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
END_MESSAGE_MAP()

メッセージをマップしたら、メッセージにマップした関数を実装します。

OnToolTipTextの実装
BOOL CHogeListCtrl::OnToolTipText( UINT id, NMHDR * pNMHDR, LRESULT * pResult ){
    TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
    TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
    CString strTipText;
    UINT nID = pNMHDR->idFrom;

    if( nID == 0 )      // Notification in NT from automatically
        return FALSE;       // created tooltip

    int row = ((nID-1) %gt;> 10) & 0x3fffff ;
    int col = (nID-1) & 0x3ff;
    strTipText = GetItemText( row, col );
#ifndef _UNICODE
    if (pNMHDR->code == TTN_NEEDTEXTA)
        lstrcpyn(pTTTA->szText, strTipText, 80);
    else
        _mbstowcsz(pTTTW->szText, strTipText, 80);
#else
    if (pNMHDR->code == TTN_NEEDTEXTA)
        _wcstombsz(pTTTA->szText, strTipText, 80);
    else
        lstrcpyn(pTTTW->szText, strTipText, 80);
#endif
    *pResult = 0;

    return TRUE;    // message was handled
}
コメントが英語なのは参考ページからソースを持ってきて改良しているからです。
はじめのpNMHDRのキャスト部分で、ASCII と UNICODE の TOOLTIPTEXT構造体 を取得しています。
で、文字とか取得します。

コンパイル環境の文字コードがUNICODEかそうでないかでコンパイルされるソースが変わります。
文字の変換部分で引数の最後が 80 となっています。
これはツールチップに表示可能な文字のバイト数です。

次に、OnToolHitTest()関数をオーバーライドします。
これはClassWizardで追加可能です。
OnToolHitTest()関数の実装(.h)
// オーバーライド
    // ClassWizard は仮想関数のオーバーライドを生成します。
    //{{AFX_VIRTUAL(CTreeListCtrl)
    ・・・
    //}}AFX_VIRTUAL
    virtual int OnToolHitTest( CPoint point, TOOLINFO* pTI ) const;

OnToolHitTest()関数の実装(.cpp)
int CHogeListCtrl::OnToolHitTest( CPoint point, TOOLINFO* pTI) const{
    int row, col;
    RECT cellrect;
    row = CellRectFromPoint(point, &cellrect, &col );

    if ( row == -1 ) {
        return -1;
    }
    pTI->hwnd = m_hWnd;
    pTI->uId = (UINT)((row<<10)+(col&0x3ff)+1);
    pTI->lpszText = LPSTR_TEXTCALLBACK;

    pTI->rect = cellrect;
    return pTI->uId;
}
オーバーライドするとヘッダファイルに上記のように関数のプロトタイプが宣言されます。
実際の処理部分では、ツールチップの出力場所と大きさをCellRectFromPoint()関数を利用して取得します。
この関数はMFCやWindowsAPIではなく作成する必要があります。(Ex.4参照)

で、サイズ用を取得したら、TOOLINFO構造体に必要な情報を設定します。
これでツールチップの表示ができます。

CellRectFromPoint()関数の実装
int CHogeListCtrl::CellRectFromPoint(CPoint & point, RECT * cellrect, int * col) const{
    int colnum;
    // LVS_REPORTスタイルでない場合は無効にする
    if( (GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT )
        return -1;

    // 表示されているトップとボトムのアイテムの数を取得する
    int row = GetTopIndex();
    int bottom = row + GetCountPerPage();
    if( bottom > GetItemCount() )
        bottom = GetItemCount();

    //カラムの数を取得する
    CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);
    int nColumnCount = pHeader->GetItemCount();

    for( ;row <=bottom;row++){
        CRect rect;
        GetItemRect( row, &rect, LVIR_BOUNDS );
        if( rect.PtInRect(point) ){
            // Now find the column
            for( colnum = 0; colnum < nColumnCount; colnum++ ){
                int colwidth = GetColumnWidth(colnum);
                if( point.x >= rect.left && point.x <= (rect.left + colwidth ) ){
                    RECT rectClient;
                    GetClientRect( &rectClient );
                    if( col ) *col = colnum;
                    rect.right = rect.left + colwidth;

                    // Make sure that the right extent does not exceed
                    // the client area
                    if( rect.right > rectClient.right ){
                        rect.right = rectClient.right;
                    }
                    *cellrect = rect;
                    return row;
                }
                rect.left += colwidth;
            }
        }
    }
    return -1;
}

参考資料

ListView Control - Table of Contents(英語)