Combobox selection loading previous selection - c++

I have a combobox containing "1,2,3,4,5". When I make a selection in my combobox, the previous selection is loaded. For example, if the value in the combobox is "1" and I select "2", "1" is loaded into my variable nApplication and "2" will be displayed on my combobox. If i want to load "2", I need to change the selection one more time. Example change from "2" to "3". How do I make it load the number I select? My code looks like this.
void CAppDlg::DoDataExchange(CDataExchange* pDX)
{
DDX_Text(pDX, IDC_Application, nApplication);
.
.
void CAppDlg::OnCbnSelchangeapplication()
{
UpdateData(TRUE);
int j = nApplication-1;
for(int i=0; i<MAX_LABEL; i++){
bool bShow = i < arrLabel[j];
((CButton *)GetDlgItem(IDC_Label1+i))->ShowWindow(bShow);
}
}
Any help would be appreciated. Thanks.

As explained in my comments, I suspect that the contents of the combo-box are updated after the CBN_SELCHANGE message is processed, and that's what causing you problems. Therefore a workaround could be post a custom (application-defined) message, to be processed later.
The type of the function must be afx_msg LRESULT (CWnd::*)(WPARAM, LPARAM) (check the documentation).
So, in your class declaration, add:
public:
afx_msg LRESULT OnComboSelChanged(WPARAM wParam, LPARAM lParam);
In your message-map:
ON_MESSAGE(WM_APP+100, OnComboSelChanged)
And finally the implementation:
void CAppDlg::OnCbnSelchangeapplication()
{
// Place a message in the message queue
PostMessage(WM_APP+100);
}
LRESULT CAppDlg::OnComboSelChanged(WPARAM wParam, LPARAM lParam)
{
UpdateData(TRUE);
int j = nApplication-1;
// I have simplified your code a little
for(int i=0; i<MAX_LABEL; i++)
GetDlgItem(IDC_Label1+i)->ShowWindow(i < arrLabel[j]);
return 0L;
}
However, all this, ie calling UpdateData(TRUE);, get the string there, convert it to int and take actions depending on its value, may just be an overkill, esp if the combo-box contains just a contiguous number-range (1..5 in your case). UpdateData(TRUE); transfers the values in ALL your controls to variables, and this may just not be needed. It seems to be a mechanism controlling the appearance of other controls, it's not really "data-entry" (I guess the combod-box is a non-editable drop-down list). So, you could just not use DDX/DDV at all, instead check the selected item yourself. It's a zero-based index. Here is the code:
void CAppDlg::OnCbnSelchangeapplication()
{
// Get selected item
int j = ((CComboBox *)GetDlgItem(DC_Application))->GetCurSel();
if (j == CB_ERR) return;
for(int i=0; i<MAX_LABEL; i++)
GetDlgItem(IDC_Label1+i)->ShowWindow(i < arrLabel[j]);
}
Please note that the code fragments above have not been tested, it's just what I would do.

Related

C++ MFC CListCtrl - How to sort item upon startup (without any UI interaction)?

I have a list of item to be loaded in a MFC window with CListCtrl in it.
Upon OnInitDialog(), I will perform
m_List.InsertItem(&item);
where this item contain data such as: filename, last date modified, comment.
The current output that I have now is that it will sort it with last date modified when I open this dialog, then I would need to manually click on the list header to trigger an event to sort the data according to filename with functions such as below:
ON_NOTIFY(LVN_COLUMNCLICK, IDC_FILELIST, OnColumnclickFilelist)
int CALLBACK CDlg::AlphaNumericSorting(LPARAM lParam1, LPARAM lParam2, LPARAM ParameterSort)
{
std::wstring wv1(((CLVData*)lParam1)->strFileName);
std::wstring wv2(((CLVData*)lParam2)->strFileName);
return StrCmpLogicalW(wv1.c_str(), wv2.c_str()) < 0;
}
void Dlg::OnColumnclickFilelist(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
m_List.SortItems(AlphaNumericSorting, pNMListView->iSubItem);
*pResult = 0;
}
Now I'm trying to make it sort automatically upon OnInitDialog(), is there a correct way to do so? From what I searched so far, all samples I got are those with event handling.

CBN_SELCHANGE in Dynamically created ComboBoxes

How to get the CBN_SELCHANGE in dynamically created CComboBoxes??.. I used a array of CComboBoxes.
MyCComboBox * p_ComboBoxes = new MyCComboBox[numberOcComboBoxes]; //numberOcComboBoxes determined at rumtime
In the message Map of MyCComboBox
BEGIN_MESSAGE_MAP(MyCComboBox , CComboBox)
ON_CONTROL_REFLECT(CBN_SELCHANGE, &CTestDlg::OnCbnSelchange)
END_MESSAGE_MAP()
void CTestDlg::OnCbnSelchange()
{
this->GetDlgCtrlID(); // Get The ComboBox ID;
CString sText;
p_ComboBoxes[0].GetLBText(p_ComboBoxes[0].GetCurSel() , sText); // I can't access like this
}
// Initialization of the p_ComboBoxes Array..
for (int i = 0 ;i < iNumber ; i++)
{
p_ComboBoxes[i].Create(WS_CHILD|WS_VISIBLE|WS_VSCROLL|CBS_DROPDOWN,
CRect(10 + MY_PIC_ADDITIONAL_WIDTH,iItemDrawHeight,10 + MY_PIC_ADDITIONAL_WIDTH +MY_PIC_PROPERTY_WIDTH
,iItemDrawHeight +MY_PIC_HEIGHT), this, pImageControlPropertyID[i]);
iItemDrawHeight += MY_PIC_PROPERTY_ADDITIONAL_HEIGHT;
}
I can't access the selected text it gives me "Access violation reading location 0x00000020" error..
As already mentioned, ON_CONTROL_REFLECT is for handling the message in the control class, not in the parent dialog class. You should be implementing your OnCbnSelchange() handler in the MyCComboBox class and adding an ON_CONTROL_REFLECT entry in the MyCComboBox message map.
If you really want to handle the message in the parent dialog, you should use the ON_CONTROL macro - one entry for each child combo each with a separate handler so you can tell which one sent the message.
You could also override OnCommand(WPARAM wParam, LPARAM lParam)
in the parent dialog window. If HIWORD(wParam) == CBN_SELCHANGE, then compare
LOWORD(wParam) against the id's of your controls.
BOOL CMyDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
if(HIWORD(wParam) == CBN_SELCHANGE) { //A combo box selection changed
int ID = LOWORD(wParam); //The ID of the corresponding ComboBox
//Perform additional handling...
}
return CDialog::OnCommand(wParam, lParam);
}

CTabCtrl SetItemRect?

I want to change the size of the tabs.
We have added a closing cross to our tabs, but it conflicts spaciously with the text of the tab.
So far I have realized the following:
GetItemRect(int i, RECT* rc) gives me the rect. What I really would like is a SetItemRect.
SetItem cannot be used as the item does not contain its size. It is calculated based upon the contents I give it.
I could add a space char in the end of the string, but that's just against the natural order of things. I will not tweak pixels with CStrings.
SetSize is supposed to set the size of a tab (all tabs?). But I cannot find out where to put it that does not trigger a redraw, which sparks an infinite loop if I put it with the WM_PAINT case.
This is where I custom draw the contents of the tab, but I cannot resize them here:
LRESULT CSkinnedTabCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_PAINT: {
...
CPaintDC dc(this);
INT nCount = GetItemCount();
for (INT i = 0; i < nCount; i++) {
CRect rc;
GetItemRect(i, rc);
DrawItem(dc, i, rc);
}
return TRUE;
}
Where do I set the size of the tabs, and how?
IIRC you need to overwrite WM_NCCALCSIZE message.

How to avoid EN_CHANGE notifications when sending WM_SETTEXT?

I have a CEdit derived control that displays the string "N/A" when the undelying data is null. I recently added code to empty the control(SetWindowText("");) when it gains focus and set if back to "N/A"(SetWindowText("N/A")) when the focus is lost if the user left the control empty.
The only problem is that setting the window text to "" or "N/A" triggers EN_CHANGE, so my dialog thinks that the data has changed.
How can I avoid EN_CHANGE from being fired when calling SetWindowText (WM_SETTEXT)?
NOTES
-I know I can set the edit control to Multiline=TRUE but that's not accpectable for me.
-My application is MBCS so I can't use SetCueBanner
-I want an elegant solution. Setting the parent window to NULL temporarily is not an elegant solution.
EDIT:
-I want the solution to be in my custom control, not in each dialog
Thanks
The way I've done it before (last time, like 20 minutes ago; in fact I was thinking about asking the same question), is by setting a flag. When I'm about to set the text programatically, I set the flag, and I check it in the EN_CHANGE handler:
void CMyDialog::MyFunction()
{
setEditTextProgramatically = true;
c_Edit.SetWindowText(_T("Whatever"));
setEditTextProgramatically = false;
}
void CMyDialog::OnEnChangeEdit()
{
if (!setEditTextProgramatically)
{
// Do whatever you need to do
}
}
I know it's not the most elegant solution, but it works, at least for me.
I've always wondered why MFC doesn't provide a way to distinguish user input from changes from code, but that's the way it is.
I finally found a suitable solution to my problem.
First, I added a flag to my derived control's header file and I initialized it to false in the constructor
bool m_bNoEnChange;
I overrode the OnChildNotify in my derived control's header file and in the implementation, I checked for the WM_COMMAND message with the EN_CHANGE parameter. I then returned TRUE to prevent the message from being sent to the parent(dialog/page)
virtual BOOL OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult);
BOOL CADEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
{
if(message == WM_COMMAND && HIWORD(wParam) == EN_CHANGE)
{
//If the flag is set, don't send the message to the parent window
if(m_bNoEnChange)
return TRUE;
}
return CEdit::OnChildNotify(message, wParam, lParam, pLResult);
}
Finally, when the control gains and loses focus, I wrapped the problematic SetWindowText with my flag
m_bNoEnChange = true;
SetWindowText(_T(""));
m_bNoEnChange = false;
This solution is the best in my case because I don't have to modify each dialog.
You could disable (EnableWindow(FALSE) or send WM_ENABLE with param FALSE) the control prior to sending WM_SETTEXT then enabling it afterwards. That should prevent the EN_CHANGE
There is probably some more elegant method :p
The below code uses a C++ 11 feature, but that can easily be changed.
HEADER
// CEditOptionalNotify.h
//
// CEdit derived class allowing the control's text value to be
// set without (optionally) causing EN_CHANGE processing.
//
#pragma once
class CEditOptionalNotify : public CEdit
{
//DECLARE_DYNAMIC(CEditOptionalNotify)
// Enable use of RUNTIME_CLASS macro and CObject::IsKindOf()
public:
CEditOptionalNotify();
virtual ~CEditOptionalNotify();
enum class PerformOnChangeProcessing { No, Yes };
void vSetText(const TCHAR* pText, PerformOnChangeProcessing e);
protected:
afx_msg BOOL bConsiderEnChangeAsHandled();
bool m_bChangeNotificationsEnabled;
DECLARE_MESSAGE_MAP()
};
IMPLEMENTATION
// EditOptionalNotify.cpp : implementation file
//
#include "stdafx.h"
#include <EditOptionalNotify.h>
//IMPLEMENT_DYNAMIC(CEditOptionalNotify, CEdit)
CEditOptionalNotify::CEditOptionalNotify() :
m_bChangeNotificationsEnabled(true)
{
}
CEditOptionalNotify::~CEditOptionalNotify()
{
}
BEGIN_MESSAGE_MAP(CEditOptionalNotify, CEdit)
ON_CONTROL_REFLECT_EX(EN_CHANGE, bConsiderEnChangeAsHandled)
END_MESSAGE_MAP()
BOOL CEditOptionalNotify::bConsiderEnChangeAsHandled()
{
return (m_bChangeNotificationsEnabled ? FALSE : TRUE);
}
void CEditOptionalNotify::vSetText(const TCHAR* pText, PerformOnChangeProcessing e)
{
bool bChangeNotificationsDesired = (PerformOnChangeProcessing::No == e ? false : true);
if (bChangeNotificationsDesired != m_bChangeNotificationsEnabled)
{
m_bChangeNotificationsEnabled = bChangeNotificationsDesired;
CEdit::SetWindowText(pText);
m_bChangeNotificationsEnabled = (bChangeNotificationsDesired ? false : true);
}
else
CEdit::SetWindowText(pText);
}
LRESULT CMainDlg::OnEnUpdateEditID(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
//using static variable
static bool isCodeChangeText = false;
if(isCodeChangeText)
return 0;
……//Deal Window Text
if(old == new)
return 0;
int nSel = m_editPID.GetSel();//record cursor pos
isCodeChangeText = true;
m_editID.SetWindowText(new);
m_editID.SetSel(nSel);
isCodeChangeText = false;
return 0;
}
In case somebody else finds this discussion...
As Steven wrote UpdateData does not cause an EN_CHANGE being sent.
Under the hood MFC calls AfxSetWindowText with which one can specify one hwnd.

Accessing multiple Editboxes in MFC

I am writing a MFC program in which I have a a lot of Editboxes and I want to get all their text values and put them in a container. How can I achieve this without writing a line for each ID. I'm using this code for each ID:
CEdit *edit;
edit = reinterpret_cast<CEdit *>(GetDlgItem(IDC_NAME1));
But if I use that method I would have to write it 45 times. That doesn't seem right.
Is there a way of getting all the Editboxes in a container so I can use them that way or something like that?
You can certainly create an array (or other container) or pointers to CEdit: CEdit edits[45]; If the values of IDC_NAME1 through IDC_NAME45 are contiguous, you can just do something like:
for (int i=0; i<45; i++)
names[i] = reinterpret_cast<CEdit *>(GetDlgItem(IDC_NAME1 + i));
If those identifiers may not be contiguous, then you can put them in an array, and just index into that array as needed.
One caution: unless they're something like a grid of otherwise nearly identical edit controls, 45 on a screen may well be a bit much. If they are like a grid, you might want to look at one of the many available grid controls instead.
You do not have to use controls IDs.
Use EnumChildWindows and get test only for edit controls. Snippet follows.
Add following in dialog’s header:
afx_msg LRESULT OnFoundEdit(WPARAM wParam, LPARAM lParam);
And this to cpp:
#define WM_U_FOUND_EDIT WM_APP + 0x100
BEGIN_MESSAGE_MAP(CEditCtrlFishingDlg, CDialog)
ON_MESSAGE(WM_U_FOUND_EDIT, OnFoundEdit)
.
.
.
.
END_MESSAGE_MAP()
Write this line in the place you want to start edit text collection:
EnumChildWindows(m_hWnd, EnumChildProc, (LPARAM)m_hWnd);
Enum child procedure:
BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
CString csBuffer;
LPTSTR pBuf = csBuffer.GetBufferSetLength(MAX_PATH);
GetClassName(hwnd, pBuf, MAX_PATH);
csBuffer.ReleaseBuffer();
if(!csBuffer.CompareNoCase(_T("edit")))
{
SendMessage((HWND)lParam, WM_U_FOUND_EDIT, 0, (LPARAM)hwnd);
}
return TRUE;
}
and the handler:
LRESULT YourDlg::OnFoundEdit(WPARAM wParam, LPARAM lParam)
{
CWnd *pWnd = FromHandle((HWND)lParam);
CString csTxt;
pWnd->GetWindowText(csTxt);
// do what you need with text here
return 0;
}