JavaFX : How to make a mark where between two imageview in tilepane - javafx

I try to make a slide frame looks like powerpoint using imageview and tilepane.
Let's suppose we are in powerpoint. In photo, there are blue background panel(tilepane) and slide(imageview).
I want make a mark(line) that is red arrow pointed in photo, when I locate mouse cursor where between slide5 and slide6. Can I make?
And how to get the each hgap area's information?
ex) Which hgap clicked?
I can get the each slide(imageview) value, but I can't find how to get the hgap's information.
This is my partial code.
String[] imageName = {"slide1.png", "slide2.png", "slide3.png", "slide4.png",
"slide5.png", "slide6.png", "slide7.png", "slide8.png"};
Image img = null;
for (int i = 0; i < imageName.length; i++) {
try {
img = new Image(getClass().getResourceAsStream(imageName[i]));
} catch (Exception e) {
System.out.println("exception : " + e);
}
ImageView imageview = new ImageView(img);
imageview.setUserData(imageName[i]);
imageview.setFitWidth(widthImageView);
imageview.setFitHeight(heightImageView);
HBox hbox= new HBox();
hbox.getChildren().add(imageview);
tile1.getChildren().add(hbox); }

The hgap and vgap parameters are used for calculating the space between the nodes. You can't put a node in there. You should evaluate your requirements and consider using a different layout like e. g. GridPane.

Related

Scaling start screen with fullscreen JavaFX

I'm stuck on full scaling for my JavaFX application. I'm in the process of making a full screen feature for the application and I'm running into issues on trying to get the aspect ratio and positioning right without manually editing the values.
With the way I've been trying, the values butcher the game's start screen making the positioning change making the designs of the game offset from the center of the application. I can understand the reasoning behind it with the way I set it up. My problem is wondering how to scale the start screen and keep it's original position without having to manually edit the values.
What I thought of was trying to input the value and having it scale according to that value then putting the result in the position of objects X and Y.
if (fullscreen) {
WIDTH = (Enter aspect ratio here) * 1.5;
HEIGHT = (Enter aspect ratio here) * 1.5;
} else {
WIDTH = 990;
HEIGHT = 525;
}
with Obvious flaws this butchers the start screen.
My solution was to make a double() that you just enter the value of the application WIDTH/HEIGHT then entering the amount you want to divide by (since I couldn't come up with exact cords, I grabbed the WIDTH and divided by specific value for it to align in the center) following with a boolean to state whether it's full screened or not. Though my only issue with this theory is that it'll only work with 1920x1080 monitors so I'd assume I would have to manually enter all types of aspect ratios to make it fit otherwise the start screen would be butchered.
I've seen a way of scaling here:
JavaFX fullscreen - resizing elements based upon screen size
Though I'm not sure how to correctly implement it.
public static boolean fullscreen = false;
public static double WIDTH = 990;
public static double HEIGHT = 525;
public static Pane pane = new Pane();
public static void StartScreen() {
pane.setPrefSize(WIDTH, (HEIGHT - 25)); // the 25 is for the text field/input.
pane.setStyle("-fx-background-color: transparent;");
Group sGroup = new Group();
Image i = new Image("file:start/So7AA.png");
ImageView outer = new ImageView(i);
// outer.setX(Ce.WIDTH/4.75); //4.75 // The functioning code for the snippit
// outer.setY(-10); //-10
outer.setX(Ce.WIDTH/position(3.60, fullscreen)); //4.75 // The non functioning code.
outer.setY(position(-1, Ce.fullscreen)); //-10
outer.setFitWidth(550);
outer.setFitHeight(550);
outer.setOpacity(.3);
GaussianBlur gBlur = new GaussianBlur();
gBlur.setRadius(50);
ImageView seal = new ImageView(i);
// seal.setX(Ce.WIDTH/3.83); //247.5 - 3.83
// seal.setY(39); //39
seal.setX(Ce.WIDTH/position(3.83, fullscreen)); //247.5 - 3.83
seal.setY(position(32, Ce.fullscreen)); //39
seal.setFitWidth(450);
seal.setFitHeight(450);
ImageView sealBlur = new ImageView(i);
// sealBlur.setX(Ce.WIDTH/3.83); //247.5 - 3.83
// sealBlur.setY(39); //39
sealBlur.setX(Ce.WIDTH/position(3.83, fullscreen)); //247.5 - 3.83
sealBlur.setY(position(32, Ce.fullscreen));
sealBlur.setFitWidth(450);
sealBlur.setFitHeight(450);
sealBlur.setEffect(gBlur);
}
For getting the values of the WIDTH and HEIGHT:
public static double getWidth(double W, boolean fs) {
if (fs) {
return WIDTH = Screen.getPrimary().getBounds().getMaxX();
} else {
return WIDTH = W;
}
}
public static double getHeight(double H, boolean fs) {
if (fs) {
return HEIGHT = Screen.getPrimary().getBounds().getMaxY();
} else {
return HEIGHT = H;
}
}
I know there's a way around this, I'm just not sure how to pull it off.
I'm not sure exactly what the requirements are here, but it looks like you have three images, which you want centered, and you want them all scaled by the same amount so that one of the images fills the available space in its container. (Then, you just need to make sure its container grows to fill all the space, and you can call stage.setFullScreen(true) or stage.setMaximized(true) as needed.)
You can do this with a pretty simple custom pane that manages the layout in the layoutChildren() method:
public class ImagePane extends Region {
private final Image image1;
private final ImageView imageView1;
private final Image image2;
private final ImageView imageView2;
private final Image image3;
private final ImageView imageView3;
public ImagePane(Image image1, Image image2, Image image3) {
this.image1 = image1;
this.image2 = image2;
this.image3 = image3;
imageView1 = new ImageView(image1);
imageView2 = new ImageView(image2);
imageView3 = new ImageView(image3);
getChildren().addAll(imageView1, imageView2, imageView3);
}
#Override
protected void layoutChildren() {
double xScale = getWidth() / image1.getWidth();
double yScale = getHeight() / image1.getHeight();
double scale = Math.min(xScale, yScale);
for (ImageView view : List.of(imageView1, imageView2, imageView3) {
scaleAndCenter(view, scale);
}
}
private void scaleAndCenter(ImageView view, scale) {
double w = scale * view.getImage().getWidth();
double h = scale * view.getImage().getHeight();
view.setFitWidth(w);
view.setFitHeight(h);
view.relocate((getWidth()-w) / 2, (getHeight()-h) / 2);
}
}
The rest of your layout looks something like:
Label label = new Label("Type in 'start'.\nType in 'options' for options.\n(Demo)");
TextField textField = new TextField();
ImagePane imagePane = new ImagePane(new Image(...), new Image(...), new Image(...));
AnchorPane anchor = new AnchorPane(imagePane, label);
AnchorPane.setTopAnchor(imagePane, 0.0);
AnchorPane.setRightAnchor(imagePane, 0.0);
AnchorPane.setBottomAnchor(imagePane, 0.0);
AnchorPane.setLeftAnchor(imagePane, 0.0);
AnchorPane.setTopAnchor(label, 5.0);
AnchorPane.setLeftAnchor(label, 5.0);
BorderPane root = new BorderPane();
root.setCenter(anchor);
root.setBottom(textField);
Now everything should just respond to whatever size is assigned to the root pane, so setting full screen mode should "just work".

How to disable the arrow button in JavaFX combo box

I have a project I am working on. I am trying to make a dictionary. For that, I have a .csv file with about 55000 words.I am using the trie data structure which has a startsWith() method which checks whether there is a word in the .csv file which matches the given prefix. I had managed to get it to work to find all words that match the given prefix and display them. Now, I have to develop this into a JavaFX app.
So, I thought of using a ComboBox which has its editable attribute set to true so that I could type into it and then the handler associated with the textProperty() of its editor would display all the words starting with given prefix in the listview of the combobox.
Now, the problem I have is that whenever I click the arrow button of the combobox the application stops responding (I think it's because the list view tries to resize itself to fit the items which are 55000).
So, what I want to know is how to disable the arrow button entirely. I have tried to set its background-color to transparent but even then it can still be clicked I want to make it so that it is disabled and transparent basically the combobox ends up looking like a text field.
If there are better, more efficient ways of implementing a dictionary I would appreciate it if you could guide me.
The ListView is a virtual control that only shows a certain number of cells at a time, it doesn't need to "resize itself to the number of items" in any way that would lock up your GUI.
Does this demo program do what you want?
public class Main extends Application {
public static void main(String[] args) throws IOException, URISyntaxException {
launch(args);
}
#Override
public void start(Stage stage) {
List<String> rawWords = Collections.emptyList();
try {
URI wordURI = new URI("https://www-cs-faculty.stanford.edu/~knuth/sgb-words.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(wordURI.toURL().openStream(), StandardCharsets.UTF_8));
rawWords = reader.lines().collect(Collectors.toCollection(() -> new ArrayList<>(6000)));
} catch (IOException | URISyntaxException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
// make the list at least as big as in the question
while(rawWords.size() < 55000) {
ArrayList<String> nextWords = new ArrayList<>(rawWords.size() * 2);
nextWords.addAll(rawWords);
nextWords.addAll(rawWords);
rawWords = nextWords;
}
Collections.sort(rawWords);
ObservableList<String> wordList = FXCollections.observableArrayList(rawWords);
FilteredList<String> filteredList = new FilteredList<>(wordList);
ComboBox<String> combo = new ComboBox<>(filteredList);
combo.setEditable(true);
combo.getEditor().textProperty().addListener((obs, oldVal, newVal) -> {
filteredList.setPredicate(s -> newVal == null || newVal.isEmpty() || s.startsWith(newVal));
});
VBox vbox = new VBox(8,new Label("Dictionary ComboBox"),
combo,
new Label("\n\n\n\nThis space intentionally left blank.\n\n\n\n"));
vbox.setPadding(new Insets(8));
Scene scene = new Scene(vbox, 400, 300);
stage.setTitle("Demo - Filtered Combobox List");
stage.setScene(scene);
stage.show();
}
}

Showing texts over the face of a Box based on the visible area on zooming in/out

I have a sample 3D application (built by taking reference from the Javafx sample 3DViewer) which has a table created by laying out Boxes and Panes:
The table is centered wrt (0,0,0) coordinates and camera is at -z position initially.
It has the zoom-in/out based on the camera z position from the object.
On zooming in/out the object's boundsInParent increases/decreases i.e. area of the face increases/decreases. So the idea is to put more text when we have more area (always confining within the face) and lesser text or no text when the face area is too less. I am able to to do that using this node hierarchy:
and resizing the Pane (and managing the vBox and number of texts in it) as per Box on each zoom-in/out.
Now the issue is that table boundsInParent is giving incorrect results (table image showing the boundingBox off at the top) whenever a text is added to the vBox for the first time only. On further zooming-in/out gives correct boundingBox and does not go off.
Below is the UIpane3D class:
public class UIPane3D extends Pane
{
VBox textPane;
ArrayList<String> infoTextKeys = new ArrayList<>();
ArrayList<Text> infoTextValues = new ArrayList<>();
Rectangle bgCanvasRect = null;
final double fontSize = 16.0;
public UIPane3D() {
setMouseTransparent(true);
textPane = new VBox(2.0)
}
public void updateContent() {
textPane.getChildren().clear();
getChildren().clear();
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
textPane.setTranslateY(getHeight() / 2 - textPane.getHeight() / 2.0);
bgCanvasRect = new Rectangle(getWidth(), getHeight());
bgCanvasRect.setFill(Color.web(Color.BURLYWOOD.toString(), 0.10));
bgCanvasRect.setVisible(true);
getChildren().addAll(bgCanvasRect, textPane);
}
public void resetInfoTextMap()
{
if (infoTextKeys != null || infoTextValues != null)
{
try
{
infoTextKeys.clear();
infoTextValues.clear();
} catch (Exception e){e.printStackTrace();}
}
}
public void updateInfoTextMap(String pKey, String pValue)
{
int index = -1;
boolean objectFound = false;
for (String string : infoTextKeys)
{
index++;
if(string.equals(pKey))
{
objectFound = true;
break;
}
}
if(objectFound)
{
infoTextValues.get(index).setText(pValue.toUpperCase());
}
else
{
if (pValue != null)
{
Text textNode = new Text(pValue.toUpperCase());
textNode.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, fontSize));
textNode.wrappingWidthProperty().bind(widthProperty());
textNode.setTextAlignment(TextAlignment.CENTER);
infoTextKeys.add(pKey);
infoTextValues.add(textNode);
}
}
}
}
The code which get called at the last after all the manipulations:
public void refreshBoundingBox()
{
if(boundingBox != null)
{
root3D.getChildren().remove(boundingBox);
}
PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.web(Color.CRIMSON.toString(), 0.25));
Bounds tableBounds = table.getBoundsInParent();
boundingBox = new Box(tableBounds.getWidth(), tableBounds.getHeight(), tableBounds.getDepth());
boundingBox.setMaterial(blueMaterial);
boundingBox.setTranslateX(tableBounds.getMinX() + tableBounds.getWidth()/2.0);
boundingBox.setTranslateY(tableBounds.getMinY() + tableBounds.getHeight()/2.0);
boundingBox.setTranslateZ(tableBounds.getMinZ() + tableBounds.getDepth()/2.0);
boundingBox.setMouseTransparent(true);
root3D.getChildren().add(boundingBox);
}
Two things:
The table3D's boundsInParent is not updated properly when texts are added for the first time.
What would be the right way of putting texts on 3D nodes? I am having to manipulate a whole lot to bring the texts as required.
Sharing code here.
For the first question, about the "jump" that can be noticed just when after scrolling a new text item is laid out:
After digging into the code, I noticed that the UIPane3D has a VBox textPane that contains the different Text nodes. Every time updateContent is called, it tries to add a text node, but it checks that the vbox's height is always lower than the pane's height, or else the node will be removed:
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
While this is basically correct, when you add a node to the scene, you can't get textPane.getHeight() immediately, as it hasn't been laid out yet, and you have to wait until the next pulse. This is why the next time you scroll, the height is correct and the bounding box is well placed.
One way to force the layout and get the correct height of the textNode is by forcing css and a layout pass:
for (Text textNode : infoTextValues) {
textPane.getChildren().add(textNode);
// force css and layout
textPane.applyCss();
textPane.layout();
textPane.autosize();
if (textPane.getHeight() > getHeight()) {
textPane.getChildren().remove(textNode);
textPane.autosize();
break;
}
}
Note that:
This method [applyCss] does not normally need to be invoked directly but may be used in conjunction with Parent.layout() to size a Node before the next pulse, or if the Scene is not in a Stage.
For the second question, about a different solution to add Text to 3D Shape.
Indeed, placing a (2D) text on top of a 3D shape is quite difficult, and requires complex maths (that are done quite nicely in the project, by the way).
There is an alternative avoiding the use of 2D nodes directly.
Precisely in a previous question, I "wrote" into an image, that later on I used as the material diffuse map of a 3D shape.
The built-in 3D Box places the same image into every face, so that wouldn't work. We can implement a 3D prism, or we can make use of the CuboidMesh node from the FXyz3D library.
Replacing the Box in UIPaneBoxGroup:
final CuboidMesh contentShape;
UIPane3D displaypane = null;
PhongMaterial shader = new PhongMaterial();
final Color pColor;
public UIPaneBoxGroup(final double pWidth, final double pHeight, final double pDepth, final Color pColor) {
contentShape = new CuboidMesh(pWidth, pHeight, pDepth);
this.pColor = pColor;
contentShape.setMaterial(shader);
getChildren().add(contentShape);
addInfoUIPane();
}
and adding the generateNet method:
private Image generateNet(String string) {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
Label label5 = new Label(string);
label5.setFont(Font.font("Consolas", FontWeight.BLACK, FontPosture.REGULAR, 40));
GridPane.setHalignment(label5, HPos.CENTER);
grid.add(label5, 3, 1);
double w = contentShape.getWidth() * 10; // more resolution
double h = contentShape.getHeight() * 10;
double d = contentShape.getDepth() * 10;
final double W = 2 * d + 2 * w;
final double H = 2 * d + h;
ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(d * 100 / W);
ColumnConstraints col2 = new ColumnConstraints();
col2.setPercentWidth(w * 100 / W);
ColumnConstraints col3 = new ColumnConstraints();
col3.setPercentWidth(d * 100 / W);
ColumnConstraints col4 = new ColumnConstraints();
col4.setPercentWidth(w * 100 / W);
grid.getColumnConstraints().addAll(col1, col2, col3, col4);
RowConstraints row1 = new RowConstraints();
row1.setPercentHeight(d * 100 / H);
RowConstraints row2 = new RowConstraints();
row2.setPercentHeight(h * 100 / H);
RowConstraints row3 = new RowConstraints();
row3.setPercentHeight(d * 100 / H);
grid.getRowConstraints().addAll(row1, row2, row3);
grid.setPrefSize(W, H);
grid.setBackground(new Background(new BackgroundFill(pColor, CornerRadii.EMPTY, Insets.EMPTY)));
new Scene(grid);
return grid.snapshot(null, null);
}
Now all the 2D related code can be removed (including displaypane), and after a scrolling event get the image:
public void refreshBomUIPane() {
Image net = generateNet(displaypane.getText());
shader.setDiffuseMap(net);
}
where in UIPane3D:
public String getText() {
return infoTextKeys.stream().collect(Collectors.joining("\n"));
}
I've also removed the bounding box to get this picture:
I haven't played around with the number of text nodes that can be added to the VBox, the font size nor with an strategy to avoid generating images on every scroll: only when the text changes this should be done. So with the current approach is quite slow, but it can be improved notably as there are only three possible images for each box.

Image not displayed in ImageView: JavaFX

Below is the code I use to view image and caption text together:
try
{
Scanner scanner = new Scanner( new File("data.txt") );
String text = scanner.useDelimiter("ENDOFFILE").next();
scanner.close(); // Put this call in a finally block
System.out.println(text);
String[] data = text.split("\n");
for(int i=0;i<data.length;i++)
{
if(i%2==0)
{
System.out.println("This is "+data[i]);
Image img = new Image(new File(data[i]).toURI().toString(), 100, 0, false, false);
ImageView selectedImage = new ImageView();
selectedImage.setImage(img);
v.getChildren().addAll(selectedImage);
}
else
{
Text t = new Text(data[i]);
t.setFont(Font.font("Tahoma", FontWeight.NORMAL, 14));
v.getChildren().add(t);
}
}
}
catch(Exception E)
{
E.printStackTrace();
}
root2.getChildren().add(v);
Scene scene = new Scene(root2, 1000, 1000);
stage.setScene(scene);
stage.show();
}
data[i] for the image is Udiagram.jpg which is present within the same directory as the program. No exceptions occur but the image is not displayed, it shows empty with only text shown as follows: Text goes here.
The file data.txt is as follows:
UDiagram.jpg
Hit and Run
ENDOFFILE
What could be the reason for not displaying the image but only the text?
I believe you just need to concatenate the file and folder name to the beginning of your String.
Image image = new Image("file:FOLDER_NAME/imageName.gif");
ImageView imageView = new ImageView();
imageView.setImage(image);
imageView.setFitWidth(100);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);

Show Image Dynamically in ScrollPane JavaFx

I want to add Multiple images in Scollpane by clicking button i try below code but it will not display image any idea about that?
#FXML private void OnClick(ActionEvent ae)
{
getGalleryView();
}
public void getGalleryView()
{
ScrolPane sp=new ScroPane();
Hbox hb=new Hbox();
Image [] images=new Image[5];
ImageView []pics=new ImageView[5];
final String [] imageNames = new String [] {"fw1.jpg", "fw2.jpg",
"fw3.jpg", "fw4.jpg", "fw5.jpg"};
for (int i = 0; i < 5; i++) {
images[i] = new Image(getClass().getResourceAsStream(imageNames[i]));
pics[i] = new ImageView(images[i]);
pics[i].setFitWidth(100);
pics[i].setPreserveRatio(true);
hb.getChildren().add(pics[i]);
sp.setContent(hb);
}
}
You need to add the scrollpane to the scene:
#FXML private void OnClick(ActionEvent ae)
{
getGalleryView(ae);
}
public void getGalleryView(ActionEvent ae)
{
ScrolPane sp=new ScroPane();
Hbox hb=new Hbox();
Image [] images=new Image[5];
ImageView []pics=new ImageView[5];
final String [] imageNames = new String [] {"fw1.jpg", "fw2.jpg",
"fw3.jpg", "fw4.jpg", "fw5.jpg"};
for (int i = 0; i < 5; i++) {
images[i] = new Image(getClass().getResourceAsStream(imageNames[i]));
pics[i] = new ImageView(images[i]);
pics[i].setFitWidth(100);
pics[i].setPreserveRatio(true);
hb.getChildren().add(pics[i]);
sp.setContent(hb);
}
Scene scene = ((Node) ae.getSource()).getScene();
((Pane) scene.getRoot()).getChildren().add(sp);
}
I assumed here that your root node is a Pane or one of its subclasses.
ScrolPane sp=new ScroPane(); error?
EDIT:
I was developing similar method. Mine works fine. You can check if you want to.
private List<String> listFileNames(File folder) throws NullPointerException{
List<String> list = new ArrayList<>();
for (File file : folder.listFiles()) {
if (file.isDirectory())
listFileNames(file);
else {
System.out.println(file.getName());
list.add(file.getName());
}
}
return list;
}
private void insertImages(List<String> list, Hero thisHero) {
int column = 0;
int row = 0;
for (String path:list) {
String fullPath = "file:"+thisHero.getHeroClass().getFile()+"\\"+path;
ToggleButton button = new ToggleButton();
button.setBackground(Background.EMPTY);
button.setGraphic(new ImageView(new Image(fullPath)));
grid.add(button,column,row);
column++;
if (column == 5) {
row++;
column = 0;
}
}
}
I can write more if you want. I use Lists because of it's ease of adding items.
You can use first method to just get all file names to list, from your folder filled with image files.
Second method does the job of making new ImageViews filled with ToggleButtons with graphic. I just changed the concept to buttons, so sorry about my laziness of not changing code to exactly fit your needs.
Path is the exact file name, thisHero.getHeroClass().getFile() returns path to the directory which contains this image.
grid.add(button, column, row) adds this button to the grid pane which i made before. It's my app, so sorry for not sharing all the code, but i thought that this snippet could be usefull.
EDIT2: You could also provide us with error information if there is any.

Resources