Мир InterBase

         

Локальная сортировка


Рассмотрим локальную сортировку на примере Sorting, который входит в поставку FIBPlus.

Как и все остальные примеры, приложение Sorting вы можете наши на сайте http://www.fibplus.net

Пример использует таблицу EMPLOYEE из базы данных Employee.gdb (рис. 2.67)

Рис 2.67. Внешний вид формы приложения, демонстрирующего локальную сортировку в TpFIBDataSet

MainDS.SelectSQL: SELECT * FROM EMPLOYEE

Все записи, полученные от сервера в результате запроса, сохраняются в локальном буфере компонента TpFIBDataSet.

Это одна из причин, из-за которой не рекомендуется использовать TpFIBDataSet для слишком больших выборок, - вам просто может не хватить оперативной памяти и работа приложения замедлится.

Предположим, что пользователь хочет отсортировать записи по полю FIRST_NAME. Очевидно, что мы можем сформировать новый запрос:

SELECT * FROM EMPLOYEE
ORDER BY FIRST_NAME

Чтобы получить новый порядок записей, необходимо переоткрыть наш запрос. Это работает, но означает, что мы будем повторно получать от сервера все •записи, несмотря на то что нам нужно было всего лишь поменять их визуальный порядок. Если предполагать, что нашим приложением могут пользоваться одновременно несколько пользователей, то затраты на подобные "сортировки" могут оказаться довольно значительными. Тем не менее выход существует, поскольку TpFIBDataSet позволяет сортировать данные внутри своего локального буфера, т. е. без необходимости получения всех записей заново.
Для этого мы должны воспользоваться одним из возможных вариантов метода DoSort или DoSortEx:

procedure DoSort(Fields: array of const; Ordering: array of Boolean); virtual;
procedure DoSortEx(Fields: array of integer; Ordering: array of Boolean); overload;
procedure DoSortEx(Fields: TStrings; Ordering: array of Boolean); overload;

Метод DoSortEx доступен в FIBPlus начиная с версии Delphi 4.

Первый параметр всех трех процедур - это список полей, по которым мы хотим отсортировать данные. В случае DoSort это могут быть названия полей или номера полей. Первый вариант DoSortEx позволяет нам использовать только список с номерами полей, а второй вариант DoSortEx предполагает, что мы заполнили список Fields названиями полей. Параметр Ordering во всех трех случаях указывает направление сортировки по каждому из полей. Таким образом, мы можем отсортировать наш запрос по полю FIRST_NAME следующим образом: 


DoSort(['FIRSN_NAME'], [True]);
Или: DoSortEx([1], [True]);

[True] означает, что поле сортируется по возрастанию (ASCENDING).

В сущности, использование этих методов очевидно, однако иногда возникает вопрос, связанный с динамическим формированием списков полей. Рассмотрим подробнее наш пример, который демонстрирует, как создавать параметры для DoSortEx динамически. Предполагается, что пользователь сможет нажимать на заголовки DBGridl, указывая, таким образом, поле, которое будет участвовать в сортировке. Повторное нажатие на колонку, которая уже участвует в сортировке, будет изменять порядок сортировки по этой колонке на противоположный. Сначала мы опишем вспомогательный класс для хранения информации о молях, которые будут участвовать в сортировках.

type
TOrderStringList = class(TStringList) 
protected
function GetAscending(Index: Integer): boolean;
procedure SetAscending (Index: Integer; Value: boolean); 
public
property Ascending[Index: Integer]: boolean read GetAscending write SetAscending; 
end;
...
{ TOrderStringList }
function TOrderStringList.GetAscending(Index: Integer):
boolean;
begin
Result := boolean(integer(Objects[Index])); 
end;
procedure TOrderStringList.SetAscending(Index: Integer; Value:
boolean); 
begin
Objects [ Index] := pointer (integer (Value)); 
end;

Очевидно, что данный класс является списком строк - названия полей, а также хранит для каждого поля, включенного в список, порядок сортировки в виде свойства Ascending. Ниже вы видите описание класса формы, а также два основных обработчика событий (OnCreate, OnDestroy). При создании формы мы создаем экземпляр класса TOrderStringList, а при ее уничтожении - удаляем. 

TMainForm = class(TForm) 
MainDB: TpFIBDatabase; 
MainDS: TpFIBDataSet; 
MainTr: TpFIBTransaction; 
DataSourcel: TDataSource; 
DBGridl: TDBGrid; 
Buttonl: TButton; 
Label2: TLabel;
procedure DBGridlTitleClick(Column: TColumn); 
procedure FormCreate(Sender: TObject); 
procedure FormDestroy(Sender: TObject); 
procedure ButtonlClick(Sender: TObject); 
private
{ Private declarations } 
public
{ Public declarations } 
SortFields: TOrderStringList; 
procedure ReSort; 
end;
procedure TMainForm.FormCreate(Sender: TObject); 
begin
SortFields := TOrderStringList.Create;
end;
procedure TMainForm.FormDestroy(Sender: TObject); 
begin
SortFields.Free; 
end;
Теперь напишем обработчик события OnTitleClick у компонента DBGridl: procedure TMainForm. DBGndlTitleCiiCK (Column: TColumn); 
const OrderScr: array [boolean] of string = ('(DESC)',
'(ASC) ') ; 
var aField: string;
aFieldlndex: integer; 
begin
aField := Column.FieldName;
aFieldlndex := SortFields.IndexOf(aField) ;
if aFieldlndex = -1 then begin
SortFields.Add(aField);
SortFields.Ascending[SortFields.Count - 1] := true;
Column.Field.Display-Label := Column. Field.FieldName + OrderStr[true]; 
end 
else begin
SortFields.Ascending[aFieldlndex] := not 
SortFields.Ascending[aFieldlndex];
Column.Field.Display-Label := Column.Field.FieldName +
OrderStr[SortFields.Ascending[aFieldlndex]]; 
end; 
ReSort; 
end;



Смысл обработчика состоит в следующем: при нажатии пользователем на заголовок мы проверяем, есть ли данное поле в нашем списке сортировки. Если нет, то мы добавляем его и формируем новый заголовок для колонки, который теперь будет состоять из названия поля и порядка сортировки (ASC) или (DESC). Если же поле уже было включено в сортировку, то мы лишь меняем порядок сортировки. В обоих случаях мы должны вызвать процедуру ReSort, описанную ниже:

procedure TMainForm.ReSort; 
var Orders: array of boolean;
Index: Integer; 
begin
if SortFields.Count = 0 then begin MainDS.CloseOpen(false); 
exit; 
end;
SetLength(Orders, SortFields.Count);
for Index := 0 to pred(SortFields.Count) do
Orders[Index] := SortFields.Ascending[Index];
MainDS.DoSortEx(SortFields, Orders); 
end;

Данная процедура формирует списки для метода DoSortEx на основе списка SortFields и пересортировывает записи. В случае, если наш список сортировки пустой, мы должны вернуться к "стандартному" порядку записей. Для этого вызывается метод CloseOpen.

Параметр False означает, что мы не хотим автоматически получать сразу все записи от сервера.

Если наш список полей не пустой, то мы должны сформировать массив Orders. Это делается, как видно, достаточно легко. Используя динамические массивы, мы сначала задаем длину Orders равной количеству полей в списке SortFields, а потом последовательно заполняем Orders значениями свойства Ascending списка SortFields. Остается только вызвать метод DoSortEx - и сортировка б\дет выполнена.
Чтобы закрыть вопрос целиком, нам остается только предоставить пользователю возможность исключать поля из сортировки. Для этого мы положим на форму кнопку Button 1 (см. рис. 46, заголовок "Delete column from Sorting"):

procedure TMainForm.ButtonlClick(Sender: TObject); 
var aField: string;
aFieldlndex: integer; 
begin
aField := DBGridl.SelectedField.FieldName;
aFieldlndex := SortFields.IndexOf(aField);
if aFieldlndex <> -1 then begin
SortFields.Delete(aFieldlndex); 
DBGridl.SelectedField.DisplayLabel := aField; 
ReSort; 
end; 
end;

Пользователь выделяет поле, которое хочет удалить из сортировки, и нажимает на кнопку "Delete column from Sorting", после чего это поле удаляется из списка SortFields и производится пересортировка записей.


Содержание раздела