Управляющий элемент таблица для
ленивых
Как работают клетки с контролами?
Горизонтальная прокрутка однородных
столбцов.
Горизонтальная прокрутка
неоднородных столбцов
В статье описывается управляющий элемент таблица.
Документация по PalmOS
очень скупо объясняет как использовать таблицу. Более того, начинающего
разработчика часто вводят в заблуждения попытки провести аналогию с другими
аналогичными элементами. Статья рассказывает о том, что такое таблица и как
ее правильно использовать. Описанные способы использования отнюдь не
единственные, они просто близки к тому, как планировали использовать таблицу
разработчики PalmOS.
В тексте используются кальки с английского языка. Control, элемент управления, обозначается как “контрол”. Форма, диалог – это form. Термин таблица используется для обозначения table control или абстрактной структуры. Там где нужно различать табличное как элемент интерфейса будет использоваться слово контрол, а для структуры - массив. Custom переводится как кастом. Scroll переводится как прокрутка.
Таблица – это контрол, предназначенный для визуализации данных из двумерного массива. Идеология таблиц отличается от идеологии различных Grid Control, используемых на больших компьютерах. В чем же отличия?
Как и любой другой контрол таблицу нужно описать в файле ресурсов. Рассмотрим формат описания таблицы:
TABLE ID
<Id.n> AT (<Left.p> <Top.p> <Width.p>
<Height.p>) [ROWS <NumRows.n>] [COLUMNS
<NumCols.n>] [COLUMNWIDTHS <Col1Width.n> ... <ColNWidth.n>]
Видно, что в ресурсах описываются только самые необходимые параметры таблицы:
У строк и столбцов имеется свойство usable. ОС рисует только те ячейки, у которых и строки и столбцы usable. Необходимо при инициализации таблицы выставить для нужных свойств и столбцов это свойство.
Также у строк и столбцов есть свойство masked. Это свойство было введено для поддержки режима mask hide records, при котором private записи показываются заштрихованными прямоугольниками.
При описании свойств в скобках идет имя поля, функции для доступа по чтению и записи.
У клетки есть 4 свойства:
Смысл полей полностью зависит от стиля клетки.
Псевдо-свойствами клетки являются:
Стиль клетки (TableItemStyleType) определяет, что будет в ней выводиться. Данные, хранящиеся в клетке, интерпретируются по-разному в зависимости от ее стиля.
Стиль |
Описание |
Какие поля в
клетке используются |
|
В клетке рисуется чекбокс. |
|
|
Содержимое клетки определяется программой. Высота клетки 11 пикселей. |
Никакие. Клетка рисуется пользовательской функцией |
|
Нередактируемя дата в виде month/day или прочерк, если дата –1. После даты выводится восклицательный знак, если дата уже прошла. |
В поле
Первые 7 бит – год – 1904, следующие 4 бита – месяц и последние 5 бит – день. Всегда используется стандартный шрифт. |
|
Текст, который не нужно редактировать. |
|
|
Нередактируемое число |
Числа всегда показываются жирным шрифтом. |
|
Листбокс с попап-триггером. |
Списки показываются стандартным шрифтом. |
|
Содержимое клетки определяется программой. Высота клетки определяется высотой строки. Этот тип был добавлен в PalmOS 4. |
Никакие. Клетка рисуется пользовательской функцией |
|
Редактируемый текст. Максимальная длина текста – tableMaxTextItemSize (255) байт. |
Используется контрол редактирования для вывода поля.
Функция |
|
Редактируемый текст с иконкой дополнительного текста справа. Размеры иконки заданы константами tableNoteIndicatorWidth tableNoteIndicatorHeight. Максимальная длина текста – tableMaxTextItemSize (255) байт. |
Используется контрол редактирования для вывода поля.
Функция |
|
Не реализовано. |
|
|
Редактируемый текст со свободным пространством справа. Максимальная длина текста – tableMaxTextItemSize (255) байт. |
|
5. Функции TblGrabFocus/TblReleaseFocus используются для установки режима редактирования. TblGrabFocus рекомендуется вызывать в фиксированной последовательности: FrmSetFocus на таблицу, TblGrabFocus на клетки и FldGrabFocus на редактор.
Чтобы не создавать множество контролов, таблица использует создание “по необходимости”. Так, чекбокс создается для отрисовки элемента и в случае отслеживания клика по клетке с ним. После выполнения этих операций контрол удаляется.
При выборе клетки с полем редактирования создается контрол редактора (Field). К этому контролу можно получить непосредственный доступ.
Первый пример посвящен созданию простой таблицы, целиком помещающейся на экране. Пример написан максимально просто для демонстрации разнообразия стилей клеток.
В ресурсах таблица описывается просто и понятно. Также описывается список для соответствующего стиля клеток.
TableDemo.rcp:
LIST "foo"
"bar" "boz" ID MainDemoList AT (100 100 30 35) NONUSABLE
TABLE ID
MainDemoTable AT (0 15 160 130) ROWS 10 COLUMNS 5
COLUMNWIDTHS 30 30 30 30 30
TableDemo.c:
//…
// ширина свобоного поля справа у текстового поля
#define
NARROW_FIELD_SIZE 10
// функция инициализации. Обратите внимание, что она
должна вызываться до
// FrmDrawForm.
Стандартная болванка от CodeWarrior
вызывает ее после. Либо
// переместите функцию, либо вызовите TblDrawTable в концк MainFormInit.
static void
MainFormInit(FormType *frmP)
{
UInt16 rows, cols, i, j;
TablePtr tableP;
//
получить указатель на объект
tableP = GetObjectPtr(MainDemoTable);
// получить размеры таблицы
rows = TblGetNumberOfRows(tableP);
cols = TblGetNumberOfColumns(tableP);
// сделать видимыми все
строки
for(i = 0; i < rows; ++i){
TblSetRowUsable(tableP,
i, true);
}
//
сделать видимыми все столбцы
// установить расстояние
между клетками в 2 пикселя
for(j = 0; j < cols; ++j){
TblSetColumnUsable(tableP,
j, true);
TblSetColumnSpacing(tableP,
j, 2);
}
//
установить стиль заголовков в первой строке
// для
демонстрации реализуем заголовки стилем customTableItem
for(i =
0; i < cols; ++i){
TblSetItemStyle(tableP, 0, i, customTableItem);
TblSetCustomDrawProcedure(tableP,
i, drawProc);
}
//
установить стиль ячеек из остальных строк
for(i = 1; i < rows; ++i){
char
*ptr;
//
колонка номер 0 показывает стиль labelTableItem
// для каждой ячейки выделяется блок памяти
со строкой
// память нужно освобождать при закрытии
формы
TblSetItemStyle(tableP, i, 0, labelTableItem);
ptr
= MemPtrNew(15);
StrPrintF(ptr,
"row%d", i);
TblSetItemPtr(tableP,
i, 0, ptr);
//
колонка номер 1 реализует чекбоксы и попапы
if (i % 2 == 0){
TblSetItemStyle(tableP,
i, 1, checkboxTableItem);
TblSetItemInt(tableP,
i, 1, i%3);
}else{
ListType
*listP = GetObjectPtr(MainDemoList);
TblSetItemStyle(tableP,
i, 1, popupTriggerTableItem);
TblSetItemInt(tableP,
i, 1, 0);
TblSetItemPtr(tableP,
i, 1, listP);
}
//
колонка номер 2 показывет различные виды клеток с редактируемым
// текстом
{
Int16
style;
// стиль клетки определятется остатком от
деления номера
// строки на три
switch (i % 3){
case
0:
style = textTableItem;
break;
case
1:
style = textWithNoteTableItem;
break;
case
2:
style = narrowTextTableItem;
break;
}
// выставляем стиль
TblSetItemStyle(tableP,
i, 2, style);
// выставляем ширину правого края для
узкого поля
TblSetItemInt(tableP, i, 2, NARROW_FIELD_SIZE);
// задаем процедуры загрузки и сохранения
TblSetLoadDataProcedure(tableP, 2, loadProc);
TblSetSaveDataProcedure(tableP,
2, saveProc);
}
//
колонка номер 3 показывает форматирование дат
TblSetItemStyle(tableP, i, 3, dateTableItem);
{
DateType
dd;
// получить текущюю дату в нужном формате
DateSecondsToDate(TimGetSeconds(), &dd);
// получить даты в диапазоне от “4 дня
назад”
//
до после-после-завтра
//
диапазон демонстрирует форматирование
//
прошедших и последующих дат
DateDaysToDate(DateToDays(dd) - 4 + i, &dd);
// структура преобразуется в 16-битное
число
TblSetItemInt(tableP, i, 3, *(Int16 *)&dd);
}
//
в колонке номер 4 заносятся числовые значения
TblSetItemStyle(tableP, i, 4, numericTableItem);
TblSetItemInt(tableP,
i, 4, i);
}
}
// функция рисования пользовательских клеток
static void
drawProc(void *tableP, Int16 row, Int16 column,
RectangleType *bounds)
{
// наименования столбцов
static const char *(names[]) =
{ "label", "checkbox",
"text", "date", "numeric"};
//
получить имя текущего столбца
const char *p = names[column];
// получить
отцентрированную координату по вертикали
Int16 y = bounds->topLeft.y + (bounds->extent.y
- FntCharHeight()) / 2;
RectangleType r;
if (row != 0){
//
эта функция будет вызываться для narrowTextTableItem, где будет
//
отрисовываться правый фрагмент
//
нарисуем справа символ подсказки
FontID fnt = FntSetFont(symbolFont);
char
ch = symbolHelp;
WinDrawChars(&ch,
1,
bounds->topLeft.x + bounds->extent.x
- NARROW_FIELD_SIZE + 1, bounds->topLeft.y);
FntSetFont(fnt);
return;
}
// здесь
рисуются заголовки в прямоугольной рамке
WinDrawTruncChars(p, StrLen(p), bounds->topLeft.x +
2, y,
bounds->extent.x);
r = *bounds;
RctInsetRectangle(&r, 1);
WinDrawRectangleFrame(rectangleFrame,
&r);
}
// функция загрузки текстового поля
static Err
loadProc(void *tableP, Int16 row, Int16 column, Boolean editable,
MemHandle
* dataH, Int16 *dataOffset, Int16 *dataSize, FieldPtr fld){
// набор параметров
позволяет подсовывать не только блоки памяти из
// кучи, но и записи в
базах. Ограничимся простейшим случаем.
// просто выделим
фиксированный блок.
MemHandle h;
char *p;
const int size = 30;
h = MemHandleNew(size);
p = MemHandleLock(h);
StrIToA(p, row);
MemHandleUnlock(h);
*dataH = h;
*dataOffset = 0;
*dataSize = size;
return errNone;
}
// функция сохранения текстового поля. Имеет смысл
для сохранения в базе.
// вызывается после потери фокуса полем редактора.
static Boolean
saveProc(void *tableP, Int16 row, Int16 column){
return true;
}
Что делать, если число строк в вашем массиве больше, чем помещается на экране? В этом случае стоит применить метод “окна”. Визуально представьте контрол как окно, скользящее по массиву. Кнопки вверх и вниз перемещают “окно” с контролом по массиву.
Для отслеживания позиции окна заведите переменную – индекс верхней видимой строки массива. Тогда операции с таблицей будут выглядеть следующим образом:
Рисование:
Прокрутка вверх:
Прокрутка вниз:
Поскольку все операции перерисовывают таблицу полностью, пользоваться TblRedrawTable бессмысленно.
Общепризнанно, что горизонтальная прокрутка - это моветон. Но иногда избавиться от нее невозможно. Можно выделить два способа реализации горизонтальной прокрутки:
Пример однородных столбцов можно найти в реализации табличного процессора. Все клетки в такой таблице несут одну и ту же функциональность. В этом случае горизонтальную прокрутку можно реализовать по аналогии с вертикальной прокруткой методом “скользящего окна”. Окно будет скользить не по одному, а по двум измерения, и его положение будет задаваться двумя индексами. Все алгоритмы вертикальной прокрутки прекрасно адаптируются под этот случай.
Чаще встречается иная задача: показать таблицу с десятью столбцами. Число столбцов заведомо известно и невелико, но все они на экран не помещаются. В этом случае можно упростить реализацию.
Реализуем таблицу из всех столбцов массива. Заполним все клетки таблицы своими стилями и данными.
Заведем переменную с индексом первого видимого столбца. Кнопки горизонтальной прокрутки будут изменять его. Разумнее всего прокручивать по одному столбцу.
А теперь важный шаг: перед рисованием выставьте атрибут usable только у тех столбцов, которые помещаются в границы таблицы. В вычислениях не забудьте про межклеточные интервалы. В принципе можно установить признак usable у всех клеток, начиная с первой видимой, при этом данные, выходящие за границы будут отсечены (clip), но процедура рисования будет честно рисовать все ячейки. Если скорость такого пустого рисования устраивает, то процедуру прокрутки можно упростить.
Большое преимущество описанной схемы в том, что не нужно изменять содержимое ячеек каждый раз при сдвиге на столбец, все изменения касаются только свойств столбцов.