Click here to Skip to main content
14,976,321 members
Articles / Desktop Programming / MFC
Article
Posted 19 May 2021

Tagged as

Stats

4.5K views
240 downloads
14 bookmarked

CSplitButton with Images

Rate me:
Please Sign up or sign in to vote.
5.00/5 (18 votes)
19 May 2021CPOL3 min read
How to enhance the MFC CSplitButton control to support images
I was looking for a way to add a button which will have pull down menu options, each of them having a PNG image next to the option's text.

Introduction

The CSplitButton is an MFC control which performs a default behavior when a user clicks the main part of the button and displays a drop-down menu when a user clicks the drop-down arrow of the button.

That works great however there aren't any examples for using images for the main and drop-down menu options.

Background

I was working on a commercial project where I wanted to add a "Save" button which will allow various types of files to be saved, so pressing "Save" will save as the default format (Excel) in my case, while it’s also possible to select other formats via a drop-down menu options, which will be CSV or the default (Excel).
I couldn’t find any source code snippet which does that.

Image 1

The SGSplitImageButton Class

I created a new SGSplitImageButton class which is derived from CSplitButton.

C++
class SGSplitImageButton : public CSplitButton
{
    DECLARE_DYNAMIC(SGSplitImageButton)

public:
    SGSplitImageButton();
    virtual ~SGSplitImageButton();
    CMenu *menu;

    void InsertMenu(CString title, UINT imgId, UINT menuID);
    void SetDropDown();

protected:
    DECLARE_MESSAGE_MAP()
public:
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
};

InsertMenu - allows inserting pull-down menu options which can have not only text but also PNG images.

To add an image, the image needs to be defined in the .rc file as follows:

<IMAGE ID> "PNG" <path to image file>

For example:

IDB_SAVESPLIT_XLS       PNG                     "res\\save_excel.png"

Then when the InsertMenu() function is called, the imgId would be IDB_SAVESPLIT_XLS.

For example:

C++
m_SaveWorksheet.InsertMenu(_T("Save Excel"), IDB_SAVESPLIT_XLS, WM_SAVE_XSL);

SetDropDown - calls CSplitButton's SetDropDownMenu() function.

The InsertMenu Function

The following function inserts the pull-down menu, along with the associate images. We are using CPngImage which is an internal class of MFC.

C++
void SGSplitImageButton::InsertMenu(CString title, UINT imgId, UINT menuID)
{
    CPngImage btnImg;
    btnImg.Load(imgId, nullptr);
    menu->AppendMenu(MF_STRING, menuID, title);
    menu->SetMenuItemBitmaps(menuID, MF_BYCOMMAND, &btnImg, &btnImg);
    btnImg.Detach();
}

Initializing Our Button

We initialize our button as follows:

C++
CPngImage btnImg;
btnImg.Load(IDB_MAINIMAGE, nullptr);
m_MySplit.SendMessageW(BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)btnImg.GetSafeHandle());

m_MySplit.InsertMenu(_T("Menu Option 1"), IDB_MENU1, WM_MENUOPTION1);
m_MySplit.InsertMenu(_T("Menu Option 2"), IDB_MENU2, WM_MENUOPTION2);
m_MySplit.InsertMenu(_T("Menu Option 3"), IDB_MENU3, WM_MENUOPTION3);

m_MySplit.SetDropDown();

We use four images in this example:

  • IDB_MAINIMAGE will appear on the button next to its text.
  • IDB_MENU1 will appear on the first menu option next to its text.
  • IDB_MENU2 will appear on the second menu option next to its text.
  • IDB_MENU3 will appear on the third menu option next to its text.

Receiving Messages Per Menu Option

We would also want to be notified when a menu option is selected by the user. To do so, we define private messages per menu option.

We use WM_USER + 1, 2, etc. You can learn more about WM_USER here.

For example:

C++
#define WM_SAVE_XSL             WM_USER + 5
#define WM_SAVE_CSV             WM_USER + 6

or:

C++
#define WM_MENUOPTION1          WM_USER + 1
#define WM_MENUOPTION2          WM_USER + 2
#define WM_MENUOPTION3          WM_USER + 3

Now, let's see how and when we use these custom messages.

First, let's check the BEGIN_MESSAGE_MAP macro.

We map messages (menu options selected), and also the press of the button itself, IDC_SPLIT1).

C++
BEGIN_MESSAGE_MAP(SGSplitImageButtonDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_COMMAND(WM_MENUOPTION1, &OnMenuOption1)    // Menu option 1
    ON_COMMAND(WM_MENUOPTION2, &OnMenuOption2)    // Menu option 2
    ON_COMMAND(WM_MENUOPTION3, &OnMenuOption3)    // Menu option 3
    // Main button pressed
    ON_BN_CLICKED(IDC_SPLIT1, &SGSplitImageButtonDlg::OnBnClickedSplit1)
END_MESSAGE_MAP()

Next, all we need is to create functions to accept the following events:

  1. Button was clicked:
    C++
    OnBnClickedSplit1()
  2. A menu option was selected:
    C++
    OnMenuOption1()
    
    OnMenuOption2()

    and:

    C++
    OnMenuOption3()

Tips for Creating and Using Custom Controls

Generally speaking, when a custom or inherited control is created, we would still want to use the Resource Editor which is available when clicking the dialog from the Resource View.

To make that possible, please do the following:

  1. Create a normal CSplitButton and place it on the dialog.
  2. Give the control a meaningful ID in the Properties tab.

    Image 2

  3. Right click the control and press "Add Variable". Give the control a variable name.

    In our case, the ID is IDC_BUTTON_SAVE and the variable is m_SaveWorkSheet.

    Image 3

  4. Search for the variable name you have given. You will find it in the dialog's header file.

The Resource Editor will add the following line:

C++
CSplitButton m_SaveWorksheet;

and you need to change it to:

C++
SGSplitImageButton m_SaveWorksheet;

or:

C++
SGSplitImageButton m_MySplit;;

(in the attached source code).

You will need to include "SGSplitImageButton.h".

The Resource Editor should have already added the following line to the dialog .cpp file:

C++
DDX_Control(pDX, IDC_BUTTON_SAVE, m_SaveWorksheet);

or:

C++
DDX_Control(pDX, IDC_SPLIT1, m_MySplit);

(in the attached source code).

So now, you have a custom control which can be used like a normal control from the IDE.

Creating a Default Action

In order to create a default action for the button, and change it to the last selected menu, I added the following member function.

C++
void SGSplitImageButton::SetMainImage(int MenuImageNumber)
{
    CPngImage btnImg;
    if (m_ImageIDs.size() > MenuImageNumber)
    {
        btnImg.Load(m_ImageIDs[MenuImageNumber], nullptr);
        this->SendMessageW(BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)btnImg.GetSafeHandle());
    }
}

and created a vector (m_ImageIDs) which holds the bitmap IDs of all menus.

We need to add:

C++
#include <vector>

to stdafx.h.

Next, we need to update our InsertMenu function and add:

C++
m_ImageIDs.insert(m_ImageIDs.end(), imgId);

to it. Now InsertMenu will look like this:

C++
void SGSplitImageButton::InsertMenu(CString title, UINT imgId, UINT menuID)
{
    CPngImage btnImg;
    btnImg.Load(imgId, nullptr);
    menu->AppendMenu(MF_STRING, menuID, title);
    menu->SetMenuItemBitmaps(menuID, MF_BYCOMMAND, &btnImg, &btnImg);
    m_ImageIDs.insert(m_ImageIDs.end(), imgId);
    btnImg.Detach();
}

Last, we call SetMainImage whenever a menu is selected, changing the button's image to the image of the selected menu:

C++
void SGSplitImageButtonDlg::OnMenuOption1()
{
    m_MySplit.SetMainImage(0);
}

void SGSplitImageButtonDlg::OnMenuOption2()
{
    m_MySplit.SetMainImage(1);
}

void SGSplitImageButtonDlg::OnMenuOption3()
{
    m_MySplit.SetMainImage(2);
}

It should then look like this:

Image 4

History

  • 19th May, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Michael Haephrati
CEO Secured Globe, Inc.
United States United States
Michael Haephrati, Musician and CEO and co-founder of Secured Globe, Inc. Worked on many ventures starting from HarmonySoft, designing Rashumon, the first Graphical Multi-lingual word processor for Amiga computer. During 1995-1996 he worked as a Contractor with Apple at Cupertino.

Can be hired here for freelancing work or you can Buy me coffee.





Comments and Discussions

 
PraiseCongrats for winning 1st prize in the May competition! Pin
Shao Voon Wong23-Jun-21 20:18
mvaShao Voon Wong23-Jun-21 20:18 
GeneralRe: Congrats for winning 1st prize in the May competition! Pin
Michael Haephrati3-Jul-21 3:21
professionalMichael Haephrati3-Jul-21 3:21 
GeneralMy vote of 5 Pin
ZhuXing Jin18-Jun-21 17:49
MemberZhuXing Jin18-Jun-21 17:49 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA6-Jun-21 15:41
professionalȘtefan-Mihai MOGA6-Jun-21 15:41 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
Praisegreat info Pin
Southmountain27-May-21 14:35
MemberSouthmountain27-May-21 14:35 
QuestionMessage Closed Pin
19-May-21 22:21
Membertin box19-May-21 22:21 
GeneralMy vote of 5 Pin
Shao Voon Wong19-May-21 14:50
mvaShao Voon Wong19-May-21 14:50 
GeneralRe: My vote of 5 Pin
Michael Haephrati21-May-21 8:56
professionalMichael Haephrati21-May-21 8:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.