Am using the following code to create a dialog for my login page. I need to write SWTBot test cases for the dialog that I create.
I have written SWTBot for windows but not for jface dialog.
How to get the access of the dialog in the test class by using the active shell? so that it can detect the buttons or the fields in the dialog.
Besides using active shell, is there any other method to access the dialog that I have created?
package com.login.model;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.layout.GridData;
import com.database.*;
import com.login.controller.UserValidation;
public class PasswordDialog extends Dialog {
private Text txtUser;
private Text txtPassword;
private String user = "";
private String password = "";
protected Composite container;
public Button ok;
public Button cancel;
public PasswordDialog(Shell parentShell) {
super(parentShell);
}
#Override
protected void setShellStyle(int arg0) {
// Use the following not to show the default close X button in the title
// bar
super.setShellStyle(SWT.TITLE);
}
#Override
protected Control createDialogArea(Composite parent) {
container = (Composite) super.createDialogArea(parent);
org.eclipse.swt.graphics.Color color = new org.eclipse.swt.graphics.Color(
container.getDisplay(), 255, 255, 255);
container.setBackground(color);
System.out.println(container);
GridLayout layout = new GridLayout(2, false);
layout.marginRight = 5;
layout.marginLeft = 10;
container.setLayout(layout);
// Grid for labels user and password
GridData gdForLabel = new GridData(SWT.LEFT, SWT.CENTER, false, false,
10, 5);
gdForLabel.horizontalIndent = 84;
// Grid for text box user and password
GridData gdForText = new GridData(SWT.CENTER, SWT.NONE, true, true, 10,
5);
gdForText.horizontalIndent = -30;
gdForText.minimumWidth = 200;
// To display the application image
Image image = new Image(container.getDisplay(), new ImageData(
"C:\\Users\\myName\\workspace\\Login\\Image\\c.gif"));
Label lblImg = new Label(container, SWT.NONE);
lblImg.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true,
10, 10));
lblImg.setImage(image);
lblImg.setBackground(color);
// The username label
Label lblUser = new Label(container, SWT.NONE);
lblUser.setLayoutData(gdForLabel);
lblUser.setFont(new Font(container.getDisplay(), new FontData("Corbel",
12, SWT.NORMAL)));
lblUser.setText("Username");
lblUser.setBackground(color);
// The username text box
txtUser = new Text(container, SWT.BORDER);
txtUser.setFont(new Font(container.getDisplay(), new FontData("Corbel",
12, SWT.NORMAL)));
txtUser.setLayoutData(gdForText);
txtUser.setText(user);
// The password label
Label lblPassword = new Label(container, SWT.NONE);
lblPassword.setLayoutData(gdForLabel);
lblPassword.setFont(new Font(container.getDisplay(), new FontData(
"Corbel", 12, SWT.NORMAL)));
lblPassword.setText("Password");
lblPassword.setBackground(color);
// The password text box
txtPassword = new Text(container, SWT.BORDER | SWT.PASSWORD);
txtPassword.setFont(new Font(container.getDisplay(), new FontData(
"Corbel", 12, SWT.NORMAL)));
txtPassword.setLayoutData(gdForText);
txtPassword.setText(password);
return container;
}
#Override
protected Point getInitialSize() {
// Initialise window size
return new Point(400, 400);
}
#Override
protected void createButtonsForButtonBar(Composite parent) {
// Set Color of Background to white
org.eclipse.swt.graphics.Color color = new org.eclipse.swt.graphics.Color(
container.getDisplay(), 255, 255, 255);
// Change parent layout data to fill the whole bar
parent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
parent.setBackground(color);
// Create a spacer label
Label spacer = new Label(parent, SWT.NONE);
spacer.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
spacer.setBackground(color);
// Update layout of the parent composite to count the spacer
GridLayout layout = (GridLayout) parent.getLayout();
layout.numColumns += 16;
layout.makeColumnsEqualWidth = false;
// The button creation
ok=createButton(parent, IDialogConstants.OK_ID, "OK", true);
cancel=createButton(parent, IDialogConstants.CANCEL_ID, "Cancel", false);
}
#Override
protected void okPressed() {
// Method to invoke connection to database and authenticate
user = txtUser.getText();
password = txtPassword.getText();
DataBaseConnectivity.createConnection();
boolean valid = UserValidation.validateString(user);
if(valid){
boolean auth = DataBaseConnectivity.authenticate(user, password);
// Password authentication
if (auth)
super.okPressed();
else {
Label rt = new Label(container, SWT.CENTER);
rt.setSize(200, 150);
rt.setLocation(100, 295);
org.eclipse.swt.graphics.Color color = new org.eclipse.swt.graphics.Color(
container.getDisplay(), 255, 0, 0);
rt.setForeground(color);
color = new org.eclipse.swt.graphics.Color(container.getDisplay(),
255, 255, 255);
rt.setBackground(color);
rt.setText("Username or Password is wrong");
System.out.println(rt.getText());
}
}
else{
Label rt = new Label(container, SWT.CENTER);
rt.setSize(200, 150);
rt.setLocation(100, 295);
org.eclipse.swt.graphics.Color color = new org.eclipse.swt.graphics.Color(
container.getDisplay(), 255, 0, 0);
rt.setForeground(color);
color = new org.eclipse.swt.graphics.Color(container.getDisplay(),
255, 255, 255);
rt.setBackground(color);
rt.setText("Username is invalid");
System.out.println(rt.getText());
}
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
You have to first invoke login dialog using some action assigned to it. Then you can perform different operations like setting text in userName and password text boxes, clicking OK button etc using SWTWorkbenchBot methods.
If you are trying to test login controls on splash screen, then it will not be posible using SWTBot. Because SplashHandler does not get loaded when called from SWTBot.
The only way to launch your application in that case is to bypass your splash screen programatically. It means you have to write a code to skip login dialog/splash login screen.
Related
Using Xamarin.Forms, how do I get the same effect as the application pictured below, specifically to show a centred image on the Action Bar / page tool bar (the section in a blue box)?
I would like to have a long width image in that section, and the solution must work for Android, iOS, Windows Phone, and Universal Windows (even if it means writing custom renderers or platform specific xamarin code).
I suggest you create your own Xamarin.Forms view and handle the navigation by yourself something similar to this:
public class CustomBackNavigationBar : StackLayout
{
public Image BackIcon;
public Image Icon;
public Label IconTitle;
public StackLayout IconContainer;
public CustomBackNavigationBar(string title, string icon)
{
Padding = new Thickness(15,5);
HeightRequest = 40;
Orientation = StackOrientation.Horizontal;
VerticalOptions = LayoutOptions.Start;
BackgroundColor = StaticData.BlueColor;
Spacing = 15;
BackIcon = new Image
{
Source = StaticData.BackIcon,
HorizontalOptions = LayoutOptions.Start
};
Label Title = new Label
{
Text = title,
TextColor = Color.White,
FontSize = Device.GetNamedSize(NamedSize.Default, typeof(Label)),
FontAttributes = FontAttributes.Bold,
VerticalTextAlignment = TextAlignment.Center
};
Icon = new Image
{
Source = icon
};
IconTitle = new Label
{
Text = StaticData.CallAgent,
TextColor = Color.White,
FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)),
};
IconContainer = new StackLayout
{
HorizontalOptions = LayoutOptions.EndAndExpand,
Spacing = 2,
Children = { Icon, IconTitle }
};
Children.Add(BackIcon);
Children.Add(Title);
Children.Add(IconContainer);
#region Events
BackIcon.GestureRecognizers.Clear();
BackIcon.GestureRecognizers.Add(new TapGestureRecognizer
{
Command = new Command(PopAsync)
});
#endregion
}
async void PopAsync()
{
await App.AppNavigation.PopAsync();
}
}
I have created an javafx gui. I also have a custom class, which i use to create an object of that class everytime the add object button is pressed.
when i create a object of the class and print the object to the console the toString() method is called. but when i add the object a combobox and select it java throws a NullPointerException .I used the setOnAction method for the combobox but it doesnt even look at the handle method. Can anyone see whats going on in the stack trace?
The martian class is an abstract class. Martians are either red or green.
protected ComboBox<Martian> currentMartians = new ComboBox<>();
public class MartianSelectorHandler implements EventHandler<ActionEvent>{
#Override
public void handle(ActionEvent event) {
// TODO Auto-generated method stub
System.out.println("Inside combobox handler");
Martian m = (Martian) currentMartians.getValue();
txtaResults.setText(m.toString());
}
public Pane buildRightPane(){
VBox leftPane = new VBox();
leftPane.setPadding(new Insets(15,5,5,0));
leftPane.setAlignment(Pos.TOP_LEFT);
Label label = new Label("Curent Martians");
currentMartians.setPromptText("Martians");
leftPane.getChildren().addAll(label,currentMartians);
MartianSelectorHandler comboBox = new MartianSelectorHandler();
currentMartians.setOnAction(comboBox);
return leftPane;
}
Stack Trace
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at martian_stuff.Martian.equals(Martian.java:20)
at javafx.scene.control.MultipleSelectionModelBase.select(MultipleSelectionModelBase.java:385)
at javafx.scene.control.MultipleSelectionModelBase.clearAndSelect(MultipleSelectionModelBase.java:348)
at javafx.scene.control.ListView$ListViewBitSetSelectionModel.clearAndSelect(ListView.java:1400)
at com.sun.javafx.scene.control.behavior.CellBehaviorBase.simpleSelect(CellBehaviorBase.java:260)
at com.sun.javafx.scene.control.behavior.CellBehaviorBase.doSelect(CellBehaviorBase.java:224)
at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mousePressed(CellBehaviorBase.java:150)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:95)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3758)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3486)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2495)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:350)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$350(GlassViewEventHandler.java:385)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$$Lambda$315/1947016627.get(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:404)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:384)
at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
at com.sun.glass.ui.View.notifyMouse(View.java:927)
the Martian class is a jar file. but the equals method shoud look something like
public boolean equals(Object o){
//boolean returnValue = ((getId() == ((Martian) o).getId())); ? true : false);
return ((getId() == ((Martian) o).getId()));
}
These methods add to the combo box
public Pane buildRow2(){
GridPane row2 = new GridPane();
row2.setHgap(10);
row2.setVgap(10);
row2.setPadding(new Insets(25, 25, 25, 25));
Button addGM = new Button("Add GM");
addGM.setOnAction(e->{
System.out.println("Adding Green Martian");
int newId = Integer.parseInt(txtfId1.getText());
int newVolume = Integer.parseInt(txtfVolume.getText());
GreenMartian gm = new GreenMartian(newId);
gm.setVolume(newVolume);
if (mm.addMartian(gm) == true){
mm.addMartian(gm);
currentMartians.getItems().add(gm);
txtaResults.appendText("Green Martian ID: " + newId + " added.\n");
}
else{System.out.println("Martian not added");}
});
Button addRM = new Button("Add RM");
addRM.setOnAction(e->{
System.out.println("Adding Red Martian");
int newId = Integer.parseInt(txtfId1.getText());
int newVolume = Integer.parseInt(txtfVolume.getText());
RedMartian rm = new RedMartian(newId);
rm.setVolume(newVolume);
if (mm.addMartian(rm) == true){
mm.addMartian(rm);
currentMartians.getItems().add(rm);
txtaResults.appendText("Red Martian ID: " + newId + " added.\n");
}
else{
txtaResults.appendText("**Martian not added. Duplicate ID.**");
System.out.println("Cant add");
}
});
Button groupSpeak = new Button("Group Speak");
groupSpeak.setOnAction(e->{
String speak = mm.groupSpeak();
txtaResults.setText(speak+"\n");
});
Button groupTele = new Button("Group Teleport");
Label stringDest = new Label("Enter Destination: ");
txtfDest = new TextField();
stringDest.setVisible(false);
txtfDest.setVisible(false);
Button okay1 = new Button("Okay!");
okay1.setOnAction(e->{
if(txtfDest != null)
txtaResults.setText(mm.groupTeleport(txtfDest.getText())+"\n");
stringDest.setVisible(false);
txtfDest.setVisible(false);
okay1.setVisible(false);
});
okay1.setVisible(false);
groupTele.setOnAction(e->{
stringDest.setVisible(true);
txtfDest.setVisible(true);
okay1.setVisible(true);
});
row2.add(addGM, 0, 0);
row2.add(addRM,1,0);
row2.add(groupSpeak,0,1);
row2.add(groupTele, 1,1);
row2.add(stringDest,2,0);
row2.add(txtfDest, 2,1);
row2.add(okay1, 2, 2);
return row2;
}
I am looking to create an editable label at an arbitrary position on the pane on which I am writing. I am under the impression that TextField or TextArea objects are what I could use to implement that capability. There is obviously more to it as I don't know how to position the object when I create it. I have found an example on the "Chaotic Java" website but I need to do a bit more work to understand what's going on there. http://chaoticjava.com/posts/another-javafx-example-the-editable-label/
I am looking for more input from this group.
(There are no errors because I have not written any code.)
I was kind of curious about how to achieve this, so I gave it a try. This is what I came up with.
The approach used is pretty the same as that suggested by James in his comment:
I would start with a Pane, . . ., TextFields to represent text while being edited. Register mouse listeners with the Pane and Text objects, and use the layoutX and layoutY properties to position things . . . just to use text fields, and to use CSS to make them look like labels when not focused and text fields when focused.
The only significantly tricky part was working out how to correctly size the text fields as the Text inside the text field is not exposed via public API to allow you to listen to it's layout bounds. You could perhaps use a css lookup function to get at the enclosed Text, but I chose to use a private sun FontMetrics API (which may be deprecated in the future), to get the size of the text. In the future with Java 9, you should be able to perform the task without using the private API.
The solution doesn't try to do anything tricky like deal with multi-format or multi-line text, it is just for short, single line comments of a few words that can be placed over a scene.
TextCreator.java
// ## CAUTION: beware the com.sun imports...
import com.sun.javafx.tk.FontMetrics;
import com.sun.javafx.tk.Toolkit;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* Displays a map of the lonely mountain upon which draggable, editable labels can be overlaid.
*/
public class TextCreator extends Application {
private static final String MAP_IMAGE_LOC =
"http://images.wikia.com/lotr/images/archive/f/f6/20130209175313!F27c_thorins_map_from_the_hobbit.jpg";
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(final Stage stage) throws Exception {
Pane pane = new Pane();
pane.setOnMouseClicked(event -> {
if (event.getTarget() == pane) {
pane.getChildren().add(
new EditableDraggableText(event.getX(), event.getY())
);
}
});
EditableDraggableText cssStyled =
new EditableDraggableText(439, 253, "Style them with CSS");
cssStyled.getStyleClass().add("highlighted");
pane.getChildren().addAll(
new EditableDraggableText(330, 101, "Click to add a label"),
new EditableDraggableText(318, 225, "You can edit your labels"),
cssStyled,
new EditableDraggableText(336, 307, "And drag them"),
new EditableDraggableText(309, 346, "Around The Lonely Mountain")
);
StackPane layout = new StackPane(
new ImageView(
new Image(
MAP_IMAGE_LOC
)
),
pane
);
Scene scene = new Scene(layout);
scene.getStylesheets().add(getClass().getResource(
"editable-text.css"
).toExternalForm());
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
/**
* A text field which has no special decorations like background, border or focus ring.
* i.e. the EditableText just looks like a vanilla Text node or a Label node.
*/
class EditableText extends TextField {
// The right margin allows a little bit of space
// to the right of the text for the editor caret.
private final double RIGHT_MARGIN = 5;
EditableText(double x, double y) {
relocate(x, y);
getStyleClass().add("editable-text");
//** CAUTION: this uses a non-public API (FontMetrics) to calculate the field size
// the non-public API may be removed in a future JavaFX version.
// see: https://bugs.openjdk.java.net/browse/JDK-8090775
// Need font/text measurement API
FontMetrics metrics = Toolkit.getToolkit().getFontLoader().getFontMetrics(getFont());
setPrefWidth(RIGHT_MARGIN);
textProperty().addListener((observable, oldTextString, newTextString) ->
setPrefWidth(metrics.computeStringWidth(newTextString) + RIGHT_MARGIN)
);
Platform.runLater(this::requestFocus);
}
}
/**
* An EditableText (a text field which looks like a label), which can be dragged around
* the screen to reposition it.
*/
class EditableDraggableText extends StackPane {
private final double PADDING = 5;
private EditableText text = new EditableText(PADDING, PADDING);
EditableDraggableText(double x, double y) {
relocate(x - PADDING, y - PADDING);
getChildren().add(text);
getStyleClass().add("editable-draggable-text");
// if the text is empty when we lose focus,
// the node has no purpose anymore
// just remove it from the scene.
text.focusedProperty().addListener((observable, hadFocus, hasFocus) -> {
if (!hasFocus && getParent() != null && getParent() instanceof Pane &&
(text.getText() == null || text.getText().trim().isEmpty())) {
((Pane) getParent()).getChildren().remove(this);
}
});
enableDrag();
}
public EditableDraggableText(int x, int y, String text) {
this(x, y);
this.text.setText(text);
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(mouseEvent -> {
this.toFront();
// record a delta distance for the drag and drop operation.
dragDelta.x = mouseEvent.getX();
dragDelta.y = mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
});
setOnMouseReleased(mouseEvent -> getScene().setCursor(Cursor.HAND));
setOnMouseDragged(mouseEvent -> {
double newX = getLayoutX() + mouseEvent.getX() - dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setLayoutX(newX);
}
double newY = getLayoutY() + mouseEvent.getY() - dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setLayoutY(newY);
}
});
setOnMouseEntered(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseExited(mouseEvent -> {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
});
}
// records relative x and y co-ordinates.
private class Delta {
double x, y;
}
}
}
editable-text.css
.editable-text {
-fx-background-color: transparent;
-fx-background-insets: 0;
-fx-background-radius: 0;
-fx-padding: 0;
}
.editable-draggable-text:hover .editable-text {
-fx-background-color: yellow;
}
.editable-draggable-text {
-fx-padding: 5;
-fx-background-color: rgba(152, 251, 152, 0.2); // translucent palegreen
}
.editable-draggable-text:hover {
-fx-background-color: orange;
}
.highlighted {
-fx-background-color: rgba(255, 182, 93, 0.3); // translucent mistyrose
-fx-border-style: dashed;
-fx-border-color: firebrick;
}
If you have time, you could clean the sample implementation up and donate it to the ControlsFX project.
You can use a function of label: setGraphic().
Here is my code:
public void editableLabelTest(Stage stage){
Scene scene = new Scene(new VBox(new EditableLabel("I am a label"),
new EditableLabel("I am a label too")));
stage.setScene(scene);
stage.show();
}
class EditableLabel extends Label{
TextField tf = new TextField();
/***
* backup is used to cancel when press ESC...
*/
String backup = "";
public EditableLabel(){
this("");
}
public EditableLabel(String str){
super(str);
this.setOnMouseClicked(e -> {
if(e.getClickCount() == 2){
tf.setText(backup = this.getText());
this.setGraphic(tf);
this.setText("");
tf.requestFocus();
}
});
tf.focusedProperty().addListener((prop, o, n) -> {
if(!n){
toLabel();
}
});
tf.setOnKeyReleased(e -> {
if(e.getCode().equals(KeyCode.ENTER)){
toLabel();
}else if(e.getCode().equals(KeyCode.ESCAPE)){
tf.setText(backup);
toLabel();
}
});
}
void toLabel(){
this.setGraphic(null);
this.setText(tf.getText());
}
}
I have a Tabpane with multiple tabs.
I want to re-position tabs by just dragging them at a particular position(just like the way we are able to arrange tabs in browser.)
Is there any way i can achieve it?
We achieved it in a slightly different way.Instead of drag/drop feature we provided the move left/move right functionality on tab context menu which in turns moves the tab.
We wanted to have this feature on priority so implemented it with this workaround for now.
Code snippet for MoveRight:
public void moveRight() {
protected TabPane workBook;
int cTabIndex = bem.workBook.getTabs().indexOf(bem.activeSheet);
int tabCount = workBook.getTabs().size();
if (tabCount > 1 && cTabIndex > 0) {
workBook.getTabs().remove(bem.activeSheet);
workBook.getTabs().add(cTabIndex - 1, bem.activeSheet);
}
}
I've implemented a class that handles both draggable and detachable tabs - more details here. The implementation is not the tidiest, nor the most resilient but works pretty well for me in the simple cases I've tried so far. I've deliberately kept everything in the one class to make it easier for others to copy / use / modify as they see fit.
The basic concept that I'm using (arguably mis-using) is that the graphic you can set on a tab can be any node, not just an ImageView (or similar.) So instead of using the setText() on Tab directly, I'm not adding any text at all, just setting the graphic to be a Label containing the desired text. Now that the label is present in the tab header (and is pretty much the tab header spacially), that makes it much easier (and skin-independant) to grab the global co-ordinates of each tab header in the pane. From then it's just a case of some relatively simple positioning logic to work out when to detach tabs into a new window, when to re-add them and when to reorder them.
Of course, this isn't an ideal solution but unfortunately I haven't seen much else on the subject!
import java.util.HashSet;
import java.util.Set;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
/**
* A draggable tab that can optionally be detached from its tab pane and shown
* in a separate window. This can be added to any normal TabPane, however a
* TabPane with draggable tabs must *only* have DraggableTabs, normal tabs and
* DrragableTabs mixed will cause issues!
* <p>
* #author Michael Berry
*/
public class DraggableTab extends Tab {
private static final Set<TabPane> tabPanes = new HashSet<>();
private Label nameLabel;
private Text dragText;
private static final Stage markerStage;
private Stage dragStage;
private boolean detachable;
static {
markerStage = new Stage();
markerStage.initStyle(StageStyle.UNDECORATED);
Rectangle dummy = new Rectangle(3, 10, Color.web("#555555"));
StackPane markerStack = new StackPane();
markerStack.getChildren().add(dummy);
markerStage.setScene(new Scene(markerStack));
}
/**
* Create a new draggable tab. This can be added to any normal TabPane,
* however a TabPane with draggable tabs must *only* have DraggableTabs,
* normal tabs and DrragableTabs mixed will cause issues!
* <p>
* #param text the text to appear on the tag label.
*/
public DraggableTab(String text) {
nameLabel = new Label(text);
setGraphic(nameLabel);
detachable = true;
dragStage = new Stage();
dragStage.initStyle(StageStyle.UNDECORATED);
StackPane dragStagePane = new StackPane();
dragStagePane.setStyle("-fx-background-color:#DDDDDD;");
dragText = new Text(text);
StackPane.setAlignment(dragText, Pos.CENTER);
dragStagePane.getChildren().add(dragText);
dragStage.setScene(new Scene(dragStagePane));
nameLabel.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
dragStage.setWidth(nameLabel.getWidth() + 10);
dragStage.setHeight(nameLabel.getHeight() + 10);
dragStage.setX(t.getScreenX());
dragStage.setY(t.getScreenY());
dragStage.show();
Point2D screenPoint = new Point2D(t.getScreenX(), t.getScreenY());
tabPanes.add(getTabPane());
InsertData data = getInsertData(screenPoint);
if(data == null || data.getInsertPane().getTabs().isEmpty()) {
markerStage.hide();
}
else {
int index = data.getIndex();
boolean end = false;
if(index == data.getInsertPane().getTabs().size()) {
end = true;
index--;
}
Rectangle2D rect = getAbsoluteRect(data.getInsertPane().getTabs().get(index));
if(end) {
markerStage.setX(rect.getMaxX() + 13);
}
else {
markerStage.setX(rect.getMinX());
}
markerStage.setY(rect.getMaxY() + 10);
markerStage.show();
}
}
});
nameLabel.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
markerStage.hide();
dragStage.hide();
if(!t.isStillSincePress()) {
Point2D screenPoint = new Point2D(t.getScreenX(), t.getScreenY());
TabPane oldTabPane = getTabPane();
int oldIndex = oldTabPane.getTabs().indexOf(DraggableTab.this);
tabPanes.add(oldTabPane);
InsertData insertData = getInsertData(screenPoint);
if(insertData != null) {
int addIndex = insertData.getIndex();
if(oldTabPane == insertData.getInsertPane() && oldTabPane.getTabs().size() == 1) {
return;
}
oldTabPane.getTabs().remove(DraggableTab.this);
if(oldIndex < addIndex && oldTabPane == insertData.getInsertPane()) {
addIndex--;
}
if(addIndex > insertData.getInsertPane().getTabs().size()) {
addIndex = insertData.getInsertPane().getTabs().size();
}
insertData.getInsertPane().getTabs().add(addIndex, DraggableTab.this);
insertData.getInsertPane().selectionModelProperty().get().select(addIndex);
return;
}
if(!detachable) {
return;
}
final Stage newStage = new Stage();
final TabPane pane = new TabPane();
tabPanes.add(pane);
newStage.setOnHiding(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent t) {
tabPanes.remove(pane);
}
});
getTabPane().getTabs().remove(DraggableTab.this);
pane.getTabs().add(DraggableTab.this);
pane.getTabs().addListener(new ListChangeListener<Tab>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Tab> change) {
if(pane.getTabs().isEmpty()) {
newStage.hide();
}
}
});
newStage.setScene(new Scene(pane));
newStage.initStyle(StageStyle.UTILITY);
newStage.setX(t.getScreenX());
newStage.setY(t.getScreenY());
newStage.show();
pane.requestLayout();
pane.requestFocus();
}
}
});
}
/**
* Set whether it's possible to detach the tab from its pane and move it to
* another pane or another window. Defaults to true.
* <p>
* #param detachable true if the tab should be detachable, false otherwise.
*/
public void setDetachable(boolean detachable) {
this.detachable = detachable;
}
/**
* Set the label text on this draggable tab. This must be used instead of
* setText() to set the label, otherwise weird side effects will result!
* <p>
* #param text the label text for this tab.
*/
public void setLabelText(String text) {
nameLabel.setText(text);
dragText.setText(text);
}
private InsertData getInsertData(Point2D screenPoint) {
for(TabPane tabPane : tabPanes) {
Rectangle2D tabAbsolute = getAbsoluteRect(tabPane);
if(tabAbsolute.contains(screenPoint)) {
int tabInsertIndex = 0;
if(!tabPane.getTabs().isEmpty()) {
Rectangle2D firstTabRect = getAbsoluteRect(tabPane.getTabs().get(0));
if(firstTabRect.getMaxY()+60 < screenPoint.getY() || firstTabRect.getMinY() > screenPoint.getY()) {
return null;
}
Rectangle2D lastTabRect = getAbsoluteRect(tabPane.getTabs().get(tabPane.getTabs().size() - 1));
if(screenPoint.getX() < (firstTabRect.getMinX() + firstTabRect.getWidth() / 2)) {
tabInsertIndex = 0;
}
else if(screenPoint.getX() > (lastTabRect.getMaxX() - lastTabRect.getWidth() / 2)) {
tabInsertIndex = tabPane.getTabs().size();
}
else {
for(int i = 0; i < tabPane.getTabs().size() - 1; i++) {
Tab leftTab = tabPane.getTabs().get(i);
Tab rightTab = tabPane.getTabs().get(i + 1);
if(leftTab instanceof DraggableTab && rightTab instanceof DraggableTab) {
Rectangle2D leftTabRect = getAbsoluteRect(leftTab);
Rectangle2D rightTabRect = getAbsoluteRect(rightTab);
if(betweenX(leftTabRect, rightTabRect, screenPoint.getX())) {
tabInsertIndex = i + 1;
break;
}
}
}
}
}
return new InsertData(tabInsertIndex, tabPane);
}
}
return null;
}
private Rectangle2D getAbsoluteRect(Control node) {
return new Rectangle2D(node.localToScene(node.getLayoutBounds().getMinX(), node.getLayoutBounds().getMinY()).getX() + node.getScene().getWindow().getX(),
node.localToScene(node.getLayoutBounds().getMinX(), node.getLayoutBounds().getMinY()).getY() + node.getScene().getWindow().getY(),
node.getWidth(),
node.getHeight());
}
private Rectangle2D getAbsoluteRect(Tab tab) {
Control node = ((DraggableTab) tab).getLabel();
return getAbsoluteRect(node);
}
private Label getLabel() {
return nameLabel;
}
private boolean betweenX(Rectangle2D r1, Rectangle2D r2, double xPoint) {
double lowerBound = r1.getMinX() + r1.getWidth() / 2;
double upperBound = r2.getMaxX() - r2.getWidth() / 2;
return xPoint >= lowerBound && xPoint <= upperBound;
}
private static class InsertData {
private final int index;
private final TabPane insertPane;
public InsertData(int index, TabPane insertPane) {
this.index = index;
this.insertPane = insertPane;
}
public int getIndex() {
return index;
}
public TabPane getInsertPane() {
return insertPane;
}
}
}
I just found out that this has been implemented in JavaFX 10.
tabPane.tabDragPolicy = TabPane.TabDragPolicy.REORDER
...does the trick.
Update Feb 2016
There is an open feature request you can use to track implementation:
JDK-8092098 [TabPane] Support for draggable tabs
The feature request is currently scheduled for implementation in Java 9. Patches for obtaining drag and drop functionality are attached to the feature request.
Drag and Drop for tab headers is not implemented in the base JavaFX 2.2 platform.
Until that is implemented in the standard JDK, you will need to implement the feature yourself using JavaFX's Drag and Drop functionality. A similar feature is implemented for dragging table column headers, so perhaps you could look to the TableColumnHeader.java code for inspiration in implementing your feature.
Should you implement it (if you wish) you can contribute the modifications back to OpenJFX via patches to the TabSkin.java source.
A very descriptive answer can be found where you can create custom tabs for the same:
http://0divides0.wordpress.com/2010/10/21/movable-tabbed-panes-in-javafx/
A JavaFX cooked solution is hard to find as dev blog for the same states that such functionality is not present for Tabs and they plan to incorporate later.
http://grokbase.com/p/openjdk/openjfx-dev/123fq9k310/draggable-tabs
The following code shows how to solve the problem in a very simple way without tricks.
.....
.....
Tab tab1 = new Tab("Tab1");
Tab tab2 = new Tab("Tab21");
TabPane tabPane = new TabPane(tab1, tab21);
root.getChildren().add(tabPane);
....
....
System.out.println("Tabs size()= " + tabPane.lookupAll(".tab").size());
tabPane.lookupAll(".tab").forEach(t -> {
System.err.println("tab.bounds = " + t.getLayoutBounds());
});
You can get an access to other areas of TabPane by using style classes such as tab-content-area, tab-header-area, tab-header-background, headers-region, control-buttons-tab. Just use lookup or lookupAll methods of TabPane
So, I made a class that takes arrays and calculates a value from them. I then decided (unknowingly) to incorporate it into a GUI interface. All went well until I noticed this strange error; one of the jtextfields (prarie) would not store text while the other (yard) does.
I looked around and found my problem similiar to mine on this site;
Updating text in a JTextField
But he had one that doesn't work at all, where I have one that works and one that doesn't.
The Code is here (it's a bit long, but most of it is GUI), so hold your breath!:
import java.awt.GridLayout;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Window {
/**
* #param args
*/
private static int numb;
private static double micro, centi;
private static JTextField[] yard,prarie;
private static double[] charges,distances;
public static void main(String[] args)
{
//create a small dialog window to take in number of charged objects
JPanel startup = new JPanel();
JTextField many = new JTextField(5);
startup.add(many);
int result = JOptionPane.showConfirmDialog(null,startup , "Please Enter How Many Charged Objects are Being Evaluated", JOptionPane.OK_CANCEL_OPTION);
many.requestFocusInWindow();
//once ok is clicked, then the number input will be stored under 'numb'
//then proceed to inputFields
if (result == JOptionPane.OK_OPTION)
{
numb = Integer.parseInt(many.getText());
inputFields();
}
}
//this window opens the various JTextFields for input
public static void inputFields()
{
//top JTextFields
yard = new JTextField[numb];
JPanel chargePanel = new JPanel();
for(int x=0;x<numb;x++)
{
yard[x] =new JTextField(5);
chargePanel.add(new JLabel("Charge "+ Integer.toString(x+1)+":"));
chargePanel.add(yard[x]);
chargePanel.add(Box.createVerticalStrut(15)); // a spacer
}
//bottom JTextFields
prarie = new JTextField[numb-1];
JPanel meterPanel = new JPanel();
for(int x=0;x<numb-1;x++)
{
prarie[x]=new JTextField(5);
meterPanel.add(new JLabel("Meters "+ Integer.toString(x+1)+":"));
meterPanel.add(new JTextField(5));
meterPanel.add(Box.createVerticalStrut(15)); // a spacer
}
//JCheckBoxes
JCheckBox isMicro = new JCheckBox("Charges are in terms of microCoulombs");
JCheckBox isCm = new JCheckBox("Distances are in terms of centiMeters");
JPanel chechBox = new JPanel();
chechBox.add(isMicro);
chechBox.add(Box.createVerticalStrut(20));
chechBox.add(isCm);
//Paste them all together into one window
GridLayout gufi = new GridLayout(3,1);
JPanel host = new JPanel(gufi);
host.add(chargePanel);
host.add(meterPanel);
host.add(chechBox);
int result1 = JOptionPane.showConfirmDialog(null, host, "Please Enter Charge and Distance Values", JOptionPane.OK_CANCEL_OPTION);
//if ok is clicked, then go to 'printArr()' to print the JTextFields
//then go to assign the values from the JTextFields to private double arrays 'yard' and 'prarie'
if (result1 == JOptionPane.OK_OPTION)
{
micro = (isMicro.isSelected())? Math.pow(10, -6): 1;
centi = (isCm.isSelected())? .01: 1;
printArr();
assign();
}
}
//a makeshift method to print the value from the JTextFields
//to fix the problem of why prarie wouldn't store numbers
public static void printArr()
{
System.out.println("Charges are:");
for(int x=0;x<numb;x++)
System.out.print(yard[x].getText() + " ");
System.out.println("Distances are:");
for(int x=0;x<numb-1;x++)
System.out.print(prarie[x].getText() + " ");
}
//assigns values from JTextFields to the private double arrays 'yard' and 'prarie'
public static void assign()
{
try {
charges = new double[numb];
for(int x=0;x<numb;x++)
charges[x]=micro*Double.parseDouble(yard[x].getText().trim());
distances = new double[numb-1];
for(int x=0;x<numb-1;x++)
distances[x]=centi*Double.parseDouble(prarie[x].getText().trim());
} catch (NumberFormatException e) {
e.printStackTrace();
//inputFields();
}
calculate();
}
public static void calculate()
{
JPanel sample = new JPanel();
JTextField whichOne = new JTextField(5);
sample.add(whichOne);
int result = JOptionPane.showConfirmDialog(null,sample , "Please Enter Which Charged Object thy Wishs For", JOptionPane.OK_CANCEL_OPTION);
whichOne.requestFocusInWindow();
if (result == JOptionPane.OK_OPTION)
{
int target = Integer.parseInt(whichOne.getText());
}
}
}
Anyone who runs the code and takes the time to enter dummy values will see that 'yard' stores values while 'prarie' does not. Why is this?
*I'm pretty sure I'm overlooking obvious (as always).
Change:
meterPanel.add(new JTextField(5));
to:
meterPanel.add(prarie[x]);
in the for loop for the prarie textfields