Mike Torrettinni 198 Posted August 14, 2020 I have interposer class that draws custom border around TPanel. But I only want this for selected panel on the form, not all. So, I create a list of panels to be customized in xCustomizedPanels, on FormCreate. A very simple implementation: TPanel = class(Vcl.ExtCtrls.TPanel) protected procedure Paint; override; end; var xCustomizedPanels: TArray<TPanel>; procedure TPanel.Paint; var vRect: TRect; vPanel: TPanel; begin for vPanel in xCustomizedPanels do if vPanel = Self then begin vRect := GetClientRect; // Frame panel Canvas.Brush.Color := clSilver; Canvas.FrameRect(vRect); end; end; But! Isn't there a better solution so I assign customized Paint/OnPaint method only to selected panels, something like this: procedure TfrmMainForm.FormCreate(Sender: TObject); begin // assign customization Panel1.OnPaint/Paint := CustomPaint; Panel2.OnPaint/Paint := CustomPaint; ... end; Thanks for any suggestion! Share this post Link to post
Dave Nottage 557 Posted August 14, 2020 I usually add a Boolean value to the interposer and set it to True when the form is created, only for those components with the specialised behaviour. 1 Share this post Link to post
Mike Torrettinni 198 Posted August 14, 2020 3 minutes ago, Dave Nottage said: only for those components with the specialised behaviour. How do you do that, how do you limit which controls the interposer applies to? Like I did, with a list of controls or similar? Share this post Link to post
Dave Nottage 557 Posted August 14, 2020 2 minutes ago, Mike Torrettinni said: How do you do that 9 minutes ago, Dave Nottage said: I usually add a Boolean value to the interposer For your case: TPanel = class(Vcl.ExtCtrls.TPanel) private FSpecialised: Boolean; protected procedure Paint; override; property Specialised: Boolean read FSpecialised write FSpecialised; end; When the form is created, set the Specialised property to True for the TPanels where they need the special treatment. In the Paint method, check the FSpecialised flag. Share this post Link to post
Mike Torrettinni 198 Posted August 14, 2020 Just now, Dave Nottage said: For your case: TPanel = class(Vcl.ExtCtrls.TPanel) private FSpecialised: Boolean; protected procedure Paint; override; property Specialised: Boolean read FSpecialised write FSpecialised; end; When the form is created, set the Specialised property to True for the TPanels where they need the special treatment. In the Paint method, check the FSpecialised flag. This looks good, the boolean flag. But this applies to all panels on the form, right? Do you always do that, or you have a way to limit to which panel this applies to? Share this post Link to post
FPiette 382 Posted August 14, 2020 Instead of an interposer class, I would simply create a new component inheriting from TPanel. The component code is what @DaveNottage shown except for the class name. Then this component is added to a package (Actually two: a run time and a design time package) so that it can be installed in the IDE and available where you need: you drop your specialized component on the form/frame instead of the standard panel. If you don't know how to create your component, there are a lot of tutorials on the Internet showing how to create your own TMyButton. It's the same process with a panel Google is your friend. Share this post Link to post
Mike Torrettinni 198 Posted August 14, 2020 4 minutes ago, FPiette said: I would simply create a new component inheriting from TPanel. Aha, I see now. I kind am trying to avoid creating components, I would really like to not do that, if possible. Share this post Link to post
Dave Nottage 557 Posted August 14, 2020 55 minutes ago, Mike Torrettinni said: But this applies to all panels on the form, right? The property applies to all of them, yes.. but you set it to True only for the ones you want the specialised behaviour on. Share this post Link to post
David Heffernan 2345 Posted August 14, 2020 Another option is to use a layered window to add the additional painting. This is very flexible, not limited to panels, doesn't require any changes to the implementation of the controls. Share this post Link to post
Fr0sT.Brutal 900 Posted August 14, 2020 1 hour ago, FPiette said: Instead of an interposer class, I would simply create a new component inheriting from TPanel. This means all the visual component mess: separate package, installation, registration, replacing existing panels, bundling it with an app etc. 4 hours ago, Mike Torrettinni said: Isn't there a better solution TPanel = class(Vcl.ExtCtrls.TPanel) protected procedure Paint; override; public OnCustomPaint: TNotifyEvent; end; procedure TPanel.Paint; begin if Assigned(OnCustomPaint) then OnCustomPaint(Self) else inherited; end; I would do something alike 2 1 Share this post Link to post
Mike Torrettinni 198 Posted August 14, 2020 3 hours ago, Fr0sT.Brutal said: This means all the visual component mess: separate package, installation, registration, replacing existing panels, bundling it with an app etc. TPanel = class(Vcl.ExtCtrls.TPanel) protected procedure Paint; override; public OnCustomPaint: TNotifyEvent; end; procedure TPanel.Paint; begin if Assigned(OnCustomPaint) then OnCustomPaint(Self) else inherited; end; I would do something alike Great! It works: TPanel = class(Vcl.ExtCtrls.TPanel) protected procedure Paint; override; public var OnCustomPaint: TNotifyEvent; procedure CustomPaint(Sender: TObject); // Custom Paint end; procedure TPanel.Paint; begin if Assigned(OnCustomPaint) then OnCustomPaint(Self) else inherited; end; procedure TPanel.CustomPaint(Sender: TObject); var vRect: TRect; vPanel: TPanel; begin vRect := GetClientRect; // Frame panel Canvas.Brush.Color := clSilver; Canvas.FrameRect(vRect); end; procedure TfrmMainForm.Initialize; begin Panel1.OnCustomPaint := Panel1.CustomPaint; Panel2.OnCustomPaint := Panel2.CustomPaint; end; Share this post Link to post
Mike Torrettinni 198 Posted August 14, 2020 (edited) And now I have CustomPaint in its own unit, and I just use it on the Form/Frame when needed, and assign only panels that I need customized. It's a little thing, but I used to have double panels behind Edit, to make Edit look like vertical centered with grayish border (not black). Then I started used TAdvPanel, which has a single colored border, but it flickers a lot more than double TPanels. So, now I have this simple implementation. This just made my day @Fr0sT.Brutal 🙂 This is the wanted end result: Edited August 14, 2020 by Mike Torrettinni Share this post Link to post
Fr0sT.Brutal 900 Posted August 14, 2020 1 hour ago, Mike Torrettinni said: It works: Well, it's somewhat an overhead. My solution was to assign an event handler like with any component: Panel1.OnPaint := Form1.Panel1Paint Thus you can have special paint for every of your panels Alternatively, if you prefer storing methods in the object itself, you can just use a flag and add the method: TPanel.Paint begin if DoCustomPaint then CustomPaint; end; Panel1.CustomPaint := True but then all panels will share the same painting method Share this post Link to post
Mike Torrettinni 198 Posted August 14, 2020 1 minute ago, Fr0sT.Brutal said: My solution was to assign an event handler like with any component: Panel1.OnPaint := Form1.Panel1Paint But then it's not designed for any form, but just on Form1, right? I need this to be used at any form I need. 5 minutes ago, Fr0sT.Brutal said: Alternatively, if you prefer storing methods in the object itself, you can just use a flag and add the method: TPanel.Paint begin if DoCustomPaint then CustomPaint; end; Panel1.CustomPaint := True OK, I like this, I will just name it Pane1.CustomBorder := True; or similar. This is still in finalizing screens the new project, so it will be changed, improved many times. I can be very picky with my UI design. Share this post Link to post
Fr0sT.Brutal 900 Posted August 14, 2020 2 hours ago, Mike Torrettinni said: But then it's not designed for any form, but just on Form1, right? I need this to be used at any form I need. It's just a common practice. You can easily define a standalone class with such method if you want. But overriding and inheriting is more powerful solution. Everything depends on your needs Share this post Link to post
Rollo62 536 Posted August 14, 2020 (edited) 14 minutes ago, Fr0sT.Brutal said: It's just a common practice. You can easily define a standalone class with such method if you want. But overriding and inheriting is more powerful solution. Everything depends on your needs I can agree to that, since I use interposer heavily. Some years ago I have heard that some people have the philosophy to wrap everything into interposers. I'm not going that far yet, but close. I can understand well to have a "safety" layer between the framework and your app. That serves a lot of useful purposes: to easily manage bugfixes at a centralized place for each component to make workarounds around some conceptual limitations of some components to enhance the components with new, custom features they don't interfear with normal designer process, so no need to install custom components That are enough reasons for me to consider interposers everywhere 🙂 Edited August 14, 2020 by Rollo62 1 Share this post Link to post
Remy Lebeau 1393 Posted August 14, 2020 (edited) 10 hours ago, FPiette said: Then this component is added to a package (Actually two: a run time and a design time package) so that it can be installed in the IDE and available where you need: you drop your specialized component on the form/frame instead of the standard panel. If the component does not have extra design-time functionality that directly interacts with IDE APIs (property/component editors, OpenTools plugin, etc), then you do not need to make a separate design-time package. You can create 1 package and mark it as being BOTH a runtime package AND a design-time package. That will work just fine. Edited August 14, 2020 by Remy Lebeau 1 Share this post Link to post
Anders Melander 1782 Posted August 14, 2020 1 hour ago, Remy Lebeau said: You can create 1 package and mark it as being BOTH a runtime package AND a design-time package. That will work just fine. ...or just mark it as a design-time package. That will work equally fine. Share this post Link to post
Remy Lebeau 1393 Posted August 14, 2020 3 hours ago, Anders Melander said: ...or just mark it as a design-time package. That will work equally fine. Um no, because designtime-only packages are not used in runtime executables. The component needs to be in a runtime package, which in this particular example can ALSO be used as a design-time package. Share this post Link to post
Remy Lebeau 1393 Posted August 14, 2020 14 hours ago, David Heffernan said: Another option is to use a layered window to add the additional painting. This is very flexible, not limited to panels, doesn't require any changes to the implementation of the controls. If you make the layered window be a child of the panel, then that will only work on Windows 8+. On earlier Windows, you would have to make the layered window be a top-level overlay window that follows the panel onscreen. 1 Share this post Link to post
Anders Melander 1782 Posted August 14, 2020 1 minute ago, Remy Lebeau said: Um no, because designtime-only packages are not used in runtime executables. The component needs to be in a runtime package, which in this particular example can ALSO be used as a design-time package. Um yes. As you know we've had this discussion before. I can accept that in your opinion it is best to have both design- and run-time packages (or both in one), but it's not a requirement and never have been. Share this post Link to post
Remy Lebeau 1393 Posted August 15, 2020 1 hour ago, Anders Melander said: As you know we've had this discussion before. I can accept that in your opinion it is best to have both design- and run-time packages (or both in one), but it's not a requirement and never have been. http://docwiki.embarcadero.com/RADStudio/Sydney/en/Package-specific_Compiler_Directives Quote Packages compiled with the {$DESIGNONLY ON} directive should not ordinarily be used in applications, since they contain extra code required by the IDE. https://www.oreilly.com/library/view/delphi-in-a/1565926595/re414.html Quote Use the $DesignOnly directive in a package’s .dpk file to mark the package as being a design-time package. You cannot link the package with an application or library. You can only install the package in the IDE of Delphi or C++ Builder. Share this post Link to post
FPiette 382 Posted August 15, 2020 21 hours ago, Fr0sT.Brutal said: This means all the visual component mess: separate package, installation, registration, replacing existing panels, bundling it with an app etc. I really don't agree with this sentence! Creating a separate package helps having clean code which is reused easily. Registration is just one procedure call in the design time package. You write it one and forget it. You don't replace an existing panel: you use your specialized one when needed instead of the standard. You have both the standard panel and your specialized available in the component toolbar. You can even use both in the same form without worrying. There is no bundling with app required. Simply don't add that package in the run time package list. The component code will automatically bundled in the application. Nothing to deploy. Building component is the best solution. You do it once and them you use it. If you have an existing application and want to use your specialized panel, it is easy to edit the .dfm and .pas files using an external editor (NotePad or NotePad++) to do a simple search and replace to change TPanel by TMyPanel where is is needed. It is always easier to do that edit than opening the form, deleting the standard panel, dropping the specialized panel and reassigning properties and events! Share this post Link to post
David Heffernan 2345 Posted August 15, 2020 7 hours ago, Remy Lebeau said: make the layered window be a top-level overlay window that follows the panel onscreen. Yes, that's what I envisage Share this post Link to post
Anders Melander 1782 Posted August 15, 2020 4 hours ago, FPiette said: If you have an existing application and want to use your specialized panel, it is easy to edit the .dfm and .pas files using an external editor (NotePad or NotePad++) to do a simple search and replace to change TPanel by TMyPanel where is is needed. It is always easier to do that edit than opening the form, deleting the standard panel, dropping the specialized panel and reassigning properties and events! If you use interposers then you don't need all that. You just declare the interposer and use the unit where it's declared (or declare it in the unit where you use it). That's the point of it: You don't need to change the existing forms and often not even the code. When I use interposers to fix bugs in existing components (mostly VCL), or alter their behavior, I place the interposer in a separate unit and use that. When I need to alter a few controls on a single form I just declare the interposer in that forms unit. Problem solved, next case. I'm not saying one shouldn't use proper design-time registered components, I still consider interposers somewhat of a hack, but given the hassle of maintaining design-time packages across multiple projects and versions, interposers are a blessing. In order to remove the need for interposer classes Delphi would need: A package management system that allowed us the define per project design-time packages. The current project package configuration doesn't work. It still operate on a global package list. A mechanism to redirect the class resolver when forms are streamed. Since it already uses a global class registry when streaming a form it shouldn't be that hard to implement. I do wish the VCL had an official mechanism to solve the same problems that interposer does - but it doesn't. 3 Share this post Link to post