📜 ⬆️ ⬇️

How to make tooltips in javafx

Forgotten art tips


Long ago, when people were still doing home pages, the Internet was on an effervescent modem, and Napster seemed like a dangerous provocation by the communists, simple windows applications under Windows were often written in the VCL library. Some used Delphi (but were recognized only as home), others boldly launched it from C ++ Builder (and were surprised by another String, as well as lists that start with 1). And someone managed to write on it under unix (do you remember Kylix? And he was!)

In the VCL, almost all visual components had the properties ShowHint and Hint . To be precise, they were all components of the window type (buttons, drop-down lists and other panels).

The string Hint was written in the string Hint , and the Boolean ShowHint could disable it. And the more advanced ones even knew that it was possible to make an extended version of the hint. If you write in Hint Click me | Button asks you to press it , then the left side will pop up, and the right side will be passed to the event. This event intercepted and showed the resulting text in the status bar.
')
So simple and convenient hints were the subject of the greatest envy of those who sat on the MFC (no WinForms were in Visual Studio at that time, let alone WPF), supported OWL or stormed pure WinAPI with Petzold at the ready. Who knows, maybe someone of them put his callous hand from coding to the fact that the convenience of the hint technology, which was unique in its convenience, was completely lost in JavaFX.

What do we have?


In JavaFX, Tooltip is responsible for hints. Apparently, this is a secret component, because, for example, SceneBuilder does not know it.

If we create a new Tooltip, and then we tie it through setTooltip, then when we hover the cursor on the component we tied to, we will really see a hint. On a black background (as necessary). And with image support (thanks).

But JavaFX will not allow us to relax: the Tooltip property (and the corresponding methods) is only in the heirs of the javafx.scene.control.Control class. And all panels and other areas are inherited from javafx.scene.layout.Region. And no clues to them emerge, it turns out, can not. Apparently, since there is a panel in the application, it should be clear to the user what is there.

Or maybe developers at school too often were forced to solve the problem in two different ways? As for the support of prompts in the status bar, they are not even close.

And TZ required a status bar and tips in it, including on the panels. And I managed on my own.

Show and suggest


I did not try to force Tooltip to float above the components of the "wrong" type. I just wanted to get a simple standard interface to bind prompts to the components in the status bar, and, if possible, to make pop-up prompts.

Moreover, the interface should be so simple that I could entrust the direct attachment of prompts to a trainee student who works part time.

As befits a working prototype, this solution looks almost trivial. Therefore, I have excluded all comments that would only stretch the article. But he has a small, but an advantage: to adapt it to his project is much faster and easier than writing from scratch.

Especially if you are as lazy as me.

Prompts


The very clue we will store in two classes, inherited from a common ancestor. First, create an abstract hint class.

The letter A in front of the name went from the old C ++ textbooks that I learned to program. I do not mind if you are accustomed to another way (for example, with postfix Base, etc.). I love the initial A, because it is short.

And, as you probably guessed, I also have a love for templates and genetics from C ++:

import javafx.scene.Node; public abstract class ATooltipHintItem<N extends Node> { private N attachedNode; protected void setAttachedNode(N node) { attachedNode = node; } public N getAttachedNode() { return attachedNode; } private String statusBarHint; protected void setStatusBarHint(String hint){ statusBarHint = hint; } public String getStatusBarHint(){ return statusBarHint; } private ITooltipHintController tooltipHintController; public ITooltipHintController getTooltipHintController() { return tooltipHintController; } public void showStatusBarHint(){ tooltipHintController.setStatusBarText(statusBarHint); } public ATooltipHintItem(N attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) { this.attachedNode = attachedNode; this.tooltipHintController = tooltipHintController; if(statusBarHint != null && !statusBarHint.equals("")){ initStatusBar(); this.setStatusBarHint(statusBarHint); } } private void initStatusBar() { getAttachedNode().setOnMouseEntered(observableValue -> { this.showStatusBarHint(); }); getAttachedNode().setOnMouseExited(observableValue -> { getTooltipHintController().setDefaultStatusBarText(); }); } } 

Now create a class for both cases. Here is the class for the Region:

 import javafx.scene.control.Control; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; public final class TooltipHintRegionItem extends ATooltipHintItem<Region>{ public TooltipHintRegionItem(Region attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) { super(attachedNode, tooltipHintController, statusBarHint); } } 

But for Control:

 import javafx.scene.control.Control; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; public final class TooltipHintControlItem extends ATooltipHintItem<Control> { private Tooltip tooltip; public Tooltip getTooltip() { return tooltip; } private String tooltipHint; public TooltipHintControlItem setTooltipHint(String hint){ tooltipHint = hint; if(hint == null || hint.trim().length() <= 0) return this; if(tooltip == null) { initTooltip(); } tooltip.setText(hint); return this; } public String getTooltipHint(){ return tooltipHint; } private Image tooltipImage; public TooltipHintControlItem setTooltipImage(Image image){ tooltipImage = image; if(tooltip != null) tooltip.setGraphic((image != null) ? new ImageView(image) : null); return this; } public Image getTooltipImage(){ return tooltipImage; } public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint, Image imageHint) { super(attachedNode, tooltipHintController, statusBarHint); if(tooltipHint != null && !tooltipHint.isEmpty()){ initTooltip(); } setTooltipHint(tooltipHint); if(imageHint == null) { setTooltipImage(imageHint); } } public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint) { this(attachedNode, tooltipHintController, statusBarHint, tooltipHint, null); } public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) { this(attachedNode, tooltipHintController, statusBarHint, null, null); } private void initTooltip() { tooltip = new Tooltip(); getAttachedNode().setTooltip(tooltip); } } 

We control


Now we take on the Controller. And the controller starts with the interface. Java without an interface is like a hacker without a laptop.

 public interface ITooltipHintController { void setStatusBarText(String text); String getStatusBarText(); void setDefaultStatusBarText(); } 

And here is the controller itself:

 import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.control.Control; import javafx.scene.control.Labeled; import javafx.scene.image.Image; import javafx.scene.layout.Region; import java.util.ArrayList; import java.util.Iterator; public final class TooltipHintController implements ITooltipHintController { private final String DefaultStatusBarText = ""; private final Labeled statusBarControl; private final ObservableList<ATooltipHintItem> tooltipHintItems; private boolean isStatusBarLocked = false; public boolean getIsStatusBarLocked() { return isStatusBarLocked; } public void setIsStatusBarLocked(boolean isStatusBarLocked) { this.isStatusBarLocked = isStatusBarLocked; } public Labeled getStatusBarControl() { return this.statusBarControl; } public void setStatusBarTextForce(String text) { if(statusBarControl == null) { return; } statusBarControl.setText(text); } @Override public void setStatusBarText(String text) { if(!isStatusBarLocked){ setStatusBarTextForce(text); } } @Override public String getStatusBarText() { return (statusBarControl != null) ? statusBarControl.getText() : ""; } @Override public void setDefaultStatusBarText(){ setStatusBarTextForce(DefaultStatusBarText); } //   ,     public void addTooltipHint(Region region, String statusBarHint){ // Tooltip    -    JavaFX     ATooltipHintItem tooltipHintItem = findTooltipHint(region); if(tooltipHintItem == null) { tooltipHintItem = new TooltipHintRegionItem(region, this, statusBarHint); tooltipHintItems.add(tooltipHintItem); } else { TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem; if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null) tooltipHintControlItem.setStatusBarHint(statusBarHint); } } public void addTooltipHint(Control control, String statusBarHint){ addTooltipHint(control, statusBarHint, null, null); } public void addTooltipHint(Control control, String statusBarHint, String tooltipHint){ addTooltipHint(control, statusBarHint, tooltipHint, null); } public void addTooltipHint(Control control, String statusBarHint, String tooltipHint, Image image){ ATooltipHintItem tooltipHintItem = findTooltipHint(control); if(tooltipHintItem == null) { tooltipHintItem = new TooltipHintControlItem(control, this, statusBarHint, tooltipHint, image); tooltipHintItems.add(tooltipHintItem); } else { TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem; if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null) tooltipHintControlItem.setStatusBarHint(statusBarHint); if(tooltipHint != null && tooltipHintControlItem.getTooltipHint() == null) tooltipHintControlItem.setTooltipHint(tooltipHint); if(image != null && tooltipHintControlItem.getTooltipImage() == null) tooltipHintControlItem.setTooltipImage(image); } } public void removeTooltipHint(Node control){ ATooltipHintItem tooltipHintItem = null; Iterator<ATooltipHintItem> iteratorTooltipHintItems = tooltipHintItems.iterator(); while(iteratorTooltipHintItems.hasNext()){ tooltipHintItem = iteratorTooltipHintItems.next(); if(tooltipHintItem.getAttachedNode() == control){ tooltipHintItems.remove(tooltipHintItem); break; } } } public ATooltipHintItem findTooltipHint(Node control){ for(ATooltipHintItem tooltipHintItem : tooltipHintItems) if(tooltipHintItem.getAttachedNode() == control) return tooltipHintItem; return null; } /** *       ,   *  . * * @param statusBarControl    */ public TooltipHintController(Labeled statusBarControl){ this.statusBarControl = statusBarControl; tooltipHintItems = FXCollections.observableList(new ArrayList<>()); } public TooltipHintController(){ this(null); } private static TooltipHintController mainInstance; public static TooltipHintController getMainInstance() { if(mainInstance == null){ mainInstance = new TooltipHintController(); } return mainInstance; } public static void setMainInstance(TooltipHintController tooltipHintController) { mainInstance = tooltipHintController; } } 

Then you can safely write, without even thinking about who inherited from whom:

 TooltipHintController.getMainInstance().addTooltipHint(buttonStart, " ", "  "); TooltipHintController.getMainInstance().addTooltipHint(paneButtons, " "); 

And if we have a labelStatusBar as a status bar, then we can use it:

 TooltipHintController.setMainInstance(new TooltipHintController(labelStatusBar)); 

Of course, this implementation should be improved - after all, theoretically, in an application there can be more than one status line . If you know these (of course, modern, JavaFx and actively used) - let me know.

Conclusion


Unfortunately, this set of 4 classes is too large to be reduced to one document on pastebin. But also too small and not self-sufficient to become a proud maven-package and take an honorable place in the famous repository.

But I still hope that he will find his user (more precisely, his developer ). Or maybe it will catch the eye of developers from Oracle and convince them to make the tips more convenient.

There are libraries of extensions approved by Oracle itself - it is possible. Is it worth trying to attach this interface to one of them?

Source: https://habr.com/ru/post/311174/


All Articles