Reputation:
Hello, how can I add a window in JavaFX when I drag it over the tab as tab and double-click again to release it as a window again?
For example the tabs in Firefox or Chrome. Double-Click to get a new Window an drag over to add it to the tabs again.
Sorry for my english :) and thanks for help.
Upvotes: 1
Views: 2067
Reputation: 905
I have another solution for recent FX Versions, that you might want to adapt to your needs.
Pros:
Cons:
Remarks:
The Skin:
/**
* Modified skin, that allows detaching. This is a somewhat crude "Hack". But I
* need access to the private moushandlers. In the skin almost everything is
* private. And it consumes events I need..
*
* @author Kai
*/
public class DragTabPaneSkin extends TabPaneSkin {
private static final DragPaneDragContext dragContext = new DragPaneDragContext();
private EventHandler<MouseEvent> orig_headerMouseReleasedHandler;
private EventHandler<MouseEvent> orig_headerMousePressedHandler;
private EventHandler<MouseEvent> orig_headerMouseDraggedHandler;
private StackPane tabHeaderArea;
public void onMousePressed(MouseEvent event) {
orig_headerMousePressedHandler.handle(event);
//don't realy like this but as everything is private....
//find the tab by looking up the index of the tab skin in its parent:
final StackPane tabSkin =(StackPane) event.getSource();
final int index = tabSkin.getParent().getChildrenUnmodifiable().indexOf(tabSkin);
final Tab tab = getSkinnable().getTabs().get(index);
dragContext.start(tabHeaderArea,tab);
dragContext.onMousePressedHeader(event);
}
public void onMouseReleased(MouseEvent event) {
orig_headerMouseReleasedHandler.handle(event);
dragContext.finish(tabHeaderArea, event.getScreenX(), event.getScreenY());
}
public void onMouseDragged(MouseEvent event) {
dragContext.set(event.getScreenX(), event.getScreenY());
if (!dragContext.dragPopup.isShowing()) {
orig_headerMouseDraggedHandler.handle(event);
}
}
private <T> T hijackField(String name, T newValue) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException {
final Field field = TabPaneSkin.class.getDeclaredField(name);
field.setAccessible(true);
final T result = (T) field.get(this);
if(null != newValue) field.set(this, newValue);
return result;
}
{
try {
//hijack all the mouse handlers and replace them by ours in order to chain them).
var policy = this.getSkinnable().getTabDragPolicy();
this.getSkinnable().setTabDragPolicy(FIXED);//remove the listeners
tabHeaderArea = hijackField("tabHeaderArea", null);
orig_headerMouseDraggedHandler = hijackField("headerDraggedHandler", (EventHandler<MouseEvent>) this::onMouseDragged);
orig_headerMousePressedHandler = hijackField("headerMousePressedHandler", (EventHandler<MouseEvent>) this::onMousePressed);
orig_headerMouseReleasedHandler = hijackField("headerMouseReleasedHandler",(EventHandler<MouseEvent>) this::onMouseReleased);
this.getSkinnable().setTabDragPolicy(policy); //add new listeners.
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
}
this.getSkinnable().setSkin(this);
dragContext.connectTabPane(this.getSkinnable());
}
public DragTabPaneSkin(TabPane tp) {
super(tp);
}
}
This is the gesture:
/** Context that does the drag operation.
*
*/
public class DragPaneDragContext {
public Tab tab = null;
public boolean finishup = false;
protected final Popup dragPopup;
protected final ImageView dragView;
private final EventHandler<MouseEvent> headerMouseExited = this::onMouseExitedHeader;
private final EventHandler<MouseEvent> headerMouseEntered = this::onMouseEnteredHeader;
private final EventHandler<MouseEvent> headerDraggedDetected = this::onMouseDraggDetectedHeader;
private final EventHandler<MouseEvent> mouseExitedTabPane = this::onMouseExitedPane;
private final EventHandler<MouseEvent> mouseEnteredTabPane = this::onMouseEnteredPane;
private final EventHandler<MouseEvent> mouseDragReleasedTabPane = this::onMouseDragReleasedPane;
{
dragPopup = new Popup();
dragView = new ImageView();
dragView.setMouseTransparent(true);
dragPopup.getContent().setAll(dragView);
}
public void set(Tab tab, double x, double y) {
this.tab = tab;
this.dragPopup.setX(x);
this.dragPopup.setY(y);
}
public void set(Tab tab) {
this.tab = tab;
}
public void set(double x, double y) {
this.dragPopup.setX(x);
this.dragPopup.setY(y);
}
public double getX() {
return this.dragPopup.getX();
}
public double getY() {
return this.dragPopup.getY();
}
public void start(StackPane tabHeaderArea, Tab tab) {
finishup = false;
this.tab = tab;
connectHeader(tabHeaderArea);
dragView.setImage(tab.getTabPane().snapshot(null, null));
dragView.setFitHeight(100);
dragView.setFitWidth(100);
}
public void complete() {
if (null == tab) {
return;
}
finishup = false;
dragPopup.hide();
this.tab = null;
}
public void finish(StackPane tabHeaderArea, double x, double y) {
disConnectHeader(tabHeaderArea);
if (null == tab || !dragPopup.isShowing()) {
complete();
return;
}
finishup = true;
final SelfClosingTabPane tp = new SelfClosingTabPane();
set(x, y);
dragPopup.hide();
tab.getTabPane().getTabs().remove(tab);
tp.getTabs().add(tab);
tp.show(x - 5, y - 5);
tp.requestFocus();
tp.getSelectionModel().select(tab);
}
private void onMouseExitedHeader(MouseEvent event) {
if (null == tab) {
return; //shouldn't happen.
}
dragPopup.show(tab.getTabPane(), event.getScreenX(), event.getScreenY());
}
private void onMouseEnteredHeader(MouseEvent event) {
dragPopup.hide();
}
private void onMouseDraggDetectedHeader(MouseEvent t) {
Node source = (Node) t.getSource();
source.startFullDrag();
}
public void onMousePressedHeader(MouseEvent event) {
this.dragPopup.setX(event.getScreenX());
this.dragPopup.setY(event.getScreenY());
}
private void onMouseEnteredPane(MouseEvent event) {
if (!finishup || !(event.getSource() instanceof TabPane)) {
return;
}
final TabPane source = (TabPane) event.getSource();
Point2D local = source.screenToLocal(getX(), getY());
if (tab != null && tab.getTabPane() != source && source.contains(local.add(5,5)) && source.contains(local.subtract(5,5))) {
tab.getTabPane().getTabs().remove(tab);
source.getTabs().add(tab);
source.getSelectionModel().select(tab);
source.requestFocus();
}
complete();
}
private void onMouseExitedPane(MouseEvent t) {
if (finishup) {
complete();
}
}
private void onMouseDragReleasedPane(MouseEvent t) {
if (!(t.getSource() instanceof TabPane)) {
return;
}
final TabPane source = (TabPane) t.getSource();
if (null != tab && tab.getTabPane() != source) {
tab.getTabPane().getTabs().remove(tab);
source.getTabs().add(tab);
source.getSelectionModel().select(tab);
source.requestFocus();
}
complete();
}
public void connectTabPane(TabPane pane) {
pane.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedTabPane);
pane.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseEnteredTabPane);
pane.addEventHandler(MouseDragEvent.MOUSE_DRAG_RELEASED, mouseDragReleasedTabPane);
}
private void connectHeader(StackPane tabHeaderArea) {
tabHeaderArea.addEventHandler(MouseEvent.MOUSE_EXITED, headerMouseExited);
tabHeaderArea.addEventHandler(MouseEvent.MOUSE_ENTERED, headerMouseEntered);
tabHeaderArea.addEventHandler(MouseEvent.DRAG_DETECTED, headerDraggedDetected);
}
private void disConnectHeader(StackPane tabHeaderArea){
tabHeaderArea.removeEventHandler(MouseEvent.MOUSE_EXITED, headerMouseExited);
tabHeaderArea.removeEventHandler(MouseEvent.MOUSE_ENTERED, headerMouseEntered);
tabHeaderArea.removeEventHandler(MouseEvent.DRAG_DETECTED, headerDraggedDetected);
}
}
The Window that is created when a tab is dragged outside:
/**
* A Window with a Tapbpane, that closes when all Tabs are gone.
*
* @author Kai
*/
public class SelfClosingTabPane extends TabPane {
private final Stage stage;
private void changeListener(ListChangeListener.Change<? extends Tab> change) {
if (getTabs().isEmpty()) {
stage.close();
}
}
public SelfClosingTabPane() {
super();
setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
stage = new Stage();
stage.initModality(Modality.NONE);
stage.setScene(new Scene(this));
this.setSkin(new DragTabPaneSkin(this));
this.getTabs().addListener(this::changeListener);
}
public SelfClosingTabPane(Window owner) {
this();
stage.initOwner(owner);
}
public SelfClosingTabPane(String title, Window owner) {
this(title);
stage.initOwner(owner);
}
public SelfClosingTabPane(String title) {
this();
stage.setTitle(title);
}
public Stage getStage() {
return this.stage;
}
public void show(double x, double y) {
stage.setX(x);
stage.setY(y);
stage.show();
}
}
Finally a draggable Tabpane(It does nothing but setting the skin) :
/**
* TabPane that allows Tabs too be detached;
*
* @author Kai
*/
public class DetachableTabPane extends TabPane {
{
setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
setSkin(new DragTabPaneSkin(this));
}
public DetachableTabPane() {
super();
}
public DetachableTabPane(Tab... tabs) {
super(tabs);
}
public void add(String name,Node node){
this.getTabs().add(new Tab(name,node));
}
}
Upvotes: 1
Reputation: 7265
🐝
GitHub Library
A library exists in GitHub with name DNDTabPane
.Mention that it needs improvements ,although it works well with some modifications.
Link:
https://github.com/sibvisions/javafx.DndTabPane
Demonstration:
Similar question
Runnable Code
You can find runnable code here which doesn't use the library above
http://berry120.blogspot.gr/2014/01/draggable-and-detachable-tabs-in-javafx.html?m=1
Finally
As soon as i get back to computer i will provide more..Maybe committing a mix library of the above on GitHub.Be ready!
🐝
Upvotes: 2