Re: [External] : Aw: Proposal: Add Skin.install() method
Kevin Rushforth
kevin.rushforth at oracle.com
Fri Jul 22 19:33:22 UTC 2022
I would not be in favor of adding a no-arg constructor to SkinBase, for
the reasons Andy gave. Additionally, there would be no way to avoid
braking the contract of Skin::getSkinnable which says:
"This value will only ever go from a non-null to null value when the
Skin is removed from the Skinnable, and only as a consequence of a call
to dispose()."
> At the very minimum, we should explain in Skin javadoc that creating a
> skin for one control and setting it in the other is a no-no. Or,
> perhaps we should explicitly check for this condition in setSkin().
>
I agree completely. At a minimum this enhancement should change the docs
for setSkin to say that a skin created for one control should not (must
not?) be used in another control. And unless there is a legitimate use
case I haven't thought of, I think we could consider an explicit check,
and either throw an Exception (this seems the best choice, unless there
are compatibility concerns), or else log a warning and treat it as a no-op.
-- Kevin
On 7/22/2022 9:13 AM, Andy Goryachev wrote:
>
> You do bring a good point! I don't know the rationale behind passing
> control pointer to the Skin constructor.
>
> I think Swing got it right, clearly separating
>
> * instantiation (using either a no-arg constructor, or any other
> constructor that does not require component pointer)
> * configuration (optional step, possibly widely separated in time
> and space)
> * uninstallation of the old skin
> * installation of the new skin
>
> What you are proposing - moving to a default constructor makes the
> most sense. It comes with a high price though - everyone with a
> custom skin implementation would need to change their code.
>
> At the very minimum, we should explain in Skin javadoc that creating a
> skin for one control and setting it in the other is a no-no. Or,
> perhaps we should explicitly check for this condition in setSkin().
>
> Thank you
>
> -andy
>
> *From: *Marius Hanl <mariushanl at web.de>
> *Date: *Friday, 2022/07/22 at 05:06
> *To: *openjfx-dev at openjdk.org <openjfx-dev at openjdk.org>, Andy
> Goryachev <andy.goryachev at oracle.com>
> *Subject: *[External] : Aw: Proposal: Add Skin.install() method
>
> I had a similar idea in the past and like the idea.
> Ideally, setting/switching a skin is a one step process. Currently you
> can construct a skin for a control and set it after to a different
> control.
>
> Your approach sounds good, if you can set a skin by creating a new
> skin (with a default constructor) and then the setSkin() method will
> actually trigger the install process on the control (this), this will
> work and solve the problem above. But for backward compatibilty we
> still need to keep the skin constructor with the control as parameter
> and think about deprecating it.
>
> -- Marius
>
> Am 20.07.22, 23:40 schrieb Andy Goryachev <andy.goryachev at oracle.com>:
>
> Hi,
>
> I'd like to propose an API change in Skin interface (details
> below). Your feedback will be greatly appreciated!
>
> Thank you,
>
> -andy
>
> Summary
>
> -------
>
> Introduce a new Skin.install() method with an empty default
> implementation. Modify Control.setSkin(Skin) implementation to
> invoke install() on the new skin after the old skin has been
> removed with dispose().
>
> Problem
>
> -------
>
> Presently, switching skins is a two-step process: first, a new
> skin is constructed against the target Control instance, and is
> attached (i.s. listeners added, child nodes added) to that
> instance in the constructor. Then, Control.setSkin() is invoked
> with a new skin - and inside, the old skin is detached via its
> dispose() method.
>
> This creates two problems:
>
> 1. if the new skin instance is discarded before setSkin(), it
> remains attached, leaving the control in a weird state with two
> skins attached, causing memory leaks and performance degradation.
>
> 2. if, in addition to adding listeners and child nodes, the skin
> sets a property, such as an event listener, or a handler, it
> overwrites the current value irreversibly. As a result, either
> the old skin would not be able to cleanly remove itself, or the
> new skin would not be able to set the new values, as it does not
> know whether it should overwrite or keep a handler installed
> earlier (possibly by design). Unsurprisingly, this also might
> cause memory leaks.
>
> We can see the damage caused by looking at JDK-8241364
> <https://bugs.openjdk.org/browse/JDK-8241364> ☂/Cleanup skin
> implementations to allow switching/, which refers a number of bugs:
>
> JDK-8245145 Spinner: throws IllegalArgumentException when
> replacing skin
>
> JDK-8245303 InputMap: memory leak due to incomplete cleanup on
> remove mapping
>
> JDK-8268877 TextInputControlSkin: incorrect inputMethod event
> handler after switching skin
>
> JDK-8236840 Memory leak when switching ButtonSkin
>
> JDK-8240506 TextFieldSkin/Behavior: misbehavior on switching skin
>
> JDK-8242621 TabPane: Memory leak when switching skin
>
> JDK-8244657 ChoiceBox/ToolBarSkin: misbehavior on switching skin
>
> JDK-8245282 Button/Combo Behavior: memory leak on dispose
>
> JDK-8246195 ListViewSkin/Behavior: misbehavior on switching skin
>
> JDK-8246202 ChoiceBoxSkin: misbehavior on switching skin, part 2
>
> JDK-8246745 ListCell/Skin: misbehavior on switching skin
>
> JDK-8247576 Labeled/SkinBase: misbehavior on switching skin
>
> JDK-8253634 TreeCell/Skin: misbehavior on switching skin
>
> JDK-8256821 TreeViewSkin/Behavior: misbehavior on switching skin
>
> JDK-8269081 Tree/ListViewSkin: must remove flow on dispose
>
> JDK-8273071 SeparatorSkin: must remove child on dispose
>
> JDK-8274061 Tree-/TableRowSkin: misbehavior on switching skin
>
> JDK-8244419 TextAreaSkin: throws UnsupportedOperation on dispose
>
> JDK-8244531 Tests: add support to identify recurring issues with
> controls et al
>
> Solution
>
> --------
>
> This problem does not exist in e.g. Swing because the steps of
> instantiation, uninstalling the old ComponentUI ("skin"), and
> installing a new skin are cleanly separated. ComponentUI
> constructor does not alter the component itself,
> ComponentUI.uninstallUI(JComponent) cleanly removes the old skin,
> ComponentUI.installUI(JComponent) installs the new skin. We
> should follow the same model in javafx.
>
> Specifically, I'd like to propose the following changes:
>
> 1. Add Skin.install() with a default no-op implementation.
>
> 2. Clarify skin creation-attachment-detachment sequence in Skin
> and Skin.install() javadoc
>
> 3. Modify Control.setSkin(Skin) method (== invalidate listener in
> skin property) to call oldSkin.dispose() followed by newSkin.install()
>
> 4. Many existing skins that do not set properties in the
> corresponding control may remain unchanged. The skins that do,
> such as TextInputControlSkin (JDK-8268877), must be refactored to
> utilize the new install() method. I think the refactoring would
> simply move all the code that accesses its control instance away
> from the constructor to install().
>
> Impact Analysis
>
> -------------
>
> The changes should be fairly trivial. Only a subset of skins needs
> to be refactored, and the refactoring itself is trivial.
>
> The new API is backwards compatible with the existing code, the
> customer-developed skins can remain unchanged (thanks to default
> implementation). In case where customers could benefit from the
> new API, the change is trivial.
>
> The change will require CSR as it modifies a public API.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20220722/b289a3a3/attachment-0001.htm>
More information about the openjfx-dev
mailing list