wxRibbonBar File Menu Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
iwbnwif
Super wx Problem Solver
Super wx Problem Solver
Posts: 282
Joined: Tue Mar 19, 2013 8:52 pm

wxRibbonBar File Menu

Post by iwbnwif »

In case anyone is wondering how to implement a "File" menu at the left side of a wxRibbonBar, the author of GameDevelop has a good solution.
The button is a wxStaticBitmap. I've added events to popup a menu when the bitmap is clicked and another event to change the bitmap when it is hovered.
The most tricky part is to generate the hovered and non hovered bitmap. To do this, I create a fake wxRibbonBar with the same wxRibbonArtProvider as the original ribbon, I add a tab called "File", and I ask the art provider to draw the tab in a wxMemoryDC. Finally, when the bitmap is rendered, I just put it on the wxStaticBitmap, which is put over the ribbon. Here is the complete code used to do this :

Code: Select all

    //In this code, the ribbon is a member of MainFrame called ribbon.
    //Rendered bitmap are also members of MainFrame, and are called ribbonFileNormalBitmap and ribbonFileHoveredBitmap
    //Finally, the wxStaticBitmap is called ribbonFileBt.
    void MainFrame::RealizeRibbonCustomButtons()
    {
        wxRibbonArtProvider * artProvider = ribbon->GetArtProvider();
        if ( artProvider == NULL ) return;

        wxColor buttonColor;
        if ( !wxConfigBase::Get()->Read( _T( "/Skin/FileButtonColor" ), &buttonColor ) )
            buttonColor = wxColour(200, 200, 255);

        //Create a temporary fake ribbon used to render the button with a custom color
        wxRibbonBar * fakeRibbon = new wxRibbonBar(this);
        fakeRibbon->SetArtProvider(artProvider->Clone());
        fakeRibbon->GetArtProvider()->SetColourScheme(buttonColor, buttonColor, buttonColor);

        //The device context used to render the button in memory
        wxMemoryDC dc;

        //Compute width of the bitmap button
        int width; artProvider->GetBarTabWidth(dc, fakeRibbon, _("File"), wxNullBitmap, &width, NULL, NULL, NULL);

        //Create a fake ribbon page...
        wxRibbonPage *page = new wxRibbonPage(fakeRibbon, wxID_ANY, _("File"));
        //...and the associated wxRibbonPageTabInfo
        wxRibbonPageTabInfo tabInfo;
        tabInfo.rect = wxRect(0,0, width, 16 /*Will be changed later*/);
        tabInfo.ideal_width = width;
        tabInfo.small_begin_need_separator_width = width;
        tabInfo.small_must_have_separator_width = width;
        tabInfo.minimum_width = width;
        tabInfo.page = page;
        tabInfo.active = true;
        tabInfo.hovered = false;
        wxRibbonPageTabInfoArray pages;
        pages.Add(tabInfo);
        pages.Add(tabInfo); //Add page twice to ensure that tab have a correct height

        //Compute height of the bitmap button and create bitmap
        int height = artProvider->GetTabCtrlHeight(dc, ribbon, pages);
        wxBitmap bitmapLabel(width+2, height);
        dc.SelectObject(bitmapLabel);

        tabInfo.rect = wxRect(0,0, width, height+2); //We've got the correct height now.

        //Render the file button. Use the background of the real ribbon.
        artProvider->DrawTabCtrlBackground(dc, fakeRibbon, bitmapLabel.GetSize());
        fakeRibbon->GetArtProvider()->DrawTab(dc, fakeRibbon, tabInfo);
        ribbonFileNormalBitmap = wxBitmap(bitmapLabel);

        //Render the hovered file button
        wxBitmap bitmapHoveredLabel(ribbonFileNormalBitmap.ConvertToImage());
        dc.SelectObject(bitmapHoveredLabel);

        tabInfo.active = false;
        tabInfo.hovered = true;
        artProvider->DrawTabCtrlBackground(dc, fakeRibbon, bitmapHoveredLabel.GetSize());
        wxColour backgroundColour = wxColor(bitmapHoveredLabel.ConvertToImage().GetRed(0,0), bitmapHoveredLabel.ConvertToImage().GetGreen(0,0), bitmapHoveredLabel.ConvertToImage().GetBlue(0,0)); //For later use...
        fakeRibbon->GetArtProvider()->DrawTab(dc, fakeRibbon, tabInfo);
        ribbonFileHoveredBitmap = bitmapHoveredLabel;

        //Cut a bit the bottom of the bitmaps
        if ( ribbonFileNormalBitmap.GetSize().GetHeight() > 3 )
            ribbonFileNormalBitmap.SetHeight(ribbonFileNormalBitmap.GetSize().GetHeight()-2);

        if ( ribbonFileHoveredBitmap.GetSize().GetHeight() > 3 )
            ribbonFileHoveredBitmap.SetHeight(ribbonFileHoveredBitmap.GetSize().GetHeight()-2);

        fakeRibbon->Destroy();

        //Finally create our bitmaps and make sure the ribbon is ready.
        ribbonFileBt->SetPosition(wxPoint(3,1));
        ribbonFileBt->SetBitmap(ribbonFileNormalBitmap);
        ribbon->SetTabCtrlMargins(bitmapLabel.GetSize().GetWidth()+3+3, 0);
    }
You should be able to adapt it to any code base by renaming MainFrame and its members variables. If there is any problem, you can test if the bitmap generation is made correctly by saving the bitmaps to file ( wxBitmap::SaveFile ). :)
The original discussion is here
wxWidgets 3.1.2, MinGW64 8.1.0, g++ 8.1.0, Ubuntu 19.04, Windows 10, CodeLite + wxCrafter
Some people, when confronted with a GUI problem, think "I know, I'll use Eclipse RCP". Now they have two problems.
shawnee
Experienced Solver
Experienced Solver
Posts: 78
Joined: Tue Jan 16, 2018 1:05 am

Re: wxRibbonBar File Menu

Post by shawnee »

Nice codes! I did slight modification on it.

Code: Select all

void MainFrame::RealizeRibbonCustomButtons(wxRibbonBar * pParentRibbon, const char * Label) {
    //@@preconditions
    assert(pParentRibbon != NULL);
    assert(Label != NULL && strlen(Label) > 0);
    //@@end preconditions

    wxRibbonArtProvider * artProvider = pParentRibbon->GetArtProvider();
    if (artProvider == NULL) return;

    wxColor bgNormalColor = wxColour(12, 97, 154);  //blue

    //Create a temporary fake wxRibbon used to render the button with a custom color
    wxRibbonBar * fakeRibbon = new wxRibbonBar(this);
    fakeRibbon->SetArtProvider(artProvider->Clone());
    fakeRibbon->GetArtProvider()->SetColourScheme(bgNormalColor, bgNormalColor, bgNormalColor);
    fakeRibbon->GetArtProvider()->SetColour(wxRIBBON_ART_TAB_LABEL_COLOUR, wxColour(255, 255, 255)/*white*/);

    //The device context used to render the button in memory
    wxMemoryDC dc;

    //Compute width of the bitmap button
    int width = 0, exWidth = 15;
    artProvider->GetBarTabWidth(dc, fakeRibbon, wxT(wxString::FromUTF8(_(Label))), wxNullBitmap, &width, NULL, NULL, NULL);
    width += exWidth; //more wider

    //Create a fake wxRibbon page...
    wxRibbonPage *page = new wxRibbonPage(fakeRibbon, wxID_ANY, wxT(wxString::FromUTF8(_(Label))));
    //...and the associated wxRibbonPageTabInfo
    wxRibbonPageTabInfo tabInfo;
    tabInfo.rect = wxRect(0, 0, width, 16 /*Will be changed later*/);
    tabInfo.ideal_width = width;
    tabInfo.small_begin_need_separator_width = width;
    tabInfo.small_must_have_separator_width = width;
    tabInfo.minimum_width = width;
    tabInfo.page = page;
    tabInfo.active = true;
    tabInfo.hovered = false;
    wxRibbonPageTabInfoArray pages;
    pages.Add(tabInfo);
    pages.Add(tabInfo); //Add page twice to ensure that tab have a correct height

    //Compute height of the bitmap button and create bitmap
    int height = artProvider->GetTabCtrlHeight(dc, pParentRibbon, pages);
    wxBitmap bitmapLabel(width + 2, height);
    dc.SelectObject(bitmapLabel);

    tabInfo.rect = wxRect(0, 0, width, height + 2); //We've got the correct height now.

    //Render the normal button. Use the background of the real wxRibbon.
    artProvider->DrawTabCtrlBackground(dc, fakeRibbon, bitmapLabel.GetSize());
    fakeRibbon->GetArtProvider()->DrawTab(dc, fakeRibbon, tabInfo); assert(bitmapLabel.IsOk());
    wxSize bmSize = bitmapLabel.GetSize();
    wxBitmap RibbonxNormalBitmap_File = wxBitmap(bitmapLabel);
    assert(RibbonxNormalBitmap_File.IsOk());

    //Render the hovered button
    wxBitmap bitmapHoveredLabel(RibbonxNormalBitmap_File.ConvertToImage());
    dc.SelectObject(bitmapHoveredLabel);

    wxColor bgHoverColor = wxColor(41, 140, 225);  //light blue
    fakeRibbon->GetArtProvider()->SetColour(wxRIBBON_ART_TAB_HOVER_BACKGROUND_COLOUR,                        bgHoverColor);
    fakeRibbon->GetArtProvider()->SetColour(wxRIBBON_ART_TAB_HOVER_BACKGROUND_GRADIENT_COLOUR,        bgHoverColor);
    fakeRibbon->GetArtProvider()->SetColour(wxRIBBON_ART_TAB_HOVER_BACKGROUND_TOP_COLOUR,                 bgHoverColor);
    fakeRibbon->GetArtProvider()->SetColour(wxRIBBON_ART_TAB_HOVER_BACKGROUND_TOP_GRADIENT_COLOUR, bgHoverColor);

    tabInfo.active  = false;
    tabInfo.hovered = true;
    artProvider->DrawTabCtrlBackground(dc, fakeRibbon, bitmapHoveredLabel.GetSize());
    wxColour backgroundColour = wxColor(bitmapHoveredLabel.ConvertToImage().GetRed(0, 0), bitmapHoveredLabel.ConvertToImage().GetGreen(0, 0), bitmapHoveredLabel.ConvertToImage().GetBlue(0, 0)); //For later use...
    fakeRibbon->GetArtProvider()->DrawTab(dc, fakeRibbon, tabInfo);
    wxBitmap RibbonxHoveredBitmap_File = bitmapHoveredLabel;

    //Cut a bit the bottom of the bitmaps
    bmSize = RibbonxNormalBitmap_File.GetSize();
    if (bmSize.GetHeight() > 3) {
        RibbonxNormalBitmap_File.SetHeight(bmSize.GetHeight() - 2);
    }
    assert(RibbonxNormalBitmap_File.IsOk());

    bmSize = RibbonxHoveredBitmap_File.GetSize();
    if (bmSize.GetHeight() > 3) {
        RibbonxHoveredBitmap_File.SetHeight(bmSize.GetHeight() - 2);
    }
    assert(RibbonxHoveredBitmap_File.IsOk());

    fakeRibbon->Destroy();

    int xOff = 2;
    wxPoint wdgOriginalPt(0 + xOff, 0);
    wxSize  wdgOriginalSz(bitmapLabel.GetSize().GetWidth(), bitmapLabel.GetSize().GetHeight());

    wxBitmapButton * csbAppMenuButton = new wxBitmapButton(pParentRibbon, wxID_ANY, RibbonxNormalBitmap_File, wdgOriginalPt, wdgOriginalSz, wxBU_ALIGN_MASK | wxBU_AUTODRAW | wxNO_BORDER);
    csbAppMenuButton ->SetBitmapHover(RibbonxHoveredBitmap_File);
    csbAppMenuButton ->SetBitmapPressed(RibbonxHoveredBitmap_File);

    wxRect csbRect;
    csbRect.SetX(wdgOriginalPt.x);
    csbRect.SetY(wdgOriginalPt.y);
    csbRect.SetWidth(wdgOriginalSz.GetWidth());
    csbRect.SetHeight(wdgOriginalSz.GetHeight());

    //reset TabCtrl margins of passed-in ribbon.
    int tabGap = 2;
    pParentRibbon->SetTabCtrlMargins(csbRect.GetWidth()+xOff+tabGap, 0);
}
I didn't use wxStaticBitmap and I used wxBitmapButton, because I always got crash at this line: ribbonFileBt->SetBitmap(ribbonFileNormalBitmap);
Post Reply