Differentiate between user click and SetChecked() in CListCtrl - mfc

I have a CListCtrl with checkboxes that I need to enable or disable based on some external factor. However, when I have more items in the list that can be displayed I cannot use EnableWindow(FALSE) on the control as it also disables the scrollbar.
So, I have searched and came up with the following code in the message map:
ON_NOTIFY(LVN_ITEMCHANGED, IDC_CHECKBOX_LIST, OnCheckboxChanged)
The callback function is implemented as:
void CUserPropertiesDialog::OnCheckboxChanged(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;
LVHITTESTINFO hitInfo;
hitInfo.pt = pNMListView->ptAction;
int nItem = m_checkBoxList.HitTest(&hitInfo);
if (hitInfo.flags != LVHT_ONITEMSTATEICON) return;
std::string groupName = static_cast<LPCTSTR>(m_checkBoxList.GetItemText(nItem, 0));
if (!CCharmUserAdminGUIApp::getTheCharmUserAdminGUIApp().isAdministrator())
{
if (pNMListView->uChanged & LVIF_STATE)
{
if (((pNMListView->uNewState & INDEXTOSTATEIMAGEMASK(2)) != 0) && ((pNMListView->uOldState & INDEXTOSTATEIMAGEMASK(1)) != 0))
{
CH_INFO1("CUserPropertiesDialog::OnCheckboxChanged - CheckBox Now Selected", groupName);
}
else if (((pNMListView->uNewState & INDEXTOSTATEIMAGEMASK(1)) != 0) && ((pNMListView->uOldState & INDEXTOSTATEIMAGEMASK(2)) != 0))
{
CH_INFO1("CUserPropertiesDialog::OnCheckboxChanged - CheckBox Now Unselected", groupName);
}
}
}
}
The problem is that this function is called when a user clicks the checkbox (good!) but also when the SetChecked() function is called from code.
I had hoped the check on hitInfo.flags would enable me to tell the click and the function apart but this is not the case.
Is there, besides setting some global flag before/after the function call and use that in the callback, any other way to tell whether the click or the function call is used?

I use the same inmy program and I used a flag.
But I use LVN_ITEMCHANGING. With this message I can prevent any change.
I overwrote SetCheck (even it is not virtual) and set a flag before I Change the Status of a list box item. The internal OnItemChanging Routine sees the flag set and allows the Change. The flag is directly cleared after the return.
So if the same Action is done with the mouse the flag is not set and you Need to check in a different way.
Same when I am loading the box. I set the flag, so that all changes can pass through...

Related

MFC/C++ ComboBox: disable drawing of Dropdown closing & opening (UI freeze)

I've just added an Item-Filter-Feature to a CComboBox derived class called
ComboBoxFbp in an old MFC application.
BOOL CComboBoxFbp::OnEditChange()
{
CString csText;
if (m_wFbpMode & _FbpMode_UserTextFiltersList) {
GetWindowText(csText);
// This makes the DropDown "flicker"
// ShowDropDown(false);
// Just insert items that match
FilterItems(csText);
// Open DropDown (does nothing if already open)
ShowDropDown(true);
}
return FALSE; // Notification weiterleiten
}
void CComboBoxFbp::FilterItems(CString csFilterText)
{
CString csCurText;
int nCurItem;
DWORD wCurCursor;
// Text/selection/cursos restore
GetWindowText(csCurText);
nCurItem = GetCurSel();
if (nCurItem != CB_ERR && nCurItem >= 0 && nCurItem < GetCount()) {
CString csCurItemText;
GetLBText(nCurItem, csCurItemText);
if (csCurItemText == csCurText) csCurText = csCurItemText;
else nCurItem = CB_ERR;
} else {
nCurItem = CB_ERR;
}
wCurCursor = GetEditSel();
// Delete all items
ResetContent();
csFilterText.MakeLower();
// Add just the items (from the vector of all possibles) that fit
for (auto item : m_vItems)
{
CString csItemText = item.first;
csItemText.MakeLower();
if (!csFilterText.IsEmpty() && csItemText.Find(csFilterText) < 0)
continue;
const int i = AddString(item.first);
SetItemData(i, item.second);
}
// Text/selection/cursos restore
if (nCurItem != CB_ERR) SelectString(-1, csCurText);
else SetWindowText(csCurText);
SetEditSel(LOWORD(wCurCursor), HIWORD(wCurCursor));
}
So when the user types, the long list of items in the DropDown gets filtered accordingly. Everything's fine so far.
The size/height of the ListBox/DropDown doesn't change once its open. It does change accordingly when die DropDown opens. Meaning if there are only 2 items the DropDown is only 2 items high.
My issue
When the user enters a text where just one item fits the DropDown is only 1 item in height (this happens with some user workflows, i.e. user manually closes & opens the DropDown).
Now when the user now changes the text so multiple items are fitting the height stays 1 item and it looks weird as even the scrollbar doesn't look correct as it doesn't fit.
What I've tried so far
I cannot use CComboBox::SetMinVisibleItems (or the MSG behind it) as it only works in a Unicode CharacterSet (which I'm not able to change in this old application) and from WinVista onwards (app runs on WinXP).
The only other option is to close and open the DropDown so it gets redrawn correctly with the correct height (see // This makes the DropDown "flicker" in Source Code above).
Now going with option 2 I don't want the user to see the closing and opening ("flicker") of the DropDown after every key he is pressing.
To prevent this I've tried a couple of solutions I've found but none works in my case with a ComboBox-DropDown. Here's a list of methods I've put just before the ShowDropDown(false) and just after the ShowDropDown(true).
EnableWindow(false/true);
(Un)LockWindowUpdate();
SendMessage(WM_SETREDRAW, FALSE/TRUE, 0)
With all three calls I still see the DropDown closing/opening.
Do you guys have other ideas how I can prevent this flicker?
Thanks in advance
Soko
This is an XY question.
It should be easier to use the following approach to adjust the height of the ComboBox
Use GetComboBoxInfo to get the handle of the list control.
Use OnChildNotify or ON_CONTROL_REFLECT and capture CBN_DROPDOWN.
In the handler of the message resize the window as needed Use SetWindowPos and just change the size.

Is there an event that fires from a C++ program when a control is about to lose focus?

I am trying to fix a validation bug in a MFC CEdit control. Currently, validation is performed in an OnChange event handler. But this does not work because it validates data before the user is finished entering it.
So, instead, I am trying to validate inside an OnKillFocus event handler. If validation fails, then I use GotoDlgCtrl() to return focus to the edit box that contained the invalid data. And when I call GotoDlgCtrl(), the kill focus event fires again, and I'm in an infinite loop.
So, I'd like to handle an event that fires just before the control loses focus, so that if I determine that the data is invalid, I can stop focus from leaving and instead get the user to enter correct data.
I know I've seen a Validating event someplace, but that was probably in the .Net world. But it offers the functionality I'm looking for.
Right-click the dialog resource and invoke Class Wizard:
Next, go to the Virtual Functions tab, locate PreTranslateMessage and add it:
Then, you can do something like this:
BOOL CTestDlgDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_CHAR)
{
CWnd *pControl = GetDlgItem(IDC_EDIT1);
if (pControl->GetSafeHwnd() == pMsg->hwnd)
{
if (pMsg->wParam == _TINT('!'))
{
AfxMessageBox(_T("Not allowed ! character"));
return TRUE;
}
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
Normally the control is a member variable of type CEdit so you could compare against m_edit.GetSafeHwnd() instead.
Results:
Update
I realise you stated:
But this does not work because it validates data before the user is finished entering it.
You could use WM_KEYUP instead:
BOOL CTestDlgDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYUP)
{
CWnd *pControl = GetDlgItem(IDC_EDIT1);
if (pControl->GetSafeHwnd() == pMsg->hwnd)
{
CString str;
GetDlgItemText(IDC_EDIT1, str);
if (str.Find(_T("!")) >= 0)
{
AfxMessageBox(_T("Not allowed ! character"));
return TRUE;
}
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
That is give you a chance to validate after the display has been updated.
An alternative it to customize your DoDataExchange handler. In there you can validate as required. Then in your code you simple test the return value of UpdataData(TRUE) for FALSE.

CMFCToolBarComboBoxEdit handle delete button

CMFCToolBarComboBoxEdit handles the BackSpace button but it doesn't handle the delete button.
Is there any way to handle the delete button except PreTranslateMessage?
if yes, what is this way?
if no, then how can I get the current cursor position in the control and how to remove specific char using its index so I can remove the char which on the right of the cursor if nothing is selected?
Thanks in advance.
Yes use, PreTranslateMessage. If you detected the sequence that should be handled, call:
if (..) // Check if you have a message that should
// be passed to the window directly
{
TranslateMessage(pMsg);
DispatchMessage(pMsg);
return TRUE;
}
You can do this always in PreTranslateMessage, when you detect that the message should be handled by the default control, and should not be handled by any other control in the chain of windows that execute PreTranslateMessage. This is also helpful if you have a combo box open and want the Page Down/Up handled internally and not by the view or any accelerator.
I've handled the delete key in the PreTranslateMessage as follows:
BOOL PreTranslateMessage(MSG* pMsg)
{
if(WM_KEYDOWN == pMsg->message && VK_DELETE == pMsg->wParam)
{
int iStartChar = -1, iEndChar = -1;
GetSel(iStartChar, iEndChar);
if(iStartChar != iEndChar)
Clear(); //clear the selected text
else
{
SetSel(iStartChar, iStartChar + 1);
Clear();
}
}
return CMFCToolBarComboBoxEdit::PreTranslateMessage(pMsg);
}

Delete Key is not triggering KeyUp & KeyDown Event

I am currently dealing with a multi-form application and am having issue registering a del key press, the application that requires the del key is a form with a frame on it with objects painted on it that can be selected, upon pressing the del key the selected objects are to be deleted via a deleteObjects method. The code I am currently using is as follows
void __fastcall TF_Image::KeyUpKbd( WORD &Key )
{
if(Key == VK_DELETE || Key == VK_DKEY)
{
deleteSelectedObjects();
}
}
(Note: There are other paramenters in the function call but they aren't used)
TF_Image inherits from TFrame
I have tried mapping other keys other than the del key ie the D key and have found that the method is called with no problem. I have discovered that when pressing (physically) the del key the methods associated with KeyUp & KeyDown are not called.
Edit: So i've attempted to add the DeleteSelectedOb() method to my WndProc method without much luck either.
void __fastcall TF_ImgBrowserOA::WndProc(TMessage &Message)
{
if (Message.Msg == WM_KEYDOWN)
{
if (Message.WParam == VK_DELETE)
{
F_Image->DeleteSelectedOb();
}
}
//code that manages window resize
TForm::WndProc(Message);
}
The WndProc method doent appear to respond to keystrokes
So after cleaning up some code in some other modules and removing unneccessary menu's I decided to go back and look at this section again after I found a similar piece of code implementing a similar function, I couldn't see much difference between them and so I recompiled and attempted to run my Delete function from the KeyDown event and for some reason it just worked, I suspect it came down to an issue of another element holding focus in the Application. As a precaution I also called a SetFocus() to the frame in which I required this code to operate in. Its still a mystery to me why this didn't work intially though.
Here is a snippet for my TRichEdit control (Script_Edit).
TWndMethod *PrevWndProc = Script_Edit->WindowProc;
Script_Edit->WindowProc = MyWndProc;
void __fastcall My_Form::MyWndProc(TMessage &Message) {
switch (Message.Msg) {
case WM_KEYDOWN: {
// Check for DELETE and BACKSPACE keys
if( Message.WParam == VK_BACK ||
Message.WParam == VK_DELETE
) {
// Do whatever you need
}
break;
default:
// call default handler if not processed
PrevWndProc(Message);
}
}
You can't get much closer to the message core than this with VCL...

How does MFC's "Update Command UI" system work?

I'd like to know more about how this system works, specifically when and how the framework actually decides to update a UI element.
My application has a 'tools' system where a single tool can be active at a time. I used the "ON_UPDATE_COMMAND_UI" message to 'check' the tool's icon/button in the UI, which affected both the application menu and the toolbars. Anyway, this was all working great until some point in the last couple of days, when the toolbar icons stopped getting highlighted properly.
I investigated a little and found that the update command was only being received when the icon was actually clicked. What's strange is this is only affecting the toolbars, not the menu, which is still working fine. Even when the buttons in the menu are updated the toolbar icon stays the same.
Obviously I've done something to break it - any ideas?
EDIT:
Never mind. I'd overwritten the Application's OnIdle() method and hadn't called the original base class method - that is, CWinApp::OnIdle() - which I guess is where the update gets called most of the time. This code snippet from https://msdn.microsoft.com/en-us/library/3e077sxt.aspx illustrates:
BOOL CMyApp::OnIdle(LONG lCount)
{
// CWinApp's original method is involved in the update message handling!
// Removing this call will break things
BOOL bMore = CWinApp::OnIdle(lCount);
if (lCount == 0)
{
TRACE(_T("App idle for short period of time\n"));
bMore = TRUE;
}
// ... do work
return bMore;
// return TRUE as long as there are any more idle tasks
}
Here's a good article that kinda explains how to do it. Don't use his code example with WM_KICKIDLE though, instead scroll down to the comments section. There are two code samples that explain how to do it better. I quote:
//Override WM_INITMENUPOPUP
void CDialog::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CDialog::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
// TODO: Add your message handler code here
if(pPopupMenu &&
!bSysMenu)
{
CCmdUI CmdUI;
CmdUI.m_nIndexMax = pPopupMenu->GetMenuItemCount();
for(UINT i = 0; i < CmdUI.m_nIndexMax; i++)
{
CmdUI.m_nIndex = i;
CmdUI.m_nID = pPopupMenu->GetMenuItemID(i);
CmdUI.m_pMenu = pPopupMenu;
// There are two options:
// Option 1. All handlers are in dialog
CmdUI.DoUpdate(this, FALSE);
// Option 2. There are handlers in dialog and controls
/*
CmdUI.DoUpdate( this, FALSE );
// If dialog handler doesn't change state route update
// request to child controls. The last DoUpdate will
// disable menu item with no handler
if( FALSE == CmdUI.m_bEnableChanged )
CmdUI.DoUpdate( m_pControl_1, FALSE );
...
if( FALSE == CmdUI.m_bEnableChanged )
CmdUI.DoUpdate( m_pControl_Last, TRUE );
*/
}
}
}
See if this helps - http://msdn.microsoft.com/en-us/library/essk9ab2(v=vs.80).aspx