Replace a label with textfield in javafx at same position - javafx

I have a number of Labels in my VBox and on click of a button i want to replace all these labels with textfields.

I wouldn't recommend using a VBox, since replacing the children will likely modify the layout causing a effect that could confuse the user.
Instead I recommend using a GridPane, which allows you to place multiple children in a single cell. This way you could put all the Labels and VBoxes in the grid, but set the visible property of the TextFields to false. This means both Label and TextField will be used for layout calculation and you can simply swap between "editing mode" and "normal mode" by inverting the visible property of all children:
FXML
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.LabelReplaceController">
<children>
<GridPane fx:id="grid">
<children>
<TextField fx:id="t1" text="Hello World!" visible="false" GridPane.rowIndex="0"/>
<Label text="${t1.text}" GridPane.rowIndex="0" />
<TextField fx:id="t2" text="foo" visible="false" GridPane.rowIndex="1"/>
<Label text="${t2.text}" GridPane.rowIndex="1" />
<TextField fx:id="t3" text="bar" visible="false" GridPane.rowIndex="2"/>
<Label text="${t3.text}" GridPane.rowIndex="2" />
</children>
</GridPane>
<ToggleButton selected="false" onAction="#selectionChanged" text="edit"/>
</children>
</HBox>
public class LabelReplaceController {
#FXML
private Pane grid;
#FXML
private void selectionChanged(ActionEvent event) {
for (Node child : grid.getChildren()) {
child.setVisible(!child.isVisible());
}
}
}

You can iterate over your VBox and replace the:
VBox vBox = ...
int currentPos = 0;
Map<Integer, TextField> toInsert = new HashMap<>(); // map with TextFields that need to be inserted at position
for (Iterator<Node> iterator = vBox.getChildren().iterator(); iterator.hasNext(); ) {
Node child = iterator.next();
if (child instanceof Label) {
Label lbl = (Label)child;
TextField text = new TextField(lbl.getText());
iterator.remove(); // remove the label that is at index currentPos
toInsert.put(currentPos, text);
}
currentPos++;
}
for (Integer pos : toInsert.keySet()) {
TextField field = toInsert.get(pos);
vBox.getChildren().add(pos, field); // Add the Text field at the old position of the Label
}
In the first loop you remove the labels and create the replacement TextFields and remember at which position you need to insert them later on. This cannot happen in the same loop. Then in the second loop you add those TextFields to the VBox at the position you assigned them.
Edit:
Based on #fabian's valid comments the above proposed solution may cause problems. Here is an alternative approach, that creates a second VBox where all non Label nodes are copied over and the Labels are replaced with TextField. I assume that the original VBox is a child element of some Parent.
Parent parent = ... // Parent of VBox
VBox vbox = ...
// Find the index of vBox in Parent
int position = 0;
for (int i = 0; i<parent.getChildren().size(); i++) {
if (parent.getChildren().equals(vBox)) {
position = i;
break; // found our position, no need to look further
}
}
VBox copyVBox = new VBox();
for (Node child : vBox.getChildren()) {
if (child instanceof Label) {
Label lbl = (Label)child;
TextField text = new TextField(lbl.getText());
copyVBox.getChildren().add(text);
} else {
copyVBox.getChildren().add(child);
}
}
parent.getChildren().remove(vBox);
parent.getChildren().add(position, copyVBox);

Related

JavaFx : Enable outer scrollview once the end of inner scrollview has been reached

I have a parent scrollpane which contains a webview as one of its children. When I scroll over the webview by having the mouse pointer on the webview area, it continues scrolling as expected till the end of the webview. However, is there a way where I can make the parent scrollpane scroll down, once the end of the child-webview is reached, while still the mouse pointer is on the webview-area ?
Sample Structure :
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.web.*?>
<ScrollPane prefHeight="455.0" prefWidth="602.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="wes.we.fx.view.ui.express.reports.generic.Demo">
<content>
<AnchorPane prefHeight="400.0" prefWidth="600.0">
<children>
<VBox layoutX="26.0">
<children>
<WebView prefHeight="316.0" prefWidth="530.0" />
</children>
</VBox>
</children>
</AnchorPane>
</content>
</ScrollPane>
Explanation of my solution: When using a WebView you can inject javascript which is what I used to pull values of the page to figure out when I was at the bottom. I am no JavaScript wiz so Im am not sure if there is a better solution here(I could not find one). I used the formula of screenTopPosition + screenTotalHeight to get the screenBottomPosition and compared that to the totalPageHeight if they are equal then you are at the bottom of the page this all worked as expected. This is where I feel it gets "Hacky" I couldn't figure out how to get the focus of the scrollbar from the scrollPane when at the bottom of the page. So my only other idea was to increment the scrollPane Vvalue by a fixed value when you were at the bottom of the webpage. The problem is the value you choose to increment it by may be different than the actual scrolling value incrementing I chose .1 once the Vvalue hits 1 it is at the bottom of the scrollPane
This is not exactly how I wanted to solve this problem hopefully someone can recommend a better solution that feels less "Hacky" and I would be glad to edit my answer if provided with guidance on how to figure out what the default incrementing is for the Vvalue
public class Main extends Application {
#Override
public void start(Stage stage) {
ScrollPane scrollPane = new ScrollPane();
scrollPane.setPrefSize(602, 455);
AnchorPane anchorPane = new AnchorPane();
anchorPane.setPrefSize(600, 400);
scrollPane.setContent(anchorPane);
VBox vBox = new VBox();
vBox.setPrefWidth(530);
vBox.setLayoutX(26);
anchorPane.getChildren().add(vBox);
WebView webView = new WebView();
webView.setPrefSize(530,316);
webView.getEngine().load("http://www.google.com");
webView.setOnScroll(event -> {
Integer screenTopPosition = (Integer) (webView.getEngine().executeScript("document.body.scrollTop"));
Integer screenTotalHeight = (Integer) webView.getEngine().executeScript("document.body.clientHeight");
Integer screenBottomPosition = screenTopPosition + screenTotalHeight;
Integer totalPageHeight = (Integer) webView.getEngine().executeScript("document.body.scrollHeight");
if(screenBottomPosition.equals(totalPageHeight)){
System.out.println("Bottom of Page Reached");
scrollPane.setVvalue(scrollPane.getVvalue()+.1);
}
});
vBox.getChildren().add(webView);
Label label = new Label("Content\nContent");
label.setFont(new Font(100));
vBox.getChildren().add(label);
stage.setScene(new Scene(scrollPane));
stage.show();
}
}

Xamarin Forms Collection View Extra Padding

I"m trying out the Xamarin Forms Collection View. It appears to have some extra padding around each item in the list. I can't see how to remove it.
Here is a picture of my view. The view has an aqua background. The 2 column grid in the template has labels set to red.
And here is the XAML/C#
<CollectionView
x:Name="BodyView"
SelectionMode="Single"
HorizontalOptions="FillAndExpand"
BackgroundColor="Aqua"
Grid.Row="2" />
BodyView.ItemTemplate = new DataTemplate(() =>
{
var converter = (IValueConverter) Application.Current.Resources["GridCellConverter"];
Grid grid = new Grid( );
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
foreach (var col in _gridColumns)
{
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
var content = new Label();
content.Style = (Style)Application.Current.Resources["GridCellLabel"];
content.BackgroundColor = Color.Red;
Binding binding = new Binding("CellData", BindingMode.OneWay, converter, col.PageFieldId);
content.SetBinding(Label.TextProperty, binding);
grid.Children.Add(content, grid.ColumnDefinitions.Count - 1, 0);
}
return grid;
});
I zeroed everything I could find to zero.
I tried setting a negative margin on the grid but it won't move left.
Is there any way to remove the padding?
What you are looking for is the Grid's Row and Column Spacing!
Grid grid = new Grid( ){ ColumnSpacing= "0", RowSpacing="0" };
Also, this needs to be removed:
grid.Margin = new Thickness(-20, 0, 0, 0);
A quick question though are you using it in UWP?
Also since you are using CollectionView why not use a single item in DataTemplate that goes into a GridItemsLayout?
More information here: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/layout#vertical-grid

JavaFx; Label Center depend on width

i have Pane and Label on it.
How to set layoutXProperty of Label so that it would be in Center on X line?
I think i should use formula like this: (Pane.width-Label.width)/2
But i don't know how it write right. And i have not found answer in google.
Can some one hint me?
thxs.
Upd. Text of the Label i update after if declaration, and it text can be different length. So i want to set layoutXProperty to it depend on length of text and would be on CENTER.
UPD: code inserted
public void start(Stage myStage) throws FileNotFoundException {
myStage.setTitle("OrthographyChecker");
Pane pane = new Pane();
Scene scene = new Scene(pane, 700, 300);
Label labelUp = new Label();
labelUp.setFont(new Font(fontSize));
labelUp.layoutXProperty().bind(pane.widthProperty().subtract(labelUp.widthProperty()).divide(2));
labelUp.layoutYProperty().bind(pane.heightProperty().subtract(labelUp.heightProperty()).divide(5));
labelUp.setText("Click to start");
pane.getChildren().addAll(labelUp);
TextField inputTxt = new TextField("");
inputTxt.setVisible(false);
inputTxt.layoutXProperty().bind(pane.widthProperty().subtract(inputTxt.widthProperty()).divide(2));
inputTxt.layoutYProperty().bind(labelUp.layoutYProperty().add(35));
pane.getChildren().addAll(inputTxt);
Label chkL = new Label("");
chkL.setFont(new Font(fontSize));
chkL.layoutYProperty().bind(inputTxt.layoutYProperty().add(30));
chkL.layoutXProperty().bind(pane.widthProperty().subtract(chkL.widthProperty()).divide(2));
chkL.setStyle("-fx-border-color: blue;");
pane.getChildren().addAll(chkL);
inputTxt.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
try {
for (String OneWrd : chkWrd[0].replace(", ", ",").split(",")) {
if (inputTxt.getText().equalsIgnoreCase(OneWrd)) {
chkL.setText("You are right!");
chkL.setVisible(true);
notfound[0] = false;
break;
}
}
if (notfound[0]){
chkL.setText("Wrong! Right answer: \"" + chkWrd[0] + "\"");
chkL.setVisible(true);
}
} catch (Exception e) {
errPrint(e, "Установки ожидания ");
}
inputTxt.setText("");
pause[0] = false;
notfound[0] = true;
chkL.layoutXProperty().bind(pane.widthProperty().subtract(labelUp.widthProperty()).divide(2));
}
});
myStage.setScene(scene);
myStage.show();
}
It part of code, which use it trouble label (variable chkL). As you can see, i have binded it to center, like another Label. But this label changes alingment after first iteration of change text of label, and starts to looks like that screen (label in border)
and i am trying to solve exact it proplem. Binds works, but not like i am expecting
The correct way to center a label is to use a layout pane and configure the pane and/or label so that it is centered. For example, if you wanted the label horizontally centered at the top of the layout (or a region of the layout), you might use a BorderPane as follows:
<BorderPane>
<top>
<Label alignment="CENTER" text="A Label" fx:id="myLabel">
<maxWidth><Double fx:constant="POSITIVE_INFINITY"/></maxWidth>
</Label>
</top>
<center>
<!-- other content ... -->
</center>
<!-- other BorderPane regions as required -->
</BorderPane>
All of the settings here can be made using Scene Builder, if you choose to use it.

FXML: Include custom component

I have control that extends HBox called TablePagination that I created purely in code that I want to include in a FXML file, so when I do this:
<VBox fx:id="box" spacing="15" styleClass="sectionStyle">
<StackPane>
<TablePagination fx:id="pagination" StackPane.alignment="CENTER"/>
</StackPane>
</VBox>
nothing appears. But when I do it in code like this:
pagination = new TablePagination(itemTable,items);
StackPane pane = new StackPane();
pane.setAlignment(pagination, Pos.CENTER);
pane.getChildren().add(pagination);
box.getChildren().add(pane);
My control gets rendered but not in center. So what am I missing?
In your code version TablePagination is centered within StackPane, but nodes inside TablePagination are not. Call:
pagination.setAlignment(Pos.CENTER);
Note that StackPane.setAlignment method is static, and you sould call:
StackPane.setAlignment(pagination, Pos.CENTER);

How to bind divider position to pixels instead of percentage on SplitPanes in JavaFX?

After resizing the window my SplitPane's divider moves to another position according to the given percentage (e.g.: dividerPositions="0.05").
But I want it to stay on the same position after resizing (like it was bound to pixel size and not percentage) where it was set before. I don't want it "move" (follow the percentage constraint) while resizing it's parent pane.
Is it possible to do this somehow?
(I would like to fix this preferably from code, the CSS solution is secondary for me)
The perfect solution for me would something be similar to this:
<SplitPane orientation="VERTICAL" dividerPositionFromTop="100px"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" >
...
</SplitPane>
Where dividerPositionFromTop sadly does not exist by my knowledge.
You can add a listener to the heightProperty of your SplitPane and recalculate the percentage value:
SplitPane splitPane = new SplitPane();
splitPane.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
double height = splitPane.getHeight();
double dividerPositionFromTop = 100;
splitPane.setDividerPosition(0, dividerPositionFromTop/height);
}
});
splitPane.getItems().addAll(new TextArea("1"), new TextArea("2"));
splitPane.setOrientation(Orientation.VERTICAL);

Resources