Application of callback functions in modaless dialog

in #utopian-io7 years ago

Github repository :https://github.com/github/VisualStudio

What Will I Learn?

  • Learn the basic knowledge of modeless dialog.
  • Learn how to registe callback function.
  • Learn to pass messages between dialogs through callback functions.

Requirements

  • The basic knowledge of C++.
  • Messages between dialogs.
  • The basic response functions for dialog.

Difficulty

  • Intermediate

Tutorial Contents

issue raised

Some time ago, I was doing Github development based on Visual Studio 2015 . The need to pick up parameters from different dialog boxes is generated when I was writing a feature recently. The idea is to meet the demand by passing information between modeless dialogs.

Model dialog and modeless dialog

Unlike modal dialogs, modeless dialogs do not monopolize user input, and users can still interact with other interfaces when they open a modeless dialog. The modeless dialog is basically similar to the modal dialog box, it includes both the design dialog template and the derived class of the design CDialog class.

There are several differences between modal and modeless dialog:

differentModel dialogModeless dialog
VisibleCWnd::ShowWindow(SW_SHOW)Must set Visible property
CreateLocal variablesnew CModelessDialog
ShowCDialog::DoModalCDialog::Create
DestroyCDialog::EndDialogCWnd::DestroyWindow
DeleteNo need to delete itselfCWnd::PostNcDestroy delete this

several points of knowledge

What is callback funtion?

A callback function is a function called by a function pointer. If you pass a function's pointer (address) as an argument to another function which is used to call the function it points to, we say this is a callback function. The callback function is not invoked directly by the implementing party of the function, but is invoked by another party when a particular event or condition occurs, and is used to respond to the event or condition.

page source:CSDN

callback function definition: function pointer

The callback is implemented by the function pointer in C language, and the callback is implemented by passing the address of the callback function to the modulated function. Therefore, to implement the callback, you must first define the function pointer, see the following example:

void Func(char *s)// The prototype of the function
void (*pFunc) (char *);//function pointer

As you can see, the definition of a function is very similar to the definition of a function pointer. In general, we need to customize the function pointer type in order to simplify the variable definition of function pointer type and improve the readability of the program.

typedef void(*pFunc)(char *);

Composition of callback functions

The implementation process of the callback function consists of the following sections:

  • registe
  • unregiste
  • callback Function

Code design

Create a modeless dialog

Modaless dialog objects are created dynamically in the heap with the new operator, instead of being embedded in other objects in the form of member variables or built on the stack as local variables. You should typically declare a pointer member variable to a dialog class within the owner window Class of the dialog to access the dialog object. Start the dialog by calling the Cdialog::create function.

void CGithubDlg::OnBnClickedButtonView()
{
    //New a modeless dialog
    m_pBranchDlg = new CBranchDlg();
    
    //Create window
    m_pBranchDlg->Create(IDD_DIALOG_BRANCH);
    m_pBranchDlg->ShowWindow(SW_SHOW);
}

define SelParamFunc

What we want the callback function to do is that once the user clicks on an item in the branch list in the GitHub dialog, the information in this row will be returned to the Branch dialog as shown in the following figure.

So two parameters are required in the callback function, CString & strRowData represents the selected row of data, translates into a CString for delivery, and the second CWnd * Pwnd represents the window to accept the data.

Therefore, the response function should be as follows:

void SelFunc_Parameter(CString &strSelData, CWnd * pWnd)
{
  //do something to set strSelData to the editctrls of Branch dialog
}

strSelData is a combination string with a # delimiter, in which we need to parse the string into three substrings. I wrote the following function to split the string.

//strline is the string need to split. ch is is Separator. arrRes is a string array after the split
// For example: strLine is "Rose # Branch_check_in # new function check in history", ch is "#", arrRes will has three child(Rose,Branch_check_in,new function check in history).
void GetSplitStrArr(CStringArray &arrRes, const CString strLine, TCHAR ch)
{
    CString strTemp;
    int nStart = 0, nEnd = 0;
    if (strLine.IsEmpty())
    {
        return;
    }
    while ((nEnd = strLine.Find(ch, nStart)) > 0)
    {
        strTemp = strLine.Mid(nStart, nEnd - nStart);
        nStart = nEnd + 1;
        strTemp.Trim();
        arrRes.Add(strTemp);
    }
    strTemp = strLine.Mid(nStart);
    strTemp.Trim();
    arrRes.Add(strTemp);
}

The next step is to put these strings into the control of the corresponding Branch dialog box, respectively.

void SelFunc_Parameter(CString &strSelName, CWnd * pWnd)
{
    CBranchDlg * pDlg = (CBranchDlg *)pWnd;
    if (pDlg != NULL)
    {
        CStringArray    arrLineInfo;
        GetSplitStrArr(arrLineInfo, strSelName, _T('#'));
        
        if (arrLineInfo.GetSize() >= 3)
        {
           // set the first one to IDC_EDIT_USER ctrl
            CEdit * pEdtUser = (CEdit *)pDlg->GetDlgItem(IDC_EDIT_USER);
            pEdtUser->SetWindowTextW(arrLineInfo[0]);
            
            // set the second one to IDC_EDIT_BRANCH ctrl
            CEdit * pEdtBranch = (CEdit *)pDlg->GetDlgItem(IDC_EDIT_BRANCH);
            pEdtBranch->SetWindowTextW(arrLineInfo[1]);
            
            // set the third one to IDC_EDIT_NOTE ctrl
            CEdit * pEdtNote = (CEdit *)pDlg->GetDlgItem(IDC_EDIT_NOTE);
            pEdtNote->SetWindowTextW(arrLineInfo[2]);
        }
    }
}

The corresponding effect is probably:

Now let's take a look at the application of typedef in the definition of callback functions.

typedef void(*SelParamFunc)(CString & strRowData, CWnd * pWnd);

The function of typedef is to define a new type. The first sentence defines a SelParamFunc type and defines the type as a pointer to a function that takes a cstring& and CWnd * as two parameters and returns a void type. The definition is completed as shown in the following illustration:

Registe and unRegiste

The callback function needs to be registered before responding so that the function entry can be found at the appropriate time.
Similarly, you need to unRegiste at the end so that the next response will not be affected by the original function pointer. Add the following code to the Cgithubdlg class:

class CGithubDlg : public CDialogEx
{

////////////////////////////////
public:
    SelParamFunc mpSelFunc;
    CWnd * m_pWnd;
    void RegisteSelFunc(SelParamFunc pFunc, CWnd * pWnd);
    void UnRegisteSelFunc();
};

Add the following code to the CGithubDlg.cpp:

void CGithubDlg::RegisteSelFunc(SelParamFunc pFunc, CWnd * pWnd)
{
   // Registe the callback funtion and set the target window
    mpSelFunc = pFunc;
    m_pWnd = pWnd;
}

void CGithubDlg::UnRegisteSelFunc()
{
   // UnRegiste the callback funtion and set the target window to NULL
    mpSelFunc = NULL;
    m_pWnd = NULL;
}

So what's the right time to register this callback function? We chose to register the callback function after the modeless dialog was just bounced.

Response callback

The response of the callback function needs to be manually invoked in its own specific place.

In our example, you should respond when you select a row in the list. First SELECT Branch List CTRL, and then find Contrl event in the Properties page. Select the NM_CLICK event.

Automatically adds a function:afx_msg void OnNMClickListBranch(NMHDR *pNMHDR, LRESULT *pResult);

In this function, the following code is automatically generated:

LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);

Let's take a look at the structure of LPNMITEMACTIVATE:

typedef struct tagNMITEMACTIVATE
{
    NMHDR   hdr;
    int     iItem;
    int     iSubItem;
    UINT    uNewState;
    UINT    uOldState;
    UINT    uChanged;
    POINT   ptAction;
    LPARAM  lParam;
    UINT    uKeyFlags;
} NMITEMACTIVATE, *LPNMITEMACTIVATE;

You can get the selected row from this variable exactly. So you can get the data for that row directly here, and then feed back to branch this dialog box by using the callback function. The code for the implementation is as follows:

void CGithubDlg::OnNMClickListBranch(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);

    //Get the row of the selected item
    int nRow = pNMItemActivate->iItem;
    //Get data from branchs vector
    sBranch hit = m_vecBranchs[nRow];

    //Create the reasult string need to be send back
    CString strRes;
    strRes = hit.m_strUser + _T("#") + hit.m_strName + _T("#") + hit.m_strNote;

    //Call the callback function
    mpSelFunc(strRes, m_pBranchDlg);

    *pResult = 0;
}

PostNcDestroy

Because the modeless dialog object is created with the new operator, you must delete the dialog object with the Delete operator after the dialog closes. Generally,After a window is deleted on the screen, the frame will calls CWnd::PostNcDestroy, a virtual function in which the program can complete the task of deleting a Window object, as follows:

void CBranchDlg::PostNcDestroy()
{
    delete this;

    CDialogEx::PostNcDestroy();
}

The results of the final implementation are as follows:

Conclusion

The application of callback functions is flexible and extensive.
The above example is just one of the small applications. Because the caller can be separated from the callee, the caller does not care who the callee is. It only needs to know that there is a called function with a specific stereotype and a restricted condition. Therefore, it is widely used in the process of software development.

I hope this tutorial will help you.

Thank you for your attention.
@hushuilan

Sort:  

Thank you for your contribution.
While I liked the content of your contribution, I would still like to extend few advices for your upcoming contributions:

  • Work on improving your post for English mistakes and badly written words and sentences, as those could lead your author to lose track of your intent.
  • When you use a specific reference (such as the image, include a full reference url and not just mention the name of the site)
  • Include a github repository that contains the code that you made in this post here, and provide the link at the end of the post. This would be helpful for your readers to be able to simply try to run and re-use your work.
    Looking forward to your upcoming tutorials.

Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Dear @mcfarhat. Thank you for your valuable advice.

Hey @hushuilan
Thanks for contributing on Utopian.
We're already looking forward to your next contribution!

Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Congratulations @hushuilan! You received a personal award!

1 Year on Steemit

Click here to view your Board

Do not miss the last post from @steemitboard:

SteemWhales has officially moved to SteemitBoard Ranking
SteemitBoard - Witness Update

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @hushuilan! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!