(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Таблицы TGrid и TStringGrid в FireMonkey

Продолжая переносить клиент на платформу 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);
var c,r : integer;
begin
    with grid do begin
        for r := 0 to RowCount - 1 do begin
            cells[0,r] := Format('Cell[0,%d]', [r]);
        end;
    end;
end;

Для работы со столбцами у таблицы есть несколько свойств и методов^
    function ColumnByIndex(const Idx: Integer): TColumn;
    function ColumnByPoint(const X, Y: Single): TColumn;
    property ColumnCount: Integer read GetColumnCount;
    property ColumnIndex: Integer read FColumnIndex write SetColumnIndex;
    property Columns[Index: Integer]: TColumn read GetColumn;

Первые два метода возвращают столбец, впрочем, как и свойство Columns. Заметьте ColumnByIndex получает в качестве параметра - индекс столбца, хотя лично я ожидал, наличия перегруженной версии с параметром - именем столбца. Ибо если мы реализуем в коде перестановку столбцов местами, то такой метод просто необходим. Как и в запросах - FieldByName(). Возможно в будущем такой функционал и появится, а сейчас мы можем реализовать класс помощник:

  TStringGridHelper = class helper for TStringGrid
    public
      function ColumnByName(cName : string) : TColumn;
  end;
......
function TStringGridHelper.ColumnByName(cName: string): TColumn;
var c : integer;
begin
    for c := 0 to ColumnCount - 1 do begin
        result := Columns[c];
        if result.Name = cName then exit;
    end;
    result := nil;
end;

Теперь давайте попробуем создать новый столбец с чекбоксом. Расширим код обработчика создания формы:
var col : TColumn;
begin
    col := TCheckColumn.Create(grid);
    col.Name := 'CheckColumn';
    grid.AddObject(col);

 AddObject здесь добавляет столбец в конец коллекции. Однако, например, InsertObject не переопределен. Так что в середину столбец добавить не получится.
 Теперь мы хотели бы обрабатывать событие изменения состояния флажка. Для этого давайте обратимся к тому, как выглядит описание самого столбца TCheckColumn:

 TCheckColumn = class(TColumn)
  private
    function CreateCellControl: TStyledControl; override;
    procedure DoCheckChanged(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
  end;

Мы видим, что колонка с чекбоксом это почти самая обычная колонка, в которой перекрыт по сути один метод - CreateCellControl, который судя по названию делает ни что иное, как создание элемента управления, который будет находится в ячейке столбца. Конструктор в данном случае просто вызывает родительский.

 Посмотрим на CreateCellControl:

function TCheckColumn.CreateCellControl: TStyledControl;
begin
  Result := TCheckCell.Create(Self);
  Result.StyleLookup := 'checkboxstyle';
  TCheckCell(Result).OnChange := DoCheckChanged;
end;

Создается экземпляр TCheckCell, являющийся прямым наследником TCheckBox, но без добавления свойств и методов, можно сказать они одинаковы. Устанавливается стиль, задается обработчик события OnChange - изменение состояния флага. Теперь посмотрим на установленный обработчик:

procedure TCheckColumn.DoCheckChanged(Sender: TObject);
begin
  if Grid = nil then
    Exit;
  if FUpdateColumn then
    Exit;
  with StringToPoint(TFmxObject(Sender).TagString) do
  begin
    Grid.SetValue(trunc(X), trunc(Y), TStyledControl(Sender).Data);
    if Assigned(Grid.FOnEdititingDone) then
      Grid.FOnEdititingDone(Grid, trunc(X), trunc(Y));
  end;
end;

Как мы видим, обработчик перебрасывает нас в то самое событие 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);
const      colors : array[0..2] of TAlphaColor = (claRed, claBlue, claGreen);
var bm : TBitmap;

begin
    case col of
        0 : value := Format('cell[%d,%d]',[col, row]);
        1 : value := (row mod 2) = 0;
        2 : value := Row * 10;
        3 : begin
                bm := TBitmap.Create(20,20);
                with bm.Canvas do begin
                    BeginScene();
                    Clear(colors[row mod 3]);
                    EndScene();
                end;
                value := ObjectToVariant(bm);
            end;
        4 : value := row mod 3;
    end;
end;

что дает нам следующую картинку:

 Здесь например для TCheckColumn значение value будет устанавливать сам флажок. Но сам элемент - TCheckBox имеет еще и текст, который мы тоже иногда хотим задавать, даже не иногда о часто. Уже после создания формы мы также можем получить доступ к самим элементам:

procedure TMainForm.Button1Click(Sender: TObject);
var tc : TTextCell;
    r : integer;
    cc : TCheckCell;
begin
    with grid do begin
        for r := 0 to RowCount - 1 do begin
            tc := columns[0].CellControlByRow(r) as TTextCell;
            if assigned(tc) then
                tc.Text := Format('_Cell[0,%d]', [r]);

            cc := columns[1].CellControlByRow(r) as TCheckCell;
            if assigned(cc) then
                cc.Text := boolToStr(cc.IsChecked);

        end;
    end;
end;

Можно вернуться к той проблеме, почему у нас нельзя получить контролы ячеек пока форма не создана. Так говорить на самом деле не корректно, ибо контролы нельзя получить пока таблица не показана, а не форма не создана. Элементы управления ячеек находятся в поле FCellControls: array of TStyledControl в TColumn. Мы можем заметить, что массив этот инициализируется в protected-методе UpdateColumn(), следовательно напрямую его вызвать мы не можем. UpdateColumn() вызывается в TCustomGrid.UpdateColumns, который может быть вызван из унаследованного от TStyledControl - ApplyStyle(), где тот вызывается в ApplyStyleLookup и UpdateStylе. Так что вызов этих двух методов таблицы в обработчике OnCreate формы позволит нам создать из заполнить таблицу, получив доступ к элементам ячеек (в OnGetValue прогресс бар (2) закомментирован):

procedure TMainForm.FormCreate(Sender: TObject);
var r : integer;
    pc : TProgressCell;
begin
    grid.ApplyStyleLookup(); // UpdateStyle();
    for r := 0 to grid.RowCount - 1 do begin
        pc := grid.columns[2].CellControlByRow(r) as TProgressCell;
        pc.Value := pc.Max / (r+1);
    end;
end;

Последний момент - создание своего типа колонки, например, TComboColumn, можно реализовать, например, так:

  TComboBoxCell = class(TComboBox)
  end;

  TComboColumn = class(TColumn)
    private
      FItems : TStrings;
      function CreateCellControl: TStyledControl; override;
    public
      constructor Create(AOwner: TComponent); override;
      destructor Destroy(); override;
      property Items : TStrings  read FItems write FItems;
  end;
....

constructor TComboColumn.Create(AOwner: TComponent);
begin
    inherited;
    FItems := TStringList.Create();
end;

function TComboColumn.CreateCellControl: TStyledControl;
var li : TListBoxItem;
    str : string;
begin
    result := TComboBoxCell.Create(grid);
    for str in FItems do begin
        li := TListBoxItem.Create(grid);;
        li.Text := str;
        li.parent := result;
        result.AddObject(li);
    end;
end;

destructor TComboColumn.Destroy();
begin
    FItems.Free();
    inherited;
end;

а создание колонки соответственно такое:
    cc := TComboColumn.Create(grid);
    with cc.Items do begin
        add('two');
        Add('one');
    end;
    grid.AddObject(cc);

Пробрасывание OnChange можно реализовать так же как и, например, в TCheckColumn.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 20.01.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
JIRA Software Commercial (Cloud) Standard 10 Users
КОМПАС-3D v17 Home
SAP CRYSTAL Server 2013 WIN INTL 5 CAL License
Allround Automation PL/SQL Developer - Unlimited license
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
CASE-технологии
СУБД Oracle "с нуля"
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
Delphi - проблемы и решения
Мастерская программиста
ЕRP-Форум. Творческие дискуссии о системах автоматизации
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100