Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Hello, World!
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  12:57[Войти] | [Зарегистрироваться]

Grid с человеческим лицом

Андрей Пляко
дата публикации 19-09-2002 10:53

Grid с человеческим лицом

    Сетка (Grid) — очень удобный компонент для представления данных. К несчастью, внешний вид Borland'овских сеток крайне непригляден; да и работать с ними пользователю, подчас, неудобно. Я выделил два критерия, которым, на мой взгляд, должна удовлетворять хорошая сетка:

  1. Сетка должна "покрывать" весь компонент; компонент не должен быть шире представляемой им сетки. Другими словами, в сетке должна быть (хотя бы одна) растягиваемая колонка, ширина которой меняется при изменении размеров сетки (например, при resize'е формы).

  2. При изменении пользователем ширины колонки, должна соответствующим образом меняться ширина смежной с ней колонки, так чтобы создавалось впечатление, будто пользователь просто передвинул "границу" между двумя колонками.

Пример такой сетки представлен в проекте GridResize.

    Так как многие пользуются не стандартными сетками, а каким-то их модификациями, то вместо написания очередного компонента на базе TStringGrid, я решил написать специальный модуль (VCLRoutine) и продемонстрировать в проекте GridResize, как с его помощью можно сделать любую сетку хорошей. То есть, воспользовавшись модулем VCLRoutine вы можете привить своей любимой сетке либо способность растягиваться на всю доступную ширину, либо возможность по-умному изменять ширину колонки, либо и то и другое. Например, все это можно проделать с DataAware сетками; пример такой модификации стандартной сетки TDBGrid представлен в проекте GridDBResize. Оба демонстрационных проекта (под Delphi 5) расположены здесь.

    Кроме того, модуль VCLRoutine содержит еще несколько полезных процедур, которые, впрочем, никак не задействованы в проектах Grid[DB]Resize, но которые могут понравится и/или пригодится читателю. В меру возможностей код модуля снабжен пояснительными комментариями.

    В последующих двух подразделах приводится описание модуля VCLRoutine и дается комментарий по наиболее сложным кускам кода проекта GridResize.

Модуль VCLRoutine
  • ResizeCustomGrid (aGrid: TCustomGrid; SizeColumn: Integer=-1)
    Процедура изменяет ширину колонки с номером SizeColumn так, чтобы сетка "покрывала" весь компонент. Если SizeColumn не указан, то растягиваться будет последняя колонка сетки. Если растягиваемая колонка не видна на экране, то ее ширина не изменяется. Вызов этой функции логично производить при изменении ширины сетки; например, в обработчике события OnResize формы.

  • ResizeCustomDBGrid (aGrid: TCustomDBGrid; SizeColumn: Integer=-1)
    Это DB Aware аналог процедуры ResizeCustomGrid; в ней просто учитывается возможное наличие "индикатора" dgIndicator in Options

  • ResizeStatusBar (aBar: TStatusBar; DontResize: SetOfByte=[])
    Процедура делает ширину всех панелей TStatusBar одинаковыми; ширина панелей, номера которых указаны в множестве DontResize остается прежней. Эту процедуру тоже логично вызывать из обработчика события OnResize.

  • CanResizeCustomGridColumn (aGrid: TCustomGrid; const x,y: Integer): Integer
    Эта функция позволяет узнать, может ли пользователь вручную изменить ширину колонки, если курсор мышки имеет координаты x,y. Функция возвращает номер колонки, размер которой будет изменяться; функция возвращает -1, если пользователь не может изменить ширину никакой колонки. Пояснения по использованию этой функции даны в следующем подразделе.

  • CanResizeCustomDBGridColumn (aGrid: TCustomGrid; const x,y: Integer): Integer
    Это DB Aware аналог процедуры CanResizeCustomGridColumn

  • SwapRows (aGrid: TStringGrid; const Row1, Row2: Integer)
    Простенькая процедура; она меняет местами две строки (Row1 и Row2) сетки TStringGrid

Комментарии к проекту GridResize

    Процедура ResizeCustomGrid позволяет легко удовлетворить первому критерию хорошей сетки; достаточно вот так описать обработчик события OnResize формы:

procedure TGridForm.FormResize(Sender: TObject);
begin
  // Растягиваем последнюю колонку
  ResizeCustomGrid(MyGrid);
end;

Если бы сетка MyGrid "лежала" не на форме, а на панели (TPanel) или еще каком-нибудь контейнере, то мы бы делали вызов ResizeCustomGrid в обработчике события OnResize этого контейнера. С этим все ясно.

    А вот с задачей "по-умному изменять ширину колонок" придется повозиться. Когда пользователь нажмет левую клавишу мышки, то функция CanResizeCustomGridColumn позволит нам узнать начинает ли пользователь растягивать/сужать колонку, или же нет. К сожалению, событие OnMouseDown у сетки не возникает, если щелчок происходит на "шапке" сетки. А так как нас интересуют именно щелчки в области "шапки", то вместо использования события OnMouseDown нам придется напрямую обрабатывать Windows-сообщение WM_LBUTTONDOWN, которое возникает при нажатии пользователем левой клавиши мышки.

    Для этого нам надо будет "подменить" функцию WindowProc, отвечающую за обработку Windows-сообщений. Делается это так: в private секции формы заводится переменная типа TWndMethod (эта переменная будет хранить ссылку на "старую" WindowProc сетки) и описывается процедура, которая станет новой WindowProc:

TGridForm = class(TForm)
// [ ... Skiped ... ]
private { Private declarations }
// [ ... Skiped ... ]
  FWndOrigin: TWndMethod;  // WindowProc сетки MyGrid
  // Подложная WindowProc сетки MyGird, обрабатывает WM_LBUTTONDOWN
  procedure FGridDownMtd(var Msg: TMessage);
end;
Сама "подмена" осуществляется в обработчике OnCreate формы:
procedure TGridForm.FormCreate(Sender: TObject);
begin
// [ ... Skiped ... ]
  // Подмена WindowProc
  FWndOrigin := MyGrid.WindowProc;
  MyGrid.WindowProc := FGridDownMtd;
end;

Все, теперь обработку Windows-сообщений сетки осуществляет метод FGridDownMtd. Эта процедура имеет следующую структуру:

procedure TGridForm.FGridDownMtd(var Msg: TMessage);
begin
  if MSg.Msg = WM_LBUTTONDOWN then
    begin
// [ ... Skiped ... ]
    end;
  // Передаем сообщение изначальной WindowProc
  FWndOrigin(Msg);
end;

То есть наша процедура в обязательном порядке "доверяет" обработку Windows-сообщения старому обработчику, ссылку на который мы сохранили в FWndOrigin. Но если на обработку пришло сообщение WM_LBUTTONDOWN, то FGridDownMtd предварительно проделает некоторые дополнительные действия.

    Если мы хотим, чтобы колонки изменяли ширину "по-умному", то надо, в процедуре FGridDownMtd запоминать: колонку, ширина которой будет изменяться, изначальную ширину этой колонки и, например, сумму ширин этой и смежной колонок. В соответствии с этим, мы заведем еще три приватных поля в форме:

TGridForm = class(TForm)
// [ ... Skiped ... ]
private { Private declarations }
  FResizeColumn: Integer;  // Номер resize'уемой колонки
  FOriginSize: Integer;    // Ее изначальная ширина
  FResizeSum: Integer;     // Сумма ширин resize'уемой и смежной колонок
  FWndOrigin: TWndMethod;  // WindowProc сетки MyGrid
  // Подложная WindowProc сетки MyGird, обрабатывает WM_LBUTTONDOWN
  procedure FGridDownMtd(var Msg: TMessage);
end;
инициируем их при создании формы:
procedure TGridForm.FormCreate(Sender: TObject);
begin
  // Инициализация переменных
  FResizeColumn := -1;
  FResizeSum := 0;
  FOriginSize := 0;
  // Подмена WindowProc
  FWndOrigin := MyGrid.WindowProc;
  MyGrid.WindowProc := FGridDownMtd;
end;
и соответствующим образом опишем процедуру FGridDownMtd:
procedure TGridForm.FGridDownMtd(var Msg: TMessage);
begin
  if MSg.Msg = WM_LBUTTONDOWN then
    begin
     // Смотрим, какую колонку при этом будем растягивать
     FResizeColumn :=
       CanResizeCustomGridColumn(MyGrid, Msg.LParamLo, Msg.LParamHi);
     // Если какую-то, то запоминаем параметры изменяемых колонок
     if FResizeColumn>=0 then
        with MyGrid do
         begin
           FOriginSize := ColWidths[FResizeColumn];
           FResizeSum := FOriginSize;
           // Если risize'аем не последнюю колонку, то...
           if ColCount>FResizeColumn+1 then
              // ...прибавляем к FResizeSum ширину смежной (справа) колонки
              inc(FResizeSum, ColWidths[FResizeColumn+1]);
         end;
    end;
  // Передаем сообщение изначальной WindowProc
  FWndOrigin(Msg);
end;

    Осталось совсем простая задача: правильно отреагировать на отжатие клавиши мыши; для этого вполне походит событие OnMouseUp. В обработчике этого события нам надо так изменить ширину смежной колонки, чтобы FResizeSum (сумма растянутой и смежной колонок) осталось прежней. Добавим к этому возможность отменить действия пользователя, и в результате получим вот такой код:

procedure TGridForm.MyGridMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var NewWidth: Integer;
begin
  // Если мы перетаскиваем какую-нибудь колонку, то...
  if FResizeColumn>=0 then
     with Sender as TStringGrid do
      begin
       // Если это не последняя колонка, то
       if FResizeColumn < ColCount-1 then
         begin
          // Смотрим, какой размер должен быть у смежной колонки
          NewWidth := FResizeSum-ColWidths[FResizeColumn];
          // Если отрицательный
          if NewWidth<=0 then // то отменяем действие пользователя
               ColWidths[FResizeColumn] := FOriginSize
               // иначе, модифицируем ширину смежной колонки
          else ColWidths[FResizeColumn+1] := NewWidth;
         end;
       // На всякий случай выравниваем ширину последней колонки
       ResizeCustomGrid(Sender as TStringGrid);
      end;
  // Сбрасываем флаг перетаскиваемой колонки
  FResizeColumn := -1;
end;

    Вот мы и получили хорошую сетку! Разумеется, если мы предполагаем пользоваться такой сеткой во многих проектах; то имеет смысл сделать собственный компонент, применив продемонстрированную выше технику.

Исходники проекта GridResize и GridDBResize (Zip-архив 11K)

Пляко Андрей
Специально для Королевства Delphi




Смотрите также материалы по темам:
[TCustomGrid] [TDBGrid] [Изменение размеров компонент, нестандартная форма] [Отображение списков, сеток]

 Обсуждение материала [ 16-08-2009 13:56 ] 28 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования