djsoft
djsoft

Reputation: 1061

TListView columns order bug after windows theme change

TListView's column contents become incorrect after windows theme change. I've narrowed it down to CM_RECREATE message, that's when VCL recreates TListView's window handle in response to system theme change. Below are some screenshots illustrating the problem.

Original list view state

Initial list view state

Last column has been moved moved to the first position. Everything is fine.

Col moved

After Windows theme was changed, the columns positions are preserved, however, the contents are no longer correct.

Theme change

Currently I overcome the issue by simply recreating the columns manually in my custom CM_RECREATEWND handler. Is it a bug? It it a good solution to recreate columns or is there a better way?

I'm using Delphi10 but the same behavior was observed in the previous versions as well.

Upvotes: 2

Views: 257

Answers (1)

djsoft
djsoft

Reputation: 1061

I'll post my workaround in case anyone needs a quick fix for this bug. Just include this unit as a last used unit in a Form's uses list.

unit LVFix;

interface

uses
  Winapi.Windows, Winapi.Messages, System.Classes, System.UITypes, 
  Vcl.Controls, Vcl.ComCtrls;

type
  TListView = class(Vcl.ComCtrls.TListView)
  strict private
    type
    TColumnRec = record
      Alignment: TAlignment;
      AutoSize: Boolean;
      Caption: String;
      ImageIndex: TImageIndex;
      MaxWidth, MinWidth, Width: TWidth;
      Tag: Integer;
      ID: Integer;
    end;
    var
      FSavedCols: TArray<TColumnRec>;
      FSavedColOrder: TArray<Integer>;
  private
    procedure SaveColumnState;
    procedure RestoreColumnState;
  protected
    procedure CMRecreate(var M: TMessage); message CM_RECREATEWND;
  end;

implementation

uses
  Winapi.CommCtrl;

{ TListView }

procedure TListView.CMRecreate(var M: TMessage);
begin
  SaveColumnState;
  inherited;
  RestoreColumnState;
end;

procedure TListView.RestoreColumnState;
var
  I: Integer;
begin
  Items.BeginUpdate; //lock to prevent unnecessary events firing
  try
    //recreate columns
    Columns.Clear;
    for I := 0 to High(FSavedCols) do
    begin
      with Columns.Add do
      begin
        Alignment := FSavedCols[I].Alignment;
        AutoSize := FSavedCols[I].AutoSize;
        Caption := FSavedCols[I].Caption;
        ImageIndex := FSavedCols[I].ImageIndex;
        MinWidth := FSavedCols[I].MinWidth;
        MaxWidth := FSavedCols[I].MaxWidth;
        Width := FSavedCols[I].Width;
        Tag := FSavedCols[I].Tag;
      end;
    end;
    //restore column order
    if Length(FSavedColOrder) <> 0 then
      ListView_SetColumnOrderArray(Handle, Columns.Count, PInteger(FSavedColOrder));
  finally
    Items.EndUpdate;
  end;
end;

procedure TListView.SaveColumnState;
var
  R: LongBool;
  I: Integer;
  J: Integer;
  T: TColumnRec;
begin
  //save column order
  SetLength(FSavedColOrder, Columns.Count);
  R := ListView_GetColumnOrderArray(Handle, Columns.Count, PInteger(FSavedColOrder));
  if not R then
    SetLength(FSavedColOrder, 0);
  //save original columns in original order
  SetLength(FSavedCols, Columns.Count);
  for I := 0 to Columns.Count - 1 do
  begin
    FSavedCols[I].Alignment := Columns[I].Alignment;
    FSavedCols[I].AutoSize := Columns[I].AutoSize;
    FSavedCols[I].Caption := Columns[I].Caption;
    FSavedCols[I].ImageIndex := Columns[I].ImageIndex;
    FSavedCols[I].MinWidth := Columns[I].MinWidth;
    FSavedCols[I].MaxWidth := Columns[I].MaxWidth;
    FSavedCols[I].Width := Columns[I].Width;
    FSavedCols[I].Tag := Columns[I].Tag;
    FSavedCols[I].ID := Columns[I].ID;
  end;
  for I := 0 to High(FSavedCols) - 1 do
    for J := I + 1 to High(FSavedCols) do
      if FSavedCols[J].ID < FSavedCols[I].ID then
      begin
        T := FSavedCols[I];
        FSavedCols[I] := FSavedCols[J];
        FSavedCols[J] := T;
      end;
end;

end.

Upvotes: 2

Related Questions