How to make fxml property "text : " in JavaFX custom control? - javafx

I've developing JavaFX custom button. Like this.
public class MyButton extends Control{
private static final String DEFAULT_STYLE_CLASS = "satrec-button";
private Button button;
public MyButton(){
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
}
#Override
public String getUserAgentStylesheet(){
return "sample/css/button.css";
}
public void setButton(Button button){
this.button = button;
}
public void setText(String text){
this.button.setText(text);
}
}
I want to use this control in FXML. But original button and label can set text to use " test : " Like this
<Button text="Search" textFill="white" prefWidth="130" GridPane.columnIndex="4" GridPane.rowIndex="1" styleClass="control" fx:id="search_btn" />
But MyControl can not use this function. I looked up Labeled class. I know text is a StringProperty.
But I don't know what is this, and how can I use. How do I do?? Please, help me.

The default interpretation of an attribute or child property element of an instance FXML element is that it maps to a JavaFX property in the instance. (Essentially it is relying on the control using the JavaFX properties pattern.)
In other words, in your FXML snippet:
<MyButton text="Search" ... />
or, equivalently,
<MyButton>
<text>
<String fx:value="Search" />
</text>
</MyButton>
<MyButton> is an instance element, and it causes the FXMLLoader to create a new instance of the MyButton class, calling the default constructor by default. In either version, text (an attribute in the first version, a property element in the second) is going to cause the FXMLLoader to look for a textProperty() method, returning a WritableValue instance (for example a StringProperty). Assuming it finds one, it will call setValue(...) on that WritableValue, passing in the string "Search". (See the FXML documentation for a full description of all this terminology.)
So you just need your MyButton class to define an appropriate textProperty() method. Since you want this to be the text of the included button, you can just delegate to that button's method:
public class MyButton extends Control{
private static final String DEFAULT_STYLE_CLASS = "satrec-button";
private Button button;
public MyButton(){
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
}
#Override
public String getUserAgentStylesheet(){
return "sample/css/button.css";
}
public void setButton(Button button){
this.button = button;
}
public StringProperty textProperty() {
if (button == null) { // might be better to write the class so that this is never true?
button = new Button();
}
return button.textProperty();
}
public final void setText(String text) {
textProperty().set(text);
}
public final String getText() {
return textProperty().get();
}
}

You can use the NamedArg Annotation:
public MyButton(#NamedArg("text") String text) {
this();
this.setText(text);
}
otherwise the fxml doesn't know which constructor argument you mean.
Now can use your Button in your fxml as you asked:
<MyButton text="Search" ....

Related

How to attach event handlers to controller in MVC | JavaFX

I am having trouble understanding how to apply the mvc pattern with JavaFX.
Here are my questions with respect to the code below, since I need to follow the pattern given in the code:
a) How can I attach an event handler of the button which is present in my ViewA to the code in my ControllerA (specifically, attachEventHandlers() method). For example, I want my button to populate the comboBox in ViewA with the results of getModelItems() method from controller.
Note that the method getModelItems() is private.
b) I would have multiple buttons and event handlers in my view. How will I bind each one of them uniquely to the controller?
c) I want to invoke setName(String name) on my model in the controller, and the parameter I want to pass is the selected value on the comboBox in viewA. How can I achieve this?
Thank you so much for any help!
Below is the code referred in the description.
Controller:
import model.ModelA;
import view.ViewA;
public class ControllerA {
private ViewA view;
private ModelA model;
public ControllerA(ViewA view, ModelA model) {
//initialise model and view fields
this.model = model;
this.view = view;
//populate combobox in ViewB, e.g. if viewB represented your ViewB you could invoke the line below
//viewB.populateComboBoxWithCourses(setupAndRetrieveCourses());
this.attachEventHandlers();
}
private void attachEventHandlers() {
}
private String[] getModelItems() {
String[] it = new String[2];
it[0] = "0";
it[1] = "1";
return it;
}
}
Model:
public class ModelA {
private String name;
public Name() {
name = "";
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Name = " + name;
}
}
View:
import javafx.scene.layout.BorderPane;
//You may change this class to extend another type if you wish
public class ViewA extends BorderPane {
public BorderPane bp;
public ViewA(){
this.bp = new BorderPane();
ComboBox comboBox = new ComboBox();
Button button1 = new Button("Populate");
bp.setTop(button1);
bp.setBottom(comboBox);
}
}
Loader:
public class ApplicationLoader extends Application {
private ViewA view;
#Override
public void init() {
//create model and view and pass their references to the controller
ModelA model = new ModelA();
view = new ViewA();
new ControllerA(view, model);
}
#Override
public void start(Stage stage) throws Exception {
//whilst you can set a min width and height (example shown below) for the stage window,
//you should not set a max width or height and the application should
//be able to be maximised to fill the screen and ideally behave sensibly when resized
stage.setMinWidth(530);
stage.setMinHeight(500);
stage.setTitle("Final Year Module Chooser Tool");
stage.setScene(new Scene(view));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You add delegates to your ViewA to allow for access:
public class ViewA extends BorderPane {
ComboBox comboBox;
Button button1;
public ViewA(){
comboBox = new ComboBox();
button1 = new Button("Populate");
setTop(button1);
setBottom(comboBox);
}
// Add delegates for all functionality you want to make available through ViewA
public ObservableList<String> getItems() { return comboBox.getItems(); }
public void setOnButton1Action(...) { ... }
public void setOnButton2Action(...) { ... }
...
}
You can go as broad or as narrow as you like, based on how much you want to manage through ViewA.

Accessing the OK button from inside the dialog controller [javaFX]

I am trying to disable the OK button in a javaFX dialog untill all of the text fields have content.
Due to the ButtonType not having FXML support it has to be added to the Dialog in the Controller class of the main Window. due to this I'm unable to (cant find a way) to link the button to a variable inside the dialog controller.
I have tried handling the process in the main Controller class as follows:
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("addDialog.fxml"));
try {
dialog.getDialogPane().setContent(loader.load());
} catch(IOException e) {
e.getStackTrace();
}
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
dialog.getDialogPane().lookupButton(ButtonType.OK).setDisable(true);
AddDialogController controller = loader.getController();
// below calls on a getter from the addDialogController.java file to check if the input fields are full
if (controller.getInputsFull()) {
dialog.getDialogPane().lookupButton(ButtonType.OK).setDisable(false);
}
unfortunately this didn't work, the above code can only be run once before or after the window is called and cant run during.
so is there a way to access the OK ButtonType that comes with javaFX inside the dialog controller if it has been declared outside?
Or is there another way to disable the button based of information from the dialog controller that is being updated by the user?
thanks for any help
Edit 1:
As requested the addDialogController, this is very bare bones and incomplete, hopefully it helps:
import data.Contact;
import data.ContactData;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
public class AddDialogController {
#FXML
private TextField firstNameField;
#FXML
private TextField lastNameField;
#FXML
private TextField numberField;
#FXML
private TextArea notesArea;
private boolean inputsFull;
public void processResults() {
String first = firstNameField.getText().trim();
String last = lastNameField.getText().trim();
String number = numberField.getText().trim();
String notes = notesArea.getText().trim();
Contact contact = new Contact(first, last, number, notes);
// ContactData.add(contact);
}
#FXML
public void handleKeyRelease() {
boolean firstEmpty = firstNameField.getText().trim().isEmpty() && firstNameField.getText().isEmpty();
boolean lastEmpty = lastNameField.getText().trim().isEmpty() && lastNameField.getText().isEmpty();
boolean numberEmpty = numberField.getText().trim().isEmpty() && numberField.getText().isEmpty();
boolean notesEmpty = notesArea.getText().trim().isEmpty() && notesArea.getText().isEmpty();
inputsFull = !firstEmpty && !lastEmpty && !numberEmpty && !notesEmpty;
System.out.println(firstEmpty);
System.out.println(lastEmpty);
System.out.println(numberEmpty);
System.out.println(notesEmpty);
System.out.println(inputsFull);
System.out.println();
}
public boolean isInputsFull() {
return this.inputsFull;
}
First, delete your handleKeyRelease method. Never use key event handlers on text input components: for one thing they will not work if the user copies and pastes text into the text field with the mouse. Just register listeners with the textProperty() instead, if you need. Also, isn't (for example)
firstNameField.getText().trim().isEmpty() && firstNameField.getText().isEmpty()
true if and only if
firstNameField.getText().isEmpty();
is true? It's not clear what logic you are trying to implement there.
You should simply expose inputsFull as a JavaFX property:
public class AddDialogController {
#FXML
private TextField firstNameField;
#FXML
private TextField lastNameField;
#FXML
private TextField numberField;
#FXML
private TextArea notesArea;
private BooleanBinding inputsFull ;
public BooleanBinding inputsFullBinding() {
return inputsFull ;
}
public final boolean getInputsFull() {
return inputsFull.get();
}
public void initialize() {
inputsFull = new BooleanBinding() {
{
bind(firstNameField.textProperty(),
lastNameField.textProperty(),
numberField.textProperty(),
notesArea.textProperty());
}
#Override
protected boolean computeValue() {
return ! (firstNameTextField.getText().trim().isEmpty()
|| lastNameTextField.getText().trim().isEmpty()
|| numberField.getText().trim().isEmpty()
|| notesArea.getText().trim().isEmpty());
}
};
}
public void processResults() {
String first = firstNameField.getText().trim();
String last = lastNameField.getText().trim();
String number = numberField.getText().trim();
String notes = notesArea.getText().trim();
Contact contact = new Contact(first, last, number, notes);
// ContactData.add(contact);
}
}
and then all you need is
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
AddDialogController controller = loader.getController();
dialog.getDialogPane().lookupButton(ButtonType.OK)
.disableProperty()
.bind(controller.inputsFullBinding().not());

JavaFX how to change value of existing text field upon Button press

I'm working on a GUI calculator and am having trouble modifying the text field at the top of the calculator when a button is pressed. My text field is labeled 'text', but in the listener class I'm having trouble referencing it. This is my listener class:
class ClearListener implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent e) {
calculator1.clear();
text.setText(0.0);
}
}
Obviously the reference to the TextField in the ClearListener class doesn't work because the scope doesn't include that class, but I'm not sure how else I would reference it.
I tried this:
public class UserInterfaceCalculator extends Application {
private MemoryCalculator calculator1 = new MemoryCalculator();
private String operator = null;
private TextField text = new TextField("0.0");
private Boolean equalsPressed = true;
#Override
public void start(Stage primaryStage) throws Exception {
This fixes the scope issue, and the text field still shows correctly with the initial value of 0.0, but then the value doesn't change from 0.0 even when I set a new value in the listener classes upon a button press.

JavaFx: Static fields

Could you explain me why eclipse want getMissionFromMissionController() to be static if i haven't annotated listView with the static word ?
Whenever i want to create this function i've got an error:
"Cannot make a static reference to the non-static method getMissionFromMissionController() from the type MainController"
LogPanelController:
public void printLog()
{
textLog.appendText(MainController.getMissionFromMissionController());
}
MainController:
public String getMissionFromMissionController() {
return MissionController.listView.getSelectionModel().getSelectedItem();
}
And the Missionontroller fields:
#FXML private MainController mainController;
#FXML private Label missionsLabel;
#FXML public ListView<String> listView;
#FXML private TextArea textArea;
Here is one of the problems:
return MissionController.listView.getSelectionModel().getSelectedItem();
You access the listView field as though it's static, and it's not - mind the upper case letter in the word MissionController, in this case you access a class, not the missionController field, which I guess you implied.
And the second one: you call non static method as though it's static:
textLog.appendText(MainController.getMissionFromMissionController());
Mind the upper case letter of the MainController in this line. It should be a small one, if you probably try to access the field.

Updating after data binding does not update content in UI

Sorry, but I must have a mental lapsus right now, because I don't see where the problem is, and should be trivial. I've prepared a simple scenario where I bind a field to a bean property using the BeanFieldGroup, and when I click the Change and Reset buttons, the model is set with the correct values, but the textfield in the UI is not being updated.
I'm using Vaadin4Spring, but should not be the issue.
import com.vaadin.data.fieldgroup.BeanFieldGroup;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.ui.Button;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import java.io.Serializable;
#SpringView(name = "test")
public class TestView extends VerticalLayout implements View {
private TextField txtTest = new TextField("Test");
private Button btnChange = new Button("Click!");
private Button btnReset = new Button("Reset");
private TestBean testBean = new TestBean();
public TestView() {
txtTest.setImmediate(true);
addComponent(txtTest);
addComponent(btnChange);
addComponent(btnReset);
BeanFieldGroup<TestBean> binder = new BeanFieldGroup<>(TestBean.class);
binder.setItemDataSource(testBean);
binder.setBuffered(false);
binder.bind(txtTest, "text");
initComponents();
}
private void initComponents() {
btnChange.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent event) {
testBean.setText("Hello world!");
}
});
btnReset.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(Button.ClickEvent event) {
testBean.setText("");
}
});
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
Notification.show("Test");
}
public class TestBean implements Serializable {
private String text;
public TestBean() {
text = "";
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
}
The closest thing I have found is binder.discard(), which forces all bound fields to re-read its value from the bean. Yes, it still has to be called manually, but is still far less painful than getItemDataSource().getItemProperty(...).setValue(...). If there are any concerns with this brute-force approach then of course one can call Field.discard() directly on the fields that should be affected.
You are calling a bean setter directly and because Java doesn't provide any way to listen that kind of changes, the Vaadin property (or a TextField) doesn't know that the value has been changed. If you change the value through a Vaadin property by saying
binder.getItemDataSource().getItemProperty("text").setValue("new value");
then you see "new value" on the TextField, and because buffering is disabled, testBean.getText() also returns "new value".

Resources