CRichEditCtrl selects all text when it gets focus - c++

I had a dialog with a menu and a CTabCtrl. The CTabCtrl had one tab, which contained a CDialog. In turn, that contained a few static texts and a CRichEditCtrl. There was no particular issue with the window gaining and losing focus.
I since added a second identical tab, and now every time the tab is changed, all text in the CRichEditCtrl is apparently selected. It is shown in an inverted color scheme and all text is replaced should you hit a key.
The description of flag ECO_NOHIDESEL, says (emphasis mine):
Negates the default behavior for an edit control. The default behavior
hides the selection when the control loses the input focus and shows
the selection when the control receives the input focus. If you
specify ECO_NOHIDESEL, the selected text is inverted, even if the
control does not have the focus.
"shows the selection" to me sounds like "show whatever the selection was the last time this control had the focus," which is not what's happening. Normally nothing is selected before focus is lost, but if I do try to leave a selection, go back to the other tab and return, the entire text, as usual, is selected.
Can this selection be prevented?
void EditorDialog::OnTabSelChange( NMHDR * phdr, LRESULT* pResult ) {
CTabCtrl* ptab = (CTabCtrl*) GetDlgItem( IDC_TAB );
int iPageActive = ptab->GetCurSel();
if ( iPageActive >= appage.N() ) {
AKS( AKSWarn, "got tab change to tab %d when I only have %d ppages", iPageActive, appage.N() );
return;
}
ppageActive = appage[ iPageActive ];
SetActivePagePos();
SCWinUtilSetWindowTextVA( this, "Editor: %s", ppageActive->pszFileName );
}
void EditorDialog::SetActivePagePos() {
// STEP 1: Make the proper tab page visible.
for ( int i = 0; i < appage.N(); i++ )
appage[i]->ShowWindow( SW_HIDE );
ppageActive->ShowWindow( SW_SHOW );
// STEP 2: Make the new tab page the right size and position.
CTabCtrl* ptab = (CTabCtrl*) GetDlgItem( IDC_TAB );
CRect rectTab, rectItem;
ptab->GetClientRect( &rectTab );
ptab->GetItemRect( 0, &rectItem );
int iPageX = rectItem.left + 2;
int iPageY = rectItem.bottom + 4;
int iPageW = rectTab.right - 2 - iPageX;
int iPageH = rectTab.bottom - 2 - iPageY;
ppageActive->SetWindowPos( &wndTop, iPageX, iPageY, iPageW, iPageH, SWP_SHOWWINDOW | SWP_NOZORDER );
// STEP 3: Give the window focus and let it know to redraw.
ppageActive->SetFocus();
// When the tab changes the entire content of the RichEdit is selected for some reason.
// As a workaround I manually clear the selection.
CRichEditCtrl* prich = (CRichEditCtrl*) ppageActive->GetDlgItem( IDC_PATCH );
prich->SetSel(-1,-1);
// Redrawing just the prich, or the ppageActive, or the ptab, doesn't
// cause the RichEdit to redraw correctly, but Redrawing the entire dialog does.
RedrawWindow();
}

The default behavior for Edit and Rich Edit controls is to make the selection invisible when the control does not have the input focus and only make it visible when the control has the focus. The selection is not, however, changed. The ES_NOHIDESEL style overrides this default behavior and causes the selection to always appear in the control, regardless of whether or not it has the focus. You have certainly seen this behavior before: it is what both Microsoft Word and Visual Studio do.
As such, your understanding of the SDK documentation is perfectly correct. Unfortunately, there is another aspect of the Rich Edit control's behavior that is getting in the way. Whenever an Edit or Rich Edit control hosted in a dialog box receives the focus, it automatically selects all of its text, obliterating the current caret position in the process. The ES_NOHIDESEL does not have any effect on this behavior; it just changes whether or not the selection is visible when the control is unfocused. You can certainly override this select-all-on-focus behavior by doing as IInspectable suggested and subclassing the control to customize its handling of the WM_GETDLGCODE message.
But there is a much simpler solution. Along the same lines as ES_NOHIDESEL, you want to set the ES_SAVESEL style for the control upon creation. Although you can set ES_NOHIDESEL in the resource editor ("No Hide Selection"), there is no equivalent property for ES_SAVESEL. You can manually add it to the RC file, but there's no guarantee that it won't be obliterated when Visual Studio regenerates that file.
Alternatively, you can send the Rich Edit control a EM_SETOPTIONS message specifying the ECO_SAVESEL option. In MFC, the SetOptions member function wraps the sending of this message. For example, in your OnInitDialog function, you might have the following:
m_myRichEditCtrl.SetOptions(ECOOP_OR, ECO_SAVESEL); // maintain selection across focus events

The default behavior for Edit and RichEdit controls is to select the entire contents when they gain focus. ES_NOHIDESEL does not modify this behavior, but simply instructs the control to always show its selection, even if it doesn't have the input focus.
To change the default behavior of the RichEdit control to retain its selection you have to derive from it and provide your custom OnGetDlgCode implementation:
UINT RichEditSelectionPreserving::OnGetDlgCode() {
// Call the base class implementation
UINT code = CRichEditCtrl::OnGetDlgCode();
// And mask out the undesired feature
code = code & ~DLGC_HASSETSEL;
return code;
}

Related

How to manually show CMFCToolBarComboBoxButton sub-menu?

Standard behaviour for CMFCToolBarComboBoxButton is to have a clickable button plus a drop-down arrow for displaying a submenu. I want to show the submenu independently of where the click was made. How can I do it?
My code to create the button is, more or less, the following (it has been extracted from a larger project, so I apologize for any missing not-too-important piece of code):
// In class declaration:
CMenu m_menu;
CMFCToolBar m_toolbar;
// Where toolbar initialization takes place:
m_menu.CreateMenu();
// ... populate menu
// ID_BUTTON is the ID in the resource file for the toolbar button, 0 is the index for the button icon
CMFCToolBarMenuButton button(ID_BUTTON, m_menu.GetSafeHmenu(), 0);
m_toolbar.ReplaceButton(ID_BUTTON, button);
I've been looking around for awhile and cannot find a related answer.
The solution happened to be very straightforward, just call the OnClick function of the CMFCToolBarComboBoxButton button from its associated ON_COMMAND.
// ... message map
ON_COMMAND(ID_BUTTON, OnToolbarMenuButtonClicked)
// ...
void MyWnd::OnToolbarMenuButtonClicked()
{
const int index = m_toolbar.CommandToIndex(ID_BUTTON);
auto button = (CMFCToolBarComboBoxButton*)m_toolbar.GetButton(index);
button->OnClick(NULL, TRUE);
}
This behaviour is not documented and, contrary to what common sense told me, it doesn't create an infinite recursive call. It seems that the "main" button is still controlled by CMFCToolBarButton, while just the "arrow-button" is controlled by the CMFCToolBarComboBoxButton.
PS: obviously, and out of the scope of the question, the OnToolbarMenuButtonClicked can be used for a very different purpose, such as the default action while the sub-menu contains other less-frequent options.

MFC - Changing dialog item focus programmatically

I have a Modeless dialog which shows a bunch of buttons; some of these are customized to draw stuff with GDI.
Now, when the user clicks on a customized one under certain conditions, a message box appears to alert user of the error and this is fine.
The problem is that after accepting the Message Box (showed as MB_ICON_ERROR), everywhere I click in the dialog, I always get the error message as if the whole dialog send the message to the customized button and the only way to get rid this is to press tab and give the focus to another control.
This is a strange behaviour and knowing why happens wouldn't be bad, but a simple workaround for now should do the job.
Since the moment that is probably a matter of focus, I've tried to set it on another control (in the owner dialog) by doing:GetDlgItem( IDC_BTN_ANOTHER_BUTTON )->SetFocus();
and then, inside the customized control by adding:KillFocus( NULL );but had no results.
How should I use these functions?
Thanks in advance.
PS: if I comment the AfxMessageBox, the control does not show this bizarre behaviour.
EDITI'll show some code as requested.
// This is where Message Box is popping out. It is effectively inside the dialog code.
void CProfiloSuolaDlg::ProcessLBtnDownGraphProfilo(PNT_2D &p2dPunto)
{
// m_lboxProfiles is a customized CListBox
if(m_lboxProfiles.GetCurSel() == 0)
{
// This profile cannot be modified.
/*
CString strMessage;
strMessage.Format( _T("Default Profile cannot be edited.") );
AfxMessageBox( strMessaggio, MB_ICONERROR );
*/
return;
}
// Selecting a node from sole perimeter.
SelectNodo(p2dPoint);
}
Actually, the message is commented to keep the dialog working.
// This is inside the customization of CButton
void CMyGraphicButton::OnLButtonDown(UINT nFlags, CPoint point)
{
PNT_2D p2dPunto;
CProfiloSuolaDlg* pDlg = (CProfiloSuolaDlg*)GetParent();
m_pVD->MapToViewport(point,p2dPunto);
switch(m_uType)
{
case GRF_SEZIONE:
pDlg->ProcessLBtnDownGraphProfilo(p2dPunto);
break;
case GRF_PERIMETRO:
pDlg->ProcessLBtnDownGraphPerimetro(p2dPunto);
break;
}
CButton::OnLButtonDown(nFlags, point);
}
Since you are handling the button down event in the button handler for the custom control, you don't need to call the base class. Just comment out CButton::OnLButtonDown(nFlags, point).

MFC: CMFCToolBar SetButtonStyle not wirking with style TBBS_PRESSED?

byIs there are bug in control ? or I am doing something wrong ?
In .h
CMFCToolBar m_wndToolBar;
in message map
ON_COMMAND(ID_MYID, &CMainFrame::OnToolBar)
void CMainFrame::OnToolBar()
{
int nIndex = m_wndToolBar.CommandToIndex(ID_MYID);
UINT nState = m_wndToolBar.GetButtonStyle(nIndex);
if(nState & TBBS_PRESSED)
nState &= ~TBBS_PRESSED;
else
nState |= TBBS_PRESSED;
m_wndToolBar.SetButtonStyle(nIndex,nState);
m_wndToolBar.InvalidateButton(nIndex);
}
By clicking on button I need to set button pressed, and when user clicked again, button become unpressed.
Nothing happens by clicking on button :(
Just create an ON_UPDATE_COMMAND handler for the specific item.
Use pCmdUI->SetCheck to Signal the down or Up state.
The MFC updates tool bars and menus never directly. They ask the Framework to update the state of the Buttons and menu items.
Your description indicates that you want the button to have the behavior of a "check box". If that is correct, make sure you specify TBBS_CHECKBOX for the button style. You should not need to manually handle rendering the check box state each time the button is pressed.

How to implement the mouse click for URLs at rich edit control

I added a read-only rich edit 2.0 control to my dialog (code is using C windows API, the dialog is created by using function DialogBox)
At the dialog call back, at the WM_INITDIALOG, I add the following code to enable url detection and also enable the event ENM_LINK is sent to the parent dialog instead of the rich edit control itself:
LRESULT mask = SendMessage(hWndText, EM_GETEVENTMASK, 0, 0); //hWndText is rich edit control
SendMessage(hWndText, EM_SETEVENTMASK, 0, mask | ENM_LINK);
::SendMessage(hWndText, EM_AUTOURLDETECT, TRUE, NULL);
I had a little trouble to enable the url detection when dialog is initially launched (which seems a known issue or behavior since rich edit control would only enable url detection of modified text). However I worked around this issue by setting the dialog text again on every WM_PAINT event.
The code is generally working. I also implemented the following code to launch the URL at the browser when mouse is hovering over the url:
case WM_NOTIFY:
plink = (ENLINK *) lParam;
switch(LOWORD(wParam))
{
case IDC_DISPLAY_TEXT_2: //this is ID for my rich edit control
szURL =m_strDisplay.Mid(plink->chrg.cpMin, plink->chrg.cpMax - plink->chrg.cpMin);
LaunchURL(szURL); //function to launch the url with default browser
break;
default:
break;
}
It seems that I would get WM_NOTIFY event every time when I hovered the mouse over the url. However when I clicked on it, I always get same event as the mouse hover over.
Based on the structure of ENLINK, I should get more detailed NM event at the NMHDR structure, however the value plink->nmhdr.code is always 1803 which is not even NM_HOVER (its defined value is (NM_FIRST-13) and NM_FIRST is (0U- 0U), so NM_HOVER value is 4294967283 on my 64 bit machine). I know that I am missing something here. Could someone shed some lights here? How can I get the mouse click event for the rich edit control?
I think you should capture the EN_LINK notification. I implemented the following code. It enables a url link in a richedit control placed into the parent window, not into a dialog. You could adapt it for your dialog, as well.
Consider beginning with the code:
case WM_NOTIFY: {
switch (((LPNMHDR)lParam)->code) { //NMHDR structure contains information about a notification message.
case EN_LINK: {
ENLINK *enLinkInfo = (ENLINK *)lParam; // pointer to a ENLINK structure
then, if you choose to launch url on LBUTTONUP, you have to check the value contained in enLinkInfo->msg (remember to adapt it for your dialog, though)
if (enLinkInfo->msg == WM_LBUTTONUP) {
// select all the text from enLinkInfo->chrg.cpMin to enLinkInfo->chrg.cpMax
// lauch the url
}
Besides, you can intercept WM_MOUSEMOVE:
if(enLinkInfo->msg == WM_MOUSEMOVE) {
; // do nothing
}
Hope it helps.
As the answer by #A_nto2 shows, to intercept a mouse click do:
case WM_NOTIFY: {
//NMHDR structure contains information about a notification message.
switch (((LPNMHDR)lParam)->code) {
case EN_LINK: {
ENLINK *enLinkInfo = (ENLINK *)lParam; // pointer to a ENLINK structure
if (enLinkInfo->msg == WM_LBUTTONUP) {
But the tricky part is to get the link that was clicked on.
One gets a "range" that was clicked on in the enLinkInfo->chrg of the type CHARRANGE.
An answer to Detect click on URL in RichEdit suggests using the EM_EXSETSEL with the enLinkInfo->chrg. And then using the EM_GETSELTEXT to retrieve the text.
That works with auto-detected plain-text URLs (EM_AUTOURLDETECT).
A problem is with friendly name hyperlinks (i.e. those that have an anchor text different than the URL itself):
{\rtf1{\field{\*\fldinst{ HYPERLINK "https://www.example.com"}}{\fldrslt{Example}}}}
(Note that these are supported in Rich Edit 4.1 and newer only)
For these, the CHARRANGE points to the HYPERLINK "https://www.example.com" part, which is hidden and cannot be selected using the EM_EXSETSEL. Actually it can be selected on Windows 10. But it cannot be selected on Windows 7, Vista and XP. Sending the EM_EXSETSEL to these systems results in selecting a zero-length block just after the hidden part.
So either you have to go back in the rich edit buffer and scan for the link; or use another method to retrieve the clicked text.
In my case, as I have small texts only in the rich edit, I've used the WM_GETTEXT. It returns a plain-text version of the rich edit document, but with the friendly name hyperlinks preserved in this form:
HYPERLINK "https://www.example.com" Example
The CHARRANGE points to the URL, strangely including the leading quote: ("https://www.example.com).
But the indexes correspond to a text with a single-character (LF) line separators. While the WM_GETTEXT returns the CRLF separators. So you have to convert the text to the LF before extracting the URL using the CHARRANGE.
According to the documentation of EM_AUTOURLDETECT, you are supposed to get an EN_LINK notification, which should be reflected in the nmhdr.code. According to Google,
#define EN_LINK 0x70B
which is 7 * 256 + 11 = 1750 + 42 + 11 = 1803.
Please note that your code misses a check for nmhdr.code == EN_LINK.
I'm not sure if the control sends NM_HOVER messages at all.

"Smart" Linked Scrollbar and Edit Controls?

I hope that I can explain my problem well enough for someone to help.
Basically, I have a horizontal scrollbar (ranged 0 to 1000) and an edit control that represents the position of the scrollbar divided by 1000, so that the user can use either the scrollbar to select a range of numbers between 0 and 1 up to a 3 decimal precision (.001, .002, ..., .987, etc.), or enter their own number in the edit box. As they scroll the scrollbar, the number in the edit control changes to reflect the new scroll position. When a new number is entered, the scrollbar sets itself to a new position reflecting the number entered. Meanwhile I also perform some calculations with this number as it changes (through either the scrollbar or the edit control) and display the results in another dialog.
Here is my problem: I'm having trouble deciding which event handlers to use to produce the proper behavior when a user enters a number into the edit control.
I'm using a double value variable called fuelMargin to handle my edit control and a CScrollBar control variable called fuelScroll to handle the scrollbar.
In my HSCROLL event I set the edit control to the scroll position / 1000. No problems there; when the user scrolls the scrollbar the edit box is correctly updated.
As for the edit box, my first attempt was an ONCHANGE event:
void MarginDlg::OnEnChangeFueledit()
{
CEdit* editBox;
editBox = (CEdit*)GetDlgItem(IDC_FUELEDIT);
CString editString;
editBox->GetWindowText(editString);
if (editString.Compare(".") != 0 && editString.Compare("0.") != 0
&& editString.Compare(".0") != 0 && editString.Compare("0.0") != 0
&& editString.Compare(".00") != 0 && editString.Compare("0.00") != 0)
{
UpdateData();
UpdateData(FALSE);
if (fuelMargin > 1)
{
UpdateData();
fuelMargin = 1;
UpdateData(FALSE);
}
if (fuelMargin < 0)
{
UpdateData();
fuelMargin = 0;
UpdateData(FALSE);
}
fuelScroll.SetScrollPos(int(fuelMargin*1000));
}
}
I needed that first if statement in there to keep from doing an UpdateData() when the user is trying to type a number like .5 or .05 or .005. It does produce a few wonky behaviors, though; when the user tries to type something like .56, after the .5 an UpdateData() is performed, the number becomes 0.5, and the cursor is moved to the far left, so if they tried to type .56 they would accidentally end up typing 60.5 -- which goes to 1, since I won't let them enter numbers lower than 0 or higher than 1. If they enter 0.56, however, this behavior is avoided.
For my second attempt, I commented out my ONCHANGE event and put in an ONKILLFOCUS event instead:
void MarginDlg::OnEnKillfocusFueledit()
{
UpdateData();
UpdateData(FALSE);
if (fuelMargin > 1)
{
UpdateData();
fuelMargin = 1;
UpdateData(FALSE);
}
if (fuelMargin < 0)
{
UpdateData();
fuelMargin = 0;
UpdateData(FALSE);
}
fuelScroll.SetScrollPos(int(fuelMargin*1000));
}
So now the user can finish typing their number and all is hunky dory--as long as they click out of the edit box. The scrollbar won't move and results won't be calculated until the box loses focus.
I want the results to be calculated as the numbers in the box are being typed; I want the scrollbar to move as the numbers are being typed. But I don't want typing to be disrupted, i.e. the actual numbers in the box changed or the cursor moved in any way.
Suggestions?
Thanks!
With the first approach, it looks like you're almost there: the only really significant problem is that the repeated calls to UpdateData() mess with the cursor position as the user is typing.
Given that you're trying to have a reasonably complex interaction between the controls, what I'd suggest is not to do validation in the OnChange() at all, so that as the user is typing he can type what he wants (which is how most numeric edit controls work anyway). When the user closes the dialog the controls are on (or clicks a button that uses the data in some way) then validation should be triggered, and a suitable error shown.
Once you're free from validating in OnChange(), you can fix the "cursor moves" problem by simply not calling UpdateData() in OnChange(). Instead, just parse the number from "editString" and, if it's in the valid range, update the scrollbar. That way, the scrollbar updates as the user types, and if they type in an invalid value the scrollbar stays put, and they'll get an error when they move to whatever the next stage is. Something like this (not tested):
void MarginDlg::OnEnChangeFueledit()
{
CString editString;
GetDlgItem(IDC_FUELEDIT)->GetWindowText(editString);
double editValue;
if ((sscanf(editString,"%lf",&editValue) == 1)
{
if (editValue >= 0.0) && (editValue <= 1.0))
fuelScroll.SetScrollPos(int(editValue*1000));
}
}
The only remaining important problem to note is that, if the user types some invalid value, or a number out of the valid range, then the edit control and the scrollbar will be out of sync. The simplest way to deal with that is to just decide that the edit control is the "master" value: that is, when we want to know what the user entered, we always look at the edit control, not the scrollbar, and validate data.
As for your second approach, one possible solution might be to implement a timer message handler: in the timer handler you could say the equivalent of "if the user hasn't typed anything for a second, I'll assume they're done, and parse the number and update the scrollbar". I'm not so keen on that as a solution, though.
I'd suggest watching for the Enter key, and performing UpdateData() then as well as OnKillFocus and OnChange. The more user-friendly the better.
Next, make sure your UpdateData() routine only works in the direction you need:
If ".5" is entered in the edit control, run your UpdateData() routine when the OnChange event is raised, but make sure to update your scrollbar only. Don't worry about updating the edit control to "0.5" until OnKillFocus is raised. Any updates in the reverse direction (to the edit control) will mess with your cursor. If your edit control is somehow bound to this double variable and auto updates when the var changes, consider leaving the double and your edit control seperate from each other until the OnKillFocus event is raised.
The same concept applies in the other direction as well. When the user scrolls, don't mess with the scrollbar. Just update the edit control and leave it at that.
I should add that XAML's data-binding features really help in situations like this, if you know how to use them properly. It's unfortunate for us native-type developers that it's so difficult to implement similar functionality using only event handlers.