Thursday, March 10, 2016

MFC tips

Working with MFC ListCtrl


LV_ITEM lvi -- this is the structure you need to fill out to add an entry into the list.
lvi has two items that may be confusing to you, nItem and nSubItem. Think of these two as
row and column. nItem is the row and nSubItem is the column of that row.

Here is a sample code to add an entry to the CListCtrl:


        LV_ITEM lvi;
        CString sAlias(StringManipulationUtil::NQStringToCString(*iter));
        ZeroMemory(&lvi, sizeof(LV_ITEM));
        lvi.mask = LVIF_TEXT; //can be an image , text or etc.
        lvi.iItem = m_ctlList.GetItemCount(); //insert at the bottom
        lvi.pszText = (LPTSTR) (const TCHAR *) (sAlias); //text to show
        lvi.cchTextMax    = sAlias.GetLength()+ 1; //size of text
        m_ctlList.InsertItem(&lvi);

How to add your own accelerators in MFC?

Accelerators are keyboard shortcuts that will trigger some events.
For example, when you click on a button, onClicked event will be trigger for that button.
There are two information associated with an event: the event type (onClicked) and the ID of the control.
A regular message map would be the followings:

BEGIN_MESSAGE_MAP(SAATAPPDlgConsistencyCheck, CDlgHelper)
    ON_BN_CLICKED(IDC_BTN_GOTO, OnBtnGoto)
    ...
END_MESSAGE_MAP()


Notice the event type defined by the macro ON_BN_CLICKED takes in the id of the control, and the handler.

How do you capture this event with keyboard shortcuts?
First, you need to create an accelerator. You can do this by opening the edit.rc file and under accelerator add a new accelerator table. Here is the example with screenshots:

Creating a new accelerator node:

 








Add new shortcuts to existing accelerator node:
1. double click on an existing accelerator node



 2. Here is a list of shortcuts in the accelerator node IDR_NAVIGATION
This table contain the control ids, and the key board shortcuts assigned to them.
When the keyboard shortcut is executed by the users, the control having the id will receive an oncommand event.






3. To create a new keyboard shortcut, right click and select new accelerator



 4. Once a new item is created, you can select and right click the new item to assign a new keyboard shortcut for it.



Notice each shortcut has an ID which can be the ID of a control. For example, IDC_BTN_UP in the screenshot is the id of a button control. The keyboard shortcut will trigger an event similar to a mouse click on the button control. You can change the ID. Just select the id and a drop down box will be displayed. From the drop down box, you can select any of the control ID defined for the application.




In order for the keyboard shortcut to work, you need to apply/load the custom accelerator node/table to the dialog where you want to handle the shortcuts or containing the desired control(s).
The one accelerator node that will always exist or the default one loaded for the program is the IDR_MAINFRAME.

If you create you own accelerator table/node, you need to load it.
You can load the accelerator table when the dialog, containing the desired controls, is initialized.

BOOL CShtHelper::OnInitDialog()
{
    if(m_bNeedsStatusBar)
    {   
        CreateStatusBar(this);
    }
    BOOL bResult = CResizeableSheet::OnInitDialog();
    //load the accelerator table you created (IDR_NAVIGATION).
    m_hAccel = ::LoadAccelerators(AfxGetResourceHandle(),
                                                          MAKEINTRESOURCE(IDR_NAVIGATION));
...
}

If the shortcut or control is being used in many places, you may want to prevent the same shortcut being propagate to many places. By overwriting the PreTranslateMessage method, you can prevent the message from being propagated:

BOOL CShtHelper::PreTranslateMessage(MSG* pMsg)
{
//if this class/dialog can translate the message (keyboard shortcut), then don't propagate the message;
//otherwise, forward the message to the parent.
//Translate the message using your accelerator table, this will trigger the on command on the some control if
//the message (key pressed) matches an entry in the table.
    if ( !(m_hAccel && ::TranslateAccelerator(m_hWnd, m_hAccel, pMsg)) && !m_tabCtrl.TranslatePropSheetMsg(pMsg))
    {
        return CResizeableSheet::PreTranslateMessage(pMsg);
    }

    return TRUE;
}

Finally, you need to register the message map to tell the program what to do when the button is clicked.
Here we want the OnUp method to be called when the button IDC_BTN_UP is clicked.

BEGIN_MESSAGE_MAP(CDlgInitBlock2, CInitBlockSubDialog)
    ON_CBN_SELCHANGE(IDC_SOURCE_TYPE_COMBO, OnComboTypeChange)
    ON_NOTIFY(TVN_SELCHANGED, IDC_TREE, OnSelectionChanged)
    ON_BN_CLICKED(IDC_BTN_UP, OnUp)
    ON_BN_CLICKED(IDC_BTN_DOWN, OnDown)
    ON_BN_CLICKED(IDC_BTN_Delete, OnDelete)
END_MESSAGE_MAP()


How to find the child window having a given name?

If the child window is part of a MDI application, calling FindWindowEx is not enough.
FindWindowEx(AfxGetMainWnd, 0 ,0, windowTitle); //not working because this is a mdi application
FindWindowEx(MDIClientWnd, 0,0,WindowTitle); //not working
FindWindowEx(MDIClientWnd,0,registerClassWndName, WindowTitle); //working, so you need to register it.

No comments: