treeview 3-state and 2-state checkboxes on different nodes - c++

I have to create a treeview control in a class that inherits from CTreeCtrl. After many tries, I have created 3-state checkboxes for every node. And here comes my question - I need some child nodes to have only 2 states (checked or unchecked). Any clue on how can I reach that goal? I tried to add an if statement to change the behavior like this:
else if (itemState == INDEXTOSTATEIMAGEMASK(3))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK);
And it’s working! Except when an element is "selected", then it’s like ignoring every behavior I did in the OnLButtonClick event.
Is there some other event I don’t know about? Is there an easier way to get a 2-state checkbox only on some nodes and not others?
This is all i use to control the tree
void CSelDepTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
CTreeCtrl::OnLButtonDown(nFlags, point);
UINT Flags = 0;
HTREEITEM hItem = HitTest(point, &Flags);
if ((NULL != hItem) && (TVHT_ONITEMSTATEICON & Flags))
{
auto itemText = GetItemText(hItem);
if (itemText.Left(1) == "C") {
if ((nFlags & (MK_SHIFT | MK_CONTROL)) || NULL == GetParentItem(hItem)) {
SetCheckChild(hItem, GetCheck(hItem));
}
}
if (itemText.Left(1) == "L") {
if ((nFlags & (MK_SHIFT | MK_CONTROL)) || NULL == GetParentItem(hItem)) {
SetCheckChild(hItem, GetCheck(hItem));
}
}
if (itemText.Left(1) == "S") {
auto itemState = GetItemState(hItem, TVIS_STATEIMAGEMASK);
if (itemState == INDEXTOSTATEIMAGEMASK(1))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(2), TVIS_STATEIMAGEMASK);
}
else if (itemState == INDEXTOSTATEIMAGEMASK(2))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK);
}
else if (itemState == INDEXTOSTATEIMAGEMASK(3))
{
SetItemState(hItem, INDEXTOSTATEIMAGEMASK(1), TVIS_STATEIMAGEMASK);
}
}
}
}

Related

Virtual list (Problems with parameter/pointer)

I am trying to convert my CListCtrl into a virtual list, but i dont know which parameter i have to use
// --- Virtual List ---
void CSpielebibliothekGUIDlg::OnGetdispinfoList(NMHDR* pNMHDR, LRESULT* pResult) // --- nullptr muss weg ---
{
LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem = &(pDispInfo)->item;
int itemid = pItem->iItem;
if (pItem->mask & LVIF_TEXT)
{
CString text;
// --- Welche Spalte ---
if (pItem->iSubItem == 0)
{
// --- Name ---
text = Spiele[itemid].m_Name;
}
if (pItem->iSubItem == 1)
{
//Text is slogan
text = Spiele[itemid].m_Plattform;
}
if (pItem->iSubItem == 2)
{
text = Spiele[itemid].m_Genre;
}
if (pItem->iSubItem == 3)
{
CString Release;
Release.Format(_T("%d"), Spiele[itemid].m_Erscheinungsjahr);
text = Release;
}
if (pItem->iSubItem == 4)
{
CString Preis;
Preis.Format(_T("%g"), Spiele[itemid].m_Preis);
text = Preis;
}
if (pItem->iSubItem == 5)
{
CString EAN;
EAN.Format(_T("%d"), Spiele[itemid].m_EAN);
text = EAN;
}
if (pItem->iSubItem == 6)
{
text = Spiele[itemid].Verwandschaft;
}
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
}
*pResult = 0;
}
this is my function call
OnGetdispinfoList(nullptr, nullptr);
of course the nullptr are not right, iam glad for any help.
You don't need to call message handlers, they will be called by system after you:
Set proper window styles (LVS_OWNERDATA)
Set item count
Add your OnGetdispinfoList to the message map

How to get menu ID from HMENU?

I'm trying to override WM_HELP message for my dialog window when a user presses F1 key. The window has several buttons that display context menus via TrackPopupMenu, as well as the main menu (on top.) So there's more than just one menu in this window.
I can trap menu message as such when processing WM_HELP, but I need to know which context menu sent this message:
BOOL CMyDialog::OnHelpInfo(HELPINFO* pHelpInfo)
{
if(pHelpInfo->iContextType == HELPINFO_MENUITEM)
{
HMENU hMenu = (HMENU)pHelpInfo->hItemHandle;
//How to get menu ID from HMENU?
}
//...
}
So I need to find a way to get menu resource ID from HMENU -- this one that was used to create it:
Any idea how to do it?
You'll have to "somewhat bruteforce" it. You can approach it from the top-down. Make a function like this:
BOOL IsMenuItemIDInMenu(UINT nMenuID, UINT nMenuItemID)
{
//Checks if 'nMenuItemID' belongs to 'nMenuID'
BOOL bRes = FALSE;
if(nMenuID &&
nMenuItemID)
{
HMENU hMenu = ::LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(nMenuID));
if(hMenu)
{
//Look for it
bRes = __searchForMenuItem(hMenu, nMenuItemID);
//Free menu
::DestroyMenu(hMenu);
}
}
return bRes;
}
BOOL __searchForMenuItem(HMENU hMenu, UINT nMenuItemID)
{
ASSERT(hMenu);
int nCnt = ::GetMenuItemCount(hMenu);
if(nCnt != -1)
{
for(int i = 0; i < nCnt; i++)
{
//Is it a submenu
HMENU hSubMenu = ::GetSubMenu(hMenu, i);
if(!hSubMenu)
{
UINT nID = ::GetMenuItemID(hMenu, i);
if(nID != -1 &&
nID == nMenuItemID)
{
//Found it
return TRUE;
}
}
else
{
//Process submenu
if(__searchForMenuItem(hSubMenu, nMenuItemID))
{
//Found it in submenu
return TRUE;
}
}
}
}
return FALSE;
}
And then when you get a menu ID that was highlighted when F1 was pressed, see which of your menus it belongs to:
if(pHelpInfo->iContextType == HELPINFO_MENUITEM)
{
if(IsMenuItemIDInMenu(IDR_MENU_1, pHelpInfo->iCtrlId))
{
}
else if(IsMenuItemIDInMenu(IDR_MENU_2, pHelpInfo->iCtrlId))
{
}
}

I want to display item lastly checked in tree control mfc

I tried this
void GetCheckedItems(const CTreeCtrl& tree, CArray<HTREEITEM> *checkedItems, HTREEITEM startItem = NULL)
{
if (startItem == NULL)
startItem = tree.GetRootItem();`
for (HTREEITEM item = startItem; item != NULL; item = tree.GetNextItem(item, TVGN_NEXT))
{
// figure out if this item is checked or not
UINT state = (tree.GetItemState(item, TVIS_STATEIMAGEMASK) >> 12) & 15;
if (state == 2)
checkedItems->Add(item);
// deal with children if present
HTREEITEM child = tree.GetNextItem(item, TVGN_CHILD);
if (child != NULL)
GetCheckedItems(tree, checkedItems, child);
}
}
now I have an array contains checked values but how do i know which i checked last time.
Just use a handler to TVN_ITEMCHANGED. It is called whenever an item state is changing.
You need to check the state flags that are changed. You can detect changes to the TVIS_STATEIMAGEMASK.
CString m;
HTREEITEM selItem;
selItem=m_treeRel.GetSelectedItem();
UINT uFlags = 0;
CPoint pt(0, 0);
GetCursorPos(&pt);
m_treeRel.ScreenToClient(&pt);
HTREEITEM hItem = m_treeRel.HitTest(pt, &uFlags);
if(NULL != hItem && (TVHT_ONITEM & uFlags))
{
/*if(selItem == hItem)
{
m=_T("Selected Item....");
}
else */
if(!m_treeRel.GetCheck(hItem))
{
m=m_treeRel.GetItemText(hItem);
//m_treeRel.SetCheck(hItem,true);
}
else
{
//m=_T("ERROR");
//m_treeRel.SetCheck(hItem,false);
}
}
ShowMessage(m);
*pResult = 0;
I Used This to find out which item was checked last time....!

When code accidently starts to work without a real change

I have run into similar situations before when my code is not working properly and on my quest to solve the problem I made some changes than comment those changes and boom it fixed the problem. All of sudden just a an 'edit' in the file somewhere has fixed the issue but there was no real change in the code. I ran into similar problem again and I am just wondering how does this happen?
void CDlgResizeHelper::Init(HWND hparent)
{
m_hParent = hparent;
m_CtrlsList.clear();
if (::IsWindow(m_hParent))
{
::GetWindowRect(m_hParent, m_OrigParentSize);
// get all child windows and store their original sizes and positions
HWND hCtrl = ::GetTopWindow(m_hParent);
while (hCtrl)
{
CtrlSize cs;
cs.hctrl = hCtrl;
::GetWindowRect(hCtrl, cs.orig_size);
::ScreenToClient(m_hParent, &cs.orig_size.TopLeft());
::ScreenToClient(m_hParent, &cs.orig_size.BottomRight());
// CString msg;
// msg.Format("Old Size: %d %d %d %d\r\n", cs.orig_size.left, cs.orig_size.top, cs.orig_size.right, cs.orig_size.bottom );
// TRACE( msg );
m_CtrlsList.push_back(cs);
hCtrl = ::GetNextWindow(hCtrl, GW_HWNDNEXT);
}
}
}
This class/function resizes controls based on the dialog size. It was working in debug version but the same code doesn't work (=resize properly) in release version. I made changes and added the three lines in the loop above for TRACE function. It starts to work properly in release version as well. Than I commented these lines, it still works in release build. I removed them and it doesn't work in release build anymore. I have to have these lines just commented present for release build to do the right thing. How can this be justified? What could really cause this 'edit' of file which is really no change in the real code fix the problem?
I also want to add that I have tried 'edit' or commented new code else where in the file that doesn't necessarily fixes the problem. I only need to have the commented code in the above function that would fix it.
Update Here is the complete class. I must say this class is available free somewhere on web and I am not the original author.
Init is called from the OnInitDialog of the dialog which needs resizing. It just stores the coordinates of all controls.
OnSize() actually does the resizing.
CDlgResizeHelper::CDlgResizeHelper()
{
}
void CDlgResizeHelper::Init(HWND hparent)
{
m_hParent = hparent;
m_CtrlsList.clear();
if (::IsWindow(m_hParent))
{
::GetWindowRect(m_hParent, m_OrigParentSize);
// get all child windows and store their original sizes and positions
HWND hCtrl = ::GetTopWindow(m_hParent);
while (hCtrl)
{
CtrlSize cs;
cs.hctrl = hCtrl;
::GetWindowRect(hCtrl, cs.orig_size);
::ScreenToClient(m_hParent, &cs.orig_size.TopLeft());
::ScreenToClient(m_hParent, &cs.orig_size.BottomRight());
CString msg;
msg.Format("Old Size: %d %d %d %d\r\n", cs.orig_size.left, cs.orig_size.top, cs.orig_size.right, cs.orig_size.bottom );
Sleep( 50 );
m_CtrlsList.push_back(cs);
hCtrl = ::GetNextWindow(hCtrl, GW_HWNDNEXT);
}
}
}
void CDlgResizeHelper::Remove(HWND hwnd)
{
CtrlList::iterator it;
for (it = m_CtrlsList.begin(); it != m_CtrlsList.end(); ++it)
{
if (it->hctrl == hwnd)
{
m_CtrlsList.erase(it);
return;
}
}
}
void CDlgResizeHelper::Update(HWND hwnd)
{
if (m_hParent && hwnd)
{
CtrlList::iterator it;
for (it = m_CtrlsList.begin(); it != m_CtrlsList.end(); ++it)
{
if (it->hctrl == hwnd)
{
::GetWindowRect(hwnd, &(it->orig_size));
::ScreenToClient(m_hParent, &(it->orig_size.TopLeft()));
::ScreenToClient(m_hParent, &(it->orig_size.BottomRight()));
}
}
}
}
void CDlgResizeHelper::Add(HWND hwnd)
{
if (m_hParent && hwnd)
{
CtrlSize cs;
cs.hctrl = hwnd;
::GetWindowRect(hwnd, cs.orig_size);
::ScreenToClient(m_hParent, &cs.orig_size.TopLeft());
::ScreenToClient(m_hParent, &cs.orig_size.BottomRight());
m_CtrlsList.push_back(cs);
}
}
void CDlgResizeHelper::OnSize()
{
if (::IsWindow(m_hParent))
{
CRect currparentsize;
::GetWindowRect(m_hParent, currparentsize);
double xratio = ((double) currparentsize.Width()) / m_OrigParentSize.Width();
double yratio = ((double) currparentsize.Height()) / m_OrigParentSize.Height();
HDWP hdwp;
hdwp = BeginDeferWindowPos((int)m_CtrlsList.size());
// resize child windows according to their fix attributes
CtrlList::const_iterator it;
for (it = m_CtrlsList.begin(); it != m_CtrlsList.end(); ++it)
{
CRect currctrlsize;
HorizFix horiz_fix = it->horiz_fix;
VertFix vert_fix = it->vert_fix;
if (horiz_fix & LEFT)
currctrlsize.left = it->orig_size.left;
else
currctrlsize.left = (LONG)( ((horiz_fix & WIDTH) && (horiz_fix & RIGHT)) ? (it->orig_size.left + currparentsize.Width() - m_OrigParentSize.Width()) : (it->orig_size.left * xratio));
if (horiz_fix & RIGHT)
currctrlsize.right = it->orig_size.right + currparentsize.Width() - m_OrigParentSize.Width();
else
currctrlsize.right = (LONG)((horiz_fix & WIDTH) ? (currctrlsize.left + it->orig_size.Width()) : (it->orig_size.right * xratio));
if (vert_fix & TOP)
currctrlsize.top = it->orig_size.top;
else
currctrlsize.top = (LONG)(((vert_fix & HEIGHT) && (vert_fix & BOTTOM)) ? (it->orig_size.top + currparentsize.Height() - m_OrigParentSize.Height()) : (it->orig_size.top * yratio));
if (vert_fix & BOTTOM)
currctrlsize.bottom = it->orig_size.bottom + currparentsize.Height() - m_OrigParentSize.Height();
else
currctrlsize.bottom = (LONG)((vert_fix & HEIGHT) ? (currctrlsize.top + it->orig_size.Height()) : (it->orig_size.bottom * yratio));
UINT flags = SWP_NOZORDER;
if (! it->resize)
flags |= SWP_NOSIZE;
hdwp = ::DeferWindowPos(hdwp, it->hctrl, NULL, currctrlsize.left, currctrlsize.top, (it->resize)? currctrlsize.Width() : 0, (it->resize)? currctrlsize.Height() : 0, flags);
if (hdwp == NULL)
return;
} //end for (it = m_CtrlsList.begin(); it != m_CtrlsList.end(); ++it)
EndDeferWindowPos(hdwp);
} //end if (::IsWindow(m_hParent))
}
BOOL CDlgResizeHelper::Fix(HWND a_hCtrl, HorizFix a_hFix, VertFix a_vFix, bool resize /= true/)
{
CtrlList::iterator it;
for (it = m_CtrlsList.begin(); it != m_CtrlsList.end(); ++it)
{
if (it->hctrl == a_hCtrl)
{
it->horiz_fix = a_hFix;
it->vert_fix = a_vFix;
it->resize = resize;
return TRUE;
}
}
return FALSE;
}
BOOL CDlgResizeHelper::Fix(int a_itemId, HorizFix a_hFix, VertFix a_vFix, bool resize /= true/)
{
return Fix(::GetDlgItem(m_hParent, a_itemId), a_hFix, a_vFix, resize);
}
BOOL CDlgResizeHelper::Fix(HorizFix a_hFix, VertFix a_vFix, bool resize /= true/)
{
CtrlList::iterator it;
for(it = m_CtrlsList.begin(); it!=m_CtrlsList.end(); ++it)
{
it->horiz_fix = a_hFix;
it->vert_fix = a_vFix;
it->resize = resize;
}
return TRUE;
}
UINT CDlgResizeHelper::Fix(LPCTSTR a_pszClassName, HorizFix a_hFix, VertFix a_vFix, bool resize /= true/)
{
char cn_buf[200];
memset(cn_buf, 0, 200);
UINT cnt = 0;
CtrlList::iterator it;
for (it = m_CtrlsList.begin(); it!= m_CtrlsList.end(); ++it)
{
::GetClassName(it->hctrl, cn_buf, sizeof(cn_buf));
if (strcmp(cn_buf, a_pszClassName) == 0)
{
cnt++;
it->horiz_fix = a_hFix;
it->vert_fix = a_vFix;
it->resize = resize;
}
}
return cnt;
}
This sounds like you have uninitialized variable used somewhere. It can get different values depending on build mode and it just so happens that it is assigned something harmless in debug.
Try running CppCheck application against your code. Won't hurt anyway.

Block characters in Edit Control using MFC (Example for only floats)

I'm trying to block certain types of characters from being inserted into my Edit Control by overwriting OnChar and OnKeydown. I'm trying to block more than one point '.' and anything that is not a number.
First I check if there is already a '.' in the Edit Control that has the focus by setting a variable defined in the dialog class to false:
void MyMainDialog::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
CWnd * eb1 = GetDlgItem(IDC_EDIT1); //Reference dimension 1 box;
CWnd * eb2 = GetDlgItem(IDC_EDIT2); //Reference dimension 2 box
CWnd * eb3 = GetDlgItem(IDC_EDIT3); //Reference dimension 3 box
CString temp;
CWnd * focusedHand = MyMainDialog::GetFocus(); //Reference edit box being focused
if(focusedHand == eb1)
{
eb1->GetWindowTextA(temp);
if(temp.Find('.') != -1)
checkPoint = true;
else
checkPoint = false;
}
else if(focusedHand == eb2)
{
eb2->GetWindowTextA(temp);
if(temp.Find('.') != -1)
checkPoint = true;
else
checkPoint = false;
}
else if(focusedHand == eb3)
{
eb3->GetWindowTextA(temp);
if(temp.Find('.') != -1)
checkPoint = true;
else
checkPoint = false;
}
CDialogEx::OnKeyDown(nChar, nRepCnt, nFlags);
}
At OnChar I'm checking the character being typed. If it is not a number of if it's a point but there was already a point, then I don't call the OnChar from CDialog:
void MyMainDialog::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar == '.' && checkPoint == false) //Is a point and there is no other point
{
CDialogEx::OnChar(nChar, nRepCnt, nFlags);
}
if((nChar < '0' || nChar > '9')) //Is not a number
{
//Show message to user
}
else //Is a number
{
CDialogEx::OnChar(nChar, nRepCnt, nFlags);
}
}
Well, my code is not working. It compiles and it doesn't crash when typing in the edit control, but it simply doesn't do anything. I'm wondering if the right way to overwrite it would be to prevent the call of CDialogEx::OnChar() or if I should make nChar = 0 so that the character displayed will be null. But on top of that the message that I'm trying to display at OnChar is also not displaying, meaning that MyMainDialog::OnChar() is not even being called. Should I overwrite CDialogEx::OnChar() instead?
Thanks for your attention
A much easier way is to handle your input with an OnChange event:
// do not add numbers; ascci numbers is 48 - 57
if ((m_strYOURCONTROL[m_strYOURCONTROL.GetLength() - 1]) > 47 &&
m_strYOURCONTROL[m_strYOURCONTROL.GetLength() - 1]) < 58)
{
m_strYOURCONTROL = m_strYOURCONTROL.Mid(0, m_strYOURCONTROL.GetLength() - 1);
}
This will not allow numbers. With this implementation you can handle easier the input to the editbox
I found the solution. The reason why the code doesn't seem to be making any effect in my application is because it is making effect only to MyMainDialog. When typing in the Edit Control, the OnChar() being called if from CEdit, so that is the one I have to intercept. You are not allowed to overwrite CEdit::OnChar(). The solution is to create a class that derives from CEdit and then intercept the OnChar() from that class. You you also have to use your class instead of CEdit for manipulating your Edit Control.
My code was then reformulated to this:
OnChar():
void CustomEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if(nChar == '.' && checkPoint == true) //Is a point and there is already a point
{
//Show message
}
else if(nChar == '.' && checkPoint == false) //Is a point but there is no point
{
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
if((nChar < '0' || nChar > '9') && nChar != 8) //Is not a number or backspace
{
//Show message
}
else //Valid
{
CEdit::OnChar(nChar, nRepCnt, nFlags);
}
}
OnKeyDown():
void CustomEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
CString temp;
this->GetWindowTextA(temp);
if(temp.Find('.') == -1)
checkPoint = false; //There is no point
else
checkPoint = true; //There is a point
CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}
Hope this helps anyone with a similar question. By the way, there are many classes and automated solutions in the internet, I did this manually for learning purposes.
Another solution is to handle WM_CHAR earlier, in PreTranslateMessage.