How to catch the click event of checkbox which is in a listctrl cell? - c++

Insert checkbox column on a listctrl
I made a list with a checkbox column consulting the answer above.
Now my superior asks me to disable the OK button at first, enable it when at least there is one line is checked.
I looked up seems there is easy way to catch the click event when a checkbox is in a listctrl.

Add LVN_ITEMCHANGED to the message map. This will notify the dialog when changes are made to the list item:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, OnItemChanged)
...
END_MESSAGE_MAP()
Next, handle the message and respond each time a list item is checked or unchecked. Then you have to go through all the items in the list box and use CListCtrl::GetCheck. Example:
void CMyDialog::OnItemChanged(NMHDR* pNMHDR, LRESULT*)
{
NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
if(pNMListView->uChanged & LVIF_STATE)
{
if(pNMListView->uNewState & LVIS_STATEIMAGEMASK && pNMListView->iItem >= 0)
{
BOOL checked_once = FALSE;
for(int i = 0; i < m_list.GetItemCount(); i++)
if(m_list.GetCheck(i))
checked_once = TRUE;
GetDlgItem(IDOK)->EnableWindow(checked_once);
}
}
}
You can add GetDlgItem(IDOK)->EnableWindow(FALSE); in OnInitDialog so that the OK button is initially disabled.
Side note, your dialog is using the old style look. See this link on using the modern style UI:
Upgraded MFC application still looks old

Related

Closing Popup and setting button label

I'm writing a C++ wxWidgets calculator application. I want to compress trigonometric function buttons into a single one to save on space, using what's basically a split button. If you left click on it, the current option is used. If you right click, a popup menu is opened, which contains all the buttons; when you click on one of them, it is used and the big button changes.
I've been suggested to use wxComboBox and other stuff for this job, but I preferred using wxPopupTransientWindow because this way I can display the buttons in a grid, making everything - in my opinion - neater.
Problem is, when I choose an option from the menu, the main button's ID changes (because when I reopen the menu the previously clicked button is light up as its ID and the big button's ID match), but the label does not. Furthermore, the popup is supposed to close itself when you left click on one of the buttons, but it does not.
This is the code for the custom button in the popup which is supposed to do all that stuff:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetParent()->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
this->GetParent()->Close(true);
}
}
This is the code for the custom button in the main frame which opens up the popup (temporary setup just to test if the whole thing is working):
void ikeButton::rightClick(wxMouseEvent& evt) // CREA PANNELLO ESTENSIONE
{
if (flags & EXPANDABLE)
{
std::vector<expandMenuInfo> buttons;
buttons.push_back(expandMenuInfo(L"sin", 3001));
buttons.push_back(expandMenuInfo(L"cos", 3002));
buttons.push_back(expandMenuInfo(L"tan", 3003));
buttons.push_back(expandMenuInfo(L"arcsin", 3004));
buttons.push_back(expandMenuInfo(L"arccos", 3005));
buttons.push_back(expandMenuInfo(L"arctan", 3006));
wxPoint p = this->GetScreenPosition();
size_t sz = this->GetSize().GetHeight() / 1.15;
expandMenu* menu = new expandMenu(this, buttons, sz, wxPoint(
p.x, p.y + this->GetSize().GetHeight() + 2));
menu->SetPosition(wxPoint(
menu->GetPosition().x - ((menu->GetSize().GetWidth() - this->GetSize().GetWidth()) / 2),
menu->GetPosition().y));
menu->Popup();
}
}
Let me know if I need to show more code.
This is probably a terrible way of doing this, but this is basically the first "serious" application I'm creating using the wxWidgets framework. Any help is appreciated.
when I choose an option from the menu, the main button's ID changes
(because when I reopen the menu the previously clicked button is light
up as its ID and the big button's ID match), but the label does not.
If you're creating the popup menu like in your previous post, you had a popup window with a panel as its child and the buttons were then children of the panel layed out with a sizer.
If that's still the case, this->GetParent() and should be the panel, this->GetParent()->GetParent() should be the popup. So this->GetParent()->GetParent()->GetParent() should be the trig function button (assuming you created the popup with the trig function button as the parent).
So I think the line wxWindow* mBtn = this->GetParent()->GetParent(); should be changed to wxWindow* mBtn = this->GetParent()->GetParent()->GetParent();.
Or slightly shorter wxWindow* mBtn = this->GetGrandParent()->GetParent();;
the popup is supposed to close itself when you left click on one of
the buttons, but it does not.
It looks like wxPopupTransientWindow has a special Dismiss method that is supposed to be used to close it.
So I think the line this->GetParent()->Close(true); should be changed to this->GetGrandParent()->Dismiss(); (Assuming as above that the buttons in the popup are children pf a panel).
Alternately, if you want a solution that will work regardless of the parentage of the controls in the popup window, you could have a utility function to find the popup ancestor which would look something like this:
wxPopupTransientWindow* FindPopup(wxWindow* w)
{
wxPopupTransientWindow* popup = NULL;
while ( w != NULL )
{
popup = wxDynamicCast(w, wxPopupTransientWindow);
if ( popup )
{
break;
}
w = w->GetParent();
}
return popup;
}
This uses the wxDynamicCast function which is slightly different from the c++ dynamic_cast expression. wxDynamicCast uses wxWidgets' RTTI system to check if the given pointer can be converted to the given type.
Then the mouseReleased method could use this utility function something like this:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxPopupTransientWindow* popup = FindPopup(this);
if (popup ) {
wxWindow* mBtn = popup->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
mBtn->Refresh();
popup->Dismiss();
}
}
}
I'm not sure why you're setting trig function button to have a new id, but I assume you have a reason.
To make the SetLabel method work in your custom button class, I think the easist thing is to call the SetLabel() method in the button's constructor. This will store the string passed to the constructor in the button's internal label member.
Based on other questions, I think the ikeButton constructor looks something like this:
ikeButton::ikeButton(wxFrame* parent, wxWindowID id, wxString text,...
{
...
this->text = text;
To store the label, you would need to change the line this->text = text; to
SetLabel(text);
And when you draw the button, I think the method you use looks like this:
void ikeButton::render(wxDC& dc)
{
...
dc.DrawText(text, ...);
}
You would need to change, the line dc.DrawText(text, ...); to
dc.DrawText(GetLabel(), ...);
Likewise, any other references to the button's text member should be changed to GetLabel() instead.
Finally, when you set the label in the expandButton::mouseReleased method, it might be a good idea to call button's Refresh() method to force the button to redraw itself. I added that my suggestion for the mouseReleased method above.

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.

Why double click event detecting on empty area of listBox in mfc

case 1 : I have a MFC dialog box having a LisBox.
I have added two items in listbox.
Whenever i am double clicking on empty area of list box i.e. not double clicking
on either of two item.
Double click is detecting on empty area of listbox.
case 2: When i created a small MFC test application with listbox. it iis detecting double click only on item, not on empty area.
I compared all properties of both cases but couldn't figure out what is the problem.
Anyone has idea what is going wrong in case 1.
I think it is abnormal process. I've tested your situation in VS2010. In my MFC test application sent LBN_DBLCLK when I double clicked on empty area. If you do not really want to know the reason this weired situation, you can just check whether double click event is occurred on empty area or not. I think it is better way for saving your time.
void CMfcDlgTestDlg::OnLbnDblclkList2()
{
// TODO: Add your control notification handler code here
CListBox* list = (CListBox*)(GetDlgItem(IDC_LIST2));
int cur_sel = list->GetCurSel();
if (cur_sel == -1)
{
return;
}
}
EDIT : FOR ANOTHER CASE
When one of list box item is already selected, how can it handle on ON_LBN_DBLCLK handler?
I think there will be some available methods for solving this, however I use below code and it can be useful way, also.
void CMfcDlgTestDlg::OnLbnDblclkList2()
{
// TODO: Add your control notification handler code here
CListBox* list = (CListBox*)(GetDlgItem(IDC_LIST2));
CPoint cursor;
cursor.x = GetCurrentMessage()->pt.x;
cursor.y = GetCurrentMessage()->pt.y;
list->ScreenToClient(&cursor);
BOOL is_outside = FALSE;
UINT item_index = list->ItemFromPoint(cursor, is_outside);
if(is_outside)
{
//mouse clicked on empty area
return ;
}
else
{
// do something with 'item_index'
}
}
I hope this will help you a little.

When deleting item in Combobox the secong one pop-up MFC

I have combo box and delete button. I want to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item.
I tried several methods with indexes but even one wont help me.
there is my code:
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
}
How I can to make next combo box item pop-up when delete button pressed and when last item deleted clean combo box selected item?
I found a solution:
void CL2HamsterDlg::OnBnClickedButtonDelete(){
if(Validate()){
if(IDYES == MessageBox(L"Delete save?",L"Delete", MB_YESNO|MB_ICONQUESTION)){
CString pFileName = L"Save\\"+str+".dat";
CFile::Remove(pFileName);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
lookforfile();
int nIndex = pComboBox->GetCurSel();
if (nIndex == CB_ERR)
pComboBox->SetCurSel(0);
else{
pComboBox->SetEditSel(0, -1);
pComboBox->Clear();
}
}
LoadSave(false);
}else
AfxMessageBox(L"Please select or write correct name!");
}
the function look for file refreshes index
void CL2HamsterDlg::lookforfile()
{
Value.GetWindowText(str);
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_SAVE);
pComboBox->ResetContent();
GetCurrentDirectory(MAX_PATH,curWorkingDir);
_tcscat_s(curWorkingDir, MAX_PATH, _T("\\Save\\*.dat"));
BOOL bWorking = finder.FindFile(curWorkingDir);
while (bWorking){
bWorking = finder.FindNextFile();
if (!finder.IsDots())
pComboBox->AddString(finder.GetFileTitle());
}
GetDlgItem(IDC_COMBO_SAVE)->SetWindowText(str);
}
so, in this case you do not need to use ResetContent(). Provided you already know the currently selected Item in the combobox (I think somewhere along the track you would have used the line int iSel = pComboBox->GetCurSel();) you could use this code IN PLACE OF YOUR pComboBox->ResetContent();:
pComboBox->DeleteString(iSel);
if(iSel < pComboBox->GetCount())
pComboBox->SetCurSel(iSel);
else if(iSel > 0)
pComboBox->SetCurSel(iSel-1);
However, I think this will not be necessary. I think the item will move by itself. So, forget about the code above, just use this:
pComboBox->DeleteString(pComboBox->GetCurSel())

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