Reputation: 27276
I have a component which has a corresponding persistent. This persistent is of course published as a property of the component, showing in the object inspector. It has a few different properties of its own, mainly 4 integers (Left, Top, Right, Bottom). I also have a property in this persistent called Auto
which is intended to automatically calculate the 4 integers based on the size of the component.
More specifically, this graphic component has a border around it, each edge can have a different sized border. This persistent and its properties specify the thickness of the border on each side. When Auto
is enabled, the 4 border edges are calculated based on the size of the component.
Now it seems to work fine for the most part, except, somehow this Auto
property keeps going back to False randomly. Also, in design-time, when resizing the component with Auto
enabled, it does in fact calculate things accordingly, perfectly. But, in run-time, it goes back to False, and no longer calculates them anymore. Upon saving with Auto
enabled, closing the form, then re-opening it, this Auto
property is back to False again.
The 4 integer properties have setters, which if they are set, it turns this Auto
property to false. I'm assuming this is what's causing it to keep going to false again, except, I'm not telling it to set these properties anywhere.
Here's the persistent:
TJDGlassBorder = class(TPersistent)
private
fOwner: TJDGlass; //This is the parent component
fGlow: Integer;
fBottom: Integer;
fLeft: Integer;
fTop: Integer;
fRight: Integer;
fColor: TColor;
fOnEvent: TNotifyEvent;
fAuto: Bool;
procedure SetBottom(const Value: Integer);
procedure SetColor(const Value: TColor);
procedure SetGlow(const Value: Integer);
procedure SetLeft(const Value: Integer);
procedure SetRight(const Value: Integer);
procedure SetTop(const Value: Integer);
function GetBottom: Integer;
function GetLeft: Integer;
function GetRight: Integer;
function GetTop: Integer;
procedure SetAuto(const Value: Bool);
public
constructor Create(AOwner: TJDGlass);
destructor Destroy; override;
procedure Event;
procedure Assign(Source: TPersistent); override;
published
property Auto: Bool read fAuto write SetAuto default True;
property Left: Integer read GetLeft write SetLeft default 3;
property Top: Integer read GetTop write SetTop default 2;
property Right: Integer read GetRight write SetRight default 3;
property Bottom: Integer read GetBottom write SetBottom default 4;
property Color: TColor read fColor write SetColor;
property Glow: Integer read fGlow write SetGlow default 1;
property OnEvent: TNotifyEvent read fOnEvent write fOnEvent;
end;
/////////////
{ TJDGlassBorder }
procedure TJDGlassBorder.Assign(Source: TPersistent);
begin
inherited Assign(Source);
Event;
end;
constructor TJDGlassBorder.Create(AOwner: TJDGlass);
begin
fOwner:= AOwner;
fAuto:= True;
fColor:= clBlack;
fGlow:= 1;
Event;
end;
destructor TJDGlassBorder.Destroy;
begin
inherited;
end;
procedure TJDGlassBorder.Event;
begin
if assigned(fOwner) then
if fOwner <> nil then
fOwner.Invalidate;
if assigned(fOnEvent) then
fOnEvent(Self);
end;
function TJDGlassBorder.GetBottom: Integer;
begin
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= Max(2, fOwner.Height div 10);
fBottom:= Result;
end;
end;
end else begin
Result:= fBottom;
end;
end;
function TJDGlassBorder.GetLeft: Integer;
begin
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= (Top + Bottom) div 2;
fLeft:= Result;
end;
end;
end else begin
Result:= fLeft;
end;
end;
function TJDGlassBorder.GetRight: Integer;
begin
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= (Top + Bottom) div 2;
fRight:= Result;
end;
end;
end else begin
Result:= fRight;
end;
end;
function TJDGlassBorder.GetTop: Integer;
begin
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= Max(1, fOwner.Height div 30);
fTop:= Result;
end;
end;
end else begin
Result:= fTop;
end;
end;
procedure TJDGlassBorder.SetAuto(const Value: Bool);
begin
fAuto := Value;
Event;
end;
procedure TJDGlassBorder.SetBottom(const Value: Integer);
begin
fAuto:= False;
fBottom := Value;
Event;
end;
procedure TJDGlassBorder.SetColor(const Value: TColor);
begin
fColor := Value;
Event;
end;
procedure TJDGlassBorder.SetGlow(const Value: Integer);
begin
fGlow := Value;
Event;
end;
procedure TJDGlassBorder.SetLeft(const Value: Integer);
begin
fAuto:= False;
fLeft := Value;
Event;
end;
procedure TJDGlassBorder.SetRight(const Value: Integer);
begin
fAuto:= False;
fRight := Value;
Event;
end;
procedure TJDGlassBorder.SetTop(const Value: Integer);
begin
fAuto:= False;
fTop := Value;
Event;
end;
EDIT:
I attempted 3 more things in the above code, and still have the problem. This is what I did:
1: Published the Auto
property after the other 4 properties, thinking of the order that these properties are retrieved.
published
property Auto: Bool read fAuto write SetAuto default True;
property Left: Integer read GetLeft write SetLeft default 3;
property Top: Integer read GetTop write SetTop default 2;
property Right: Integer read GetRight write SetRight default 3;
property Bottom: Integer read GetBottom write SetBottom default 4;
Changed to:
published
property Left: Integer read GetLeft write SetLeft default 3;
property Top: Integer read GetTop write SetTop default 2;
property Right: Integer read GetRight write SetRight default 3;
property Bottom: Integer read GetBottom write SetBottom default 4;
property Auto: Bool read fAuto write SetAuto default True;
2: In the property setters for these integers, I'm checking to see if the new value differs from the existing value...
procedure TJDGlassBorder.SetTop(const Value: Integer);
begin
if Value <> fTop then begin
fAuto:= False;
fTop := Value;
Event;
end;
end;
3: In the property getters for these integers, I changed how it checks the existing value...
function TJDGlassBorder.GetTop: Integer;
begin
Result:= fTop;
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= Max(1, fOwner.Height div 30);
fTop:= Result;
end;
end;
end;
end;
Again, none of these attempts worked, I still have this problem.
Upvotes: 3
Views: 763
Reputation: 27276
FIXED!
The 3 attempts I put in my edit above were partially the problem, but the actual fix was removing the default
of the Auto
property. The thing is, I had this defaulted to True, which, in this case, that property is not saved in the DFM file. Therefore it did not even attempt to set this Auto
property. Removing the Default fixed, because now whether it's true or false, it's always saved in the DFM file, therefore always setting this value. The order of publishing the properties was also half of the problem.
Here's the final code for what I posted above:
TJDGlassBorder = class(TPersistent)
private
fOwner: TJDGlass; //This is the parent component
fGlow: Integer;
fBottom: Integer;
fLeft: Integer;
fTop: Integer;
fRight: Integer;
fColor: TColor;
fOnEvent: TNotifyEvent;
fAuto: Bool;
procedure SetBottom(const Value: Integer);
procedure SetColor(const Value: TColor);
procedure SetGlow(const Value: Integer);
procedure SetLeft(const Value: Integer);
procedure SetRight(const Value: Integer);
procedure SetTop(const Value: Integer);
function GetBottom: Integer;
function GetLeft: Integer;
function GetRight: Integer;
function GetTop: Integer;
procedure SetAuto(const Value: Bool);
public
constructor Create(AOwner: TJDGlass);
destructor Destroy; override;
procedure Event;
procedure Assign(Source: TPersistent); override;
published
property Left: Integer read GetLeft write SetLeft default 3;
property Top: Integer read GetTop write SetTop default 2;
property Right: Integer read GetRight write SetRight default 3;
property Bottom: Integer read GetBottom write SetBottom default 4;
property Color: TColor read fColor write SetColor;
property Glow: Integer read fGlow write SetGlow default 1;
property OnEvent: TNotifyEvent read fOnEvent write fOnEvent;
property Auto: Bool read fAuto write SetAuto;
end;
/////////////
{ TJDGlassBorder }
procedure TJDGlassBorder.Assign(Source: TPersistent);
begin
inherited Assign(Source);
Event;
end;
constructor TJDGlassBorder.Create(AOwner: TJDGlass);
begin
fOwner:= AOwner;
fAuto:= True;
fColor:= clBlack;
fGlow:= 1;
Event;
end;
destructor TJDGlassBorder.Destroy;
begin
inherited;
end;
procedure TJDGlassBorder.Event;
begin
if assigned(fOwner) then
if fOwner <> nil then
fOwner.Invalidate;
if assigned(fOnEvent) then
fOnEvent(Self);
end;
function TJDGlassBorder.GetBottom: Integer;
begin
Result:= fBottom;
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= Max(2, fOwner.Height div 10);
fBottom:= Result;
end;
end;
end;
end;
function TJDGlassBorder.GetLeft: Integer;
begin
Result:= fLeft;
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= (Top + Bottom) div 2;
fLeft:= Result;
end;
end;
end;
end;
function TJDGlassBorder.GetRight: Integer;
begin
Result:= fRight;
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= (Top + Bottom) div 2;
fRight:= Result;
end;
end;
end;
end;
function TJDGlassBorder.GetTop: Integer;
begin
Result:= fTop;
if fAuto then begin
if assigned(fOwner) then begin
if fOwner <> nil then begin
Result:= Max(1, fOwner.Height div 30);
fTop:= Result;
end;
end;
end;
end;
procedure TJDGlassBorder.SetAuto(const Value: Bool);
begin
fAuto := Value;
Event;
end;
procedure TJDGlassBorder.SetBottom(const Value: Integer);
begin
if Value <> fBottom then begin
fAuto:= False;
fBottom := Value;
Event;
end;
end;
procedure TJDGlassBorder.SetColor(const Value: TColor);
begin
fColor := Value;
Event;
end;
procedure TJDGlassBorder.SetGlow(const Value: Integer);
begin
fGlow := Value;
Event;
end;
procedure TJDGlassBorder.SetLeft(const Value: Integer);
begin
if Value <> fLeft then begin
fAuto:= False;
fLeft := Value;
Event;
end;
end;
procedure TJDGlassBorder.SetRight(const Value: Integer);
begin
if Value <> fRight then begin
fAuto:= False;
fRight := Value;
Event;
end;
end;
procedure TJDGlassBorder.SetTop(const Value: Integer);
begin
if Value <> fTop then begin
fAuto:= False;
fTop := Value;
Event;
end;
end;
Upvotes: 2
Reputation: 125669
First, as you've already noted, remove the default
. If you want the property to be True
unless specifically set to another value, set it in the constructor. When the DFM
is streamed in from your executable, any value stored in it will replace the one set in the constructor.
Second, your issue is based in part on faulty logic. :) If you want the Auto
property to control the others (meaning if Auto = True
then ignore any values set for the other properties), then test for that in the setters:
procedure TJDGlassBorder.SetTop(const Value: Integer);
begin
// Only change the value if Auto is not True
if (not FAuto) and (Value <> fTop) then
begin
fTop := Value;
Event;
end;
end;
procedure TJDGlassBorder.SetAuto(const Value: Boolean);
begin
if (Value <> FAuto) then
begin
FAuto := Value;
if FAuto then
begin
FTop := 0; // Or whatever. Set field and not property to
FLeft := 0; // avoid the setter's side effects
FWidth := 0;
FHeight := 0;
end;
// Whatever you need to do now.
end;
end;
Doing it this way means that you avoid the automatic changes by calling the setter.
I'd suggest if you can, you defer the call to Event
at runtime by using Loaded
; this is called after the component is fully streamed from the .dfm, and you can perform any calculations or redraws there after all properties are set. You can determine if you're in the IDE or at runtime by examining the ComponentState
property; if it contains csDesigning
, you're in the IDE at designtime, and if csLoading
is set you're in runtime. (ComponentState
is a set, so you check using if csDesigning in ComponentState
.)
Upvotes: 2