4
votes

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();
}
2

2 Answers

8
votes

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
4
votes

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;
}