タグ:Ribbon ( 1 ) タグの人気記事

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(千趣会イイハナ) 花を贈るなら日比谷花壇