25 мая 2006

Auto-sized editbox

При создании grid-а понадобилось автоматически расширять редактируемую ячейку (стандартный editbox) по высоте в зависимости от содержимого. A-la редактирование таблиц в Microsoft Word.

Загвоздка вся в том, что editbox - стандартный Windows control, в исходники которого залезть нельзя, а функциональность у него весьма ограниченная.

Задача такая: при изменении содержимого editbox-а установить соответствующую его высоту, чтобы весь текст был виден на экране.

Что имеется у стандартного edit-а:
1. Область format rectangle, которая задает область редактирования. Как правило, чуть меньше чем client area. При изменении размера edit-а, эта format rectangle ставится как ей угодно. (Нет чтобы сделать format margins, которые не меняются...)
2. Сообщение EM_GETLINECOUNT - можно узнать текущее количество строк в контроле.
3. Уведомления EN_UPDATE и EN_CHANGE - можно отслеживать изменения содержимого контрола. EN_UPDATE приходит до прорисовки, EN_CHANGE - после.

При реализации сразу начались проблемы.

Проблема 1. Не приходит уведомление EN_UPDATE. Решилось просто:
EditBox.SendMessage(EM_SETEVENTMASK, 0, ENM_UPDATE);

Проблема 2. После изменения размера контрола происходит изменение format rectangle, приходится возвращать его на место:
EditBox.GetClientRect(ClientRect);
EditBox.SendMessage(EM_SETRECTNP, 0, (LPARAM)(&ClientRect));


Проблема 3. Самая большая проблема. Суть ее такова: необходимая нам высота client area равна КоличествоСтрок * ВысотаСтроки. Количество строк мы получить можем, а вот как высоту строки от edit control-а получить? Долго пытался использовать разумные значения, но никак не выходило правильной отрисовки во всех случаях. Перекопал весь Google.Groups, советов много, но ни один точно не соответсвует тому, как рисует текст editbox.

Магическая формула оказалась такая:
ТребуемаяВысота = КоличествоСтрок * TEXTMETRIC.tmHeight + 2

Нет, представляете - "+ 2". Что это за magic number я не знаю, но его я нашел где-то в ньюсгруппах.

Итого получилось:
void AdjustEditSize()
{
int nLines = (int) EditBox.SendMessage(EM_GETLINECOUNT);
// if (nLines == PrevLines) return;

CRect ClientRect, WinRect;
EditBox.GetClientRect(ClientRect);
EditBox.GetWindowRect(WinRect);
EditBox.SendMessage(EM_GETRECT, 0, (LPARAM)(&FormatRect));

CClientDC DC(EditBox);
DC.SelectFont((HFONT) EditBox.SendMessage(WM_GETFONT));

TEXTMETRIC tm;
DC.GetTextMetrics(&tm);
int LineHeight = tm.tmHeight;

MapWindowPoints(HWND_DESKTOP, EditBox.GetParent(), (LPPOINT)&WinRect, 2);
WinRect.bottom = WinRect.top
+ (WinRect.Height() - ClientRect.Height()) // adding non-client area
+ nLines * LineHeight + 2; // what da hell is "2" here?
EditBox.MoveWindow(WinRect);

// restore fucking format margins. set format rect = client area (i.e. margins=0)
EditBox.GetClientRect(ClientRect);
EditBox.SendMessage(EM_SETRECTNP, 0, (LPARAM)(&ClientRect));
}

2 комментария:

prizrak комментирует...

а чо ? вероятно высота строки = высота букв в пунктах плюс минимальный отступ - по одному пункту сверху&&снизу или 2 сверху||снизу

Raider комментирует...

Да, по одному пикселю сверху и снизу. Но как от editbox-а получить эти 2 пикселя? А то сейчас - два, на другом компе с другими настройками - 4... И потом посыпятся bug report-ы!