Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Listview aktualisieren und zur selben Position kommen


acnut - Mo 18.07.11 18:22
Titel: Listview aktualisieren und zur selben Position kommen
ich hab schon alles durchprobiert und das ding funzt einfach nicht. nach jedem update des listviews springt das ding einfach wieder nach oben.
hab onkel google gefragt, auch er kann mir nicht weiterhelfen :(

also mit diesem procedure füge ich alle items&subitems in das listview



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
procedure TForm1.DoAfterUpdateProcesses(Sender: TObject);
var
  Process: TProcessItem;
  ListItem : TListItem;
  i : integer;
  CurrentItem : string;
begin
  if DontUpdateList then
    Exit;

  ListView1.Items.BeginUpdate;
  try
    if Assigned(ListView1.Selected) then
      CurrentItem := ListView1.Selected.Caption;
    ListView1.Clear;
//    for Process in ProcessInfo1.RunningProcesses do
    for I := 0 to ProcessInfo1.RunningProcesses.Count- 1 do
    begin
      Process := ProcessInfo1.RunningProcesses[i];
      ListItem := ListView1.Items.Add;
      ListItem.SubItems.Add(IntToStr(Process.ParentProcessID));
      ListItem.Caption := IntToStr(Process.ProcessID);
 //   ListItem.SubItems.Add(IntToStr(Process.CpuUsage));
      ListItem.SubItems.Add(IntToStr(Process.ThreadsCount));
      ListItem.SubItems.Add(IntToStr(Process.PriorityClassBase));
      ListItem.SubItems.Add(IntToStr(process.MemoryInfo.WorkingSetSize));
      ListItem.SubItems.Add(Process.UserName);
      ListItem.SubItems.Add(Process.ExeFile); 
      ListItem.Data := Process;
    end;
    ListView1.Selected := FindIdInList(ListView1,CurrentItem);
    if Assigned(ListView1.Selected) then
      ListView1.Selected.MakeVisible(False);
  finally
    ListView1.Items.EndUpdate;
   end;
end;


und hier die funktion damit ich die process id in der liste krieg und diese dann wieder vergleiche:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
function TForm1.FindIdInList(ListView: TListView; const ID: string): TListItem;
var
  I : Integer;
begin
  Result := nil;
  for I := 0 to ListView.Items.Count-1 do
  begin
    if CompareStr(ListView.Items[I].Caption,ID) = 0 then
    begin
      Result := ListView.Items[I];
      Exit;
    end;
  end;
end;



nun ist mein ansatz überhaupt richtig oder mach ich eigentlich die ganze zeit nur schwachsinn :) bitte helft mir ;)



mfg
ACnut


Narses - Mo 18.07.11 21:37

Moin!

user profile iconacnut hat folgendes geschrieben Zum zitierten Posting springen:
ist mein ansatz überhaupt richtig
Also grundsätzlich würde ich sagen, ist das schon OK. Allerdings würde ich das Selektieren ausserhalb der Update-Klammer machen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
procedure TForm1.DoAfterUpdateProcesses(Sender: TObject);
  var
    Process: TProcessItem;
    ListItem : TListItem;
    i: Integer;
    CurrentItem: String;
begin
  if DontUpdateList then
    Exit;

  CurrentItem := '';
  if Assigned(ListView1.Selected) then
    CurrentItem := ListView1.Selected.Caption;
  ListView1.Items.BeginUpdate;
  try
    ListView1.Clear;
    for i := 0 to ProcessInfo1.RunningProcesses.Count- 1 do begin
      Process := ProcessInfo1.RunningProcesses[i];
      ListItem := ListView1.Items.Add;
      ListItem.SubItems.Add(IntToStr(Process.ParentProcessID));
      ListItem.Caption := IntToStr(Process.ProcessID);
      //...
    end;
  finally
    ListView1.Items.EndUpdate;
  end;
  if (CurrentItem <> ''then begin
    ListView1.Selected := FindIdInList(ListView1, CurrentItem);
    if Assigned(ListView1.Selected) then
      ListView1.Selected.MakeVisible(False);
  end;
end;

function TForm1.FindIdInList(ListView: TListView; const ID: String): TListItem;
  var
    i: Integer;
begin
  for i := 0 to ListView.Items.Count-1 do begin
    Result := ListView.Items[i];
    if (CompareStr(Result.Caption, ID) = 0then
      Exit;
  end;
  Result := NIL;
end;
cu
Narses


jaenicke - Mo 18.07.11 21:44

Besser wäre allerdings die Items gar nicht alle zu löschen, sondern nur die ggf. überflüssigen zu löschen bzw. fehlende zu ergänzen.

Dann stellt sich das Problem gar nicht erst, da die Items gar nicht unnötig gelöscht werden, und schneller ist es auch noch.


acnut - Di 19.07.11 13:26

@Narses danke für den geänderten code allerdings ändern tut sich da gar nichts (war ja eig klar).
bin echt verzeifelt :(

@jaenicke also wie meinst du das? soll ich die process ids in einem array speichern und dann nach dem update dann mit der neuen liste vergleichen? und änderungen dann einfach darunter einfügen? und das geht wirklich schneller als die liste zu löschen und wieder einfügen?

gibt es vllt eine andere komponente mit der das updaten wirklich schön flüssig funktioniert?


mfg
acnut


Narses - Di 19.07.11 13:51

Moin!

user profile iconacnut hat folgendes geschrieben Zum zitierten Posting springen:
@Narses danke für den geänderten code allerdings ändern tut sich da gar nichts (war ja eig klar).
Hm, probier das mal aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
procedure TForm1.DoAfterUpdateProcesses(Sender: TObject);
  var
    Process: TProcessItem;
    ListItem : TListItem;
    i: Integer;
    CurrentItem: String;
begin
  CurrentItem := '';
  if Assigned(ListView1.Selected) then
    CurrentItem := ListView1.Selected.Caption;
  ListView1.Items.BeginUpdate;
  try
    ListView1.Clear;
    for i := 0 to ProcessInfo1.RunningProcesses.Count- 1 do begin
      Process := ProcessInfo1.RunningProcesses[i];
      ListItem := ListView1.Items.Add;
      ListItem.SubItems.Add(IntToStr(Process.ParentProcessID));
      ListItem.Caption := IntToStr(Process.ProcessID);
      //...
    end;
  finally
    ListView1.Items.EndUpdate;
  end;
  if (CurrentItem <> ''then begin
    ListItem := FindIdInList(ListView1, CurrentItem);
    ShowMessage(ListItem.Caption);
    ListView1.Selected := ListItem;
    if Assigned(ListView1.Selected) then
      ListView1.Selected.MakeVisible(False);
  end;
end;

function TForm1.FindIdInList(ListView: TListView; const ID: String): TListItem;
  var
    i: Integer;
begin
  for i := 0 to ListView.Items.Count-1 do begin
    Result := ListView.Items[i];
    if (Result.Caption = ID) then
      Exit;
  end;
  Result := NIL;
end;


user profile iconacnut hat folgendes geschrieben Zum zitierten Posting springen:
soll ich die process ids in einem array speichern
Wieso in einem Array, du hast doch eine Referenz in der .Data-Eigenschaft des ListItems. :nixweiss:

user profile iconacnut hat folgendes geschrieben Zum zitierten Posting springen:
und dann nach dem update dann mit der neuen liste vergleichen? und änderungen dann einfach darunter einfügen? und das geht wirklich schneller als die liste zu löschen und wieder einfügen?
Naja, wenn es sehr viele Objekte sind, schon. Aber eine Prozessliste? Das sollte man kaum merken. :| Ich würde das erstmal so lassen und schauen, warum das mit dem Fokus nicht klappt, denn daran wird auch eine andere Komponente nix ändern. ;)

user profile iconacnut hat folgendes geschrieben Zum zitierten Posting springen:
gibt es vllt eine andere komponente mit der das updaten wirklich schön flüssig funktioniert?
Falsche Frage, jetzt wird user profile iconjaenicke dir gleich den VirtualStringTree ans Herz legen. 8) OK, ist auch besser, aber auch deutlich schwerer zu verstehen. :?

cu
Narses


jaenicke - Di 19.07.11 14:03

Ohne sich zu merken welcher Eintrag vorher als erster angezeigt wurde, kann das nicht klappen. Denn so wird nur der markierte wieder markiert und dieser auch sichtbar gemacht, aber die Scrollposition wird nicht wiederhergestellt. :nixweiss:

Dieser erste sichtbare Eintrag steht bei einer ListView in TopItem, bei einer TVirtualStringTree in TopNode und einer ListBox in TopIndex. Es reicht also hier zusätzlich TopItem.Index in eine Variable zu speichern und danach TopItem auf Items[OldTopIndex] zu setzen.


Narses - Di 19.07.11 14:10

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Ohne sich zu merken welcher Eintrag vorher als erster angezeigt wurde, kann das nicht klappen. Denn so wird nur der markierte wieder markiert und dieser auch sichtbar gemacht, aber die Scrollposition wird nicht wiederhergestellt. :nixweiss:
Von einer Scrollposition war bisher auch nicht die Rede. :idea: ;)

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Dieser erste sichtbare Eintrag steht bei einer ListView in TopItem, bei einer TVirtualStringTree in TopNode und einer ListBox in TopIndex. Es reicht also hier zusätzlich TopItem.Index in eine Variable zu speichern und danach TopItem auf Items[OldTopIndex] zu setzen.
Theoretisch - ja. Praktisch ist allerdings zu berücksichtigen, dass sich die Liste verändert haben könnte und z.B. der Eintrag gar nicht mehr da ist oder die Differenz TopItemIndex zu SelectedItemIndex nicht mehr passen könnte. :shock: Ganz soo einfach ist das also auch nicht. Deshalb hätte ich gesagt, dass:

Delphi-Quelltext
1:
2:
if Assigned(ListView1.Selected) then
  ListView1.Selected.MakeVisible(False);
zwar unter Umständen einmal "Ruckelt", aber ausreichd ist.

cu
Narses


jaenicke - Di 19.07.11 14:16

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Ganz soo einfach ist das also auch nicht.
Doch, zuerst den TopItem wiederherstellen und danach den zuletzt markierten falls vorhanden. ;-)

Und falls man auch den Eintrag aus dem TopItem wiederherstellen will, muss man eben dessen Inhalt speichern und danach suchen und nicht nur den Index. Dann kann man entweder nach Name oder bei Fehlschlag nach Index die Position wiederherstellen und wenn der zuletzt markierte noch existiert danach auch die Markierung.

Das habe ich hier gerade auch gemacht, wenn auch mit den VirtualTrees.


acnut - Di 19.07.11 14:25

aha jetzt funktionerts (thanks brother @Narses, i'm pushin' tethanks buttn for you!) :D
allerdings muss ich sehr schnell sein damit ich ein prozess auswählen kann sonst rutscht es wieder zur markierten stelle (war ja unser ziel :mrgreen:)
nun will ich jetzt weiterscrollen und nicht zur markierten stelle zurückspringen:
also in welchen on(mouseup,...od anderes)...events vom listview muss ich jetzt sozusagen ein "dontupdatelist" machen?
oder kann ich das so machen, so dass ich mit der änderung des scrollbars auch die position der markierten stelle erhöhe bzw. erniedrige?



mfg
ACnut


so sieht es mal zurzeit aus:
Moderiert von user profile iconNarses: Programm als Anhang hochgeladen.


jaenicke - Di 19.07.11 14:29

user profile iconacnut hat folgendes geschrieben Zum zitierten Posting springen:
also in welchen on(mouseup,...od anderes)...events vom listview muss ich jetzt sozusagen ein "dontupdatelist" machen?
oder kann ich das so machen, so dass ich mit der änderung des scrollbars auch die position der markierten stelle erhöhe bzw. erniedrige?
Habe ich ja gerade geschrieben wie das geht. ;-)


acnut - Di 19.07.11 14:51

Zitat:
Habe ich ja gerade geschrieben wie das geht. ;-)

danke ich lese es gerade versteh aber nur cornflakes aber ohne milch. mehr saft bzw. milch wäre von vorteil :mrgreen:

also laut der delphi hilfe bekomme ich das:
Zitat:
//
TopItem:
Gibt das oberste sichtbare Element einer Listenansicht an.

Mit TopItem können Sie das oberste sichtbare Element der Listenansicht ermitteln und daraus den Umfang eines vorangegangenen Bildlaufs ablesen. Eine Wertzuweisung an TopItem ermöglicht außerdem Der Wert der Eigenschaft TopItem kann nicht geändert werden.
//

aber wird mit dem bildlauf einfach nur die anzahl der prozesse gemeint?

du hast geschrieben das ich den topitem speichern soll (ok kein problem) und wie mach ich das mit dem "oldtopindex"?
iwas hab ich da nicht verstanden bzw. ist mir unklar und ich kanns mir nicht vorstellen :(
Zitat:
Theoretisch - ja. Praktisch ist allerdings zu berücksichtigen, dass sich die Liste verändert haben könnte und z.B. der Eintrag gar nicht mehr da ist oder die Differenz TopItemIndex zu SelectedItemIndex nicht mehr passen könnte.
eben das verwirrt mich an diesem ganzen topitem-ding

Moderiert von user profile iconNarses: Zitat repariert.


jaenicke - Di 19.07.11 14:56

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Es reicht also hier zusätzlich TopItem.Index in eine Variable zu speichern

Delphi-Quelltext
1:
2:
3:
4:
var
  OldTopIndex: Integer;
begin
  OldTopIndex := ListView1.TopItem.Index;
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
und danach TopItem auf Items[OldTopIndex] zu setzen.

Delphi-Quelltext
1:
2:
if OldTopIndex < ListView1.Items.Count then
  ListView1.TopItem := ListView1.Items[OldTopIndex];


acnut - Di 19.07.11 15:19


Delphi-Quelltext
1:
2:
if OldTopIndex < ListView1.Items.Count then
  ListView1.TopItem := ListView1.Items[OldTopIndex];

schön und gut (jetzt versteh ich es glaube ich) allerdings ist topitem nur readonly. wie mach ich das jetzt? mit assign?


Tryer - Mi 20.07.11 05:12


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
var
  TpItemIndex: Integer;
  TpItemPos, Pos: TPoint;
begin
  TpItemIndex :=  ListView.TopItem.Index;
  TpItemPos :=  ListView.TopItem.GetPosition;

  // mach was

  Pos := ListView.Items[TpItemIndex].GetPosition;
  ListView.Scroll(Pos.X - TpItemPos.X, Pos.Y - TpItemPos.Y);
end;


Grüsse, Dirk


acnut - Mi 20.07.11 14:30

user profile iconTryer hat folgendes geschrieben Zum zitierten Posting springen:
[delphi]var
TpItemIndex: Integer;
TpItemPos, Pos: TPoint;
begin
...
Grüsse, Dirk



wenn ich mehrmals auf danke drücken dürfte, dann würde ich mindestens 20 mal weiter drücken ;)

erstens danke für Narses
zweitens danke für jaenicke
drittens danke für Tryer
ihr seid die coolsten ;)