The case: I'd like to make shortcuts with numpad so users can use my application fast. I implemented this in PreTranslateMessage and this worked.
But the case is that I have an Edit Control where the user should enter some number. So at the time the user has focus on a Edit Control (CEdit), the shortcuts should be disabled.
To cover this, I added
CWnd* pControl;
pControl = this->GetFocus();
if(!(pControl->IsKindOf(RUNTIME_CLASS(CEdit)))){
But now whenever my application dialog loses focus, it closes (see video) and I get the following exeption:
This is the full code:
// Handles keypresses for fast acces of functions
BOOL COpenFilesDlg::PreTranslateMessage(MSG *pMsg){
CWnd* pControl;
pControl = this->GetFocus();
if(!(pControl->IsKindOf(RUNTIME_CLASS(CEdit)))){ //when this statement is commented the program doesn't crash
if(pMsg->message == WM_KEYDOWN)
{
if((pMsg->wParam == 0x31 || pMsg->wParam == VK_NUMPAD1))
someFunction();
else if((pMsg->wParam == 0x33 || pMsg->wParam == VK_NUMPAD3)){
someOtherFunction();
}
}
}
return CDialog::PreTranslateMessage(pMsg);
}
Now is my question: Why does my program crash when it is not in focus and how do I check if the focus is on a Edit Control in a proper way?
CWnd::GetFocus returns a pointer to the window that has the current focus, or NULL if there is no focus window.
pControl = this->GetFocus();
if ( pControl != NULL )
{
if(!(pControl->IsKindOf(RUNTIME_CLASS(CEdit))))
...
}
Another way is to compare pControl value with pointer to CEdit class member (or members) of the dialog class. For example, if CEdit m_edit is edit box class member, test:
if ( pControl == (CWnd*)&m_edit )
{
// focus is on m_edit control
}
Related
I am working on unit test with MFC. My software is a SDI.
I have 2 threads : the first one is the graphic thread and the second one a unit test procedure which reproduce a user behavior.
It is working well when there isn't modal dialog, I use SendMessage (to simulate click on button, or change text, etc.) which is blocking until the message has been treated so I haven't any sync issue.
But when the button opens a Modal CDialog I can't use SendMessage because the unit test thread is going to be blocked as long as the modal dialog is opened. So I use PostMessage and Sleep. And now my problem is to get the pointer of the current opened CDialog.
Here is the code of the unit test
bool UnitTestBoite()
{
// Here I am in unit test thread
CFrameWnd *pMainFrame = (CFrameWnd *)AfxGetMainWnd();
// Post Message to notify the button ID_INSERER_BOITE is clicked
pMainFrame->PostMessageW(WM_COMMAND, MAKELONG(ID_INSERER_BOITE, 0), 0);
// In the handler of ID_INSERER_BOITE
// there is something like CDialog dlg(pMainFrame, IDD_BASE_BOITE); dlg.DoModal();
Sleep(1000);
UINT myModalTemplateId = IDD_BASE_BOITE;
...
To get modal dialog pointer I tried :
CWnd* pChild = pMainFrame->GetWindow(GW_CHILD);
while (pChild != nullptr)
{
// Retrieve template ID
UINT nResourceId = GetWindowLong(pChild->GetSafeHwnd(), GWL_ID);
if (nResourceId == myModalTemplateId )
break;
pChild = pChild->GetWindow(GW_HWNDNEXT);
}
if (pChild == nullptr)
return false;
or
HWND hwndForeGround = ::GetForegroundWindow();
// Retrieve template ID
UINT nResourceId = GetWindowLong(hwndForeGround, GWL_ID);
if (nResourceId != myModalTemplateId )
return false;
or
CWnd *pModal = pMainFrame_->GetForegroundWindow();
// Retrieve template ID
UINT nResourceId = GetWindowLong(pModal->GetSafeHwnd(), GWL_ID);
if (nResourceId != myModalTemplateId )
return false;
None of these code snippet worked...
The last solution I have thought was to make all my CDialog classes inherits from a custom class and register all opened CDialog, but it is a bit invasive...
Is there an "elegant" means to do that ?
Thank for reading,
Lucas.
I am trying to detect in my application, if the Enter/Return buttons are pressed. My problem is that the LVN_KEYDOWN event (Indicates that a key has been pressed) does not detect the Enter/Return key.
I have seen similar questions for other languages, but can not find a solution for C++.
My event to read the key press is:
void ListOption::OnLvnKeydownList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLVKEYDOWN pLVKeyDow = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR);
// TODO: Add your control notification handler code here
if(pLVKeyDow->wVKey == VK_RETURN)
{
OnItemActivateList1(pNMHDR, pResult);
*pResult = 1;
}
*pResult = 0;
}
This code works for almost any key, execept for the Enter key.
My dialog has only one button, and it's "Default Button" value is FALSE. How is it possible to detect the keypress?
Update: My application uses modal dialogs.. It contains a CImageSheet that contains CImagePages(tabs). Here is an image to explain better (I have placed grey blocks to hide some private data).
When I press Enter, I wish to open a new dialog to change the option. Currently this is done with the LVN_ITEMCTIVATE event (when the user double clicks an item):
You can override PreTranslateMessage in the window which owns the ListView. In this case it seems to be a CPropertyPage.
BOOL CMyPropertyPage::PreTranslateMessage(MSG* pMsg)
{
//optional: you can handle keys only when ListView has focus
if (GetFocus() == &List)
if (pMsg->message == WM_KEYDOWN)
{
if (pMsg->wParam == VK_RETURN)
{
//return 1 to eat the message, or allow for default processing
int sel = List.GetNextItem(-1, LVNI_SELECTED);
if (sel >= 0)
{
MessageBox("VK_RETURN");
TRACE("ListView_GetNextItem %d\n", sel);
return 1;
}
else
TRACE("ListView_GetNextItem not-selected, %d\n", sel);
}
if (pMsg->wParam == VK_ESCAPE)
{
//do nothing!
}
}
return CPropertyPage::PreTranslateMessage(pMsg);
}
I am trying to change the focus of a CDialog controls from a CFormView by using PostMessage:
[CHelpView is inherited from CFormView.
And m_wndDlg is a CSampleDlg(inherited from CDialog) object]
void CHelpView::OnEnterbutton()
{
pSplitterFrame->m_dlgPane->m_wndDlg->PostMessage(WM_KEYDOWN, 'r', 2);
}
BOOL CSampleDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message >= WM_KEYFIRST && // for performance
pMsg->message <= WM_KEYLAST)
{
if (pMsg->wParam=='r' && pMsg->lParam==2){
NextDlgCtrl();
return TRUE;
}
}
}
The dialog receives the message but the NextDlgCtrl method doesn't change the focus. I realized that if I change the PreTranslateMessage method so that if the Return key is pressed, within the dialog, this NextDlgCtrl method properly changes the focus each time the user hit the return key(from the dialog). Yet this I couldn't achieve through another dialog.
Does anyone possibly know the reason behind it or any hints or workaround ?
Thanks.
EDIT:
Here's (part of)the log file for the dialog from SPY++.
<01128> 0016013E R WM_GETDLGCODE fuDlgCode:0000
<01129> 0016013E P WM_KEYDOWN nVirtKey:00726574 cRepeat:2 ScanCode:00 fExtended:0 fAltDown:0 fRepeat:0 fUp:0
<01130> 0016013E S WM_NEXTDLGCTL wCtlFocus:(null) (next control receives focus) fHandle:False
<01131> 0016013E R WM_NEXTDLGCTL
<01132> 0016013E S WM_GETDLGCODE
Your PreTranslateMessage handler, as written, will never work. This line,
if (pMsg->wParam=='r' && pMsg->wParam==2){
NextDlgCtrl();
return TRUE;
can never be true. How can pMsg->wParam equate to two values at the same time? I think you meant to check for lParam?
You are posting a lParam value of 2, but checking for '2' -- they are not the same!
Try
if (pMsg->wParam == 'r' && pMsg->wParam == 2)
EDIT: realised after rrirower's answer: it should of course be
if (pMsg->wParam == 'r' && pMsg->lParam == 2)
I have edit control in a dialog box which input is checked for validity.
I should indicate validity by changing the background color of an edit control if input is invalid, otherwise I should do nothing.
I am checking the input in EN_CHANGE handler and if input is invalid I store the handle of the edit control in a vector. In the end I call InvalidateRect( (HWND)lParam, NULL, TRUE ); so edit control can be repainted with proper color.
To repaint edit control I am handling WM_CTLCOLOREDIT like this:
case WM_CTLCOLOREDIT:
{
bool IsInvalid = false; // does this edit control hold invalid text ?
// vector InvalidInput contains handles of edit controls
// with invalid input, so we check if our window is stored there
for( vector<HWND>::size_type i = 0;
!IsInvalid && ( i < InvalidInput.size() ); i++ )
{
if( InvalidInput[i] == (HWND)lParam )
IsInvalid = true;
}
// if input is invalid change background color to light gray
if( IsInvalid )
{
// Needed SetBkMode for text background transparency
SetBkMode( (HDC)wParam, TRANSPARENT );
// return light gray brush
return (INT_PTR)( (HBRUSH)GetStockObject( LTGRAY_BRUSH ) );
}
else
return FALSE; // say we didn't handle it
// so dialog procedure can do that for us
}
After I start the program edit control is painted properly.
After I type valid entry edit control is painted properly.
After I type invalid character immediately after, background is colored into light gray and everything seems to work fine.
If I delete the invalid character then the background stays gray instead of returning to default system color.
What am I doing wrong and how should I fix this ?
EDIT:
If I put InvalidateRect() in my WM_COMMAND handler for IDC_MYEDIT then the problem seems to disappear:
case WM_COMMAND:
{
switch( LOWORD(wParam) )
{
case IDC_MYEDIT:
{
if( HIWORD(wParam) == EN_CHANGE )
{
//do your validation stuff
}
InvalidateRect(...);
}
break;
// the rest of the code...
The error is here
else
return FALSE; // say we didn't handle it
// so dialog procedure can do that for us
The WM_CTLCOLOREDIT message is listed as one of the special exceptions to the rule that returning FALSE means "not handled". It must be handled. If you don't want to handle it, you can pass the message to DefWindowProc.
I have these links with code:
WMMouseWheel not working in Delphi
How to disable MouseWheel if mouse is not over VirtualTreeView (TVirtualStringTree)
Translated it to C++ Builder but it doesn't work:
UPDATE: After narrowing the problem down it appears that WM_MOUSEWHEEL messages don't work over unfocused TVirtualStringTree control only, they work over other controls. When focus is on e.g. TMemo control, other TMemo control scrolls on wheel but not TVirtualStringTree control. When focus is on TVirtualStringTree it scrolls TVirtualStringTree but not other controls. So the problem is now specific to TVirtualStringTree only.
void __fastcall TForm1::ApplicationEventsMessage(tagMSG &Msg, bool &Handled)
{
TPoint Pt;
HWND Wnd;
if (Msg.message == WM_MOUSEWHEEL ||
Msg.message == WM_VSCROLL ||
Msg.message == WM_HSCROLL)
{
if (GetCursorPos(&Pt))
{
Wnd = WindowFromPoint(Pt);
// It must be a VCL control otherwise we could get access violations
if (IsWindowEnabled(Wnd) && FindControl(Wnd) != NULL)
{
Msg.hwnd = Wnd; // change the message receiver to the control under the cursor
}
}
}
}
Different version of the similar code, also doesn't work:
TPoint pnt;
TWinControl *ctrl;
if ((Msg.message == WM_MOUSEWHEEL ||
Msg.message == WM_VSCROLL ||
Msg.message == WM_HSCROLL) &&
GetCursorPos(&pnt))
{
ctrl = FindVCLWindow(pnt);
if (ctrl != NULL)
{
SendMessage(ctrl->Handle, Msg.message, Msg.wParam, Msg.lParam); // No effect
// SendMessage(ctrl->Handle, WM_VSCROLL, 1, 0); // This is the only thing that actually moves scrollbars but this is not exactly the same message like above
// Msg.hwnd = ctrl->Handle; // No effect
this->Caption=ctrl->Name; // This shows correct control name so the message IS GETTING THROUGH!
Handled = true;
}
}
It should work but it doesn't. Tried other code as well. No effect - mouse wheel does not operate on unfocused control. As you can see, I checked for all 3 variants of wheel messages, it gets correct control under the mouse, it shows that control name but the control doesn't receive wheel messages.
Any ideas what piece of the puzzle am I missing to get it to work?
As nobody offered any proper solution, I am posting my own. The solution is not perfect but at least it does what it needs to do - mouse wheel scrolls all controls under it, including the VirtualTreeView controls. The code in solution is in C++ but Delphi version is very similar (it only needs to be translated).
My current solution is to grab WM_MOUSEWHEEL events and translate them into WM_VSCROLL or WM_HSCROLL to which VirtualTreeView reacts and scrolls the content. Additionally, it needs to take into account high-precision mouse wheels which can have smaller value than WHEEL_DELTA (which is set to 120). Finally, it needs to take into account user setting for number of lines to scroll (set in Control Panel in Windows). So here goes:
Put a TApplicationEvents to a form and in the OnMessage event do this:
void __fastcall TFormMain::ApplicationEventsMessage(tagMSG &Msg, bool &Handled)
{
// Check these 3 messages because some mouse drivers may use VSCROLL instead of MOUSESWHEEL message
if (Msg.message == WM_MOUSEWHEEL || Msg.message == WM_VSCROLL || Msg.message == WM_HSCROLL)
{
TPoint pnt;
TWinControl *ctrl;
if (!GetCursorPos(&pnt)) return;
ctrl = FindVCLWindow(pnt);
if (ctrl != NULL)
{
// ToDo: implement if user needs wheel-click - then we also need KEYSTATE but for this example it is not needed
// int fwKeys = GET_KEYSTATE_WPARAM(Msg.wParam);
int zDelta = GET_WHEEL_DELTA_WPARAM(Msg.wParam),
pvParam = 3; // Windows default value
unsigned MyMsg = WM_VSCROLL;
// ToDo: extract SystemParametersInfo somewhere else so it is not extracted for each WM_MOUSEWHEEL message which may not be needed
switch (Msg.message)
{
// This will translate WM_MOUSEWHEEL into WM_VSCROLL
case WM_MOUSEWHEEL:
case WM_VSCROLL:
// Windows setting which determines how many lines to scroll - we'll send that many WM_VSCROLL or WM_HSCROLL messages
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &pvParam, 0);
MyMsg = WM_VSCROLL;
break;
case WM_HSCROLL:
// Same as above but for WM_HSCROLL (horizontal wheel)
SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &pvParam, 0);
MyMsg = WM_HSCROLL;
break;
}
// This calculation takes into account high-precision wheels with delta smaller than 120
// Possible TODO: Round up values smaller than 1 (e.g. 0.75 * pvParam) if pvParam is 1
int ScrollBy = ((double)zDelta / (double)WHEEL_DELTA) * pvParam;
// Send multiple messages based on how much the zDelta value was
if (zDelta > 0)
{
do
{
SendMessage(ctrl->Handle, MyMsg, SB_LINEUP, 0);
}
while (--ScrollBy > 0);
}
else
{
do
{
SendMessage(ctrl->Handle, MyMsg, SB_LINEDOWN, 0);
}
while (++ScrollBy < 0);
}
Handled = true;
}
}
}