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.

Здесь направление сортировки всегда одинаково для всех столбцов. Должно быть довольно легко реализовать направление сортировки, путем изменения результата сравнения каждого столбца в соответствии с его направлением сортировки, по возрастанию или по убыванию. Я не пробовал это.

Другие вопросы по тегам