Я пытаюсь создать элемент управления для редактирования поиска в MFC, значок которого постоянно отображается в окне элемента управления (независимо от состояния и текста элемента управления). Я написал что-то подобное много лет назад и работал очень хорошо, но код больше не работает на Windows 7 и новее (может быть, даже Vista, но не пробовал). В результате изображение, отображаемое в элементе управления, перекрывается с областью ввода (см. Рисунок ниже).
Идея кода:
CEdit
(это обрабатывает рисование в OnPaint)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);
}
}
}
На следующем рисунке показана проблема с редактированием в одну строку (изображения были увеличены для лучшего обзора). Желтый фон предназначен только для подсветки, в реальном коде я использую COLOR_WINDOW
Системный цвет. Вы можете видеть, что когда редактирование в одну строку содержит текст и ввод, изображение левой стороны закрашивается. Это не происходит с многострочным редактированием, где SetRect
правильно устанавливает форматирование прямоугольника.
Я пытался использовать 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);
}
}
}
Я посмотрел на другие реализации подобных элементов управления редактирования, и все они теперь имеют ту же ошибку.
Очевидно, вопрос заключается в том, как исключить область изображения из области ввода элемента управления?
я считать что происходит в том, что 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));
Возможно, вам также придется обновить поля снова, если системный шрифт изменится.
Других решений пока нет …