I try to create an Peek
Control
for a TabPane
. So if an Panel was already opened an Snapshot
gets saved. When the user hovers over the Tab
it will show a PopOver
. This is similar to Windows Aero Peek
.
My problem is that the PopOver
gets hidden when it should not... because it receives a hide()
event from the Tabs
Graphic
.
I created a small runnable Example to demonstrate the Problem:
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.controlsfx.control.PopOver;
import org.controlsfx.control.PopOver.ArrowLocation;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Peek peek;
Tab lastPeek;
boolean show = false;
public static void main(String[] args) {
System.out.println("WE HAVE PROBLEMS WITH FOCUS HERE");
Application.launch(args);
}
@Override
public void start(Stage primaryStage) {
peek = new Peek();
primaryStage.setTitle("Tabs");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);
TabPane tabPane = new TabPane();
BorderPane borderPane = new BorderPane();
final PopOver popOver = new PopOver();
popOver.setArrowLocation(ArrowLocation.TOP_LEFT);
final ImageView preview = new ImageView();
preview.setFitHeight(100);
preview.setSmooth(true);
preview.setPreserveRatio(true);
popOver.setContentNode(preview);
for (int i = 0; i < 5; i++) {
Tab tab = new Tab();
HBox hbox = new HBox();
hbox.getChildren().add(new Label("", new ImageView(new Image(Main.class.getResourceAsStream(i + ".jpg")))));
hbox.setAlignment(Pos.CENTER);
tab.setContent(hbox);
tabPane.getTabs().add(tab);
final Label decoration = new Label("Tab" + i);
tab.setGraphic(decoration);
System.out.println("tab.getGraphic() = " + tab.getGraphic());
final int index = i;
tab.getGraphic().setOnMouseEntered(mouseEvent -> {
boolean show = false;
if (lastPeek != tab) {
// Show PopOver when mouse enters label
Image previewImage = peek.get(tab);
if (previewImage != null) {
show = true;
}
System.out.println("previewImage = " + previewImage);
preview.setImage(previewImage);
System.out.println("show() " + "Tab" + index);
lastPeek = tab;
}
if (!tab.isSelected() && !tab.isDisabled() && show) {
popOver.show((Node) mouseEvent.getSource(), -3);
}
});
tab.getGraphic().setOnMouseExited(mouseEvent -> {
// Hide PopOver when mouse exits label
// how can we handle this correct??
popOver.hide();
System.out.println("hide() " + "Tab" + index);
});
}
tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
@Override
public void changed(ObservableValue<? extends Tab> ov, Tab oldTab, Tab newTab) {
Image preview = oldTab.getContent().snapshot(new SnapshotParameters(), null);
peek.set(oldTab, preview);
}
});
tabPane.selectionModelProperty().addListener(new ChangeListener<SingleSelectionModel<Tab>>() {
@Override
public void changed(ObservableValue<? extends SingleSelectionModel<Tab>> ov,
SingleSelectionModel<Tab> oldValue, SingleSelectionModel<Tab> newValue) {
}
});
// bind to take available space
borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());
borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
and this is the Peek
class, that chaches the Snapshots
public class Peek {
ObservableMap<Object, Image> peeks = FXCollections.observableHashMap();
public void set(Object key, Image preview) {
peeks.put(key, preview);
}
public Image get(Object key) {
Image preview = peeks.get(key);
return preview;
}
}
It is actually because of the popOver you show. As soon as the popOver is shown, it takes the focus, and a MouseExit event is dispatch to your tab's graphic.
You can observe it by adding a bigger offset to your popup :
popOver.show((Node) mouseEvent.getSource(), -15);
Or simply by entering your mouse from the top of the tab instead from the bottom (the problem does not appear anymore).
Also, as the event seems not well-synchronized (when moving your mouse fast enought from a tab to another one) I advise you to create one PopOver by tab.
public class Main extends Application {
Peek peek;
Tab lastPeek;
HashMap<Label, PopOver> popMap = new HashMap<>();
boolean show = false;
public static void main(String[] args) {
System.out.println("WE HAVE PROBLEMS WITH FOCUS HERE");
Application.launch(args);
}
@Override
public void start(Stage primaryStage) {
peek = new Peek();
primaryStage.setTitle("Tabs");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);
TabPane tabPane = new TabPane();
BorderPane borderPane = new BorderPane();
for(int i = 0; i < 5; i++) {
Tab tab = new Tab();
HBox hbox = new HBox();
hbox.getChildren()
.add(new Label("", new ImageView(new Image(Main.class.getResourceAsStream(i + ".jpg")))));
hbox.setAlignment(Pos.CENTER);
tab.setContent(hbox);
tabPane.getTabs().add(tab);
final Label decoration = new Label("Tab" + i);
tab.setGraphic(decoration);
System.out.println("tab.getGraphic() = " + tab.getGraphic());
final PopOver popOver = new PopOver();
popOver.setArrowLocation(ArrowLocation.TOP_LEFT);
final ImageView preview = new ImageView();
preview.setFitHeight(100);
preview.setSmooth(true);
preview.setPreserveRatio(true);
popOver.setContentNode(preview);
popMap.put(decoration, popOver);
final int index = i;
decoration.setOnMouseEntered(mouseEvent -> {
PopOver lPop = popMap.get((Label) mouseEvent.getSource());
boolean show = false;
if(lastPeek != tab) {
// Show PopOver when mouse enters label
Image previewImage = peek.get(tab);
if(previewImage != null) {
show = true;
}
System.out.println("previewImage = " + previewImage);
preview.setImage(previewImage);
System.out.println("show() " + "Tab" + index);
}
if(!tab.isSelected() && !tab.isDisabled() && show) {
lPop.show((Node) mouseEvent.getSource(), -decoration.getHeight() / 2);
}
});
tab.getGraphic().setOnMouseExited(mouseEvent -> {
// Hide PopOver when mouse exits label
// how can we handle this correct??
PopOver lPop = popMap.get((Label) mouseEvent.getSource());
if(lPop.isShowing())
lPop.hide();
System.out.println("hide() " + "Tab" + index);
});
}
tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>() {
@Override
public void changed(ObservableValue<? extends Tab> ov, Tab oldTab, Tab newTab) {
Image preview = oldTab.getContent().snapshot(new SnapshotParameters(), null);
peek.set(oldTab, preview);
}
});
tabPane.selectionModelProperty().addListener(new ChangeListener<SingleSelectionModel<Tab>>() {
@Override
public void changed(ObservableValue<? extends SingleSelectionModel<Tab>> ov,
SingleSelectionModel<Tab> oldValue, SingleSelectionModel<Tab> newValue) {
}
});
// bind to take available space
borderPane.prefHeightProperty().bind(scene.heightProperty());
borderPane.prefWidthProperty().bind(scene.widthProperty());
borderPane.setCenter(tabPane);
root.getChildren().add(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}