XE6 TListView-Spaltenbreiten werden Null, wenn Sie column.width lesen
Es ist ein Fehler inTListView
.
Lesen einer SpalteWidth
kann dazu führen, dass die Listenansicht versucht, die Spaltenbreite vom zugrunde liegenden Windows zu ermittelnLISTVIEW
direkt steuern - bevor die Spalten des Win32-Steuerelements initialisiert wurden.
Da die Spalten nicht initialisiert wurden, ist die ListenansichtLVM_GETCOLUMNWIDTH
Die Nachricht schlägt fehl und kehrt zurückNull. DasTListView
Damit ist gemeint, dass die Breiteist Null und macht alle Spalten Null.
Dieser Fehler wurde irgendwann nach Delphi 5 eingeführt.
Schritte zum ReproduzierenFügen Sie einem Formular eine Listenansicht im Berichtsstil mit drei Spalten hinzu:
Hinzufügen einesOnResize
Eventhandler zur Listenansicht:
procedure TForm1.ListView1Resize(Sender: TObject);
begin
{
Any column you attempt to read the width of
will **cause** the width to become zero
}
ListView1.Columns[0].Width;
// ListView1.Columns[1].Width;
ListView1.Columns[2].Width;
end;
Starte es:
Der FehlerDasTListColumn
Code versucht, die Spaltenbreite aus dem Windows zu lesenlistview
Klasse direkt, bevor die Spalten wurdenhinzugefügt zu den Windowslistview
Steuerung
Formatieren des Codes zur besseren Lesbarkeit:
function TListColumn.GetWidth: TWidth;
var
IsStreaming: Boolean;
LOwner: TCustomListView;
begin
LOwner := TListColumns(Collection).Owner;
IsStreaming := [csReading, csWriting, csLoading] * LOwner.ComponentState <> [];
if (
(FWidth = 0)
and (LOwner.HandleAllocated or not IsStreaming)
)
or
(
(not AutoSize)
and LOwner.HandleAllocated
and (LOwner.ViewStyle = vsReport)
and (FWidth <> LVSCW_AUTOSIZE)
and (LOwner.ValidHeaderHandle)
) then
begin
FWidth := ListView_GetColumnWidth(LOwner.Handle, FOrderTag);
end;
Result := FWidth;
end;
Das Problem tritt auf, während das Formular erstellt wirddfm
Deserialisierung:
ComCtrls.TListColumn.GetWidth: TWidth;
TForm1.ListView1Resize(Sender: TObject);
Windows.CreateWindowEx(...)
Controls.TWinControl.CreateWindowHandle(const Params: TCreateParams);
Controls.TWinControl.CreateWnd;
ComCtrls.TCustomListView.CreateWnd;
Das Problem ist dasTCustomListView.CreatWnd
Die Spalten werden irgendwann hinzugefügtnach der anruf anCreateWnd
:
procedure TCustomListView.CreateWnd;
begin
inherited CreateWnd; //triggers a call to OnResize, trying to read the column widths
//...
Columns.UpdateCols; //add the columns
//...
end;
Der Code inTListColumn.GetWidth
merkt nicht, dass die Spalten noch nicht initialisiert wurden.
Delphi 5 verwendet ähnlicheTCustomListView
Konstruktion:
procedure TCustomListView.CreateWnd;
begin
inherited CreateWnd; //triggers a call to OnResize
//...
Columns.UpdateCols;
//...
end;
AußerDelphi 5 versucht nicht, sich selbst auszuloten und Dinge zu überdenken:
function TListColumn.GetWidth: TWidth;
begin
if FWidth = 0 then
FWidth := ListView_GetColumnWidth(TListColumns(Collection).Owner.Handle, Index);
Result := FWidth;
end;
Wenn wir eine Breite haben, benutze sie.
Die FrageWarum warTListColumn.GetWidth
geändert? Welchen Fehler wollten sie lösen? Ich sehe, dass sie ihre Codeänderungen nicht kommentieren, so dass es unmöglich ist, aus der VCL-Quelle zu sagen, was die Begründung war.
Was noch wichtiger ist, wie behebe ich das? Ich kann den Code von nicht entfernenOnResize
, aber ich kann einen erstellenTFixedListView
Zollkontrolle; außer ich müsste alles von Grund auf neu schreiben, damit es a verwendetTFixedListViewColumn
Klasse.
Das ist nicht gut.
Die wichtigste Frage: Wie behebt Embarcadero das Problem? Was ist das für ein Code?sollte in seinTListColumn.GetWidth
den Fehler beheben?ComponentState
ist leer. Es scheint, als müssten sie eine neue Variable einführen:
FAreColumnsInitialized: Boolean;
Oder sie könnten den Code so zurücksetzen, wie er war.
Worin würden Sie vorschlagen, dass sie den Code korrigieren?
Warum funktioniert es mit deaktivierten Themen?Der Bug passiert nur mitVisuelle Stile aktiviert.
Windows hat eineWM_PARENTNOTIFY
Nachricht, dass"benachrichtigt die Eltern über wichtige Ereignisse im Leben der Kontrolle". Im Falle derlistview
Dies sendet das Handle desheader
steuern, dass die Listenansicht intern verwendet. Delphi speichert dann diesen Headerhwnd
:
procedure TCustomListView.WMParentNotify(var Message: TWMParentNotify);
begin
with Message do
if (Event = WM_CREATE) and (FHeaderHandle = 0) then
begin
FHeaderHandle := ChildWnd;
//...
end;
inherited;
end;
Bei deaktivierten Designs sendet Windows das nichtWM_PARENTNOTIFY
Nachricht bisspäter im Bauzyklus. Dies bedeutet, dass bei deaktivierten Themen dieTListColumn
wird eines der Kriterien nicht erfüllen, das es ihm ermöglicht, mit der Listenansicht zu sprechen:
if (
(FWidth = 0)
and (LOwner.HandleAllocated or not IsStreaming)
)
or
(
(not AutoSize)
and LOwner.HandleAllocated
and (LOwner.ViewStyle = vsReport)
and (FWidth <> LVSCW_AUTOSIZE)
and (LOwner.ValidHeaderHandle) //<--- invalid
) then
begin
FWidth := ListView_GetColumnWidth(LOwner.Handle, FOrderTag);
end;
Aber wenn wir die neue Version von Windows verwendenlistview
Kontrolle, Windows passiert das zu sendenWM_PARENTNOTIFY
Nachricht früher in der Konstruktion:
if (
(FWidth = 0)
and (LOwner.HandleAllocated or not IsStreaming)
)
or
(
(not AutoSize)
and LOwner.HandleAllocated
and (LOwner.ViewStyle = vsReport)
and (FWidth <> LVSCW_AUTOSIZE)
and (LOwner.ValidHeaderHandle) //<--- Valid!
) then
begin
FWidth := ListView_GetColumnWidth(LOwner.Handle, FOrderTag);
end;
Auch wenn das Header-Handle gültig ist, bedeutet dies nicht, dass die Spalten noch hinzugefügt wurden.
Die vorgeschlagene LösungEs scheint, als ob der VCL-Fix verwendet werden sollWM_PARENTNOTIY
als die richtige Gelegenheit, die Spalten zur Listenansicht hinzuzufügen:
procedure TCustomListView.WMParentNotify(var Message: TWMParentNotify);
begin
with Message do
if (Event = WM_CREATE) and (FHeaderHandle = 0) then
begin
FHeaderHandle := ChildWnd;
UpdateCols; //20140822 Ian Boyd Fixed QC123456 where the columns aren't usable in time
//...
end;
inherited;
end;
Bonus ChatterMit Blick auf den Windows 2000-Quellcode weist ListView einige Kommentare auf, die erkennen, dass einige schlechte Apps vorhanden sind:
lvrept.c
BOOL_PTR NEAR ListView_CreateHeader(LV* plv)
{
...
plv->hwndHdr = CreateWindowEx(0L, c_szHeaderClass, // WC_HEADER,
NULL, dwStyle, 0, 0, 0, 0, plv->ci.hwnd, (HMENU)LVID_HEADER, GetWindowInstance(plv->ci.hwnd), NULL);
if (plv->hwndHdr)
{
NMLVHEADERCREATED nmhc;
nmhc.hwndHdr = plv->hwndHdr;
// some apps blow up if a notify is sent before the control is fully created.
CCSendNotify(&plv->ci, LVN_HEADERCREATED, &nmhc.hdr);
plv->hwndHdr = nmhc.hwndHdr;
}
...
}