Вот класс, который я создал для добавления TLabel к TTrackBar. Метка показывает значение трекбара при перетаскивании, а затем исчезает. Экземпляр создается во время выполнения, а родительский объект устанавливается в форму. Он отлично работает, но выдает ошибку при закрытии приложения, если трекбар все еще существует. Тем не менее, нет проблем, если трекбар освобождается во время выполнения, а затем приложение закрывается. При отладке этой строки при закрытии приложения (FLabel.Free;) я вижу, что FLabel и данные в нем все еще существуют, но все равно выдает эту ошибку. Меня беспокоит, что если я просто удалю эту строку, то при освобождении объекта во время выполнения произойдет утечка памяти. Я попытался изменить его на if Assigned(FLabel), затем FLabel.Free; но без изменений. Я знаю, что это должно быть как-то связано с тем, что родитель метки был установлен.
unit TrackBarLabelUnit;
interface
uses
System.Types, System.Classes, System.SysUtils, FMX.Types, FMX.StdCtrls,
FMX.Controls;
type
TValueToString = function(AValue : Single) : String of object;
TTrackBarLabel = class(TTrackBar)
private
FLabel : TLabel;
FSuffix : String;
FTimer : TTimer;
FOffset : Integer;
FValueToString : TValueToString;
procedure TimerTimer(Sender: TObject);
protected
procedure ParentChanged; override;
procedure DoTracking; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Suffix : String read FSuffix write FSuffix;
property LabelOffset : Integer read FOffset write FOffset;
property ValueToString : TValueToString write FValueToString;
end;
implementation
constructor TTrackBarLabel.Create(AOwner: TComponent);
begin
inherited;
FLabel := TLabel.Create(nil);
FLabel.Visible := False;
FTimer := TTimer.Create(nil);
FTimer.Interval := 100;
FTimer.Enabled := False;
FTimer.OnTimer := TimerTimer;
FSuffix := '';
FOffset := 22;
end;
destructor TTrackBarLabel.Destroy;
begin
FLabel.Free; // EInvalidPointer error here when application is closed
FTimer.Free;
inherited Destroy;
end;
procedure TTrackBarLabel.ParentChanged;
begin
inherited;
FLabel.Parent := Parent;
end;
procedure TTrackBarLabel.DoTracking;
begin
inherited;
if not Assigned(Thumb) then Exit;
FLabel.Visible := True;
FLabel.Tag := 10;
FLabel.Opacity := 1;
if Assigned(FValueToString) then
FLabel.Text := FValueToString(Value) + FSuffix
else
FLabel.Text := FloatToStrF(Value, ffFixed, 12, 1) + FSuffix;
if Orientation = TOrientation.Horizontal then begin
FLabel.Position.X := Position.X + Thumb.Position.X +
(Thumb.Width - FLabel.Width) * 0.5;
FLabel.Position.Y := Position.Y + FOffset;
FLabel.TextSettings.HorzAlign := TTextAlign.Center;
end else begin
FLabel.Position.X := Position.X + FOffset;
FLabel.Position.Y := Position.Y + Thumb.Position.Y - 2;
FLabel.TextSettings.HorzAlign := TTextAlign.Leading;
end;
FTimer.Enabled := False;
FTimer.Enabled := True;
end;
procedure TTrackBarLabel.TimerTimer(Sender: TObject);
begin
FLabel.Tag := FLabel.Tag - 1;
FLabel.Opacity := FLabel.Tag * 0.2;
if FLabel.Tag < 0 then begin
FLabel.Visible := False;
FTimer.Enabled := False;
end;
end;
end.
Чаще всего исключение недопустимого указателя означает, что вы дважды пытаетесь освободить объект.
Проблема в этом случае заключается в том, что элемент управления освобождает своих потомков при освобождении. Поэтому, когда форма освобождается, она также освобождает TLabel
. Таким образом, когда ваш TTrackBarLabel.Destroy
выполняется, ваш FLabel
является висячим указателем, и вы не должны делать FLabel.Free
.
Все разработчики Delphi знают, что компонент освобождает принадлежащие ему компоненты, когда он освобождается. Менее известный факт, что элемент управления также освобождает своих потомков.
В вашем случае вы можете просто удалить FLabel.Free
. Однако это приведет к утечке памяти, если вы никогда не устанавливаете свойство FLabel
Parent
.
Чтобы убедиться, что метка автоматически освобождается, когда полоса дорожки освобождается, сделайте полосу дорожки владельцем метки:
FLabel := TLabel.Create(Self);
Кстати, ваше предложение
if Assigned(FLabel) then
FLabel.Free;
не поможет, потому что FLabel
— это висячий указатель при возникновении ошибки (это не nil
).
Кроме того, в Delphi вы никогда не пишете
if Assigned(FLabel) then
FLabel.Free;
потому что TObject.Free
в основном делает if Assigned then Destroy
, так что
if Assigned(FLabel) then
FLabel.Free;
означает
if Assigned(FLabel) then
if Assigned(FLabel) then
FLabel.Destroy;
что очень глупо.
@XylemFlow: (1) Верно. Удалите свой FLabel.Free
и напишите TLabel.Create(Self)
, чтобы трекбар стал владельцем ярлыка. (2) Действительно, «все» знают, что компонент освобождает принадлежащие ему (Owner
) компоненты, когда он освобождается, но не так хорошо известно, что элемент управления освобождает своих дочерних элементов (Parent
), когда он освобождается. (3) TTimer
является компонентом (TComponent
), но не элементом управления (TControl
). Это не то, что отображается на экране; у него нет родителя. Если вы создадите его с трек-баром в качестве его владельца, он действительно будет освобожден, когда трек-бар будет, но когда вы это сделаете FTimer.Free
...
... владелец (трек-бар) уведомляется об этом, и таймер удаляется из списка принадлежащих владельцу компонентов.
Спасибо. Похоже, тогда мне вообще не нужен деструктор. Я предполагаю, что деструктор действительно нужен только для освобождения объектов, у которых нет владельца, или для очистки массива.
Технически вам нужен деструктор, но вам не нужно вызывать его самостоятельно, потому что библиотека времени выполнения сделает это за вас (в данном случае, когда владелец освобождается).
Чтобы избежать проблемы с оборванным указателем, вызовите FLabel.FreeNotification(Self)
, а затем переопределите виртуальный Notification()
, чтобы установить FLabel := nil
при уничтожении метки. Если Self
используется в качестве владельца, FreeNotification()
вызывается автоматически для вас, но вам все равно нужно реализовать Notification()
@RemyLebeau: Верно, но в большинстве ситуаций более простого подхода, описанного в моем A, конечно, вполне достаточно. Но это хороший комментарий для тех, у кого есть особые потребности.
@AndreasRejbrand в некотором роде, это особый случай, поскольку лейбл имеет другой Parent
, чем его Owner
. Как вы сказали, Parent
может освободить метку до того, как освободит TrackBar, поэтому лучше не оставлять висячий указатель на волю случая.
@RemyLebeau: Да, вы можете ссылаться на FLabel
в других местах, кроме деструктора. Это может быть опасно, и если это так, вам действительно может понадобиться уведомление.
Спасибо. Таким образом, мне не нужно освобождать его самому, и чтобы убедиться, что я делаю трекбар владельцем метки. Я сделал это изначально, но хотел освободить его сам, чтобы убедиться, что он был освобожден. Я где-то читал, что создание метки с nil будет означать, что я буду нести ответственность за ее освобождение самостоятельно, но это не похоже на тот случай, поскольку форма все равно освобождает своих детей. Почему нет проблем с TTimer? Я знаю, что родитель не установлен как метка, но если я создам его с помощью трекбара в качестве владельца, у меня все равно не возникнет проблем, когда он будет освобожден.