Друзья
Друзья


Многострочность и StringGrid

altИтак задача: требуется многострочный вывод в ячейку грида. Тут можно пойти двумя путями: первым и вторым, шучу. Можно сделать все капитально, на века, то есть создать свой компонент-наследник стандартного TStringGrid или воспользоваться встроенным обработчиком события TStringGrid.OnDrawCell.

Начнем, как водится, с простого. Итак, откроем проект и положим на форму компонент TStringGrid (закладка Additional). По умолчанию среда Делфи присвоила нашему компоненту имя StringGrid1, оставим это без изменений.

Теперь на секунду забудем о компонентах и "погрузимся" в изучение Windows API. Сегодня нам нужна будет только одна функция и имя ей DrawText. Что о ней нам может поведать справка?

function Windows.DrawText(hdc: HDC; lpString: PChar; nCount: integer; var lpRect: TRect; uFromat: Cardinal):integer;
Функция DrawText выводит форматированный текст в определенном прямоугольнике. Функция форматирует текст, согласно указанному методу (выводит табуляцию, выравнивает символы, разрывает строки и т.д.). Перевод конечно свободный. Мы прочитали об этой функции всего одно предложение, а уже ее любим – ведь она умеет разрывать строки. Читаем дальше про параметры.

hDC
Указывает на контекст устройства, напомню, что свойство Handle у TCanvas как раз и имеет тип HDC.

lpString
Строка для вывода. Если параметр nCount равен -1, строка должна быть нуль-терминированная (null-terminated), то есть заканчиваться символом #0.

nCount
Указывает количество символов в строке. Тут все ясно.

lpRect
Переменная в которой передается запись типа TRect. Именно в этот прямоугольник будет выводится наш текст.

uFormat
И наконец переменная для указания методов и типов форматирования. Все их перечислять не буду. Нас интересует константа с именем DT_WORDBREAK, которая собственно и отвечает за многострочный вывод.

Ну и напоследок, при удачном завершении функция возвращает высоту выведенного текста. Очень удобная штука.

Вернемся на секунду к нашему StringGrid'у. Нам нужно написать обработчик события OnDrawCell, который возникает каждый раз когда компонент прорисовывает себя. В обработчик передаются следующие параметры

Sender: TObject
Объект, который "просит" себя нарисовать, в нашем случае это будет StringGrid1.

ACol, ARow: Longint
Номер колонки и строчки. Отсчет начинается с нуля.

Rect: TRect
Идентифицурует прямоугольную область ячейки с номерами ACol:ARow.

State: TGridDrawState
Состояние ячейки. Может принимать значения: gdSelected - выделенная ячейка, gdFocused - ячейка имеет фокус ввода, gdFixed - фиксированная ячейка.

Все готово для первого эксперимента. Напишем обработчик.

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var s:string;
begin
s := 'Hello world! Hello world! Hello world! Hello world!';
DrawText(StringGrid1.Canvas.Handle,PChar(s),length(s),Rect,DT_WORDBREAK);
end;
Посмотрите на результат.


Как видите мы не использовали параметр Sender, а все потому, что мы точно знаем какой компонент нам нужно рисовать. Далее мы передали в функцию DrawText не просто строку S, а привели эту строку к типу PChar, который используется при вызовах функций API.
Выравнивание по левому краю это конечно классика, но, лично мне, очень часто хочется задать выравнивание отличное от стандартного. Чем нам может помочь DrawText? Читаем справку и находим константы DT_LEFT, DT_CENTER и DT_RIGHT. Я думаю, объяснять зачем нужны эти константы не нужно. Давайте модифицируем наш код. Имеем:

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var s:string;
Flag: Cardinal;
begin
s := 'Пожилая женщина ищет мужчину для вскапывания огородов';
Case AСol mod 3 of
0: Flag := DT_LEFT;
1: Flag := DT_CENTER;
else
Flag := DT_RIGHT;
end;
Flag := Flag or DT_WORDBREAK;
DrawText(StringGrid1.Canvas.Handle,PChar(s), length(s),Rect,Flag);
end;


Стоит наверное обратить внимание на включение и выключение опций. В предыдщем примере опции хранились в переменной Flag. Все делается через битовые операции. Не вдаваясь в подробности скажу, что код
Flag := Flag or DT_LEFT

устанавливает опцию связанную с константой, а код
Flag := Flag and not DT_LEFT

эту опцию снимает. Тоже самое относится и к другим константам.
Теперь обратим внимание на мелочи. Я сознательно оставил одну фиксированную строчку. Внимательно присмотритесь к нему. Наш текст вылез на элементы объемного оформления ячейки, да и в обычных ячейках он смотрится, прямо скажем, не очень. Посему давайте сделаем отступы слева и справа, таким образом:

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var s:string;
Flag: Cardinal;
begin
s := 'пожилая женщина ищет мужчину для вскапывания огородов';
Case Acol mod 3 of
0: Flag := DT_LEFT;
1: Flag := DT_CENTER;
else
Flag := DT_RIGHT;
end;
Flag := Flag or DT_WORDBREAK;
Inc(Rect.Left,3); // отступ слева
Dec(Rect.Right,3); // отступ справа
DrawText(StringGrid1.Canvas.Handle,PChar(s),length(s),Rect,Flag);
end;

Ну что ж, окончательно сформируем рабочий пример. При этом не забываем, что значения ячеек хранятся у нас в массиве Cells. Добавим и еще одну строчку кода, призванную вытереть содержимое ячейки перед непосредственно рисованием. И для того чтобы мы могли вводить текст сами, требуется в StringGrid1.Options включить опцию goEditing.

Ну и последний штрих. Помните о том, что функция DrawText возвращает высоту выведенного текста? Так давайте исключим ситуацию, когда текст не умещается в ячейке. Мы просто увеличим высоту ячейки до нужного уровня. Имеем код:

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var s:string;
Flag: Cardinal;
H: integer;
begin
StringGrid1.Canvas.FillRect(Rect);
s := StringGrid1.Cells[ACol,ARow];
Case Acol mod 3 of
0: Flag := DT_LEFT;
1: Flag := DT_CENTER;
else
Flag := DT_RIGHT;
end;
Flag := Flag or DT_WORDBREAK;
Inc(Rect.Left,3);
Dec(Rect.Right,3);
H := DrawText(StringGrid1.Canvas.Handle,PChar(s),length(s),Rect,Flag);
if H > StringGrid1.RowHeights[ARow] then
StringGrid1.RowHeights[ARow] := H; //увеличиваем
end;

При всей рабочести кода, умные люди за него не похвалят. По хорошему, надо было бы сперва расчитать высоту текста, а уж потом выводить. Но это я оставляю на домашнее задание читателю. Подскажу лишь, что нужно в справке обратить внимание на константу DT_CALCRECT.

Вывод
Все гениальное просто. Но! К моему большому сожалению, у этого метода есть огромные минусы, которые сможете заметить и вы, если немножко поэксперемен­тируете. А именно. Первое, функция DrawText по непонятным мне причинам не хочет разрывать слитный текст, то есть текст без пробелов. Второе, в процессе редактирования значений ячеек весь текст также выстраивается в одну строчку.

 
Самое популярное
Яндекс.Метрика