Таблицы TGrid и TStringGrid в FireMonkeyИсточник: blogkareliapro
Продолжая переносить клиент на платформу FireMonkey, решил использовать таблицы для отображения списка эпизодов. И столкнулся с тем, что возможности по настройки грида в режиме проектирования весьма и весьма скромны. Так что нужно немного углубиться в изучение… Эта статья писалась одновременно с изучением работы с таблицами, так что некоторые могут посчитать направление движения мысли в статье заведомо некорректным. Я создал новое тестовое приложение с одной формой MainForm и добавил на нее таблицу grid : TStringGrid, назначив ей align = alClient. Раскраску чередующихся строк можно установить с помощью свойства AlternatingRowBackground, высоту строки можно задать, используя RowHeight и количество строк - RowCount. Просмотрев доступные события таблицы вы врядли заметите что либо необычное, разве что свойство - OnEdititingDone, в котором подозреваю два раза ошибочно написан слог ti. В контекстном меню таблицы в режиме проектирования доступны два пункта - Add Item и Items Editor. В принципе при открытии мы видим достаточно уже знакомый редактор дочерних элементов. Не сказать, что редактировать столбцы таблицы удобно. Если вы добавите, такое количество столбцов в таблицу, что они не будут у вас вмещаться в таблицу по ширине при редактировании, то у вас не получится воспользоваться прокруткой. Так что настройка, например, ширины столбца будет исключительно вслепую. Вторым значимым минусом, является тот факт, что редактор столбцов позволяет нам создавать только простые строковые столбцы. Хотя тут мы как бы понимаем, что FMX должен спокойно позволять создавать столбцы с произвольными элементами. И открыв FMX.Grid.pas или справку, вы увидите что там есть такие элементы как TCheckColumn, TProgressColumn и т.д. Более того, можно заметить, что объявление подобных классов очень простое, и мы можем свободно самостоятельно их создавать. Сначала с помощью редактора создадим простой строковый столбец. Кстати, можете создать разу и второй столбец, установить ему Align=AlClient, ожидая что первый столбец при этом будет фиксированной ширины, а второй займет всю оставшуюся область, но тут возникают какие то явные проблемы с вычислением ширины, и, например, у меня при ширине таблицы в 600 пикселей, ширина столбца становится равной 6361 пиксель. Установка значения Align второго столбца в alScale, запуск и изменение размера формы (таблица имеет alClient) вообще намертво завесило систему с ошибкой вроде "Floating point overflow", спас только ctrl-alt-del. Вернемся к состоянию, когда у на столько один строковый столбец. Заполнить мы его можем при создании формы: procedure TMainForm.FormCreate(Sender: TObject); Для работы со столбцами у таблицы есть несколько свойств и методов^ Первые два метода возвращают столбец, впрочем, как и свойство Columns. Заметьте ColumnByIndex получает в качестве параметра - индекс столбца, хотя лично я ожидал, наличия перегруженной версии с параметром - именем столбца. Ибо если мы реализуем в коде перестановку столбцов местами, то такой метод просто необходим. Как и в запросах - FieldByName(). Возможно в будущем такой функционал и появится, а сейчас мы можем реализовать класс помощник: TStringGridHelper = class helper for TStringGrid Теперь давайте попробуем создать новый столбец с чекбоксом. Расширим код обработчика создания формы: AddObject здесь добавляет столбец в конец коллекции. Однако, например, InsertObject не переопределен. Так что в середину столбец добавить не получится. TCheckColumn = class(TColumn) Мы видим, что колонка с чекбоксом это почти самая обычная колонка, в которой перекрыт по сути один метод - CreateCellControl, который судя по названию делает ни что иное, как создание элемента управления, который будет находится в ячейке столбца. Конструктор в данном случае просто вызывает родительский. Посмотрим на CreateCellControl: function TCheckColumn.CreateCellControl: TStyledControl; Создается экземпляр TCheckCell, являющийся прямым наследником TCheckBox, но без добавления свойств и методов, можно сказать они одинаковы. Устанавливается стиль, задается обработчик события OnChange - изменение состояния флага. Теперь посмотрим на установленный обработчик: procedure TCheckColumn.DoCheckChanged(Sender: TObject); Как мы видим, обработчик перебрасывает нас в то самое событие onEdititingDone, которое с опечаткой в названии. Sender события - сама таблица, в дополнении мы имеем два параметра - координаты Х и У. Запустив приложение можно заметить, что клик в чекбоксы не изменяет их состояние. Очень впечатляет тот факт, что если вы покликаете в чекбоксы побольше, где будут и одинарные клики и двойные, то ваше приложение покажет вам AV при выходе, и если вы нажмете Continue, то получите Stack Overflow. Но вернемся к тому, что чекбокс не меняет состояние, можно заметить что галка там таки проскакивает, но сначала устанавливается и потом вновь сбрасывается. Давайте попробуем найти этому объяснение. Во первых мы можем поставить точку останова в TCheckBox.SetIsChecked. И убедиться, что флаг действительно устанавливается, а затем сбрасывается, т.е первый раз метод вызывается с параметром true, второй false. Первый раз это обычный клик в чекбокс, с корректным параметром. Второй раз - уже через таблицу. Чтобы убедиться смотрим стек вызовов: Первый вызов SetIsChecked выделен синим цветом, второй на вершине стека. Видим, что первый вызов исходит из самого чекбокса, и параметром там является true (на изображении не отмечено, но оно так). В чем получается суть: первый раз чекбокс меняет свое состояние. Его значение записывается в таблицу (TStringGrid.setValue) в виде уже строки. Потом обновляется столбец, где значение ячейки извлекается, и снова устанавливается. Здесь значение уже строка. И чек бокс при установке значения проверяет тип значения, и поскольку значение не булевое а строковое, то флаг сбрасывается. К чему все это нас приводит - StringGrid - только для строковых столбцов. Если вам требуется использовать различные столбцы - используйте простую таблицу TGrid. Поэтому удаляем с формы TStringGrid, и заменяем его на простой TGrid. С помощью редактора добавляем различные столбцы, здесь столбцы могут быть уже произвольные. В моем случае столбцы были в следующем порядке: TStringColumn, TCheckColumn, TProgressColumn, TImageColumn, TPopupColumn. Для заполнения ячеек таблицы свойства cells[] уже нет. Чтобы заполнить таблицу нам необходимо обращаться к самим элементам, содержащимся в ячейках. Получение элемента - TColumn.CellControlByRow(). Может возникнуть проблема - Если мы попытаемся получить элементы управления ячеек таблицы при создании формы (т.е в OnCreate) то получим пустые указатели. Для заполнения таблицы можно воспользоваться событием OnGetValue таблицы. Обработчик имеет 3 дополнительных параметра - столбец, строка и значение - вариант. Для заполнения своей таблицы я написал такой тестовый обработчик: procedure TMainForm.GridGetValue(Sender: TObject; const Col, Row: Integer; var Value: Variant); begin что дает нам следующую картинку: Здесь например для TCheckColumn значение value будет устанавливать сам флажок. Но сам элемент - TCheckBox имеет еще и текст, который мы тоже иногда хотим задавать, даже не иногда о часто. Уже после создания формы мы также можем получить доступ к самим элементам: procedure TMainForm.Button1Click(Sender: TObject); cc := columns[1].CellControlByRow(r) as TCheckCell; end; Можно вернуться к той проблеме, почему у нас нельзя получить контролы ячеек пока форма не создана. Так говорить на самом деле не корректно, ибо контролы нельзя получить пока таблица не показана, а не форма не создана. Элементы управления ячеек находятся в поле FCellControls: array of TStyledControl в TColumn. Мы можем заметить, что массив этот инициализируется в protected-методе UpdateColumn(), следовательно напрямую его вызвать мы не можем. UpdateColumn() вызывается в TCustomGrid.UpdateColumns, который может быть вызван из унаследованного от TStyledControl - ApplyStyle(), где тот вызывается в ApplyStyleLookup и UpdateStylе. Так что вызов этих двух методов таблицы в обработчике OnCreate формы позволит нам создать из заполнить таблицу, получив доступ к элементам ячеек (в OnGetValue прогресс бар (2) закомментирован): procedure TMainForm.FormCreate(Sender: TObject); Последний момент - создание своего типа колонки, например, TComboColumn, можно реализовать, например, так: TComboBoxCell = class(TComboBox) TComboColumn = class(TColumn) constructor TComboColumn.Create(AOwner: TComponent); function TComboColumn.CreateCellControl: TStyledControl; destructor TComboColumn.Destroy(); а создание колонки соответственно такое: Пробрасывание OnChange можно реализовать так же как и, например, в TCheckColumn. |