Jump to content
Dmitry Onoshko

TPen.OnChange and Invalidate in custom control at design-time

Recommended Posts

I’m writing a TCustomControl descendant that implements a custom TPen-typed property for grid lines, as well as a few scalar ones. All of the properties are defined along the lines of:

property Aspect: Single read FAspect write SetAspect;

and then

procedure TMyControl.SetAspect(const AValue: Single);
begin
  if FAspect <> AValue then
  begin
    FAspect := AValue;
    Invalidate;
  end;
end;

The same thing is done for the TPen-typed property except that Assign is used:

procedure TMyControl.SetGridPen(const AValue: TPen);
begin
  FGridPen.Assign(AValue);
  Invalidate;
end;

But when I do it this way, changing the GridPen property at design-time doesn’t take effect until I unselect the control. (Obviously, the FGridPen is created in the control constructor and doesn’t get destroyed until the destructor.)

 

I looked at the implementation of TShape and the only difference I could find is that TShape doesn’t call Invalidate directly in property write method but assigns an internal method to pen’s OnChange event, then Invalidate’ing itself in the event handler. So I tried to change my code this way:

procedure TMyControl.SetGridPen(const AValue: TPen);
begin
  FGridPen.Assign(AValue);
end;

constructor TMyControl.Create(AOwner: TComponent);
begin
  // ...
  FGridPen.OnChange := StyleChanged;
  // ...
end;

procedure TMyControl.StyleChanged(Sender: TObject);
begin
  Invalidate;
end;

and that actually did the job: the control now repaints itself immediately for GridPen changed as well.

 

But since I’m willing to understand what actually happens and why the initial piece of code was wrong, I’ve digged into TPen implementation. And I can’t see what actually is so different between doing my control invalidation right after FGridPen.Assign and in StyleChanged handler that is really called from inside the Assign method: it doesn’t look like anything should depend on the handler being assigned, I doubt invalidation of a control goes differently whether the pen is locked or unlocked inside Assign method, etc.

 

Please, rub my nose into the difference.

Share this post


Link to post
2 hours ago, Dmitry Onoshko said:

But when I do it this way, changing the GridPen property at design-time doesn’t take effect until I unselect the control.

Correct. The change doesn't take effect visually until the next time the control is painted. And since you are not detecting the change in real time, you are not able to trigger an immediate repaint.

Quote

But since I’m willing to understand what actually happens and why the initial piece of code was wrong, I’ve digged into TPen implementation. And I can’t see what actually is so different between doing my control invalidation right after FGridPen.Assign and in StyleChanged handler that is really called from inside the Assign method

When you use the Object Inspector to modify the value of a sub-property of your GridPen, the Inspector does not create a new TPen object to assign to your GridPen property's setter method. Instead, it uses your GridPen property's getter to access the original TPen object and directly update its sub-property that you are modifying. That is why you need to use the TPen's OnChange event to recognize changes in the sub-properties.

 

The same thing happens when a user of your component changes the value of a sub-property in code at runtime. They don't write code like this (but they can if they want to):

NewPen := TPen.Create;
NewPen.Assign(MyComponent.GridPen); // <-- GridPen GETTER called
NewPen.Style := ...;
MyComponent.GridPen := NewPen; // <-- GridPen SETTER called
NewPen.Free;

Instead, they write code like this:

MyComponent.GridPen.Style := ...; // <-- GridPen GETTER called and TPen.OnChange triggered

// effectively the same as:
// MyComponent.FGridPen.Style := psDash;

 

Edited by Remy Lebeau
  • Thanks 1

Share this post


Link to post
1 minute ago, Remy Lebeau said:

Correct. The change doesn't take effect visually until the next time the control is painted. And since you are not detecting the change in real time, you are not able to trigger an immediate repaint.

When you use the Object Inspector to modify the value of a sub-property of your GridPen, the Inspector does not create a new TPen object to assign to your GridPen property's setter method. Instead, it uses your GridPen property's getter to access the original TPen object and directly update its sub-property that you are modifying. That is why you need to use the TPen's OnChange event to recognize changes in the sub-properties.

 

The same thing happens when a user of your component changes the value of a sub-property in code at runtime. They don't write code like this (but they can if they want to):

 

NewPen := TPen.Create;

NewPen.Style := psDash;

MyComponent.GridPen := NewPen; // <-- GridPen SETTER called

NewPen.Free;

 

Instead, they write code like this:

 

MyComponent.GridPen.Style := psDash; // <-- GridPen GETTER called and TPen.OnChange triggered 

Makes sense now, thanks.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×