sechshelme / Lazarus-FreeVision-Tutorial

55 stars 9 forks source link

Missing a TListBox Tutorial #3

Open gasensorX opened 2 years ago

gasensorX commented 2 years ago

There is missing a TListBox Tutorial. It is useful.

I tried to use TListBox, but a memory leak always occurred.

Can you add a simple example for TListBox?

TKS

sechshelme commented 2 years ago

I took a look at the ListBox, unfortunately this seems to be quite complicated. I can't even manage to fill the ListBox with items. I've also tried using PCollection with no success.

The way you write, you've already managed to do something, which unfortunately caused a crash. If you could post this snippet of code, maybe we can work something out together.

Here is my attempt:

constructor TMyDialog.Init;
var
  Rect: TRect;
  sb: PScrollBar;
  ListBox: PListBox;
  pc: PCollection;
  ps: PString;
begin
  Rect.Assign(0, 0, 42, 21);
  Rect.Move(23, 3);
  inherited Init(Rect, 'Mein Dialog');

  // ListBox
  Rect.Assign(5, 5, 7, 15);
  sb := new(PScrollBar, Init(Rect));

  pc := new(PCollection, Init(4, 4));
  pc^.Insert(NewStr('abc'));
  pc^.Insert(NewStr('abc'));
  pc^.Insert(NewStr('abc'));
  pc^.Insert(NewStr('abc'));

  Rect.Assign(5, 2, 31, 15);
  ListBox := new(PListBox, Init(Rect, 3, nil));
  //  ListBox^.NewList(pc);
  //  ps:=new(PString);
  //  ps^:='abcd';

  //  ListBox^.Insert(ps);
  //  ListBox^.Insert(ps);
  ps := newstr('dsfdsfdsf');
  //  ListBox^.List^.Insert(ps);
  ListBox^.Insert(pc);
  //ListBox^.Insert(PString, Init('hallo'));
  //ListBox^.Insert(PString, Init('hallo'));

  Insert(ListBox);

  // Button, bei den der Titel geändert wird.
  Rect.Assign(19, 18, 32, 20);
  CounterButton := new(PButton, Init(Rect, '    ', cmCounter, bfNormal));
  CounterButton^.Title^ := '1';

  Insert(CounterButton);

  // Ok-Button
  Rect.Assign(7, 18, 17, 20);
  Insert(new(PButton, Init(Rect, '~O~K', cmOK, bfDefault)));
end;
gasensorX commented 2 years ago

This is my test code. Hope that helps.

Lazarus Forum: https://forum.lazarus.freepascal.org/index.php?topic=59237.msg441632#msg441632

Test code https://forum.lazarus.freepascal.org/index.php?action=dlattach;topic=59237.0;attach=48955

gasensorX commented 2 years ago

testfortlist.zip

sechshelme commented 2 years ago

thanks for the code All I had to do was {H-} and it compiled and ran with Fehlerfrau. This was because of the string, as FV requires ShortString. I haven't looked yet because of MemoryLeaks, but at least there is an entry in the ListBox.

With the following code even 4.

  pstr := NewStr(s);
  PPortStrList^.Insert(pstr);
  PPortStrList^.Insert(NewStr('abc'));
  PPortStrList^.Insert(NewStr('def'));
  PPortStrList^.Insert(NewStr('1234'));

I didn't think of PStringCollection, as you can see it's been a few lines since I wrote the tutorial.

Not only PListBox and PSortedList Box are worth a tutorial, but also PStringCollection.

I wrote my tutorial based on an old Turbo Pascal book, and therefore almost exclusively used components that I found there.

Another tip that will make your code more readable, please do not use "P" in front of the variable names:

Poorly:

    PSpeedStrList: PStringCollection;
    PPortStrList: PStringCollection;
    PEditPort: PInputLine;
    PEditSpeed: PInputLine;
    PRadioButtonBytes: PRadioButtons;
    PRadioButtonPairty: PRadioButtons;

Good:

    SpeedStrList: PStringCollection;
    PortStrList: PStringCollection;
    EditPort: PInputLine;
    EditSpeed: PInputLine;
    RadioButtonBytes: PRadioButtons;
    RadioButtonPairty: PRadioButtons;
sechshelme commented 2 years ago

Another question, what are you developing with? Your code doesn't look like Lazarus komfom. Are you using the FPC IDE running in the console.

And one more thing, what OS are you using?

sechshelme commented 2 years ago

Ein erster Versuch, scheint zu klappen.

https://github.com/sechshelme/Lazarus-FreeVision/tree/master/99_Test/10_ListBox

gasensorX commented 2 years ago

Define pointer variable names using "P" as the prefix in order to avoid misuse.

I use fp and Lazarus at the same time. And I work under Linux OS.

gasensorX commented 2 years ago

I edit the "10_ListBox" to adding Heap display. I find it is still memory leaking.

Strings created by NewStr() can not automatic free/dispose.

10_ListBox_Heap.zip

sechshelme commented 2 years ago

I took a closer look at the sources of "PListBox". In my opinion, a destructor is missing there, which "list" cleans up.

TListBox = OBJECT (TListViewer)
         List: PCollection;                           { List of strings }
      CONSTRUCTOR Init (Var Bounds: TRect; ANumCols: Sw_Word;
        AScrollBar: PScrollBar);
      CONSTRUCTOR Load (Var S: TStream);
// Failure Destructor
      FUNCTION DataSize: Sw_Word; Virtual;     
sechshelme commented 2 years ago

I tried the following, but then the whole program crashes.

type
  PNewListBox = ^TNewListBox;
  TNewListBox = object(TListBox)
    destructor Done; virtual;
  end;
.....

destructor TNewListBox.Done;
begin
  FreeAll;
  inherited Done;
end;
sechshelme commented 2 years ago

I also find that a bit strange, if the ListBox is empty, why is an insert simply ignored and no new ones are created.

procedure TListBox.Insert (Item : Pointer);
begin
  if (List <> nil) then
  begin
    List^.Insert(Item);
    SetRange(List^.Count);
  end;
end;   
sechshelme commented 2 years ago

This is weird too, you can't even remove an entry without it popping.

  StringCollection := new(PCollection, Init(2, 1));
  StringCollection^.Insert(NewStr('Montag'));
  StringCollection^.Insert(NewStr('Dienstag'));
  StringCollection^.Insert(NewStr('Mittwoch'));
  StringCollection^.Insert(NewStr('Donnerstag'));
  StringCollection^.Insert(NewStr('Freitag'));
  StringCollection^.Insert(NewStr('Samstag'));
  StringCollection^.Insert(NewStr('Sonntag'));

  Rect.Assign(5, 2, 31, 7);
  ListBox := new(PListBox, Init(Rect, 1, ScrollBar));
  ListBox^.NewList(StringCollection);

  Insert(ListBox);

  ListBox^.Insert(NewStr('aaaaaaaaa'));
  ListBox^.FreeItem(1); // Knall !!
sechshelme commented 2 years ago

Anything that has anything to do with deleting an entry in the ListBox causes the program to crash.

All of the following variants make a bang.

// ListBox^.FreeItem(2);
// StringCollection^.AtFree(ListBox^.Focused);
// ListBox^.List^.AtFree(ListBox^.Focused);
sechshelme commented 2 years ago

The problem must definitely be with the TListBox. The only thing I do in the dialog now is fill a StringCollection and then release it with Dispose. There are no storage corpses here.

constructor TMyDialog.Init;
var
  Rect: TRect;
  i: Sw_Integer;
const
  Tage: array [0..6] of shortstring = (
    'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag');

begin
  Rect.Assign(10, 5, 67, 17);
  inherited Init(Rect, 'ListBox Demo');

  // StringCollection
  StringCollection := new(PStringCollection, Init(2, 1));
  for i := 0 to Length(Tage) - 1 do begin
    StringCollection^.Insert(NewStr(Tage[i]));
  end;
  Dispose(StringCollection, Done);
  exit;
sechshelme commented 2 years ago

If I do the following, I get a memory corpse. Although I didn't even fill the list with dates.

begin
  Rect.Assign(10, 5, 67, 17);
  inherited Init(Rect, 'ListBox Demo');

  ListBox := new(PListBox, Init(Rect, 1, nil));
  Dispose(ListBox, Done);
  exit;

If I do the same with a PButton, the memory is cleaned up cleanly. I even tried it with a filled PRadioButtons, all clean.

I soon suspect that there is a bug in PListBox.

  bt := new(PButton, Init(Rect, '~T~ag', cmTag, bfNormal));
  Dispose(bt, Done);
  exit;
  rad := New(PRadioButtons, Init(Rect, NewSItem('~G~ross', NewSItem('~M~ittel', NewSItem('~K~lein', nil)))));
  Dispose(rad, Done);
  exit;
sechshelme commented 2 years ago

I took a closer look at the TFileListBox used by the TFileDialog. Then I came across the following:

destructor TFileList.Done;
begin
  if List <> nil then Dispose(List, Done);
  TListBox.Done;
end;

They added their own destructor there.

Now I also built a new ListBox and added a destructor and used this ListBox in my dialog. As it seems, this works. the memory leak is gone.

I'll make a few more tries. In my opinion, this is a bug in TListBox, which lacks the destructor.

type
  PNewListBox = ^TNewListBox;
  TNewListBox = object(TListBox)
    destructor Done; virtual;
  end;
...
destructor TNewListBox.Done;
begin
  if List <> nil then begin
    Dispose(List, Done);
  end;
  TListBox.Done;
end;
constructor TMyDialog.Init;
var
  Rect: TRect;
  ScrollBar: PScrollBar;
  i: Sw_Integer;
const
  Tage: array [0..6] of shortstring = (
    'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag');

begin
  Rect.Assign(10, 5, 67, 17);
  inherited Init(Rect, 'ListBox Demo');

  // StringCollection
  StringCollection := new(PStringCollection, Init(5, 5));
  for i := 0 to Length(Tage) - 1 do begin
    StringCollection^.Insert(NewStr(Tage[i]));
  end;

  // ScrollBar für ListBox
  Rect.Assign(31, 2, 32, 7);
  ScrollBar := new(PScrollBar, Init(Rect));
  Insert(ScrollBar);

  // ListBox
  Rect.Assign(5, 2, 31, 7);
  ListBox := new(PNewListBox, Init(Rect, 1, ScrollBar));
  ListBox^.NewList(StringCollection);
  Insert(ListBox);

  // Cancel-Button
  Rect.Assign(19, 9, 32, 10);
  Insert(new(PButton, Init(Rect, '~T~ag', cmTag, bfNormal)));

  // Ok-Button
  Rect.Assign(7, 9, 17, 10);
  Insert(new(PButton, Init(Rect, '~O~K', cmOK, bfDefault)));
end;
gasensorX commented 2 years ago

https://forum.lazarus.freepascal.org/index.php/topic,59237.0/topicseen.html

updated

lazarus/freepascal offical reply

sechshelme commented 2 years ago

So, now a tutorial for ListBoxes has been added. With the ListBoxes, you can manually remove the loaded list from memory. This is described in the tutorial.