Сортировка столбцов ListView со стрелками

Я использую Delphi 6 и хочу добавить функциональность сортировки ListView, как это делается в проводнике Windows.

В первом тесте я (быстро и грязно) скопировал несколько исходных кодов из нескольких источников и внес небольшие изменения:

Это то, что у меня есть до сих пор (только быстро и грязно):

uses
  CommCtrls;

var
  Descending: Boolean;
  SortedColumn: Integer;

const
  { For Windows >= XP }
  {$EXTERNALSYM HDF_SORTUP}
  HDF_SORTUP              = $0400;
  {$EXTERNALSYM HDF_SORTDOWN}
  HDF_SORTDOWN            = $0200;

procedure ShowArrowOfListViewColumn(ListView1: TListView; ColumnIdx: integer; Descending: boolean);
var
  Header: HWND;
  Item: THDItem;
begin
  Header := ListView_GetHeader(ListView1.Handle);
  ZeroMemory(@Item, SizeOf(Item));
  Item.Mask := HDI_FORMAT;
  Header_GetItem(Header, ColumnIdx, Item);
  Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN);//remove both flags
  if Descending then
    Item.fmt := Item.fmt or HDF_SORTDOWN
  else
    Item.fmt := Item.fmt or HDF_SORTUP;//include the sort ascending flag
  Header_SetItem(Header, ColumnIdx, Item);
end;

procedure TUD2MainForm.ListView3Compare(Sender: TObject; Item1,
  Item2: TListItem; Data: Integer; var Compare: Integer);
begin
  if SortedColumn = 0 then
    Compare := CompareText(Item1.Caption, Item2.Caption)
  else
    Compare := CompareText(Item1.SubItems[SortedColumn-1], Item2.SubItems[SortedColumn-1]);
  if Descending then Compare := -Compare;
end;

procedure TUD2MainForm.ListView3ColumnClick(Sender: TObject;
  Column: TListColumn);
begin
  TListView(Sender).SortType := stNone;
  if Column.Index<>SortedColumn then
  begin
    SortedColumn := Column.Index;
    Descending := False;
  end
  else
    Descending := not Descending;
  ShowArrowOfListViewColumn(TListView(Sender), column.Index, Descending);
  TListView(Sender).SortType := stText;
end;

Столбцы можно сортировать вверх и вниз, но я не вижу стрелок.

В соответствии с этим вопросом моя функция ShowArrowOfListViewColumn() должна была решить проблему.

Возможно ли, что Delphi 6 не поддерживает эту функцию, или проблема в моем коде? С другой стороны, ListView — это IIRC a Управление Windows, и поэтому я ожидаю, что WinAPI отрисовывает графику со стрелками, а не (очень старый) VCL.

Я прочитал на немецком веб-сайте, что графику со стрелкой нужно добавлять вручную, но решение на этом веб-сайте требуется изменить CommCtrl.pas Delphi (из-за сбоя при изменении размера столбца). Но мне очень не нравится изменять исходный код VCL, тем более что я разрабатываю OpenSource, и я не хочу, чтобы другие разработчики изменяли/перекомпилировали свои исходные коды Delphi.

Обратите внимание, что я не добавил манифест XP в свой двоичный файл, поэтому приложение выглядит как Win9x.


person Daniel Marschall    schedule 28.09.2015    source источник
comment
Вы используете comctl v6, то есть темы XP? Для этого требуется менеджер тем Майка Лишке.   -  person David Heffernan    schedule 28.09.2015
comment
Я не добавлял манифест XP в свой бинарный файл, поэтому приложение выглядит как Win9x.   -  person Daniel Marschall    schedule 28.09.2015


Ответы (2)


HDF_SORTDOWN и HDF_SORTUP требуется comctl32 v6. Это указано в документации для HDITEM:

HDF_SORTDOWN Версия 6.00 и выше. Рисует стрелку вниз на этом элементе. Обычно это используется для указания того, что информация в текущем окне сортируется по этому столбцу в порядке убывания. Этот флаг нельзя комбинировать с HDF_IMAGE или HDF_BITMAP.

HDF_SORTUP версии 6.00 и выше. Рисует стрелку вверх на этом элементе. Обычно это используется для указания того, что информация в текущем окне сортируется по этому столбцу в порядке возрастания. Этот флаг нельзя комбинировать с HDF_IMAGE или HDF_BITMAP.

Как вы объяснили в своих комментариях, вы не включили манифест comctl32 v6. Это объясняет то, что вы наблюдаете.

Решения включают:

  • Добавление манифеста comctl32 v6 или
  • Пользовательские стрелки заголовка чертежа.
person David Heffernan    schedule 28.09.2015
comment
Здравствуйте, большое спасибо за эту подсказку. Я действительно читал, что требуется Windows XP, но я забыл, что Windows будет использовать резервную версию ComCtl32, если не будет предоставлен манифест. -- Я все еще немного удивлен этим, потому что стрелки существуют со времен Windows 95. Microsoft скрывала эту функцию до Windows XP, или проводник Windows 95 использовал другой элемент управления, чем ListView? - person Daniel Marschall; 28.09.2015
comment
Для полноты картины я создал VCL, который также решает проблему исчезновения стрелок при изменении размера каждого столбца: viathinksoft.de/~daniel-marschall/code/delphi/vcl/ . Но я боюсь, что заново изобрел велосипед. - person Daniel Marschall; 28.09.2015
comment
Prob explorer в win 95 использовал другой элемент управления, или пользовательская настройка рисовала стрелки - person David Heffernan; 28.09.2015

Вам не нужно менять исходный код VCL, чтобы следовать немецкому примеру, вы можете просто исправить среду выполнения кода.

DISCALMER Я хотел протестировать свой код на Delphi 6, но моя установка Delphi 6 не запустилась сегодня утром, поэтому она тестировалась только на Delphi XE!

Но я думаю, что это будет работать и на Delphi 6.

Сначала вам нужен класс для исправления среды выполнения метода:

unit PatchU;

interface

type
  pPatchEvent = ^TPatchEvent;

  // "Asm" opcode hack to patch an existing routine
  TPatchEvent = packed record
    Jump: Byte;
    Offset: Integer;
  end;

  TPatchMethod = class
  private
    PatchedMethod, OriginalMethod: TPatchEvent;
    PatchPositionMethod: pPatchEvent;
  public
    constructor Create(const aSource, aDestination: Pointer);
    destructor Destroy; override;
    procedure Restore;
    procedure Hook;
  end;

implementation

uses
  Windows, Sysutils;

{ TPatchMethod }

constructor TPatchMethod.Create(const aSource, aDestination: Pointer);
var
  OldProtect: Cardinal;
begin
  PatchPositionMethod := pPatchEvent(aSource);
  OriginalMethod := PatchPositionMethod^;
  PatchedMethod.Jump := $E9;
  PatchedMethod.Offset := PByte(aDestination) - PByte(PatchPositionMethod) - SizeOf(TPatchEvent);

  if not VirtualProtect(PatchPositionMethod, SizeOf(TPatchEvent), PAGE_EXECUTE_READWRITE, OldProtect) then
    RaiseLastOSError;

  Hook;
end;

destructor TPatchMethod.Destroy;
begin
  Restore;
  inherited;
end;

procedure TPatchMethod.Hook;
begin
  PatchPositionMethod^ := PatchedMethod;
end;

procedure TPatchMethod.Restore;
begin
  PatchPositionMethod^ := OriginalMethod;
end;

end.

Тогда нам нужно его использовать. Pau listview в форме, а затем этот код:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, PatchU;

type
  TListView = class(ComCtrls.TListView)
  protected
    procedure ColClick(Column: TListColumn); override;
  end;

  TForm1 = class(TForm)
    ListView1: TListView;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  CommCtrl;

var
  ListView_UpdateColumn_Patch: TPatchMethod;

type
  THooked_ListView = class(TListView)
    procedure HookedUpdateColumn(AnIndex: Integer);
  end;

  { TListView }

procedure TListView.ColClick(Column: TListColumn);
var
  Header: HWND;
  Item: THDItem;
  NewFlag: DWORD;
begin
  Header := ListView_GetHeader(Handle);
  ZeroMemory(@Item, SizeOf(Item));
  Item.Mask := HDI_FORMAT;
  Header_GetItem(Header, Column.Index, Item);

  if Item.fmt and HDF_SORTDOWN <> 0 then
    NewFlag := HDF_SORTUP
  else
    NewFlag := HDF_SORTDOWN;

  Item.fmt := Item.fmt and not(HDF_SORTUP or HDF_SORTDOWN); // remove both flags
  Item.fmt := Item.fmt or NewFlag;
  Header_SetItem(Header, Column.Index, Item);

  inherited;
end;

{ THooked_ListView }

procedure THooked_ListView.HookedUpdateColumn(AnIndex: Integer);
begin
  ListView_UpdateColumn_Patch.Restore;
  try
    UpdateColumn(AnIndex);
  finally
    ListView_UpdateColumn_Patch.Hook;
  end;
end;

initialization

ListView_UpdateColumn_Patch := TPatchMethod.Create(@TListView.UpdateColumn, @THooked_ListView.HookedUpdateColumn);

finalization

ListView_UpdateColumn_Patch.Free;

end.

Как видите, мое демо сильно вдохновлено опубликованным вами кодом. Я только что удалил глобальные переменные. В моем примере я ничего не делаю, кроме как вызываю исходную процедуру, но вам, конечно, придется вызывать код из примера Geraman.

В общем, я просто хотел показать вам, как можно изменить VCL, не редактируя исходный код. Это должно вас заинтересовать.

person Jens Borrisholt    schedule 28.09.2015
comment
Вам не нужно взламывать, как это. Вы можете использовать код из моего ответа без каких-либо неприятных хаков в вашем ответе здесь. - person David Heffernan; 28.09.2015
comment
Это часть TListView = class(ComCtrls.TListView) или часть патча, которую вы называете неприятным взломом? - person Jens Borrisholt; 28.09.2015
comment
Объезд излишен. В любом случае, вы упустили суть. Вопрос уже сказал вам, что код в моем другом ответе не имеет никакого эффекта. Вы должны объяснить, почему это так. Настоящая причина - отсутствие тем XP. - person David Heffernan; 28.09.2015