2016-08-11 2 views
8

Я пытаюсь создать элемент управления редактирования поиска в MFC, который все время отображается в окне управления (независимо от состояния и текста элемента управления). Я написал что-то подобное много лет назад и работал очень хорошо, но код больше не работает на Windows 7 и новее (возможно, даже Vista, но не пробовал). Случается, что изображение, показанное в элементе управления, перекрывается с областью ввода (см. Рисунок ниже).Значок поиска в элементе управления редактированием, перекрываемом областью ввода

Идея коды:

  • имеет класс, производный от CEdit (который обрабатывает картину в OnPaint)
  • отображается значок справа и область редактирования стянуто на основе размера значок
  • изменение размера выполняется по-разному для однострочных и многострочных редактирований. Для одной строки я вызываю SetMargins и для многострочных редактирований звоню SetRect.
  • это исправить изменение размера применяется в PreSubclassWindow(), OnSize() и OnSetFont()

Это, как применяется размер редактирование ввода:

void CSymbolEdit::RecalcLayout() 
{ 
    int width = GetSystemMetrics(SM_CXSMICON); 

    if(m_hSymbolIcon) 
    { 
     if (GetStyle() & ES_MULTILINE) 
     { 
     CRect editRect; 
     GetRect(&editRect); 

     editRect.right -= (width + 6); 

     SetRect(&editRect); 
     } 
     else 
     { 
     DWORD dwMargins = GetMargins(); 
     SetMargins(LOWORD(dwMargins), width + 6); 
     } 
    } 
} 

На следующем рисунке показана проблема с одной строкой редактирует (изображения были увеличены для лучшего обзора). Желтый фон предназначен только для подсветки, в реальном коде я использую цвет системы . Вы можете видеть, что, когда однострочное редактирование имеет текст и имеет вход, изображение левой стороны окрашено. Это не происходит с многострочным редактированием, где SetRect правильно устанавливает прямоугольник форматирования.

enter image description here

Я попытался с помощью ExcludeClipRect удалить область редактирования, где отображается изображение.

CRect rc; 
GetClientRect(rc); 

CPaintDC dc(this); 
ExcludeClipRect(dc.m_hDC, rc.right - width - 6, rc.top, rc.right, rc.bottom); 

DWORD dwMargins = GetMargins(); 
SetMargins(LOWORD(dwMargins), width + 6); 

Это, похоже, не влияет на результат.

Для справки, это метод окраски, написанный много лет назад и хорошо используемый для работы в Windows XP, но не исправляющий больше.

void CSymbolEdit::OnPaint() 
{ 
    CPaintDC dc(this); 

    CRect rect; 
    GetClientRect(&rect); 

    // Clearing the background 
    dc.FillSolidRect(rect, GetSysColor(COLOR_WINDOW)); 

    DWORD dwMargins = GetMargins(); 

    if(m_hSymbolIcon) 
    { 
     // Drawing the icon 
     int width = GetSystemMetrics(SM_CXSMICON); 
     int height = GetSystemMetrics(SM_CYSMICON); 

     ::DrawIconEx( 
      dc.m_hDC, 
      rect.right - width - 1, 
      1, 
      m_hSymbolIcon, 
      width, 
      height, 
      0, 
      NULL, 
      DI_NORMAL); 

     rect.left += LOWORD(dwMargins) + 1; 
     rect.right -= (width + 7); 
    } 
    else 
    { 
     rect.left += (LOWORD(dwMargins) + 1); 
     rect.right -= (HIWORD(dwMargins) + 1); 
    } 

    CString text; 
    GetWindowText(text); 
    CFont* oldFont = NULL; 

    rect.top += 1; 

    if(text.GetLength() == 0) 
    {  
     if(this != GetFocus() && m_strPromptText.GetLength() > 0) 
     { 
      oldFont = dc.SelectObject(&m_fontPrompt); 
      COLORREF color = dc.GetTextColor(); 
      dc.SetTextColor(m_colorPromptText); 
      dc.DrawText(m_strPromptText, rect, DT_LEFT|DT_SINGLELINE|DT_EDITCONTROL); 
      dc.SetTextColor(color); 
      dc.SelectObject(oldFont); 
     } 
    } 
    else 
    { 
     if(GetStyle() & ES_MULTILINE) 
     CEdit::OnPaint(); 
     else 
     { 
     oldFont = dc.SelectObject(GetFont()); 
     dc.DrawText(text, rect, DT_SINGLELINE | DT_INTERNAL | DT_EDITCONTROL); 
     dc.SelectObject(oldFont); 
     } 
    } 
} 

Я рассмотрел другие реализации аналогичных элементов управления редактирования, и все они имеют одинаковую ошибку.

Очевидно, возникает вопрос, как исключить область изображения из области ввода элемента управления?

+0

Ваше переопределение «OnPaint» борется с рутиной управления Edit control. Он использует 'CPaintDC' для рисования элемента управления вручную, иногда он вызывает' CEdit :: OnPaint', который, в свою очередь, снова вызывает «CPaintDC», а затем выполняет обработку по умолчанию, которая реплицирует клиентскую область. Это не удастся, если элемент управления Edit будет изменен или неактивен, или когда будет получено какое-либо сообщение с краской. –

+0

'CEdit :: OnPaint()' предназначен только для многострочных изменений, что меня не беспокоит. Я использую только отдельные средства редактирования строк. Я упомянул многострочные изменения, потому что в этом случае установка границ работает правильно. –

+0

Что делать, если вы переопределите 'CWnd :: OnCtlColor()' и вызовите 'ExcludeClipRect()' оттуда? Кажется, здесь хорошо работать с быстрым тестом, но без MFC. Возможно, вам придется снова изменить обрезку в 'OnPaint()', чтобы показать свои собственные вещи. – isanae

ответ

0

Я думаю, что происходит в том, что CPaintDC звонки BeginPaint(), который посылает WM_ERASEBKGND в окно редактирования. Я не мог игнорировать его, поэтому я думаю, что он отправляется, возможно, в внутреннее окно STATIC? Не уверен.

Вызов ExcludeClipRect() в вашем OnPaint() обработчик ничего не будет делать, потому что EDIT сбросит область отсечения для всей клиентской области в любом BeginPaint() или его собственного WM_PAINT обработчика.

Однако EDIT отправляет WM_CTRCOLOREDIT своим родителям непосредственно перед самой покраской, но, похоже, после установки области отсечения. Таким образом, вы можете позвонить ExcludeClipRect(). Похоже на детали реализации, которые могут измениться с будущими версиями общих элементов управления. Действительно, похоже, это уже сделано.

Я сделал быстрый тест без MFC на Windows 7, вот моя оконная процедура:

LRESULT CALLBACK wnd_proc(HWND h, UINT m, WPARAM wp, LPARAM lp) 
{ 
    switch (m) 
    { 
     case WM_CTLCOLOREDIT: 
     { 
      const auto dc = (HDC)wp; 
      const auto hwnd = (HWND)lp; 

      RECT r; 
      GetClientRect(hwnd, &r); 

      // excluding the margin, but not the border; this assumes 
      // a one pixel wide border 
      r.left = r.right - some_margin; 
      --r.right; 
      ++r.top; 
      --r.bottom; 

      ExcludeClipRect(dc, r.left, r.top, r.right, r.bottom); 

      return (LRESULT)GetStockObject(DC_BRUSH); 
     } 
    } 

    return ::DefWindowProc(h, m, wp, lp); 
} 

Я тогда подклассы в EDIT окна, чтобы сделать свой собственный значок в WM_PAINT, а затем направил сообщение, так что я Жду» Мне нужно все рисовать самостоятельно.

LRESULT CALLBACK edit_wnd_proc(
    HWND h, UINT m, WPARAM wp, LPARAM lp, 
    UINT_PTR id, DWORD_PTR data) 
{ 
    switch (m) 
    { 
     case WM_PAINT: 
     { 
      const auto dc = GetDC(h); 

      // draw an icon 

      ReleaseDC(h, dc); 
      break; 
     } 
    } 

    return DefSubclassProc(h, m, wp, lp); 
} 

Обратите внимание, что я не мог назвать BeginPaint() и EndPaint() (эквивалент построения CPaintDC) в WM_PAINT потому что граница не будет втягиваться. Я предполагаю, что это как-то связано с вызовом BeginPaint() дважды (один раз вручную, один раз EDIT) и обработку WM_ERASEBKGND. YMMV, особенно с MFC.

Наконец, я установить поля сразу после создания EDIT:

SendMessage(
    e, EM_SETMARGINS, 
    EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELPARAM(0, margin)); 

Вы также, возможно, придется обновить поля еще раз, если изменения системы шрифтов.

Смежные вопросы