Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Грид с объединяемыми ячейками данных

Оформил: DeeCo

Автор: Михаил Зайкин

1. Что было нужно.

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

Опробовав замечательные MSHFlexGrid, ObjectiveGrid и получив пару десятков вопросов “а почему…”, “а как..”, и замечаний “а вотя я …, а он не…” решено было, что пусть грид будет поглупее, попроще, зато пользователь будет в полной мере контролировать структуру таблицы.

2. Что было сделано.

Определены типы объединения: TMergeMode=(mmNone,mmByCol,mmByRow,mmFree);
  • mmNone – объединение не производится, MergeCells всегда возвращает nil
  • mmByCol – объединяются клетки внутри одного столбца, из заданного для объединения диапазона используется его левая часть
  • mmByRow – объединяются клетки внутри одной строки, из заданного для объединения диапазона используется его верхняя часть
  • mmFree - объединяются клетки в любых диапазонах

Определен класс-диапазон:

TMZRange = class(TObject)
private
  FOwner: TMZMergeStringGrid;
  FRect: TGridRect;
  function GetValue: string;
  procedure SetValue(Value: string);
public
  constructor Create(AOwner: TMZMergeStringGrid;
    Range: TGridRect);
  function Contain(ACol, ARow: integer): boolean; overload;
  function Contain(ARect: TGridRect): boolean; overload;
  function Intersect(ARect: TGridRect): boolean;
  function Visible: boolean;
  function VisibleRect: TGridRect;
  property Rect: TGridRect read FRect;
  property Value: string read GetValue write SetValue;
end;
Небольшие пояснения:
  • Contain – содержит ли внутри клетку (диапазон)
  • Intersect – пересекается ли с диапазоном
  • Value – значение. В качестве оного используется значение левой верхней клетки. Данные в остальных клетках не изменяются.
  • VisibleRect – видимая часть диапазона
Остальное, думаю, понятно.
Определен, собственно, грид: TMZMergeStringGrid = class(TStringGrid)

Часть полей и функций была напрямую скопирована из приватной секции TCustomGrid.

Добавлены public функции и свойства:
function MergeCells(ARect:TGridRect):TMZRange
– пытается произвести объединение заданного диапазона и возвращает указатель в случае успеха и nil в противном случае.
Отказ функции может быть в следующих случаях:
  1. MergeMode=mmNone
  2. заданный диапазон включает только одну ячейку
  3. диапазон задан не с левого верхнего угла
  4. такой диапазон уже есть
  5. заданный диапазон конфликтует с уже имеющимися
function Range(ACol,ARow:integer):TMZRange
– диапазон, содержащий ячейку, nil – в случае отсутствия оного.

procedure SplitAllRanges


function SplitRange(ACol,ARow:integer):boolean


function SplitRange(rng:TMZRange):Boolean
– уничтожение диапазонов

property Ranges[Index:Integer]:TMZRange


property RangeCount:integer
– понятно

property RangeValue[ACol,ARow:integer]:string
– значение диапазона. Если не существует диапазона, включающего данную ячейку, возвращается Cells[ACol, ARow]

property MergeMode:TMergeMode
– понятно

Переписаны методы Paint и DrawCell.

Paint:
  • убрана неиспользуемая теперь переменная LineColor
  • в DrawCells убраны все вызовы DrawFocusRect
  • основном коде метода убран четвертый вызов DrawLines
  • в конце процедуры сами рисуем рамку фокуса DrawFocusRect
Как видите, здесь очень радикальные изменения. ;-)

DrawCell:
Краса и гордость класса. ;-) Метод полностью переписан.
Попробую объяснить, что же я тут наваял.
1) Рисуем границы.
Работаем мы не с фиксированными ячейками. Если ячейка не принадлежит ни одному диапазону – рисуем все ее границы. В противном же случае, внутри диапазона границы стираем, внешние же, напротив, пожирнее.
2) Рисуем содержимое ячейки.
Сначала рассчитываем размер области вывода, затем выводим текст. Все просто.

3. Использование компонента.

При использовании таблицы считаем, что в опциях не установлены флажки goRovMoving, goColMoving, goEditing, goAlwaysShowEditor. Эти ситуации, соответственно, не обрабатываются. Также не используется и InplaceEditor.

4. Недостатки компонента.

  1. Событие OnCellDraw отсутствует. Но нам оно и не было нужно. Хотя… Диапазоны разным цветом можно было бы…
  2. Содержимое диапазона отрисовывается при каждом вызове DrawCell для ячейки диапазона. Т.е. если на экране видно 100 ячеек диапазона, столько и будет и отрисовок. Ну и еще поскроллируйте туда, обратно.…
  3. Нет переноса слов.
  4. Нет выравнивания. Да, это Вам не Ексель.
  5. Когда курсором бегаем по гриду, пересекая объединения, замечаем разрывы на правой и нижней границе.
  6. И самое неприятное. Попробуйте поверх окна с гридом (не важно, в runtime или designtime) повозить другое окно. Или размеры окна примера поизменять. Вот такие вот артефакты. Их появление связано с тем, что, собственно, большого опыта по написанию именно визуальных компонент у меня нет.
5. Пример использования.

Приведенный пример показывает, как осуществляется создание структуры таблицы, и осуществляется ввод текстовых данных.
Скачать архив проекта: MergeGrid.zip (10 K)
6. Совместимость.

Компонент и пример были созданы в D5. Но компилировались и работали также и в D6.

Вот так вот. Надеюсь, эта тема будет интересна не только новичкам, но и продвинутым программистам.

Жду ваших замечаний, советов и пожеланий. Спасибо.
Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay