Recent

Author Topic: [New Component] ExtTabCtrl - A custom, feature-rich tab control (LGPL)  (Read 6304 times)

ovidio

  • Jr. Member
  • **
  • Posts: 68
Thank you for the testing!

I find it very promising too. Unfortunately, actually i've not much time left to check more indepth if i potentially could replicate my preferred behaviour with it.
I did a short test using the demo_page_control, by trying not to install packages, but by adding the unit path to the project's search path and setting up the ExtTabCtrl1 object and it's properties in the main form's pas file dynamically at run time.
All fine, but i saw: exttabctrl.pas includes the units ComponentEditors and PropEdits which compile only if the package IDEIntf is added to the project's dependencies.
So my first question would be: do we really need the package IDEIntf for to use the ExtTabCtrl dynamically in an app? (I hope i simply did something wrong)

You’re right; you shouldn’t need to include those units. Give me some time to see how I can split the code, keeping the component in one file and moving the registration procedures and property editors into another.

Edit: I have already split the component into two files: one containing only the component itself, and another containing the registration procedures and property editors. As a result, you should be able to use ExtTabCtrl dynamically in an application without the additional overhead. Please, can you try it?

2) Looking onto the styles, i'd preferred the "Chrome" style, but it looks we cannot set a tab's background color here (differently to the "Button" style). I could imagine the reason why, but isn't it an unneeded restriction to exclude this possibility?

I’ve revised the style rendering. For the Chrome style, I now use the user-defined tab color (when specified) to draw inactive tabs and the stripe on the active tab, unless a specific stripe color has also been defined for that tab. This also addresses a request from WP to allow users to customize the active tab’s stripe color. Does this approach make sense to you?

Additionally, the macOS style now uses the user-defined tab color for inactive tabs as well, applying it with a translucent appearance.

3) Wouldn't it be nice to have something like a TabGotFocus resp. TabLostFocused callback event for to set/reset the stripe color for focus changes too? (= special stripe color for focused tabs)
Edit: alternatively: responsible properties (StripeColor, SelectedStripeColor, FocusedStripeColor) for individual settings. Similar for Color (Color, SelectedColor, FocusedColor).

I have added two new events, OnGetFocus and OnLostFocus, which should allow you to achieve this behavior. As for the component itself, I would prefer to avoid introducing too many color configuration options.
« Last Edit: June 10, 2026, 12:47:29 pm by ovidio »

d7_2_laz

  • Hero Member
  • *****
  • Posts: 696
Thank you ovidio for your detailed reply! Sounds good, i'll try with newer version(s) soon and respond then.

Quote
unless a specific stripe color has also been defined for that tab. This also addresses a request from WP to allow users to customize the active tab’s stripe color. Does this approach make sense to you?
Absolutely! Not at least regarding potentially different setting needs when using light vs. dark theme (see sample screenshots in reply #12).

Quote
I have added two new events, OnGetFocus and OnLostFocus, which should allow you to achieve this behavior. As for the component itself, I would prefer to avoid introducing too many color configuration options.
I had assumed this, so i mentioned an exposure of OnGetFocus and OnLostFocus events first … (which will support an easier access to modify tab color/stripecolor by the app). I'll check and respond.

Finally i'd have two thoughts left. The second one later. The first one could be described by the terms: tab-MinWidth, tab-MaxWidth resp. padding of caption's text resp. min-distance between caption text and close button.
The real use case behind:
a) A caption text may be very short, the tab width, so, is short, and the close button is directly reachable beneath the caption text.
  The risc of unattended closures of tabs when, trying to select a tab, clicking on the tab's text here is very high ...
  Better to have a minimal width of a tab here resp. a certain distance to the close button.
b) The opposite: the caption's text could be for some reasons very long. Think of, e.g., the name of a folder which has a very long name. -> The tab's width runs out of control - better to specify a max width combined with a mechanism to truncate the text - e.g. using ellipsis.
===> All that could be done by the app itself manually too of course. E.g. by surrounding short texts by spaces. Or truncating long text using ellipsis strategy. Alternatively a component could support such. - I mention it only for to ask what do you think about.

Lazarus 4.8  FPC 3.2.2 Win11 64bit

d7_2_laz

  • Hero Member
  • *****
  • Posts: 696
Quote
I have already split the component into two files: one containing only the component itself, and another containing the registration procedures and property editors.
Yes - the app now can use exttabctrl.pas without having added the package IDEIntf to the project's dependencies. That's fine!

Quote
I have added two new events, OnGetFocus and OnLostFocus, which should allow you to achieve this behavior.
Probably i did misunderstand here. I see that the events are called very rarely, at least not where i would have expected (e.g. when hovering a tab, for to set reset a specific tab color and tab stripe color then).
Lazarus 4.8  FPC 3.2.2 Win11 64bit

ovidio

  • Jr. Member
  • **
  • Posts: 68
I would say that csDesignInteractive is a half-finished feature. Do not use it.

I found another way to capture mouse clicks at design time (https://wiki.freepascal.org/Extending_the_IDE#Disabling_the_designer_mouse_handler), and I almost have it working. When I click a tab, it gets selected as expected. However, as soon as I move the mouse outside the tab, the IDE starts drawing a selection rectangle, as if the mouse button were still being held down.

I've tried every solution I can think of, but nothing seems to prevent it. The rectangle keeps appearing no matter what I do. Does anyone know why this might be happening?

EDIT: I have look into the code of Lazarus designer (designer/designer.pp), and I believe that this behavior is an actual bug in Lazarus:

When CM_DESIGNHITTEST returns 1 on MouseUp, the designer calls MouseUp and Exit, without ever setting MouseDownComponent := nil. That only happens at the bottom of the normal path. So after a clean click on a tab:


  • MouseDownComponent = our control (never cleared)
  • Selection.RubberbandActive := False (was never set, since there was no move before up)

Then as soon as the mouse moves anywhere, even just off the component, MouseMoveOnControl fires, finds MouseDownComponent still set to our control, calls CM_DESIGNHITTEST with out-of-bounds coords, gets 0, falls through, and because MouseDownComponent != nil and there's no grabber, it goes straight to the rubber-band path.

Plase, can anybody check if I am right and report the bug (I would do it mysefl, but have no idea of how to do it)?


EDIT 2: I was right, a simple change on designer/designer.pp fixed the problem! I replaced:
Code: Pascal  [Select][+][-]
  1.       TControlAccess(MouseDownComponent).MouseUp(Button, Shift, p.X, p.Y);
  2.       Exit;
with
Code: Pascal  [Select][+][-]
  1.       TControlAccess(MouseDownComponent).MouseUp(Button, Shift, p.X, p.Y);
  2.       MouseDownComponent:=nil;
  3.       Exit;
and that's it!
« Last Edit: June 11, 2026, 10:51:44 am by ovidio »

ovidio

  • Jr. Member
  • **
  • Posts: 68
Probably i did misunderstand here. I see that the events are called very rarely, at least not where i would have expected (e.g. when hovering a tab, for to set reset a specific tab color and tab stripe color then).

It was probably my misunderstanding. Those events are triggered when the control gains or loses focus. I see now what you're trying to achieve. Let me think about it for a bit and see how it could be done.

ovidio

  • Jr. Member
  • **
  • Posts: 68
Finally i'd have two thoughts left. The second one later. The first one could be described by the terms: tab-MinWidth, tab-MaxWidth resp. padding of caption's text resp. min-distance between caption text and close button.
The real use case behind:
a) A caption text may be very short, the tab width, so, is short, and the close button is directly reachable beneath the caption text.
  The risc of unattended closures of tabs when, trying to select a tab, clicking on the tab's text here is very high ...
  Better to have a minimal width of a tab here resp. a certain distance to the close button.
b) The opposite: the caption's text could be for some reasons very long. Think of, e.g., the name of a folder which has a very long name. -> The tab's width runs out of control - better to specify a max width combined with a mechanism to truncate the text - e.g. using ellipsis.
===> All that could be done by the app itself manually too of course. E.g. by surrounding short texts by spaces. Or truncating long text using ellipsis strategy. Alternatively a component could support such. - I mention it only for to ask what do you think about.

Good point! I will try to implement these limits.

EDIT: I have already implemented the limits (by default the minimum length is 5 and the maximum 25, but the user can change those limits). Then, it the length of the caption is below the limit, it is padded with trailing spaces. Likewise, if the length of the caption is larger than the upper limit, it is cut (Some initial characters, depending of the limit, plus ellipsis plus five characters at the end). Can you check that it works for you?
« Last Edit: June 11, 2026, 10:37:35 am by ovidio »

wp

  • Hero Member
  • *****
  • Posts: 13630
Probably i did misunderstand here. I see that the events are called very rarely, at least not where i would have expected (e.g. when hovering a tab, for to set reset a specific tab color and tab stripe color then).

It was probably my misunderstanding. Those events are triggered when the control gains or loses focus. I see now what you're trying to achieve. Let me think about it for a bit and see how it could be done.
Isn't this the OnTabChanged and OnTabChanging events?

d7_2_laz

  • Hero Member
  • *****
  • Posts: 696
No wp, not the same. The change/changed events happen e.g. when a tab-2 instead of a tab-1 becomes the active/selected tab,
either by mouse click or set programmatically. The log memo from the demo_page_control does show that.

Differently to that, a tab-2 stays the selected one when a tab-3 is hovered - no change event.
But (with all styles, except "MacOS"), we see that the hovered tab's background color does change.
The only thing i'd like to achieve in this respect is the app's access to set color and stripe color at this mouseover event.
(I hadn't assumed this could be complicated).
« Last Edit: June 10, 2026, 08:13:10 pm by d7_2_laz »
Lazarus 4.8  FPC 3.2.2 Win11 64bit

ovidio

  • Jr. Member
  • **
  • Posts: 68
No wp, not the same. The change/changed events happen e.g. when a tab-2 instead of a tab-2 becomes the active/selected tab,
either by mouse click or set programmatically. The log memo from the demo_page_control does show that.

Differently to that, a tab-2 stays the selected one when a tab-3 is hovered - no change event.
But (with all styles, except "MacOS"), we see that the hovered tab's background color does change.
The only thing i'd like to achieve in this respect is the app's access to set color and stripe color at this mouseover event.
(I hadn't assumed this could be complicated).

What you expect are events like OnMouseEnterTab and OnMouseLeaveTab, right?

d7_2_laz

  • Hero Member
  • *****
  • Posts: 696
Exactly ovidio.
(i recognized that the term 'focused' rather leaded to unclearness here, my fault).
Lazarus 4.8  FPC 3.2.2 Win11 64bit

ovidio

  • Jr. Member
  • **
  • Posts: 68
Exactly ovidio.
(i recognized that the term 'focused' rather leaded to unclearness here, my fault).

I have added both methods. Please, can you check that they work as expected? Thank you!

d7_2_laz

  • Hero Member
  • *****
  • Posts: 696
Yep, works ... one can set / reset the stripe colors at tab mouseover/mouseleave just as desired. Great!
Lazarus 4.8  FPC 3.2.2 Win11 64bit

ovidio

  • Jr. Member
  • **
  • Posts: 68
I have said this before, by editing a message, but repeat it here to have more chances of somebody viewing it.

I am using this approach (https://wiki.freepascal.org/Extending_the_IDE#Disabling_the_designer_mouse_handler) to capture mouse clicks at design time, but there is a problem. When I click a tab, it gets selected as expected. However, as soon as I move the mouse outside the tab, the IDE starts drawing a selection rectangle, as if the mouse button were still being held down.

Then, I look into the code of Lazarus designer (designer/designer.pp), and found that this behavior was an actual bug in Lazarus:

When CM_DESIGNHITTEST returns 1 on MouseUp, the designer calls MouseUp and Exit, without ever setting MouseDownComponent := nil. That only happens at the bottom of the normal path. So after a clean click on a tab:
  • MouseDownComponent = our control (never cleared)
  • Selection.RubberbandActive := False (was never set, since there was no move before up)
Then as soon as the mouse moves anywhere, even just off the component, MouseMoveOnControl fires, finds MouseDownComponent still set to our control, calls CM_DESIGNHITTEST with out-of-bounds coords, gets 0, falls through, and because MouseDownComponent != nil and there's no grabber, it goes straight to the rubber-band path.

A simple change on designer/designer.pp can fix the problem. I have replaced:
Code: Pascal  [Select][+][-]
  1.       TControlAccess(MouseDownComponent).MouseUp(Button, Shift, p.X, p.Y);
  2.       Exit;

with

Code: Pascal  [Select][+][-]
  1.       TControlAccess(MouseDownComponent).MouseUp(Button, Shift, p.X, p.Y);
  2.       MouseDownComponent:=nil;
  3.       Exit;

and that's it, selecting tabs at design time works like charm.

Please, can anybody report the bug? I would do it myself, but have no idea of how to do it (and will be out for a couple of days).

wp

  • Hero Member
  • *****
  • Posts: 13630
There's a wiki about writing bug reports: https://wiki.freepascal.org/How_do_I_create_a_bug_report

In short: Go to https://gitlab.com/ and create a gitlab account. After logging in, go to https://gitlab.com/freepascal.org/lazarus/lazarus/-/work_items?sort=created_date&state=opened&first_page_size=20 and click "New Item". The bug report template is pretty much self-explantory, you should not have an issue. Important: give a precise description of the issue. Refer to your component and add a link to your github. Or even better (but more work): create a very simple test component to demonstrate the issue.

ovidio

  • Jr. Member
  • **
  • Posts: 68
There's a wiki about writing bug reports: https://wiki.freepascal.org/How_do_I_create_a_bug_report

In short: Go to https://gitlab.com/ and create a gitlab account. After logging in, go to https://gitlab.com/freepascal.org/lazarus/lazarus/-/work_items?sort=created_date&state=opened&first_page_size=20 and click "New Item". The bug report template is pretty much self-explantory, you should not have an issue. Important: give a precise description of the issue. Refer to your component and add a link to your github. Or even better (but more work): create a very simple test component to demonstrate the issue.

Thank you! I have already reported the bug: https://gitlab.com/freepascal.org/lazarus/lazarus/-/work_items/42359

 

TinyPortal © 2005-2018