Jak wyczyścić zmienną typu TPanel?

0

Wydaje mi się, że z każdym dniem coraz bardziej się rozwijam i wpadam na pomysły o których wcześniej nie myślałem, a które w dużym stopniu ułatwiają mi napisanie tego co chce.
Do tej pory jeśli miałem menu główne i przyciski na nim. To po kliknięciu w któryś ustawiałem JakiśPanel.Visible := True i PanelMenuGlowne.Visible := False; Dodatkowo, aby powrócić to na każdym Panelu umieszczałem przycisk Powrót. Dziś postanowiłem spróbować zrobić to z jednym przyciskiem Powrót który w "drzewku" projektu byłby na poziomie tych paneli. Po naciśnięciu w któryś z przycisków menu głównego dodatkowo do zmiennej globalnej ZmiennaPanel typu TPanel przypisuje który Panel będzie akurat włączony. Później po naciśnięciu "Powrót" robię ZmiennaPanel.Visible := False i PanelMenuGlowne.Visible := True. Pierwsza kwestia czy jest to zrobione poprawnie.
I główne pytanie, w jaki sposób "wyczyścić" ZmiennaPanel po naciśnięciu przycisku "Powrót"?

1

Pisałem Ci wcześniej, że takie kombinacje prędzej czy później przyniosą kupę problemów, ale nie posłuchałeś;

I główne pytanie, w jaki sposób "wyczyścić" ZmiennaPanel po naciśnięciu przycisku "Powrót"?

To zależy co rozumiesz przez słowo "wyczyścić"; Jeżeli chodzi Ci o usunięcie wartości z adresem na konkretną instancję komponentu, to możesz do zmiennej wpisać Nil:

ZmiennaPanel := nil;

Jeśli będziesz potrzebował sprawdzić, czy w tej zmiennej jest zapisany wskaźnik na jakiś komponent, to możesz użyć zwykłego porównania do Nil, albo funkcji Assigned:

if ZmiennaPanel = nil then

{ lub }

if Assigned(ZmiennaPanel) then
0
furious programming napisał(a):

Pisałem Ci wcześniej, że takie kombinacje prędzej czy później przyniosą kupę problemów, ale nie posłuchałeś;

To znaczy jakie kombinacje?

1

Kombinacje z ukrywaniem i pokazywaniem paneli; Przy okazji:

To po kliknięciu w któryś ustawiałem JakiśPanel.Visible := True i PanelMenuGlowne.Visible := False;

Jeśli w jakiejś metodzie pokazujesz jakiś komponent, a w innej go chowasz (czyli w danej metodzie zawsze używasz tego samego stanu dla Visible), to czytelniej jest użyć metod Show i Hide, np.:

procedure ShowMenuPanel();
begin
  MyPanel.Show();
end;

procedure HideMenuPanel();
begin
  MyPanel.Hide();
end;

Właściwość Visible nadaje się w przypadku, gdy stan potrzebujemy wstępnie obliczyć (albo zanegować), czyli jeśli nie sprawdzamy czy komponent jest widoczny czy nie, np.:

procedure ReversePanelVisibleState();
begin
  MyPanel.Visible := not MyPanel.Visible;
end;

Ale to tak przy okazji.

0

Wiem, że powinno się zakładać jeden wątek na każdy problem, jednak nie chce zakładać już trzeciego dzisiaj, szczególnie, że pytanie w dużym stopniu dotyczy tego samego.
A więc sposób w który chciałem to zrobić nie zadziałał. Na początku przycisk Powrót się pojawiał. Później po jego wciśnięciu i następnie tego samego przycisku nadal było wszystko dobrze. Ale po naciśnięciu Powrót, a później innego przycisku, Powrót już się nie pojawiał.

W związku z tym próbuję zrobić coś takiego jak dynamiczne tworzenie komponentów. Przycisk tworzy się właściwie i działa właściwie. Pytanie, jak go później usunąć? Czy w ogóle trzeba to robić?

2

Chyba chodzi Tobie o Przycisk.Free? I generalnie zasada jest taka, że wszystko co utworzyłes, a co nie jest już potrzebne powinno się zwolnić. Ale o tym już pisałem.

1
dani17 napisał(a)

Pytanie, jak go później usunąć?

@olesio napisał Ci jak to zrobić - tak samo jak każdy inny obiekt; Jednak pamiętaj, że aby móc coś zwolnić - musisz mieć do tego dostęp; Więc jeśli tworzysz zwykły obiekt (jakiejś klasy, ale nie komponent), to rezultat zwracany przez konstruktor musisz sobie gdzieś zapisać, np. to pola klasy; Jeśli tworzysz komponent, to też dobrze mieć do niego referencję wrzuconą np. do pola, czy zmiennej;

Czy w ogóle trzeba to robić?

W przypadku zwykłego obiektu, jeśli nie zapiszesz sobie referencji w zmiennej - nie będziesz mógł go zwolnić, więc będzie wyciek pamięci; Komponentu pamiętać nie musisz, o ile poprawnie nadasz mu podczas tworzenia komponent rodzica; Wtedy taki komponent zostanie automatycznie zwolniony w destruktorze klasy formularza;

Tą zaletą trzymania referencji do dynamicznie tworzonych komponentów jest fakt, iż masz do nich bezpośredni dostęp; Jeśli referencji nie będziesz posiadał - pozostaną Ci kombinacje, takie jak np. szukanie komponentu po nazwie, za pomocą FindComponent; A nie jest ani wygodne, ani szybkie.

0

W moim przypadku chodzi o przycisk. Chciałem w zdarzeniu onClick dodać Sender.Free. Oczywiście w ten sposób to nie działa. Zrozumiałem, że mam coś zapisać do zmiennej. Tylko tak na prawdę czym są te referencje i do zmiennej jakiego typu je zapisać?

Próbowałem też z użyciem Self, zamiast Sender. Ale efekt był taki sam. Więc ostatecznie zrobiłem to tak Form.FindComponent('PrzyciskPowrot').Free;
W przypadku Self które umieszczam w zdarzeniu onClick dynamicznie utworzonego przycisku, ku mojemu zaskoczeniu odnosi się on do całego formularza. Choć pewnie coś źle zrozumiałem. Jeśli używam Sender to faktycznie odnosi się do przycisku. Jednak w ten sposób nie mogę go zwolnić bo pokazuje się błąd. 'External: SIGSEGV' At address 73746E65.

Tak jak napisałem, udało się z użyciem FindComponent. Jednak @furious programming z tego co wyczytałem w innym temacie, pisałeś że nie jest to najlepsze rozwiązanie i stosujesz je tylko w ostateczności.

Idę dalej. Tworzę 6 paneli dynamicznie. Które umieszczam w globalnej tablicy typu TPanel. Po kliknięciu w przycisk chciałbym je wyczyścić. Spróbowałem to zrobić z takim kodem jaki jest poniżej, jednak i w tym przypadku nie działa to tak jak bym chciał. Teoretycznie program jest zbudowany prawidłowo, nie wyświetla się żaden błąd, jednak tak na prawdę ten kod nie działa, bo te panele nadal istnieją. Co powoduje błąd w momencie próby utworzenia paneli z takimi samymi nazwami, ponieważ takie nazwy już istnieją, chociaż powinny być moim zdaniem zwolnione.

Aha. Naciśnięcie przycisku wywołuje procedurę UsunPanele.

 
procedure UsunPanele;
var
  I: Integer;
begin
  for I := 1 to 6 do
    begin
      Form.Panele[I].Parent := Nil;
      Form.FindComponent(Form.Panele[I].Name).Free;
    end;
end;

Form.PrzyciskiMenu[I].Parent := Nil; - działa prawidłowo. Ta druga część nie przynosi żadnego efektu, jakby nie odnajdywało Paneli o takich nazwach, które te Panele mają. Mimo, że po dodaniu linijki ShowMessage(Form.Panele[I].Name); wyświetla się 6 komunikatów z nadanymi nazwami.

Oczywiście mogę to rozwiązać podając nazwę każdego, ale to się moim zdaniem trochę mija z celem. Poza tym chciałbym wiedzieć czy można to usunąć bez nadawania nazwy.

1

Chciałem w zdarzeniu onClick dodać Sender.Free. Oczywiście w ten sposób to nie działa.

Ja bym proponował Sendera nie ruszać i traktować go tylko do odczytu :]

Tylko tak na prawdę czym są te referencje i do zmiennej jakiego typu je zapisać?

Po prostu wskaźnik na zarezerwowany blok pamięci dla obiektu; Jeśli zadeklarujesz zmienną tak:

var
  Foo: TPanel;

To zmienna Foo jest wskaźnikiem na obiekt klasy TPanel; Możesz do takiej zmiennej wpisać Nil, co sprawi, że zmienna nie będzie wskazywać na żaden obiekt; Możesz też do niej wpisać wskazanie na istniejący komponent:

Foo := TPanel(Sender);

i od tej pory zmienna Foo będzie posiadać wskazanie na panel, który został przekazany w argumencie Sender; Z tego co pisałeś wcześniej, potrzebujesz zapamiętać jakiś panel, więc w ten sposób możesz to zrobić;

Tak jak napisałem, udało się z użyciem FindComponent. Jednak @furious programming z tego co wyczytałem w innym temacie, pisałeś że nie jest to najlepsze rozwiązanie i stosujesz je tylko w ostateczności.

Mniej więcej tak, ale nie dokładnie; Zauważ, że FindComponent szuka komponentu na całym formularzu, na podstawie jego nazwy; Jest to operacja dość kosztowna, tym bardziej jeśli komponentów na formularzu jest dużo, a wywołań tej metody potrzebnych jest wiele; No i pamiętaj też, że ta metoda nie znajdzie komponentu, który został utworzony dynamicznie i nie została mu nadana nazwa;

Staram się unikać tej metody, bo jeżeli np. tworzę jakieś komponenty dynamicznie, to wpisuję je do jakiejś listy czy macierzy, tak abym w każdej chwili miał do nich bezpośredni dostęp; Jeśli takiego dostępu nie posiadam, to jedyne co pozostaje to albo przerobienie kodu, albo użycie FindComponent;

Co powoduje błąd w momencie próby utworzenia paneli z takimi samymi nazwami, ponieważ takie nazwy już istnieją, chociaż powinny być moim zdaniem zwolnione.

Na jednym formularzu nie mogą istnieć dwa komponenty o tej samej nazwie, przy czym wielkość liter nie ma znaczenia; Dlatego też musisz używać unikalnych identyfikatorów, albo nie nadawać ich wcale i zapamiętać te komponenty np. w macierzy;

Wracając do problemu - obstawiam, że w ogóle nie nadałeś komponentom nazw, dlatego też metoda FindComponent ich nie znajduje; Poza tym korzystasz z niebezpiecznej konstrukcji przy zwalnianiu tych paneli:

Form.FindComponent(Form.Panele[I].Name).Free;

Jeżeli komponent nie zostanie znaleziony, metoda FindComponent zwróci Nil, na którym wywołasz metodę Free, co spowoduje pojawienie się wyjątku o naruszeniu pamięci (SIGSEGV); Musisz ten kod zabezpieczyć:

procedure TForm1.DeletePanels();
var
  pnlToken: TPanel;
  intToken: Integer;
begin
  for intToken := 1 to 6 do
  begin
    pnlToken := Panele[intToken];

    if Assigned(pnlToken) then
      pnlToken.Free();
  end;  
end;

Poza tym Twoja metoda UsunPanele w ogóle nie jest metodą, bo nie jest składową klasy formularza! Obstawiam, że masz kupkę zmiennych i procedur globalnych, więc przenieś je do wnętrza klasy formularza, aby mieć łatwy dostęp do jego zawartości;

Oczywiście mogę to rozwiązać podając nazwę każdego, ale to się moim zdaniem trochę mija z celem. Poza tym chciałbym wiedzieć czy można to usunąć bez nadawania nazwy.

Jeżeli komponentom nie nadasz nazw, to FindComponent ich nie znajdzie; Dlatego też wrzuć sobie je do macierzy i odwołuj się do nich bezpośrednio, bez szukania ich na formularzu; To jedyny sensowny sposób w Twoim przypadku.

0
furious programming napisał(a):

Poza tym Twoja metoda UsunPanele w ogóle nie jest metodą, bo nie jest składową klasy formularza! Obstawiam, że masz kupkę zmiennych i procedur globalnych, więc przenieś je do wnętrza klasy formularza, aby mieć łatwy dostęp do jego zawartości;.

Zmienne miałem w klasie, a procedur faktycznie nie, co trochę mi utrudniało. Jednak jak już pisałem ja najlepiej się uczę po prostu próbując coś robić i tak na prawdę dopiero dziś trafiłem na klasy. Poczytałem trochę na ten temat, ale tak na prawdę na razie to jest mało. Jeszcze wieczorem zamierzam doczytać więcej, ale póki co chciałbym poprawić chociaż ten kod który już mam napisany. Dlatego mam jedno pytanie. Już ostatnie, bo trochę za bardzo już odchodzimy od tematu. W której sekcji tak na prawdę powinienem umieścić zarówno procedury, jak i zmienne?

1

W której sekcji tak na prawdę powinienem umieścić zarówno procedury, jak i zmienne?

Zmienne jako składowe klasy, nazywane są polami i deklaruje się je z reguły w sekcji Private; Dzięki temu dostęp do nich będzie możliwy tylko i wyłącznie z metod danej klasy - z zewnątrz pozostaną niewidoczne;

Natomiast procedury i funkcje jako składowe klasy, zwane są metodami; W większości, bo proceduralne i funkcyjne metody służące właściwościom do modyfikacji/odczytu pól nazywane są różnie - setter i getter (to raczej wywodzi się z Javy), metoda dostępowa i zmieniająca, akcesor i mutator itd.; Do tego dochodzą konstruktory, destruktory i zdarzenia, ale zbyt dużo tego by wszystko szczegółowo opisać;

Metody deklarować można w różnych sekcjach - w sekcji Private będą do dyspozycji tylko i wyłącznie wewnątrz tej klasy, w sekcji Public będą widoczne z zewnątrz (w sekcji Published także); Jest jeszcze sekcja Protected, ale póki co nią się nie zajmuj - dowiesz się o niej podczas uczenia się o OOP w Delphi.

0

Miało być ostatnie pytanie a się pojawiło jeszcze jedno, ale już w temacie. Aby zwolnić Panel wystarczy wpisać Panel.Free? W ten sposób wydaje się że działa prawidłowo. Panel znika po kliknięciu w przycisk w zdarzeniu którego jest ten kod. Wcześniej na innych stronach widziałem, że potrzebny jest też zapis Panel.Parent := Nil lub Panel := Nil.

1

Wystarczy samo zwolnienie przez Free - z**Nil**owanie komponentu rodzica nie ma na to żadnego wpływu;

Natomiast jeśli dany obiekt tworzysz i usuwasz wielokrotnie podczas działania programu, to musisz być pewny, że obiekt został poprawnie zwioniony z pamięci, a wskaźnik na niego będzie pusty (czyli będzie posiadał wartość Nil); Do tego celu możesz użyć FreeAndNil, aby zapisać tylko jedną instrukcję, robiącą obie rzeczy - zwolnienie i wyzerowanie zmiennej.

0

Niestety. Cały czas pokazuje się to samo. Błąd SIGSEGV. W tym momencie już nie mam pomysłu jak to zrobić. Mógłby ktoś przejrzeć ten kod i powiedzieć co jest źle?

unit Unit1;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  ExtCtrls;

type
  Tlumaczenia = Array of String;

  { TForm1 }

  TForm1 = class(TForm)
    NewO: TPanel;
    MenuGlowne: TPanel;
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
    AktualnyPanelMenuGlownego: TPanel;
    PrzyciskPowrot: TPanel;
    PrzyciskiMenu: Array[1..6] of TPanel;
    procedure UstawParametryStartowe;
    procedure UtworzMenu;
    procedure UtworzPowrot;
    procedure PrzyciskMGClick(Sender: TObject);
    procedure PrzyciskMGZakonczClick(Sender: TObject);
    procedure PrzyciskPowrotClick(Sender: TObject);
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.UstawParametryStartowe;
begin
  Form1.Color := clWhite;
  Form1.Left := 0;
  Form1.Top := 0;
  Form1.Width := Screen.Width;
  Form1.Height := Screen.Height;
  Form1.BorderStyle := bsNone;
  Form1.Caption := '';

  MenuGlowne.Color := clWhite;
  MenuGlowne.Left := 0;
  MenuGlowne.Top := 0;
  MenuGlowne.Width := Screen.Width;
  MenuGlowne.Height := Screen.Height;
  MenuGlowne.Visible := True;
  MenuGlowne.Caption := '';

  NewO.Color := clWhite;
  NewO.Left := 0;
  NewO.Top := 0;
  NewO.Width := Screen.Width;
  NewO.Height := Screen.Height;
  NewO.Visible := False;
  NewO.Caption := '';
end;

procedure TForm1.UtworzPowrot;
begin
  PrzyciskPowrot := TPanel.Create(Form1.AktualnyPanelMenuGlownego);
  with PrzyciskPowrot do
    begin
      Parent := Form1.AktualnyPanelMenuGlownego;
      Width := 250;
      Height := 80;
      Left := 20;
      Top := Form1.Height - 20 - 80;
      Color := clBlack;
      Font.Color := clWhite;
      Font.Size := 16;
      Caption := 'Powrót';
      Cursor := -21;
      OnClick := PrzyciskPowrotClick;
      Name := 'PrzyciskPowrot';
    end;
end;

procedure TForm1.UtworzMenu;
var
  I: Integer;
begin
  for I := 1 to 6 do
    begin
      if (I = 1) or (I = 6) then
        begin
          PrzyciskiMenu[I] := TPanel.Create(MenuGlowne);
          with PrzyciskiMenu[I] do
            begin
              Parent := MenuGlowne;
              Width := 250;
              Height := 80;
              Left := Round((Form1.Width - PrzyciskiMenu[I].Width) / 2);
              Color := clBlack;
              Font.Color := clWhite;
              Font.Size := 16;
              Cursor := -21;
              Case I of
                1:
                  begin
                    Top := Round((Form1.Height - (6 * PrzyciskiMenu[I].Height) - (5 * 30)) / 2);
                    Name := 'PrzyciskMGNewO';
                    OnClick := PrzyciskMGClick;
                    Caption := 'New';
                  end;
                6:
                  begin
                    Top := PrzyciskiMenu[1].Top + ((I - 1) * (PrzyciskiMenu[I].Height + 30));
                    Name := 'PrzyciskMGZakoncz';
                    OnClick := PrzyciskMGZakonczClick;
                    Caption := 'Zakończ';
                  end;
              end;
            end;
        end;
    end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  UstawParametryStartowe;

  UtworzMenu;
end;

procedure TForm1.PrzyciskMGClick(Sender: TObject);
begin
  if TPanel(Sender).Name <> '' then
    begin
      if TPanel(Sender).Name = 'PrzyciskMGNewO' then
        AktualnyPanelMenuGlownego := NewO;
      MenuGlowne.Hide;
      AktualnyPanelMenuGlownego.Show;
      UtworzPowrot;
    end;
end;

procedure TForm1.PrzyciskMGZakonczClick(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TForm1.PrzyciskPowrotClick(Sender: TObject);
begin
  AktualnyPanelMenuGlownego.Hide;
  AktualnyPanelMenuGlownego := Nil;
  MenuGlowne.Show;
  PrzyciskPowrot.Free;
  //FreeAndNil(PrzyciskPowrot);
  //showmessage(przyciskpowrot.Name);
end;

end.

 

Mam wrażenie, że nie można usunąć komponentu poprzez naciśnięcie na niego. A więc w jaki inny sposób można to zrobić?

0

A więc, wydaje mi się, że błędem jest to że próbuję usunąć przycisk który właśnie naciskam. Ostatnia linijka kodu. W załączniku wstawiam folder projektu.

Nie działa również kiedy próbuję usunąć panel naciskając przycisk, który na tym panelu się znajduje.

1

wrzuć timer na formę, ustaw interwał na 1, enabled na false, w zdarzeniu onclick przycisku "Powrót" ustaw:

Timer1.Enabled:=True; 

a w ontimer timera:

Timer1.Enabled:=False;
Powrot.Free; 

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