Delphi VirtualTreeView Сортировать несколько столбцов?
Я слышал много похвал о компоненте VirtualTreeView и смотрел на его использование в процессе переписывания, который мы делаем. В настоящее время мы используем StringGrid.
Я не могу найти способ сортировки нескольких столбцов, хотя сортировка по одному столбцу работает отлично. Есть ли способ сделать что-то похожее на щелчок по столбцу 1> сортировка, Ctrl+ щелчок по столбцу 2> сортировка столбца 2 после столбца 1 и т. Д.?
В частности, я хочу отсортировать как минимум три столбца: номер заказа, позицию, выпуск.
Заранее спасибо за помощь!
Вот код (немного упрощенный), с которым я тестирую теорию (не из того же проекта, на который есть ссылки выше):
Примечание: после вашего обновления я также отредактировал свой код, чтобы показать, какой он в данный момент. Ниже я разместил результаты такого рода:
type
PBatchDetails = ^TBatchDetails;
TBatchDetails = record
TheBatchKey
OperationKey,
PO,
Line,
Release,
Temp,
Notes : String;
TransDate : TDateTime;
end;
....
Sorting_Columns: array of TColumnIndex;
....
procedure TForm1.TreeHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
var
I: Integer;
begin
if not CtrlDown then //function I have to test Ctrl state.
begin
setlength(Sorting_Columns,0);
end;
SetLength(Sorting_Columns,length(Sorting_Columns)+1);
Sorting_Columns[Length(Sorting_Columns)-1] := HitInfo.Column;
tree.SortTree(HitInfo.Column,Sender.SortDirection,True);
if Sender.SortDirection=sdAscending then
Sender.SortDirection:=sdDescending
else
Sender.SortDirection:=sdAscending
end;
procedure TForm1.TreeCompareNodes(Sender: TBaseVirtualTree; Node1,
Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
BatchRec1 : PBatchDetails;
BatchRec2: PBatchDetails;
I: Integer;
begin
if length(Sorting_Columns) > 0 then
begin
BatchRec1 := Tree.GetNodeData(Node1);
BatchRec2 := Tree.GetNodeData(Node2);
if (not Assigned(BatchRec1)) or (not Assigned(BatchRec2)) then
Result:=0
else
begin
for I := High(Sorting_Columns) downto 0 do
begin
case Sorting_Columns[i] of
0,1: Result := Result + CompareDate(BatchRec1.TransDate,BatchRec2.TransDate); // col 0 is Date and col 1 is Time.
2: Result := Result + CompareText(BatchRec1.OperationKey,BatchRec2.OperationKey);
3: Result := Result + CompareText(BatchRec1.PO,BatchRec2.PO);
4: Result := Result + CompareText(BatchRec1.Line,BatchRec2.Line);
5: Result := Result + CompareText(BatchRec1.Release,BatchRec2.Release);
6: Result := Result + CompareText(BatchRec1.Temp, BatchRec2.Temp);
7: Result := Result + CompareText(BatchRec1.Notes,BatchRec2.Notes);
end; //end case;
if Result <> 0 then
Break;
end;
end;
end;
end;
Это дало следующие результаты (я показываю только три столбца, которые пытаюсь отсортировать здесь):
При первоначальной загрузке:
PO Line Release
153 7 2
153 7 1
153 1 1
153 1 2
153 4 1
153 6 2
153 6 1
120 3 2
120 3 1
153 2 1
153 4 2
120 2 1
153 4 1
120 1 1
153 3 1
153 2 1
111 2 1
111 1 5
111 1 1
111 4 2
111 3 1
111 4 1
111 1 3
111 1 2
111 1 4
После первого клика
PO Line Release
111 2 1
111 1 5
111 1 1
111 4 2
111 3 1
111 4 1
111 1 3
111 1 2
111 1 4
120 3 2
120 3 1
120 2 1
120 1 1
153 7 2
153 7 1
153 1 1
153 1 2
153 4 1
153 6 2
153 6 1
153 2 1
153 4 2
153 4 1
153 3 1
153 2 1
После второго клика
PO Line Release
153 7 2
153 7 1
153 6 2
153 6 1
153 4 1
153 4 2
153 4 1
111 4 2
111 4 1
153 3 1
120 3 2
120 3 1
111 3 1
153 2 1
153 2 1
120 2 1
111 2 1
153 1 1
153 1 2
120 1 1
111 1 5
111 1 1
111 1 3
111 1 2
111 1 4
После третьего нажатия
PO Line Release
111 1 1
120 1 1
153 1 1
111 2 1
120 2 1
153 2 1
153 2 1
111 3 1
120 3 1
153 3 1
111 4 1
153 4 1
153 4 1
153 6 1
153 7 1
111 1 2
153 1 2
120 3 2
111 4 2
153 4 2
153 6 2
153 7 2
111 1 3
111 1 4
111 1 5
Спасибо за ваше время!
1 ответ
Отключите все параметры автосортировки в целом. Then you need to implement OnCompareNodes along with OnHeaderClick events.
Вот надеюсь рабочий код (я сделал только быстрый тест:)
Цель состоит в том, чтобы хранить сортирующие столбцы в некоторой переменной (Sorting_Columns). Эту переменную вы можете подать в событие OnHeaderClick.
In the OnCompareNodes event, which will be triggered after SortTree function call, iterate through the variable from the last added column to the first added one and to the Result parameter pass the first nonzero comparision result. Теперь по-человечески - вы должны пройти через столбцы в обратном порядке, как они были "выбраны" и проверить, совпадают ли они, если да, перейти к ранее выбранному, если не разорвать цикл и передать результат.
Note that you are comparing two nodes (rows) in one event hit, what's the reason for the iteration and subsequent comparision of sorting columns.
type
PRecord = ^TRecord;
TRecord = record
ID: integer;
Text_1: string;
Text_2: string;
Text_3: string;
Date: TDateTime;
end;
...
var Sorting_Columns: array of TColumnIndex;
...
procedure TForm1.VirtualStringTree1CompareNodes(Sender: TBaseVirtualTree;
Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var Actual_Index: integer;
Data_1: PRecord;
Data_2: PRecord;
begin
if Length(Sorting_Columns) > 0 then
begin
Data_1 := VirtualStringTree1.GetNodeData(Node1);
Data_2 := VirtualStringTree1.GetNodeData(Node2);
if Assigned(Data_1) and Assigned(Data_2) then
for Actual_Index := High(Sorting_Columns) downto 0 do
case Sorting_Columns[Actual_Index] of
0: Result := Result + Data_1^.ID - Data_2^.ID;
1: Result := Result + CompareStr(Data_1^.Text_1, Data_2^.Text_1);
2: Result := Result + CompareStr(Data_1^.Text_2, Data_2^.Text_2);
3: Result := Result + CompareStr(Data_1^.Text_3, Data_2^.Text_3);
4: Result := Result + CompareDateTime(Data_1^.Date, Data_2^.Date);
end;
if Result <> 0 then
Break;
end;
end;
Слегка измененный код от @user532231, чтобы получить рабочее решение
type
PRecord = ^TRecord;
TRecord = record
ID: integer;
Text_1: string;
Text_2: string;
Text_3: string;
Date: TDateTime;
end;
...
var Sorting_Columns: array of TColumnIndex;
...
procedure TForm1.VirtualStringTree1CompareNodes(Sender: TBaseVirtualTree;
Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var Actual_Index: integer;
Data_1: PRecord;
Data_2: PRecord;
Matrix : array of integer;
I: Integer;
begin
if Length(Sorting_Columns) > 0 then
begin
Data_1 := VirtualStringTree1.GetNodeData(Node1);
Data_2 := VirtualStringTree1.GetNodeData(Node2);
if Assigned(Data_1) and Assigned(Data_2) then
begin
SetLength(Matrix,Length(Sorting_Columns));
for Actual_Index := 0 to High(Sorting_Columns) do
begin
case Sorting_Columns[Actual_Index] of
0: Matrix[Actual_Index] := Data_1^.ID - Data_2^.ID;
1: Matrix[Actual_Index] := CompareStr(Data_1^.Text_1, Data_2^.Text_1);
2: Matrix[Actual_Index] := CompareStr(Data_1^.Text_2, Data_2^.Text_2);
3: Matrix[Actual_Index] := CompareStr(Data_1^.Text_3, Data_2^.Text_3);
4: Matrix[Actual_Index] := CompareDateTime(Data_1^.Date, Data_2^.Date);
end;
end;
for I := 0 to Length(Matrix) - 1 do
if (Matrix[i] <> 0) then
begin
Result:=Matrix[i];
break;
end;
SetLength(Matrix,0);
end;
end;
end;
Разница в том, что вы должны помнить результат сравнения каждого столбца и затем возвращать первое наиболее значимое ненулевое значение (наиболее значимым является столбец, который был добавлен к сортировке первым). Вам не нужно зацикливаться от верхнего к низшему столбцу. Этот код нуждается в процедуре ОП TreeHeaderClick для добавления / удаления столбцов в Sorting_Columns.
Здесь направление сортировки всегда одинаково для всех столбцов. Должно быть довольно легко реализовать направление сортировки, путем изменения результата сравнения каждого столбца в соответствии с его направлением сортировки, по возрастанию или по убыванию. Я не пробовал это.