Управляющий элемент таблица для ленивых

Оглавление

Управляющий элемент таблица для ленивых.. 1

Оглавление. 1

Введение. 1

Терминология. 1

Что такое таблица?. 1

Описание таблицы в ресурсах.. 2

Свойство usable. 2

Свойство masked.. 2

Свойства клетки.. 2

Стили клеток. 3

Свойства столбца. 4

Свойства строки.. 5

Свойства таблицы... 5

Прочие функции.. 6

Как работают клетки с контролами?. 6

Пример простой таблицы... 7

Вертикальная прокрутка.. 9

Горизонтальная прокрутка.. 10

Горизонтальная прокрутка однородных столбцов. 10

Горизонтальная прокрутка неоднородных столбцов. 10

 

 

Введение.

В статье описывается управляющий элемент таблица. Документация по PalmOS очень скупо объясняет как использовать таблицу. Более того, начинающего разработчика часто вводят в заблуждения попытки провести аналогию с другими аналогичными элементами. Статья рассказывает о том, что такое таблица и как ее правильно использовать. Описанные способы использования отнюдь не единственные, они просто близки к тому, как планировали использовать таблицу разработчики PalmOS.

Терминология.

В тексте используются кальки с английского языка. Control, элемент управления, обозначается как “контрол”. Форма, диалог – это form. Термин таблица используется для обозначения table control или абстрактной структуры. Там где нужно различать табличное как элемент интерфейса будет использоваться слово контрол, а для структуры - массив. Custom переводится как кастом. Scroll переводится как прокрутка.

 

Что такое таблица?

Таблица – это контрол, предназначенный для визуализации данных из двумерного массива. Идеология таблиц отличается от идеологии различных Grid Control, используемых на больших компьютерах. В чем же отличия?

  1. Таблица реализовывалась не как абстрактный контрол, а как инструмент для стандартных приложений PalmOS. Поэтому не нужно удивляться тому, что при выводе клетки со стилем “метка” автоматически добавляется двоеточие, а шрифт всегда используется стандартный. В то же время наличие кастомного стиля позволяет реализовать совершенно произвольный вывод в клетку.
  2. Контрол разрабатывался с учетом того, что все клетки будут выводиться на экран. Бессмысленно заводить таблицу 100x100 клеток. Во-первых, будут проблемы с памятью, во-вторых, ее не удастся показать.
  3. Предполагается, что столбцы содержат однородную информацию. Например, пользовательская функция глобальна для столбца.
  4. Число строк и столбцов в таблице не меняется после ее создания.
  5. Если все свелось к матрице кастомных клеток, стоит подумать, а нужно ли вообще использовать таблицу.

 

Описание таблицы в ресурсах

Как и любой другой контрол таблицу нужно описать в файле ресурсов. Рассмотрим формат описания таблицы:

 

TABLE ID <Id.n> AT (<Left.p> <Top.p> <Width.p> <Height.p>) [ROWS <NumRows.n>] [COLUMNS <NumCols.n>] [COLUMNWIDTHS <Col1Width.n> ... <ColNWidth.n>]

 

Видно, что в ресурсах описываются только самые необходимые параметры таблицы:

  1. Идентификатор на форме
  2. Координаты границ таблицы в пикселах
  3. Число строк в таблице. Этот параметр нельзя изменять в процессе работы!
  4. Число столбцов в таблице. Этот параметр нельзя изменять в процессе работы!
  5. Ширина всех столбцов

 

Свойство usable

У строк и столбцов имеется свойство usable. ОС рисует только те ячейки, у которых и строки и столбцы usable. Необходимо при инициализации таблицы выставить для нужных свойств и столбцов это свойство.

 

Свойство masked

Также у строк и столбцов есть свойство masked. Это свойство было введено для поддержки режима mask hide records, при котором private записи показываются заштрихованными прямоугольниками.

Свойства клетки

При описании свойств в скобках идет имя поля, функции для доступа по чтению и записи.

 

У клетки есть 4 свойства:

  1. Стиль (itemType/TblSetItemStyle/)
  2. Шрифт (fontID/TblGetItemFont/ TblSetItemFont)
  3. 16-битное значение (intValue/TblGetItemInt/TblSetItemInt)
  4. Указатель на строку (ptr/TblGetItemPtr/TblSetItemPtr)

 

Смысл полей полностью зависит от стиля клетки.

 

Псевдо-свойствами клетки являются:

  1. Границы клетки TblGetItemBounds. Границы нигде не хранятся, а вычисляются исходя из свойств таблицы

 

Стили клеток

Стиль клетки (TableItemStyleType) определяет, что будет в ней выводиться. Данные, хранящиеся в клетке, интерпретируются по-разному в зависимости от ее стиля.

 

Стиль

Описание

Какие поля в клетке используются

CheckboxTableItem

В клетке рисуется чекбокс.

intValue  содержит состояние клетки

CustomTableItem

Содержимое клетки определяется программой. Высота клетки 11 пикселей.

Никакие. Клетка рисуется пользовательской функцией TableDrawItemFuncType, задаваемой для столбца. В полях intValue и ptr можно хранить свои данные.

DateTableItem

Нередактируемя дата в виде month/day или прочерк, если дата –1. После даты выводится восклицательный знак, если дата уже прошла.

В поле intValue хранится значение даты (тип DateType). Этот тип представляет собой 16-битное значение в формате:

yyyyyyymmmmddddd

Первые 7 бит – год – 1904, следующие 4 бита – месяц и последние 5 бит – день.

Всегда используется стандартный шрифт.

LabelTableItem

Текст, который не нужно редактировать.

В поле ptr содержится выводимый текст, после которого выводится двоеточие. Всегда используется стандартный шрифт.

NumericTableItem

Нередактируемое число

intValue

Числа всегда показываются жирным шрифтом.

popupTriggerTableItem

Листбокс с попап-триггером.

intValue

ptr

В intValue содержится индекс текущего значения в списке, а в ptr – указатель на список. Список можно получить, создав его невидимым на форме и работать с ним с помощью функций Lst*.

Списки показываются стандартным шрифтом.

tallCustomTableItem

Содержимое клетки определяется программой. Высота клетки определяется высотой строки. Этот тип был добавлен в PalmOS 4.

Никакие. Клетка рисуется пользовательской функцией TableDrawItemFuncType, задаваемой для столбца. В полях intValue и ptr можно хранить свои данные.

TextTableItem

Редактируемый текст. Максимальная длина текста – tableMaxTextItemSize (255) байт.

fontID

ptr

Используется контрол редактирования для вывода поля. Функция TableLoadDataFuncType используется для загрузки текста в поле, а функция TableSaveDataFuncType вызывается для сохранения значения.

textWithNoteTableItem

Редактируемый текст с иконкой дополнительного текста справа. Размеры иконки заданы константами tableNoteIndicatorWidth tableNoteIndicatorHeight. Максимальная длина текста – tableMaxTextItemSize (255) байт.

fontID

ptr

Используется контрол редактирования для вывода поля. Функция TableLoadDataFuncType используется для загрузки текста в поле, а функция TableSaveDataFuncType вызывается для сохранения значения.

TimeTableItem

Не реализовано.

 

narrowTextTableItem

Редактируемый текст со свободным пространством справа. Максимальная длина текста – tableMaxTextItemSize (255) байт.

fontID

ptr

intValue

В intValue содержится число пикселей свободной области справа. Используется контрол редактирования для вывода поля. Функция TableLoadDataFuncType используется для загрузки текста в поле, а функция TableSaveDataFuncType вызывается для сохранения значения. Свободное поле справа отрисовываются  пользовательской функцией TableDrawItemFuncType, задаваемой для столбца.

 

Свойства столбца.

  1. ширина в пикселях (width/TblGetColumnWidth/TblSetColumnWidth)
  2. признак маскированности (masked//TblSetColumnMasked). Клетка с маскированным столбцом и строкой рисуется серым.
  3. Признак выделения столбца. (editIndicator//TblSetColumnEditIndicator). При выборе клетки, в ее строке выделяются слева направо все клетки, у которых editIndicator = true. Клетки, находящиеся справа от клетки с editIndicator = false никогда не выделяются. По умолчанию все клетки устанавливаются в true, кроме текстовых.
  4. признак использования (usable//TblSetColumnUsable)
  5. Размер свободного пространства после клетки (spacing/TblGetColumnSpacing / TblSetColumnSpacing). По умолчанию установите tableDefaultColumnSpacing
  6. Функция для отрисовки кастомной клетки (drawCallback//TblSetCustomDrawProcedure)
  7. Функция для загрузки редактируемого текстового поля (loadDataCallback//TblSetLoadDataProcedure)
  8. Функция для сохранения редактируемого текстового поля (saveDataCallback//TblSetSaveDataProcedure)

 

Свойства строки

  1. 16-битный идентификатор (id/TblGetRowID /TblSetRowID). Идентификатор используется для хранения произвольной информации о строке. Также существует функция TblFindRowID для поиска строки с указанным идентификатором. Функция ищет только по usable строкам.
  2. Высота строки в пикселях (height/TblGetRowHeight/TblSetRowHeight).
  3. 32-битная информация о строке (data/TblGetRowData /TblSetRowData). Используется для хранения произвольной информации о строке. Также существует функция TblFindRowData  для поиска строки с указанным полем data. Функция ищет только по usable строкам.
  4. Признак использования (usable/TblRowUsable/TblSetRowUsable)
  1. Признак маскирования (masked/TblRowMasked/TblSetRowMasked).Клетка с маскированным столбцом и строкой рисуется серым.
  1. Признак грязных данных в строке (invalid/TblRowInvalid/TblMarkRowInvalid). При вызове TblRedrawTable будут перерисованы только строки с установленным признаком. Функция TblMarkTableInvalid устанавливает признак грязных данных у всех строк таблицы.
  2. Признак фиксированной высоты строки (staticHeight//TblSetRowStaticHeight). Признак необходимо установить, если нежелательно изменять высоту при вводе текста.
  3. Признак возможности выбора строки (selectable/TblRowSelectable /TblSetRowSelectable).

 

Свойства таблицы

 

  1. Идентификатор на форме (id//).
  2. Границы поля (bounds/TblGetBounds/ TblSetBounds)
  3. Признак визуализации (visible//TblDrawTable,TblEraseTable). Этот признак присутствует у всех контролов в PalmOS. XxxDrawyyy его выставляет, а XxxEraseyyy его снимает. При снятом признаке никаких операций с таблицей не производится.
  4. Признак возможности редактирования (editable//). Странный признак, при его выставлении TblHandleEvent игнорирует все сообщения.
  5. Признак наличия активного поля редактирования (editing/TblEditing/).
  6. Признак наличия выбранной клетки (selected/TblGetSelection/ TblSelectItem, TblUnhighlightSelection). Также существует полудокументированная функция для выбора клетки без отрисовки TblSetSelection.
  7. Приложение обеспечивает полосу прокрутки для поля редактирования (hasScrollBar//TblHasScrollBar). Обратите внимание, что поле указывает на признак для поля редактора, а не для таблицы!
  8. Признак использования (usable//)
  9. Число строк (numRows/TblGetNumberOfRows/)
  10. Число столбцов (numColumns/TblGetNumberOfColumns /)
  11. Текущая строка (currentRow/TblGetSelection / TblSelectItem, TblUnhighlightSelection) Также существует полудокументированная функция для выбора клетки без отрисовки TblSetSelection.
  12. Текущий столбец (currentColumn/TblGetSelection / TblSelectItem, TblUnhighlightSelection) Также существует полудокументированная функция для выбора клетки без отрисовки TblSetSelection.
  13. Верхняя показываемая строка (topRow/TblGetTopRow/). Похоже, что это рудимент попыток использовать контрол с вертикальной прокруткой. Поскольку функции для установки topRow не существует, то документированного способа показать таблицу не с первой строки просто не существует.
  14. Массив свойств столбцов (columnAttrs//)
  15. Массив свойств строк (rowAttrs//)
  16. Массив свойств клеток (items//). Клетки располагаются построчно.
  17. Активный контрол редактора (currentField/TblGetCurrentField /). Осмысленное значение возвращается только в режиме активного поля редактирования.

 

 

Прочие функции

  1. Для рисования контрола существуют три функции: TblDrawTable, TblRedrawTable, TblEraseTable. Первая рисует таблицу, вторая перерисовывает только грязные строки с признаком invalid, а третья очищает прямоугольник в координатах таблицы.
  2. Функция TblHandleEvent обрабатывает и генерирует сообщения, затрагивающие таблицу.
  3. Функция TblGetLastUsableRow возвращает последнюю используемую строку.
  4. Функции для вставки-удаления строк TblInsertRow и TblRemoveRow просто сдвигают и раздвигают строки без изменения их числа.

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;

}

 

Вертикальная прокрутка

Что делать, если число строк в вашем массиве больше, чем помещается на экране? В этом случае стоит применить метод “окна”. Визуально представьте контрол как окно, скользящее по массиву. Кнопки вверх и вниз перемещают “окно” с контролом по массиву.

Для отслеживания позиции окна заведите переменную – индекс верхней видимой строки массива. Тогда операции с таблицей будут выглядеть следующим образом:

Рисование:

  1. Если число строк массива меньше или равно числу строк контрола, то сбросьте индекс верхней строки в 0 и запретите кнопки прокрутки. Оставьте признак usable только у необходимого числа строк контрола.
  2. Если число строк больше чем выделено, то разрешите кнопки прокрутки. Установите признак usable у всех строк контрола.
  3. Заполните все usable строки данными, начиная с первой видимой строки. Для простоты рисования в коллбэках заполните поле id или data строки индексом строки в массиве. Используйте это поле в коллбэках.
  4. Вызовите TblDrawTable

 

Прокрутка вверх:

  1. При нажатии на кнопку уменьшите индекс верхней строки на размер страницы. При получении отрицательного числа индекс следует обнулить.
  2. Перерисуйте таблицу

 

Прокрутка вниз:

  1. При нажатии на кнопку увеличьте индекс верхней строки на размер страницы.
  2. Если индекс получился больше, чем число строк массива минус число строк контрола, то уменьшите его до этой величины
  3. Если полученная величина меньше нуля, то индекс следует обнулить.
  4. Перерисуйте таблицу

 

Поскольку все операции перерисовывают таблицу полностью, пользоваться TblRedrawTable бессмысленно.

 

Горизонтальная прокрутка

Общепризнанно, что горизонтальная прокрутка - это моветон. Но иногда избавиться от нее невозможно. Можно выделить два способа реализации горизонтальной прокрутки:

 

Горизонтальная прокрутка однородных столбцов.

Пример однородных столбцов можно найти в реализации табличного процессора. Все клетки в такой таблице несут одну и ту же функциональность. В этом случае горизонтальную прокрутку  можно реализовать по аналогии с вертикальной прокруткой методом “скользящего окна”. Окно будет скользить не по одному, а по двум измерения, и его положение будет задаваться двумя индексами. Все алгоритмы вертикальной прокрутки прекрасно адаптируются под этот случай.

 

Горизонтальная прокрутка неоднородных столбцов

Чаще встречается иная задача: показать таблицу с десятью столбцами. Число столбцов заведомо известно и невелико, но все они на экран не помещаются. В этом случае можно упростить реализацию.

Реализуем таблицу из всех столбцов массива. Заполним все клетки таблицы своими стилями и данными.

Заведем переменную с индексом первого видимого столбца. Кнопки горизонтальной прокрутки будут изменять его. Разумнее всего прокручивать по одному столбцу.

А теперь важный шаг: перед рисованием выставьте атрибут usable только у тех столбцов, которые помещаются в границы таблицы. В вычислениях не забудьте про межклеточные интервалы. В принципе можно установить признак usable у всех клеток, начиная с первой видимой,  при этом данные, выходящие за границы будут отсечены (clip), но процедура рисования будет честно рисовать все ячейки. Если скорость такого пустого рисования устраивает, то процедуру прокрутки можно упростить.

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