Грид с объединяемыми ячейками данных
Оформил: 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 в противном случае.
Отказ функции может
быть в следующих случаях:
- MergeMode=mmNone
- заданный диапазон включает только одну ячейку
- диапазон задан не с левого верхнего угла
- такой диапазон уже есть
- заданный диапазон конфликтует с уже имеющимися
- 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. Недостатки компонента.
- Событие OnCellDraw отсутствует. Но нам оно и не было нужно. Хотя…
Диапазоны разным цветом можно было бы…
- Содержимое диапазона отрисовывается при каждом вызове DrawCell для
ячейки диапазона. Т.е. если на экране видно 100 ячеек диапазона, столько и
будет и отрисовок. Ну и еще поскроллируйте туда, обратно.…
- Нет переноса слов.
- Нет выравнивания. Да, это Вам не Ексель.
- Когда курсором бегаем по гриду, пересекая объединения, замечаем разрывы на
правой и нижней границе.
- И самое неприятное. Попробуйте поверх окна с гридом (не важно, в runtime
или designtime) повозить другое окно. Или размеры окна примера поизменять. Вот
такие вот артефакты. Их появление связано с тем, что, собственно, большого
опыта по написанию именно визуальных компонент у меня нет.
5. Пример использования.
Приведенный пример показывает, как
осуществляется создание структуры таблицы, и осуществляется ввод текстовых
данных. Скачать архив проекта: MergeGrid.zip (10
K)
6. Совместимость.
Компонент и пример были созданы в D5.
Но компилировались и работали также и в D6.
Вот так вот. Надеюсь, эта тема будет интересна не только новичкам, но и
продвинутым программистам. Жду ваших замечаний, советов и пожеланий. Спасибо.
|