Zwalnianie dynamicznie tworzonych komponentów

0
uses ObslugaBazy;

type
  Baza = Record
    ...
    ...
  end;

  TProgram = class(TForm)
    PanelW: TPanel;
    procedure FormCreate(Sender: TObject);            
  private
    Nazwy: Array of TLabel;
    Przyciski: Array of TLabel;
    OBazaD: TObslugaB;
    procedure WyswietlDaneNazwyM(Sender: TObject);
  public
    BazaD: Baza;
  end;

var
  Program: TProgram;

implementation

procedure TProgram.FormCreate(Sender: TObject);
var
  I: Integer;
begin
   BazaD := OBazaD.LoadDB('info.txt');
   SetLength(Nazwy, Length(BazaD.NazwyM);
   for I := 0 to Length(BazaD.NazwyM) - 1 do
     begin
       Nazwy[I] := TLabel.Create(PanelW); 
       with Nazwy[I] do
       begin
         Parent := PanelW;
         Caption := BazaD.NazwyM;
       end;  
     end;

   SetLength(Przyciski, Length(BazaD.NazwyM);
   for I := 0 to Length(BazaD.NazwyM) - 1 do
     begin
       Przyciski[I] := TLabel.Create(PanelW); 
       with Przyciski[I] do
       begin
         Parent := PanelW;
         Caption := 'Wyświetl';
         OnClik := WyswietlDaneNazwyM;
       end;  
     end;
end;

procedure TProgram.WyswietlDaneNazwyM(Sender: TObject);
var
  I: Integer;
begin
  if PanelW.ControlCount > 0 then
    for I := 0 to PanelW.ControlCount - 1 do
      PanelW.Controls[I].Free;
end;
 

Chciałbym, aby po kliknięciu któregoś z labeli w tablicy Przyciski usuwały się wszystkie komponenty na PanelW. W moim przypadku tych komponentów jest 28, problem pojawia się w pętli przy numerze 15 (`I := 14'); W rzeczywistości są usuwane co drugie komponenty, a nie każdy.

3
for I :=  PanelW.ControlCount - 1 downto 0 do

i ten if linijkę wyżej jest całkowicie zbędny

0

Faktycznie to rozwiązanie działa. Zakładam, że po zwolnieniu Controls[0] dzieje się coś takiego:

 
for I := 0 to ControlCount - 2 do
  Controls[I] := Controls[I + 1];

W ten sposób co drugi jest pomijany bo wskakuje na poprzednie miejsce. Dzieje się tak bo Komponenty muszą być ponumerowane od 0??

2

z grubsza właśnie tak się dzieje (sam mechanizm jest bardziej skomplikowany) ale koniec końców jak zwolnisz kontrolkę to ona się usuwa z listy kontrolek

0

Dodałem kilka elementów do programu i nagle przestał działać. Błąd chyba muszę mieć w tym miejscu ponieważ po usunięciu tego fragmentu kodu i zostawieniu komponentów program przechodzi dalej. Natomiast po ich usunięciu pojawia się komunikat:

Projekt SJ raised exception class `External: SIGSEGV`. At Address E0

Nie mam pojęcia jak to rozwiązać. Pierwszy raz się z czymś takim spotkałem. Ten kod oczywiście usuwa kontrolki prawidłowo, ale dalej nie rzechodzi po wyskakuje ten błąd.

Czy może to być spowodowane tym, że w zdarzeniu OnClick jakiegoś komponentu usuwam go?

1

jeśli usuwasz jego samego to to może być przyczyną

2

Przede wszystkim nie powinieneś posługiwać się tablicami dynamicznymi kontrolek. Do tego służy TObjectList. To jest klasa specjalnie przygotowana do przechowywania dynamicznie tworzonych kontrolek. Bije na głowę rozwiązania z tablicami dynamicznymi. Między innymi sama może usunąć kontrolki, kiedy jest usuwana (ale nie musi - kwestia odpowiedniego parametru w konstruktorze).

0

Próbuję na kilka sposobów i na razie doszedłem do tego, że faktycznie chodzi o usuwanie komponentu tego który kliknąłem. Tylko nadal nie mam pomysłu jak mogę go usunąć. Na jego miejscu mają powstać inne po jego kliknięciu więc muszę go jakoś usunąć.

0

Doczytałem, co robisz. Prosty błąd. Po tym jak zrobisz Controls[i].Free, rodzic (a więc w tym wypadku ten panel) usuwa sobie z listy "Controls" tą właśnie kontrolkę. Więc to, co robisz, porównywalne jest z tym kodem:

 
for i:=0 to list.Count - 1 do
begin
  list.Remove(i);
end

Taki pseudokod, ale powinien wszystko wyjaśnić. Po każdej iteracji, ilość itemów na liście jest mniejsza o jeden. Ale tak naprawdę, pod spodem to nie jest widoczne i for chce iść dopóki nie osiągnie swojego warunku OBLICZONEGO NA POCZĄTKU - jeszcze przed jakimkolwiek usunięciem.

Dlatego NIE WOLNO w pętli FOR usuwać elementów listy/tablicy itd. Należy to robić w pętli while:

 
while list.Count > 0
  list.Remove(0);
0
Juhas napisał(a)

Dlatego NIE WOLNO w pętli FOR usuwać elementów listy/tablicy itd. Należy to robić w pętli while:

Problemem nie jest dobór pętli, a kontrola zakresu dozwolonych indeksów; Pętla For zawiera sztywną indeksację, więc aby w niej usunąć wszystkie elementy (lub tylko wybrane), należy indeksować w dół, co pokazał już @abrakadaber; Wtedy indeksy zawsze będą prawidłowe;

Nic nie stoi na przeszkodzie, aby w pętli usuwać elementy od początku do końca; Wtedy można skorzystać z pętli While i zwiększać indeks jedynie wtedy, gdy bieżącego elementu nie usuwamy;


@dani17 - skorzystaj z listy generycznej i nie kombinuj z tablicami; Mniej kodu trzeba będzie napisać, a sama obsługa kolekcji będzie dużo wygodniejsza; Przy okazji - nie używaj słów kluczowych jako identyfikatorów;

Deklaracja typu listy generycznej poniżej:

uses
  StdCtrls, FGL;

type
  generic TLabelsList<TLabel> = specialize TFPGObjectList<TLabel>;

To tyle - listę dla kontrolek typu TLabel masz gotową; Teraz należy zadeklarować sobie zmienną, która będzie przechowywać listę tych kontrolek; Niech ta zmienna będzie polem klasy formularza:

type
  TMainForm = class(TForm)
  private
    FLabels: TLabelsList;
  end;

W konstruktorze formularza tworzymy listę w pamięci:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FLabels := TLabelsList.Create();
end;

Konstruktor posiada parametr FreeObjects z domyślną wartością True, więc nie trzeba jej podawać; Dzięki tej wartości komponenty będą zwalniane z pamięci, przed ich usunięciem z listy; Dlatego też wycieków pamięci nie będzie;

W destruktorze formularza zwalniamy listę:

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FLabels.Free();
end;

Aby dodać komponent do listy, należy skorzystać z metody Add, podając referencję do nowo tworzonego komponentu; Aby usunąć pojedynczy komponent z listy, należy użyć metody Remove; Nie trzeba najpierw zwalniać samego komponentu z pamięci, bo tym zajmie się lista - po to w konstruktorze pozostawiono wartość True dla parametru FreeObjects; Aby usunąć wszystkie komponenty z listy, należy skorzystać z metody Clear; I tak samo - lista sama zwolni komponenty z pamięci;

Proste rozwiązanie? Proste, a do tego wygodne w obsłudze.

0

Panowie ale o czym Wy tutaj??

Problem jest w tym, że pytacz w OnClick robi Sender.Free co wywala resztę obsługi klikniecia i tego się tak nie przeskoczy. Są dwie drogi - albo wysłać do formy komunikat albo użyć do tego timera :). Jeśli chodzi o komunikat to może to wyglądać tak

const
  WM_FREE_OBJ = WM_USER + 1;

type

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    procedure FreeObj(var message: TMessage); message WM_FREE_OBJ;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
  PostMessage(Handle, WM_FREE_OBJ, 0, integer(Self));
end;

procedure TForm1.FreeObj(var message: TMessage);
begin
  TObject(message.LParam).Free;
end;

do uses trzeba (chyba) dodać Windows i Messages. Tym kodem PostMessage(Handle, WM_FREE_OBJ, 0, integer(Self)); można zwalniać dowolne kontrolki - nie trzeba pisać dla każdego zdarzenia oddzielnego kodu.

Taki kod powoduje, że komunikat WM_FREE_OBJ jest wrzucany do kolejki komunikatów i "forma" przechodzi do jej obsługi już po skończeniu obsługi kliknięcia guzika. Tutaj tylko jedna uwaga - nie wolno w zdarzeniu używać Application.ProcessMessages bo to wysypie program (może się też zdarzyć, że kontrolka "w środku" użyje ProcessMessages i całość się posypie).

Druga metoda to w zdarzeniu zapisać w zmiennej formy kontrolkę, którą chcemy usunąć, odpalić timer z krótkim interwałem (może być nawet 1) i w OnTimer usunąć zapisaną wcześniej kontrolkę i wyłączyć timer

0

A jak się później do tego odnieść? Czy mogę zrobić tak jak to robię z tablicami?

 
var
  Tab: Array of TLabel;
  I: Integer;
begin
  SetLength(Tab, x);
  ...
  for I := Low(Tab) to High(Tab) do
    if Tab[I].Caption = Nazwa then ...
end;

Jeszcze bardziej skomplikowane wydaje mi się to w przypadku tablicy dwuwymiarowej. Chociaż zapewne to rozwiązanie jest całkiem złe w przypadku tablicy [0..60][1..9]. Może czas nauczyć się tworzyć własne komponenty :)

0

A jak się później do tego odnieść?

Ale kogo pytasz i do czego się odnieść? Jeśli mnie o tę listę generyczną i dostęp do komponentów do niej dodanych, to odniesienie się do danego elementu listy jest składniowo identyczne jak w przypadku zwykłej macierzy;

Są dwa sposoby - pierwszy sposób to odwołanie się do elementu za pomocą domyślnej właściwości Items (nie trzeba pisać jej nazwy); Poniżej przykład, bazujący na kodzie z tego posta:

FLabels[10].Caption := 'foo bald bar';

// to samo co:

FLabels.Items[10].Caption := 'foo bald bar';

Dzięki temu możesz szybko uzyskać dostęp do konkretnego elementu, bez jego szukania; Jest też przydatny przy usuwaniu niektórych komponentów z listy (nie wszystkich), jeśli spełniają dane kryteria - pętla For DownTo lub While i do dzieła;

Drugi sposób to przeiterowanie po wszystkich elementach takiej listy, bez konieczności używania i operowania na indeksach; Tym sposobem jest pętla For In:

var
  Token: TLabel;
begin
  for Token in FLabels do
    Token.Caption := 'foo bald bar';

Dla zwykłych macierzy też tak można; Należy jednak pamiętać, że zmienną (iterator pętli) należy traktować jedynie do odczytu; Można wywoływać metody, ale przypisanie nowej wartości do iteratora nie daje żadnego efektu (iterator nie jest wskaźnikiem na element z wnętrza listy czy tablicy - zawiera jedynie kopię tego elementu/referencji).

1 użytkowników online, w tym zalogowanych: 0, gości: 1