Zastanawiam się jak najprościej uzyskać adres zmiennej w takim przypadku:
with TJakasKlasa.Create(Application) do
begin
ZrobTo;
Update(Self_TJakasKlasa);
end;
//zamiast robić tak
v := TJakasKlasa.Create(Application);
v.ZrobTo;
Update(v);
Nijak – jeśli używasz with
w połączeniu z konstruktorem. Nie używaj takiej konstrukcji, bo raz, że nie masz dostępu do referencji, a dwa, że ten kod jest niebezpieczny i w przypadku wyjątku spowoduje wyciek pamięci. Używanie lokalnych zmiennych nie boli, tak samo jak konstrukcji try finally
do zabezpieczania przed wyciekami.
Tak z ciekawości, skoro Delphi posiada ”inline variables” – czy poniższy kod jest kompilowalny?
with var Foo: TFoo := TFoo.Create() do
try
Foo.DoThis();
Foo.DoThat();
DoSomething(Foo);
finally
Foo.Free();
end;
Miałbyś fikuśne rozwiązanie swojego problemu.
Niestety mam wersję Tokyo 10.2.3 a takie deklarowanie zmiennych jest w nowszym RIO :(
zrobiłem tak:
type
TFramePlus = class(TFrame)
FrameAdapter: TsFrameAdapter;
private
{ Private declarations }
public
function GetSelf: Pointer;
procedure AfterCreation; virtual;
procedure BeforeDestroy; virtual;
end;
function TFramePlus.GetSelf: Pointer;
begin
Result := Self;
end;
procedure CreateTabAndOpen(APageControl: TsPageControl; AFrame: TClassOfFrame; Typ: TTypeOperation; ATabName: string);
function NameTab: string;
begin
case Typ of
Add: Result := TFramePlus(AFrame).Name+'_ADD';
Edit: Result := TFramePlus(AFrame).Name+'_EDIT';
end;
end;
var
TabSheet: TsTabSheet;
Component: TComponent;
begin
Component := APageControl.FindComponent(NameTab);
If Component <> nil then
APageControl.ActivePage := TsTabSheet(Component)
else
begin
TabSheet := TsTabSheet.Create(APageControl);
TabSheet.Name := NameTab;
TabSheet.Caption := ATabName;
TabSheet.UseCloseBtn := true;
TabSheet.PageControl := APageControl;
with AFrame.Create(Application) do
begin
FormData.SkinManager.UpdateScale(GetSelf);
Parent := TabSheet;
APageControl.ActivePage := TabSheet;
end;
end;
end;
Funkcją GetSelf pobieram referencję
No dobra, jakieś to rozwiązanie jest, ale po co na siłę używasz with
, skoro w tym przypadku potrzebujesz referencji? Utwórz tę ramkę w sposób standardowy, z wykorzystaniem zmiennej lokalnej – w niczym to nie przeszkodzi.
Czyli takie rozwiązanie jest lepsze i pewniejsze wraz z użyciem try except?
begin
Component := APageControl.FindComponent(NameTab);
If Component <> nil then
APageControl.ActivePage := TsTabSheet(Component)
else
begin
TabSheet := TsTabSheet.Create(APageControl);
try
TabSheet.Name := NameTab;
TabSheet.Caption := ATabName;
TabSheet.UseCloseBtn := true;
TabSheet.PageControl := APageControl;
Frame := AFrame.Create(Application);
try
FormData.SkinManager.UpdateScale(Frame);
Frame.Id := Id;
Frame.Parent := TabSheet;
APageControl.ActivePage := TabSheet;
except
Frame.Free;
raise;
end;
except
TabSheet.Free;
raise;
end;
end;
end;
Czyli takie rozwiązanie jest lepsze i pewniejsze wraz z użyciem try except?
Nie do końca. Blok try except end
służy do obsługi wyjątków a samo zwalnianie obiektu należy umieścić w bloku try finally end
. Możesz je zagnieździć
try
MyClass := TMYclass.Create(Self);
try
.....
finally
MyClass.Free;
end;
except
// obsługa wyjątku.
end;
można zastosować odwrotne zagnieżdżenie
try
MyClass := TMYclass.Create(Self);
try
.....
except
// obsługa wyjątku
end;
finally
MyClass.Free;
end;
Stosowałem takie rozwiązanie w sytuacji kiedy obsługa wyjątku wymagała dostępu do obiektu generującego wyjątek .
Ale wtedy ryzykujemy że obsługa wyjątku w sekcji except może generować kolejny wyjątek .
Wtedy można zrobić podwójne zagnieżdżenie
try
try
MyClass := TMYclass.Create(Self);
try
.....
except
// obsługa pierwotnego wyjątku
end;
finally
MyClass.Free;
end;
except
// obsługa ewentualnego wyjątku powstałego w obsłudze pierwotnego wyjątku
end;
Taki wariant po pierwsze zapewnia możliwość dostępu do obiektu generującego wyjątek na etapie obsługi pierwotnego wyjątku, a po drugie zapewnia że cała sekcja jest zamknięta, czyli zwalnia obiekt i obsłuży wszystkie wyjątki bez względu na na miejsce ich powstania/
Zwalnianie komponentu – nawet w przypadku wyjątku – nie jest konieczne, bo tym zajmie się komponent będący jego rodzicem. Tak więc spokojnie można usunąć z kodu wywołania Free
, tak samo jak oba bloki try except
.
Zwalnianie komponentu – nawet w przypadku wyjątku – nie jest konieczne, bo tym zajmie się komponent będący jego rodzicem. Tak więc spokojnie można usunąć z kodu wywołania Free, tak samo jak oba bloki >try except.
W pierwszym punkcie masz rację, ale to był szkolny przykład, nie każdy obiekt musi mieć rodzica. Parentem może być nil
albo konstruktor klasy nie ma "parenta".
A w drugim,to nie rozumiem dlaczego uważasz że można usunąć bloki try except
grzegorz_so napisał(a):
W pierwszym punkcie masz rację, ale to był szkolny przykład, nie każdy obiekt musi mieć rodzica. Parentem może być
nil
albo konstruktor klasy nie ma "parenta".
Tak, ale mój post jest odpowiedzią na post OP, a nie na Twój. ;)
A uściślając – nie rodzicem, a właścicielem, bo chodzi o parametr Owner
konstruktora, a nie o właściwość Parent
. Przy czym w kodzie podanym przez OP, są tworzone komponenty wizualne, zawsze posiadające zarówno właściciela, jak i rodzica (używane jest albo Application
, albo APageControl
).
A w drugim,to nie rozumiem dlaczego uważasz że można usunąć bloki
try except
Bo są zbędne – jeśli komponent zostanie prawidłowo stworzony, to jego zwolnieniem zajmie się właściciel. A jeśli nie, to konstruktor zadba o dealokację komponentu niepoprawnie utworzonego, a kolejne instrukcje (i kolejne wywołania konstruktorów) zostaną pominięte, więc nie będzie czego sprzątać.
Tym bardziej że jedyne co się znajduje wewnąrz except
to nadmiarowy Free
oraz raise
, który puszcza wyjątek dalej. Dokładnie to samo można osiągnąć usuwając te bloki – działanie się nie zmieni, a kodu będzie mniej.
Dziękuję wszystkim za podpowiedzi. Wracając do tematu kod musi być z try.. except jak poniżej:
begin
Component := APageControl.FindComponent(NameTab);
If Component <> nil then
APageControl.ActivePage := TsTabSheet(Component)
else
begin
TabSheet := TsTabSheet.Create(APageControl);
try
TabSheet.Name := NameTab;
TabSheet.Caption := ATabName;
TabSheet.UseCloseBtn := true;
TabSheet.PageControl := APageControl;
Frame := AFrame.Create(TabSheet);
FormData.SkinManager.UpdateScale(Frame);
Frame.Id := Id;
Frame.Parent := TabSheet;
APageControl.ActivePage := TabSheet;
except
APageControl.Pages[TabSheet.PageIndex].Free;
raise;
end;
end;
end;
Ponieważ @furious programming ma rację można wywalić zwalniania obiektów bo tym zajmą się wskazane komponenty podczas tworzenia Frame i Tabsheet. Ale i to ale jest najważniejsze. W sytuacji gdy np Frame wygeneruje wyjątek. To TabSheet i Frame jest już stworzone i zwolnione by została dopiero po zwolnieniu APageControl czyli w moim przypadku po zamknięciu aplikacji. Co jest bezsensu dla mnie. Dlatego muszę użyć sekcję try .. except z Free bo inaczej otrzymam błąd przy kolejnej próbie wykonania procedury ( taka nazwa komponentu już istnieje - a dokładnie zobaczę pustą stronę TabSheet ).
Jedynie Frame.Free mogę pominąć bo tym zajmie się Tabsheet po wywołaniu tabsheet.free.
Niechcąc zakładać kolejnego posta powiąże go z tym. Powyżej przedstawiłem dynamiczne tworzenie zakładki i ramki. I teraz na tej ramce mam przycisk z poniższym kodem:
procedure Button;
begin
if Parent.ClassName = 'TsTabSheet' then
TsPageControl(Parent.Parent).Pages[TsTabSheet(Parent).PageIndex].Free;
end;
Która ma za zadanie zamknąć zakładkę TabSheet wraz z ramką. I to prawie działa bo zamyka ale po drodze otrzymuję błędy AV. No tak sobie myślę że chce zniszczyć Frame które jeszcze wykonuje kod w Button. Jakie jest najlepsze rozwiązanie takiej sytuacji?
@kAzek: na pewno powinno działać? Żaden obiekt nie może sam siebie zwolnić z poziomu swojej metody, a do tego się sprowadza to co pokazałeś. IMO trzeba by ”asynca” użyć, aby to było możliwe.
Pod Lazarusem skorzystałbym z QueueAsyncCall
, a w Delphi nie wiem co tam macie.
Działa i nie sam siebie zwalnia tylko rodzica a rodzic dopiero komponenty. Jest tak zwalnia TabSheet który zwalnia Frame a dopiero Frame zwalnia ten Button, który to wywołał (i inne komponenty).
EDIT: @furious programming
Proste sprawdzenie kod tworzenia TabSheet
i Frame
:
procedure TForm4.Button1Click(Sender: TObject);
var
TabSheet: TTabSheet;
Frame: TFrame1;
Name: string;
i: Integer;
begin
TabSheet:= TTabSheet.Create(PageControl1);
try
i:= 0;
repeat
Inc(i);
Name:= 'TabSheet' + IntToStr(i);
until (PageControl1.FindChildControl(Name) = nil);
TabSheet.Name:= Name;
TabSheet.Caption:= Name;
Frame:= TFrame1.Create(nil); //specjalnie bez własciciela
Frame.Parent:= TabSheet;
TabSheet.PageControl:= TPageControl(TabSheet.Owner);
//raise Exception.Create('Error');
except
on E: Exception do begin
TabSheet.Free;
ShowMessage(E.Message);
end
end;
end;
Button na Frame zwalniający w wersji wszystko ok:
procedure TFrame1.Button1Click(Sender: TObject);
begin
Self.Parent.Free;
end;
w wersji z wyciekiem pamięci
procedure TFrame1.Button1Click(Sender: TObject);
var
oldParent: TWinControl;
begin
oldParent:= Self.Parent;
Self.Parent:= nil; //pozbywamy się rodzica. Nie ma rodzica ani właściciela więc zwolnienie TabSheet nie zwolni Frame
oldParent.Free; //zwalniamy TabSheet
end;
Gdyby kod Self.Parent.Free
działał źle to albo by się wykrzaczał albo powodował wyciek a nic takiego nie ma miejsca.
kAzek napisał(a):
Działa i nie sam siebie zwalnia tylko rodzica a rodzic dopiero komponenty. Jest tak zwalnia TabSheet który zwalnia Frame a dopiero Frame zwalnia ten Button, który to wywołał (i inne komponenty).
No właśnie, czyli de facto sam siebie zwalnia, tyle że pośrednio – wszystkie kontrolki zostaną zwolnione (łącznie z przyciskiem wywołującym) zanim zdarzenie OnClick
zakończy swoje działanie. No ale skoro działa i nie leci wyjątek to widać implementacja pozwala na taki zabieg (jest to dozwolone).
Napisałeś, że „powinno działać”, jakbyś nie był pewny – dlatego dopytałem, bo sam też nie byłem pewny czy to zadziała.
kAzek napisał(a):
Kombinujesz taki kod powinien działać:
procedure TFrame1.Button1Click(Sender: TObject); begin Self.Parent.Free; end;
Rodzicem
Frame
jestTabSheet
, który zadba o zwolnienieFrame
.
Niestety ten kod jest nie poprawny. Owszem zamyka zakładkę ale gdy w pagecontrol próbujemy myszką przejść na inną zakładkę otrzymujemy błąd 'Invalid pointer' czego nie ma gdy użyjemy tej mojej zawiłej instrukcji:
TsPageControl(Parent.Parent).Pages[TsTabSheet(Parent).PageIndex].Free;
A sam błąd wywalał komonent z AlphaSkins - już zostało zlokalizowane i naprawione.
with
powinno się palce łamać...