| 
   
Грид с объединяемыми ячейками данных
  
Автор: Михаил Зайкин
 
    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. 
Вот так вот. Надеюсь, эта тема будет интересна не только новичкам, но и 
продвинутым программистам. Жду ваших замечаний, советов и пожеланий. Спасибо. 
 
           |