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