<   2012年 01月 ( 4 )   > この月の画像一覧

e87.com(千趣会イイハナ) 花を贈るなら日比谷花壇

CTabView/CMFCTabCtrl のタブをマウスホイールでスクロールさせる

Firefox で Tab Mix Plus とかの設定調整アドオンを使用すると、タブ上にマウスポインタを置き、ホイールをスクロールすると、次々とタブを切り替える事が出来る機能を有効にすることが出来ます。これって結構便利です。

で、MFCのCTabViewを使ってみたところ、タブが収まりきらなくなると画面外にタブが並び、それを画面内に持ってくるにはボタンで、タブを1つずつ移動していかないといけない。これがとても面倒で、タブがたくさんあるとうっとうしい。ボタンが小さくて、右の方にあるから余計に使いにくい。

そこで、マウススクロールでタブを切り替えできないかと、それらしき機能を探したが見つからない。

CTabView で使用しているタブコントロールは、 CMFCTabCtrl です。

一番簡単なのは、CMFCBaseTabCtrl::GetActiveTab() で現在アクティブなタブのインデックスを取得して、CMFCBaseTab::SetActiveTab() を使って、+1 や -1 したインデックスのタブをアクティブにして、EnsureVisible() で画面に表示する方法。

でも、今回は、タブをアクティブにしないでスクロールだけしたかった。
というのも、たくさんタブがあって、その1つ1つがアクティブになった時に画像データを読み込むんだけど、画像データのサイズが半端なくでかい。
最小100MB~最大1GB強の画像データ・・・ってどんなんだ!

A4一枚程度を 1200dpi / 48bit カラーでスキャンした生データなのです。

だから、なるべく使ってない画像は開きたくない。

そこで、次のように、ちょっと危険な手を使って、スクロールを実現してみました。

// CMFCTabCtrl のハッククラス(ポインタをキャストして使用する)
class CMFCTabCtrlEx : public CMFCTabCtrl
{
public:
    int GetTabsHorzOffset(){ return m_nTabsHorzOffset; }    // オフセット取得
    void SetTabsHorzOffset(int nOffset)                     // オフセットをセット
    { 
        m_nTabsHorzOffset = nOffset;
        AdjustTabs();
        AdjustTabsScroll();
        RedrawWindow();
    }
    int GetTabsHorzOffsetMax(){ return m_nTabsHorzOffsetMax; } // オフセットの最大値を取得
private:
    CMFCTabCtrlEx();    // インスタンス化拒否
};
 
BOOL CXxxTabView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
    CMFCTabCtrlEx* pTabCtrlEx = (CMFCTabCtrlEx*)&GetTabControl();
    pTabCtrlEx->ScreenToClient(&pt);
    if(pTabCtrlEx->IsPtInTabArea(pt))
    {
        int nPos = pTabCtrlEx->GetTabsHorzOffset();
        int nMax = pTabCtrlEx->GetTabsHorzOffsetMax();
        int nScroll = 50;    // スクロール量(ピクセル?)
        if(zDelta < 0)
        {
            int nTarget = nPos + nScroll;
            if(nTarget > nMax) nTarget = nMax;
            if(nTarget < 0) nTarget = 0;
            pTabCtrlEx->SetTabsHorzOffset(nTarget);
        }
        else
        {
            int nTarget = nPos - nScroll;
            if(nTarget < 0) nTarget = 0;
            pTabCtrlEx->SetTabsHorzOffset(nTarget);
        }
    }
    else
    {
        return CTabView::OnMouseWheel(nFlags, zDelta, pt);
    }
}



CMFCTabCtrlのソースコードを眺めて、スクロール処理している部分を特定して、擬似的にそれを実行する。
変数が、protected だったから、CMFCTabCtrl のままだとアクセスできないから、ダミーの派生クラスCMFCTabCtrlExを作成して、そのポインタに無理矢理CMFCTabCtrlのアドレスを突っ込んで、独自処理を組み込んだ見せかけの派生クラス上で処理させました。

こんな事が出来るのも、C++だからこそ。
.NETじゃ絶対に出来ないなぁ。
[PR]
by isoq | 2012-01-31 01:06 | C/C++/Win32
e87.com(千趣会イイハナ) 花を贈るなら日比谷花壇

MFC なぜかメッセージボックスが表示できなくなる原因が判明

ここのところ、作成したMFCプログラムでなぜか、どこに次のようなコードを書いてもメッセージボックスが表示できなくなってしまっていました。


MessageBox(_T("test"), _T("test"), MB_OK);


とか


AfxMessageBox(_T("test"));


とかです。

本当に基本的なメッセージボックスの表示方法ですが、どこに書いても音が鳴るだけで、メッセージボックスが表示されない!

そして、音の後に [Alt] キーを押すと、なぜかメッセージボックスが表示される・・・

メッセージボックスの表示が [Alt] キー入力を待っているような感じになってしまっていたのです。

原因がわからず、プログラム全体を 少しずつコメントしたり戻したりした結果、努力の甲斐無く、全く原因がつかめませんでした。

そして、スケルトンからやり直して、ちょっとづつコードを足していって・・・最初は問題なく、表示されていたため、ちょっとがんばってたくさんコードを移植すると発生! (^^;) もっとじっくりやればよかった・・・

それでもその間のコードを確認しながらやってみるてやっと鍵を見つけました!

ここです!


void CXxxxYyyyView::OnPaint()
{
    CPaintDC dc(this);    // これを消していました



CPaintDC は、画面の無効領域だけを更新するためのDCですが、使わなかったので、消してしまって、CDC* pDC = GetDC(); とやって、そちらのDCを使用していました。

が、どうやらここで、CPaintDC をインスタンス化しないことが原因で、メッセージボックスが出なくなっていたようです。

こんな事もあるんですね・・・・ MFC ・・・・・
[PR]
by isoq | 2012-01-29 17:52 | C/C++/Win32
e87.com(千趣会イイハナ) 花を贈るなら日比谷花壇

MFCのCImageでPNGファイルのアルファブレンドがおかしいの解決方法

CImageで、PNGファイルを読み込むと、アルファブレンドがおかしい。

Photoshop、Office、Windows Paint(on Win7)では、正常に表示できる画像が、CImageで読み込むと、色が崩れたり、アルファ値がおかしかったりする。 Visual Studio 2010 IDE のリソースエディタ で読み込んでも、CImage と同じ画像が出てくるので、CImage自体に問題があり、Visual Studio 2010にもその問題が引き継がれているらしい。

これは、おそらくCImage のバグ。

なかなか原因がつかめなかったのですが、次のサイトで解決策を発見。

wrong alpha blending with CImage::AlphaBlend() & *.png

これを参考に、次のようにして問題が解決しました。


CImage m_image;
if(SUCCEEDED(m_image->Load(strFileName)))
{
    if(m_image->GetBPP() == 32)
    {
        // PNGのアルファ問題回避
        if(strFileName.Right(3).CompareNoCase(_T("png")) == 0)
        {
            unsigned char * pCol = 0;
            long w = m_image->GetWidth();
            long h = m_image->GetHeight();
            for(long y = 0; y < h; y ++)
            {
                for(long x = 0; x < w; x ++)
                {
                    pCol = (unsigned char *)m_image->GetPixelAddress(x,y);
                    unsigned char alpha = pCol[3];
                    if(alpha != 255)
                    {
                        pCol[0] = ((pCol[0] * alpha) + 128) >> 8;
                        pCol[1] = ((pCol[1] * alpha) + 128) >> 8;
                        pCol[2] = ((pCol[2] * alpha) + 128) >> 8;
                    }
                }
            }
        }

        // アルファチャンネルON
        m_image->SetHasAlphaChannel(true);
    }
}

[PR]
by isoq | 2012-01-29 12:54 | C/C++/Win32
e87.com(千趣会イイハナ) 花を贈るなら日比谷花壇

MFCリボンのバグ。 サブウインドウのリボンでメニューを開くとメインウインドウが前面に表示される

Visual Studio 2010 / Visual C++ 2010 の MFC のリボンインターフェイスのバグ発見。
サブウインドウ上でRibbon ボタンによるメニュー表示の瞬間に、メインウインドウがアクティブになってしまう・・・

次のような場合に発生する。

① CMainFrame の代わりに、 CDialog などをメインウインドウにして、CMainFrame/View をサブウインドウにした場合。

② マルチトップレベルウインドウで、2つめ以降のCMainFrameを作成した場合

こんな時には、リボンがあるウインドウがメインウインドウではない状況が生まれるけど、このとき、サブウインドウでリボンのボタンのメニューを開こうとすると、メインウインドウがアクティブになって邪魔してくる。

原因を探ると、次の場所に行き着いた。


CMFCRibbonBaseElement* CMFCRibbonCategory::OnLButtonDown(CPoint point)
{
    CMFCRibbonBaseElement* pBtnScroll = HitTestScrollButtons(point);
    ...
    CMFCRibbonPanel* pPanel = GetPanelFromPoint(point);
    ...
    return pPanel->MouseButtonDown(point);



CMFCRibbonBaseElement* CMFCRibbonPanel::MouseButtonDown(CPoint point)
{
    if (m_pHighlighted != NULL)
    {
        ...
        m_pHighlighted->OnLButtonDown(point);



void CMFCRibbonButton::OnLButtonDown(CPoint point)
{
    ...
    OnShowPopupMenu();



void CMFCRibbonButton::OnShowPopupMenu()
{
    ASSERT_VALID(this);
    if (IsDroppedDown())
    {
        // if the button already has a menu, don't create another one!
        return;
    }
    CWnd* pWndParent = GetParentWnd();
    if (pWndParent->GetSafeHwnd() == NULL)
    {
        ASSERT(FALSE);
        return;
    }
    CMFCRibbonBar* pTopLevelRibbon = GetTopLevelRibbonBar();
    if (pTopLevelRibbon->GetSafeHwnd() == NULL)
    {
        ASSERT(FALSE);
        return;
    }
    CMFCRibbonBaseElement::OnShowPopupMenu();
    const BOOL bIsRTL = (pTopLevelRibbon->GetExStyle() & WS_EX_LAYOUTRTL);
    CWnd* pWndOwner = pTopLevelRibbon->GetSafeOwner();



CWnd* PASCAL CWnd::GetSafeOwner(CWnd* pParent, HWND* pWndTop)
{
    HWND hWnd = GetSafeOwner_(pParent->GetSafeHwnd(), pWndTop);
    return CWnd::FromHandle(hWnd);



HWND PASCAL CWnd::GetSafeOwner_(HWND hParent, HWND* pWndTop)
{
    // get window to start with
    HWND hWnd = hParent;
    if (hWnd == NULL)
    {
        CFrameWnd* pFrame = CCmdTarget::GetRoutingFrame_();        // ここがNULLになる!
        if (pFrame != NULL)
            hWnd = pFrame->GetSafeHwnd();
        else
            hWnd = AfxGetMainWnd()->GetSafeHwnd();    // だから、ここで メインウインドウが返っちゃう!
                    // → メインウインドウがポップアップメニューのオーナーになる
                    //     → メインウインドウがアクティブになっちゃう
    }



CFrameWnd* PASCAL CCmdTarget::GetRoutingFrame_()
{
    CFrameWnd* pFrame = AfxGetThreadState()->m_pRoutingFrame;    // これがNULLになっている!
    return pFrame;
}



CMFCRibbonButton::OnShowPopupMenu() をオーバーライドして、GetSafeOwner() を使わないように改造するのが一番簡単だけど、Visual C++ 2010 の リソースエディタでリボンを作成して、自動読み込みを行うと、どうもカスタムのCMFCRibbonButton派生クラスを差し込むことは難しいみたい。MFCのソースとにらめっこしたけど、どうやら不可能。こんな時は、ライブラリ内の特定クラスを無効化して、自分で再定義したクラスをはめ込みたいけど出来ないよね・・・。

だから、AfxGetThreadState()->m_pRoutingFrame に CMainFrame* を突っ込めば良いんじゃね??

って事で、次のようにしてみました。


BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
    AfxGetThreadState()->m_pRoutingFrame = this;
    return CFrameWndEx::PreTranslateMessage(pMsg);
}

CMainFrame::~CMainFrame()
{
    AfxGetThreadState()->m_pRoutingFrame = 0;
}



PreTranslateMessage で、CMainFrame にメッセージが飛んできたら常に AfxGetThreadState()->m_pRoutingFrame にポインタをセットします。

こうすることで、(正しい処置かどうかは不明ですが・・・)現在アクティブな CMainFrame 上にメニューを表示することが出来ました!

CMainFrame の消滅時に AfxGetThreadState()->m_pRoutingFrame をクリアしないと、終了時にエラーが出ました。
[PR]
by isoq | 2012-01-29 12:41 | C/C++/Win32
e87.com(千趣会イイハナ) 花を贈るなら日比谷花壇