Dmitry Onoshko 0 Posted 6 hours ago 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
Remy Lebeau 1659 Posted 3 hours ago (edited) 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 3 hours ago by Remy Lebeau 1 Share this post Link to post
Dmitry Onoshko 0 Posted 3 hours ago 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