Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / MFC

CSplitButton with Images

Rate me:
Please Sign up or sign in to vote.
5.00/5 (22 votes)
19 May 2021CPOL3 min read 12.7K   746   21   7
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)


Written By
CEO Secured Globe, Inc.
United States United States
Michael Haephrati is a music composer, an inventor and an expert specializes in software development and information security, who has built a unique perspective which combines technology and the end user experience. He is the author of a the book Learning C++ , which teaches C++ 20, and was published in August 2022.

He is the CEO of Secured Globe, Inc., and also active at Stack Overflow.

Read our Corporate blog or read my Personal blog.





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
ZhuXing 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 
Praisegreat info Pin
Southmountain27-May-21 14:35
Southmountain27-May-21 14:35 
thanks for the detailed steps for users to pick up!
diligent hands rule....

QuestionMessage Closed Pin
19-May-21 22:21
tin 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.