JavaFX-8 Make text caret visible in readonly textarea - javafx

JavaFX textfields do not show a text caret if you set them to readonly mode. Here is an example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
public class TextAreaReadOnly extends Application {
public TextAreaReadOnly() {
}
#Override
public void start(Stage primaryStage) throws Exception {
TextArea textarea = new TextArea();
textarea.setText("This is all\nreadonly text\nin here.");
textarea.setEditable(false);
Scene scene = new Scene(textarea, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
While it is still possible to select text with Shift+Cursor keys, no caret is displayed. Does anyone know a workaround for this?

Triggered by Neil's answer, I tried a quick test of my suggestion to extend TextAreaSkin and replace the caretVisible property by one that doesn't check for editability. Seems to work (not thoroughly tested, though) - but requires reflective access of super's private blink property. Obviously dirty and not possible in security restricted contexts ...
public static class MyTextAreaSkin extends TextAreaSkin {
public MyTextAreaSkin(TextArea textInput) {
super(textInput);
caretVisible = new BooleanBinding() {
{ bind(textInput.focusedProperty(), textInput.anchorProperty(),
textInput.caretPositionProperty(),
textInput.disabledProperty(), displayCaret , blinkProperty() );}
#Override protected boolean computeValue() {
return !blinkProperty().get() && displayCaret.get() && textInput.isFocused() &&
(isWindows() || (textInput.getCaretPosition() == textInput.getAnchor()))
&& !textInput.isDisabled();
}
};
// rebind opacity to replaced caretVisible property
caretPath.opacityProperty().bind(new DoubleBinding() {
{ bind(caretVisible); }
#Override protected double computeValue() {
return caretVisible.get() ? 1.0 : 0.0;
}
});
}
BooleanProperty blinkAlias;
BooleanProperty blinkProperty() {
if (blinkAlias == null) {
Class<?> clazz = TextInputControlSkin.class;
try {
Field field = clazz.getDeclaredField("blink");
field.setAccessible(true);
blinkAlias = (BooleanProperty) field.get(this);
} catch (NoSuchFieldException | SecurityException
| IllegalArgumentException | IllegalAccessException e) {
// TBD: errorhandling
e.printStackTrace();
}
}
return blinkAlias;
}
}
// usage in a custom TextArea
TextArea textarea = new TextArea() {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTextAreaSkin(this);
}
};

I want the same thing -- a read-only field, but caret visible for navigation. I tried:
.text-input:readonly { -fx-display-caret: true; }
But to no avail. Digging in to the FX source code (for 2.2), I found this:
caretVisible = new BooleanBinding() {
{ bind(textInput.focusedProperty(), textInput.anchorProperty(), textInput.caretPositionProperty(),
textInput.disabledProperty(), textInput.editableProperty(), displayCaret, blink);}
#Override protected boolean computeValue() {
// RT-10682: On Windows, we show the caret during selection, but on others we hide it
return !blink.get() && displayCaret.get() && textInput.isFocused() &&
(isWindows() || (textInput.getCaretPosition() == textInput.getAnchor())) &&
!textInput.isDisabled() &&
textInput.isEditable();
}
};
It looks like there is no way to override the requirement isEditable() at the end of that conditional. I may paint on a dummy caret as a work-around, which is ugly but I'm not sure there is another way -- looks like you can either fake the caret or fake the read-only aspect (rejecting all edits to the control).

Related

JAVAFX - Tableview with modifiable custum TimePicker cell close too quickly

I have a Tableview with modifiable custum TimePicker cell.
I wrote a function that listens the changes in that cell but when I click on a specific hour, the clock doesn't stay open and close at the first click and i have to click again to select the minutes for exemple.
How can i let the clock open and make the editCommitEvent() when the clock close?
Thank you for your help :)
Here is the code of my custum cell.
PS: I use jfoenix TimePicker
public class TimePickerTableCell<Patient> extends TableCell<Patient, LocalTime> {
private JFXTimePicker timePicker;
private boolean listening = true;
// listener for changes in the timePicker
#SuppressWarnings({ "unchecked", "rawtypes" })
private final ChangeListener<LocalTime> listener = (observable, oldValue, newValue) -> {
if (listening) {
listening = false;
TableColumn<Patient, LocalTime> column = getTableColumn();
EventHandler<TableColumn.CellEditEvent<Patient, LocalTime>> handler = column.getOnEditCommit();
if (handler != null) {
// use TableColumn.onEditCommit if there is a handler
handler.handle(new TableColumn.CellEditEvent<>(
(TableView<Patient>) getTableView(),
new TablePosition<Patient, LocalTime>(getTableView(), getIndex(), column),
TableColumn.<Patient, LocalTime>editCommitEvent(),
newValue
));
} else {
// otherwise check if ObservableValue from cellValueFactory is
// also writable and use in that case
ObservableValue<LocalTime> observableValue = column.getCellObservableValue((Patient) getTableRow().getItem());
if (observableValue instanceof WritableValue) {
((WritableValue) observableValue).setValue(newValue);
}
}
listening = true;
}
};
public TimePickerTableCell () {
this.timePicker = new JFXTimePicker();
this.timePicker.valueProperty().addListener(listener);
this.timePicker.setOnMouseEntered((event)->{timePicker.requestFocus();timePicker.show();System.err.println("OUVERTURE TIMEPICKER");});
this.timePicker.setOnMouseExited((event)->{if(event.getY()<23)timePicker.hide();});
}
#Override
protected void updateItem(LocalTime item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
listening = false;
setGraphic(null);
} else {
listening = false;
setGraphic(this.timePicker);
this.timePicker.setValue(item);
this.timePicker.getStyleClass().add("time-picker");
listening = true;
}
}
public static <E> Callback<TableColumn<E, LocalTime>, TableCell<E, LocalTime>> forTableColumn() {
return column -> new TimePickerTableCell<>();
}
}
First, the reason the JFXTimePicker hides when you go to click the clock is (most likely) because of your onMouseExited handler. When you move your mouse over the popup it "exits" the JFXTimePicker and thus hides the clock.
You're also implementing an editable TableCell the wrong way. You should be overriding the startEdit() and cancelEdit() methods of the Cell class (which TableCell inherits from). You can look at the source code of classes like TextFieldTableCell for how it's done. I also worked up an example for doing this with JFXTimePicker:
import com.jfoenix.controls.JFXTimePicker;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.input.KeyCode;
import javafx.util.Callback;
import javafx.util.converter.LocalTimeStringConverter;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class TimePickerTableCell<S> extends TableCell<S, LocalTime> {
// Static methods for creating TableColumn.cellFactory Callbacks
public static <S> Callback<TableColumn<S, LocalTime>, TableCell<S, LocalTime>> forTableColumn() {
return v -> new TimePickerTableCell<>();
}
public static <S> Callback<TableColumn<S, LocalTime>, TableCell<S, LocalTime>> forTableColumn(DateTimeFormatter formatter) {
return v -> new TimePickerTableCell<>(formatter);
}
// Formatter property
private final ObjectProperty<DateTimeFormatter> formatter = new SimpleObjectProperty<>(this, "formatter");
public final void setFormatter(DateTimeFormatter formatter) { this.formatter.set(formatter); }
public final DateTimeFormatter getFormatter() { return formatter.get(); }
public final ObjectProperty<DateTimeFormatter> formatterProperty() { return formatter; }
// JFXTimePicker field
private JFXTimePicker timePicker;
// Constructors
public TimePickerTableCell() {
this(DateTimeFormatter.ISO_LOCAL_TIME);
}
public TimePickerTableCell(DateTimeFormatter formatter) {
getStyleClass().add("time-picker-table-cell");
setFormatter(formatter);
}
// Display logic
#Override
protected void updateItem(LocalTime item, boolean empty) {
super.updateItem(item, empty);
setGraphic(null);
if (empty || item == null) {
setText(null);
} else {
setText(formatItem(item));
}
}
private String formatItem(LocalTime item) {
if (item == null) {
return null;
}
return getFormatter() == null ? item.toString() : getFormatter().format(item);
}
// Edit logic
#Override
public void startEdit() {
if (!isEditable() ||
!getTableColumn().isEditable() ||
!getTableView().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
if (timePicker == null) {
createTimePicker();
}
timePicker.setValue(getItem());
setText(null);
setGraphic(timePicker);
// Wrapped this in a Platform#runLater call because otherwise
// I couldn't get this to work properly. Despite this, there are
// times where this still seems buggy.
Platform.runLater(() -> {
timePicker.requestFocus();
timePicker.getEditor().selectAll();
});
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(formatItem(getItem()));
setGraphic(null);
}
private void createTimePicker() {
timePicker = new JFXTimePicker();
timePicker.setConverter(new LocalTimeStringConverter(getFormatter(), null));
formatter.addListener((observable, oldValue, newValue) ->
timePicker.setConverter(new LocalTimeStringConverter(newValue, null)));
timePicker.getEditor().setOnKeyReleased(event -> {
if (event.getCode() == KeyCode.ENTER) {
commitEdit(timePicker.getValue());
event.consume();
} else if (event.getCode() == KeyCode.ESCAPE) {
cancelEdit();
event.consume();
}
});
timePicker.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
cancelEdit();
}
});
}
}
Here, if the ESCAPE key is released or if the JFXTimePicker loses focus then the edit is cancelled. It appears that interacting with the clock does not cause the JFXTimePicker to lose focus (at least when I tried it).
If you want to commit the edit you have to press the ENTER key. This works (again, at least when I tried it) even if the clock is currently showing.
This doesn't commit the edit automatically when the clock closes but you should be able to add that behavior if desired. Since JFXTimePicker extends from ComboBoxBase it has properties such as onHiding and onHidden.
Note: If, after typing the time manually, you attempt to commit and the DateTimeFormatter is unable to parse the String it simply reverts to the old value. There is no indication of any error other than the fact the value hasn't changed. This seems to be behavior caused by the JFXTimePicker, however.
You also don't need to try and handle committing the value yourself, either. The TableColumn already attempts to set the new value on the underlying property by default. This is mentioned in the Javadoc of TableView (under the "Editing" header, emphasis mine):
When you call Cell.commitEdit(Object) an event is fired to the
TableView, which you can observe by adding an EventHandler via
TableColumn.setOnEditCommit(javafx.event.EventHandler). Similarly, you
can also observe edit events for edit start and edit cancel.
By default the TableColumn edit commit handler is non-null, with a
default handler that attempts to overwrite the property value for the
item in the currently-being-edited row. It is able to do this as the
Cell.commitEdit(Object) method is passed in the new value, and this is
passed along to the edit commit handler via the CellEditEvent that is
fired. It is simply a matter of calling
TableColumn.CellEditEvent.getNewValue() to retrieve this value.
If you do end up using your own EventHandler in setOnEditCommit then you need to implement the behavior yourself:
It is very important to note that if you call
TableColumn.setOnEditCommit(javafx.event.EventHandler) with your own
EventHandler, then you will be removing the default handler. Unless
you then handle the writeback to the property (or the relevant data
source), nothing will happen. You can work around this by using the
TableColumnBase.addEventHandler(javafx.event.EventType,
javafx.event.EventHandler) method to add a
TableColumn.editCommitEvent() EventType with your desired EventHandler
as the second argument. Using this method, you will not replace the
default implementation, but you will be notified when an edit commit
has occurred.

EventFilter for ComboBox selected Item

How can I write an EventFilter for the SelectedItem property of a ComboBox? This Article only describes it for user Events like a MouseEvent, and I cant seem to find out what EventType the selectedItem property changing is.
I ask because I have a 3D Application in a Dialog that displays materials on a slot. That slot can be switched with my Combobox, but I want to be able to filter BEFORE the actual change in the selection happens, see if I have any unsaved changes and show a dialog wheter the user wants to save the changes or abort. And since I have a variety of listeners on the combobox that switch out the materials in the 3D when the selection in the ComboBox changes, the abort functionality on that dialog is not easily achieved.
I am also open to other approaches of a "Do you want to save Changes?" implementation which may be better suited.
Consider creating another property to represent the value in the combo box, and only updating it if the user confirms. Then the rest of your application can just observe that property.
So, e.g.
private ComboBox<MyData> combo = ... ;
private boolean needsConfirmation = true ;
private final ReadOnlyObjectWrapper<MyData> selectedValue = new ReadOnlyObjectWrapper<>();
public ReadOnlyObjectProperty<MyData> selectedValueProperty() {
return selectedValue.getReadOnlyProperty() ;
}
public final MyData getSelectedValue() {
return selectedValueProperty().get();
}
// ...
combo.valueProperty().addListener((obs, oldValue, newValue) -> {
if (needsConfirmation) {
// save changes dialog:
Dialog<ButtonType> dialog = ... ;
Optional<ButtonType> response = dialog.showAndWait();
if (response.isPresent()) {
if (response.get() == ButtonType.YES) {
// save changes, then:
selectedValue.set(newValue);
} else if (response.get() == ButtonType.NO) {
// make change without saving:
selectedValue.set(newValue);
} else if (response.get() == ButtonType.CANCEL) {
// revert to old value, make sure we don't display dialog again:
// Platform.runLater() is annoying workaround required to avoid
// changing contents of list (combo's selected items) while list is processing change:
Platform.runLater(() -> {
needsConfirmation = false ;
combo.setValue(oldValue);
needsConfirmation = true ;
});
}
} else {
needsConfirmation = false ;
combo.setValue(oldValue);
needsConfirmation = true ;
}
}
});
Now your application can just observe the selectedValueProperty() and respond if it changes:
selectionController.selectedValueProperty().addListener((obs, oldValue, newValue) -> {
// respond to change...
});
Here's a (very simple) SSCCE:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class InterceptComboBox extends Application {
private ComboBox<String> combo ;
private boolean needsConfirmation = true ;
private Label view ;
private final ReadOnlyObjectWrapper<String> selectedValue = new ReadOnlyObjectWrapper<String>();
public ReadOnlyObjectProperty<String> selectedValueProperty() {
return selectedValue.getReadOnlyProperty();
}
public final String getSelectedValue() {
return selectedValueProperty().get();
}
#Override
public void start(Stage primaryStage) {
combo = new ComboBox<>();
combo.getItems().addAll("One", "Two", "Three");
combo.setValue("One");
selectedValue.set("One");
view = new Label();
view.textProperty().bind(Bindings.concat("This is view ", selectedValue));
combo.valueProperty().addListener((obs, oldValue, newValue) -> {
if (needsConfirmation) {
SaveChangesResult saveChanges = showSaveChangesDialog();
if (saveChanges.save) {
saveChanges();
}
if (saveChanges.proceed) {
selectedValue.set(newValue);
} else {
Platform.runLater(() -> {
needsConfirmation = false ;
combo.setValue(oldValue);
needsConfirmation = true ;
});
}
}
});
BorderPane root = new BorderPane(view);
BorderPane.setAlignment(combo, Pos.CENTER);
BorderPane.setMargin(combo, new Insets(5));
root.setTop(combo);
primaryStage.setScene(new Scene(root, 400, 400));
primaryStage.show();
}
private void saveChanges() {
System.out.println("Save changes");
}
private SaveChangesResult showSaveChangesDialog() {
DialogPane dialogPane = new DialogPane();
dialogPane.setContentText("Save changes?");
dialogPane.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
Dialog<SaveChangesResult> dialog = new Dialog<>();
dialog.setDialogPane(dialogPane);
dialog.setResultConverter(button -> {
if (button == ButtonType.YES) return SaveChangesResult.SAVE_CHANGES ;
else if (button == ButtonType.NO) return SaveChangesResult.PROCEED_WITHOUT_SAVING ;
else return SaveChangesResult.CANCEL ;
});
return dialog.showAndWait().orElse(SaveChangesResult.CANCEL);
}
enum SaveChangesResult {
SAVE_CHANGES(true, true), PROCEED_WITHOUT_SAVING(true, false), CANCEL(false, false) ;
private boolean proceed ;
private boolean save ;
SaveChangesResult(boolean proceed, boolean save) {
this.proceed = proceed ;
this.save = save ;
}
}
public static void main(String[] args) {
launch(args);
}
}
To do this you want to add a ChangeListener to the valueProperty() of the ComboBox
Here is an example:
comboBox.valueProperty().addListener(new ChangeListener<Object>()
{
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
Optional<ButtonType> result = saveAlert.showAndWait();
if(result.isPresent())
{
if(result.get() == ButtonType.YES)
{
//Your Save Functionality
comboBox.valueProperty().setValue(newValue);
}
else
{
//Whatever
comboBox.valueProperty().setValue(oldValue);
}
}
}
});

Handle multiple JavaFX application launches within a loop

My code currently reads my Gmail inbox via IMAP (imaps) and javamail, and once it finds an email with zip/xap attachment, it displays a stage (window) asking whether to download the file, yes or no.
I want the stage to close once I make a selection, and then return to the place within the loop from which the call came. My problem arises because you cannot launch an application more than once, so I read here that I should write Platform.setImplicitExit(false); in the start method, and then use primartyStage.hide() (?) and then something like Platform.runLater(() -> primaryStage.show()); when I need to display the stage again later.
The problem occuring now is that the flow of command begins in Mail.java's doit() method which loops through my inbox, and launch(args) occurs within a for loop within the method. This means launch(args) then calls start to set the scene, and show the stage. Since there is a Controller.java and fxml associated, the Controller class has an event handler for the stage's buttons which "intercept" the flow once start has shown the stage. Therefore when I click Yes or No it hides the stage but then just hangs there. As if it can't return to the start method to continue the loop from where launch(args) occurred. How do I properly hide/show the stage whenever necessary, allowing the loop to continue whether yes or no was clicked.
Here is the code for Mail.java and Controller.java. Thanks a lot!
Mail.java
[Other variables set here]
public static int launchCount = 0;#FXML public Text subjectHolder;
public static ReceiveMailImap obj = new ReceiveMailImap();
public static void main(String[] args) throws IOException, MessagingException {
ReceiveMailImap.doit();
}
#Override
public void start(Stage primaryStage) throws Exception {
loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
root = loader.load();
controller = loader.getController();
controller.setPrimaryStage(primaryStage);
scene = new Scene(root, 450, 250);
controller.setPrimaryScene(scene);
scene.getStylesheets().add("styleMain.css");
Platform.setImplicitExit(false);
primaryStage.setTitle("Download this file?");
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void doit() throws MessagingException, IOException {
Folder inbox = null;
Store store = null;
try {
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props, null);
store = session.getStore("imaps");
store.connect("imap.gmail.com", "myAccount#gmail.com", "Password");
inbox = store.getFolder("Inbox");
inbox.open(Folder.READ_WRITE);
Message[] messages = inbox.getMessages();
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(UIDFolder.FetchProfileItem.FLAGS);
fp.add(UIDFolder.FetchProfileItem.CONTENT_INFO);
fp.add("X-mailer");
inbox.fetch(messages, fp);
int doc = 0;
int maxDocs = 400;
for (int i = messages.length - 1; i >= 0; i--) {
Message message = messages[i];
if (doc < maxDocs) {
doc++;
message.getSubject();
if (!hasAttachments(message)) {
continue;
}
String from = "Sender Unknown";
if (message.getReplyTo().length >= 1) {
from = message.getReplyTo()[0].toString();
} else if (message.getFrom().length >= 1) {
from = message.getFrom()[0].toString();
}
subject = message.getSubject();
if (from.contains("myAccount#gmail.com")) {
saveAttachment(message.getContent());
message.setFlag(Flags.Flag.SEEN, true);
}
}
}
} finally {
if (inbox != null) {
inbox.close(true);
}
if (store != null) {
store.close();
}
}
}
public static boolean hasAttachments(Message msg) throws MessagingException, IOException {
if (msg.isMimeType("multipart/mixed")) {
Multipart mp = (Multipart) msg.getContent();
if (mp.getCount() > 1) return true;
}
return false;
}
public static void saveAttachment(Object content)
throws IOException, MessagingException {
out = null; in = null;
try {
if (content instanceof Multipart) {
Multipart multi = ((Multipart) content);
parts = multi.getCount();
for (int j = 0; j < parts; ++j) {
part = (MimeBodyPart) multi.getBodyPart(j);
if (part.getContent() instanceof Multipart) {
// part-within-a-part, do some recursion...
saveAttachment(part.getContent());
} else {
int allow = 0;
if (part.isMimeType("application/x-silverlight-app")) {
extension = "xap";
allow = 1;
} else {
extension = "zip";
allow = 1;
}
if (allow == 1) {
if (launchCount == 0) {
launch(args);
launchCount++;
} else {
Platform.runLater(() -> primaryStage.show());
}
} else {
continue;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if ( in != null) { in .close();
}
if (out != null) {
out.flush();
out.close();
}
}
}
public static File createFolder(String subject) {
JFileChooser fr = new JFileChooser();
FileSystemView myDocs = fr.getFileSystemView();
String myDocuments = myDocs.getDefaultDirectory().toString();
dir = new File(myDocuments + "\\" + subject);
savePathNoExtension = dir.toString();
dir.mkdir();
System.out.println("Just created: " + dir);
return dir;
}
}
Controller.java
public class Controller implements Initializable {
#FXML
private Text subjectHolder;
public Button yesButton, noButton;
public ReceiveMailImap subject;
#Override
public void initialize(URL url, ResourceBundle rb) {
subject= new ReceiveMailImap();
subjectHolder.setText(subject.returnSubject());
}
public Stage primaryStage;
public Scene scene;
#FXML
ComboBox<String> fieldCombo;
public void setPrimaryStage(Stage stage) {
this.primaryStage = stage;
}
public void setPrimaryScene(Scene scene) {
this.scene = scene;
}
public String buttonPressed(ActionEvent e) throws IOException, MessagingException {
Object source = e.getSource();
if(source==yesButton){
System.out.println("How to tell Mail.java that user clicked Yes?");
return "POSITIVE";}
else{subject.dlOrNot("no");
System.out.println("How to tell Mail.java that user clicked No?");
primaryStage.hide();
return "NEGATIVE";}
}
}
There are a lot of issues with the code you have posted, but let me just try to address the ones you ask about.
The reason the code hangs is that Application.launch(...)
does not return until the application has exited
In general, you've kind of misunderstood the entire lifecycle of a JavaFX application here. You should think of the start(...) method as the equivalent of the main(...) method in a "traditional" Java application. The only thing to be aware of is that start(...) is executed on the FX Application Thread, so if you need to execute any blocking code, you need to put it in a background thread.
The start(...) method is passed a Stage instance for convenience, as the most common thing to do is to create a scene graph and display it in a stage. You are under no obligation to use this stage though, you can ignore it and just create your own stages as and when you need.
I think you can basically structure your code as follows (though, to be honest, I have quite a lot of trouble understanding what you're doing):
public class Mail extends Application {
#Override
public void start(Stage ignored) throws Exception {
Platform.setImplicitExit(false);
Message[] messages = /* retrieve messages */ ;
for (Message message : messages) {
if ( /* need to display window */) {
showMessage(message);
}
}
}
private void showMessage(Message message) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
Scene scene = new Scene(root, 450, 250);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.setTitle(...);
// showAndWait will block execution until the window is hidden, so
// you can query which button was pressed afterwards:
stage.showAndWait();
if (controller.wasYesPressed()) {
// ...
}
}
// for IDEs that don't support directly launching a JavaFX Application:
public static void main(String[] args) {
launch(args);
}
}
Obviously your logic for decided whether to show a window is more complex, but this will give you the basic structure.
To check which button was pressed, use showAndWait as above and then in your controller do
public class Controller {
#FXML
private Button yesButton ;
private boolean yesButtonPressed = false ;
public boolean wasYesPressed() {
return yesButtonPressed ;
}
// use different handlers for different buttons:
#FXML
private void yesButtonPressed() {
yesButtonPressed = true ;
closeWindow();
}
#FXML
private void noButtonPressed() {
yesButtonPressed = false ; // not really needed, but makes things clearer
closeWindow();
}
private void closeWindow() {
// can use any #FXML-injected node here:
yesButton.getScene().getWindow().hide();
}
}

vaadin 7 layout click doesn't work

I've created MainGameTab which extends TabSheet.
In constructor I create layouts and add them as tabs. I wanted to add right click event to the layout
mainLayout.addLayoutClickListener(new LayoutClickListener() {
private static final long serialVersionUID = 1871942396979048283L;
#Override
public void layoutClick(LayoutClickEvent event) {
if (event.getButton() == MouseButton.RIGHT) {
TextQuestUi.getCurrent().addWindow(new CharacterSheet(c));
}
}
});
this.addTab(mainLayout, "Game");
CharacterSheet is a class, that extends Window
public class CharacterSheet extends Window {
But when I click on tab - I've got basic right click items for browser instead of new window.
What's the problem?
My MainGameTab looks like this
public MainGameTab() {
final Player c = new Player();
c.setName("Hero");
c.setLevel(100);
Skill skill = new Skill();
skill.setName("Help from heaven");
skill.setEffect("Full recover health");
c.addSkill(skill);
Stat stat = new Stat();
stat.setName("Attack");
stat.setValue(50);
c.addStat(stat);
HorizontalLayout mainLayout = new HorizontalLayout();
mainLayout.addLayoutClickListener(new LayoutClickListener() {
private static final long serialVersionUID = 1871942396979048283L;
#Override
public void layoutClick(LayoutClickEvent event) {
if (event.getButton() == MouseButton.RIGHT) {
TextQuestUi.getCurrent().addWindow(new CharacterSheet(c));
}
}
});
this.addTab(mainLayout, "Game");
HorizontalLayout logLayout = new HorizontalLayout();
this.addTab(logLayout, "Log");
}
And I add it in UI
#Override
protected void init(VaadinRequest request) {
this.setContent(new MainGameTab());
}
I'll suggest you to use one of the existing Vaadin addons. See here
Or, I am assuming that you're probably looking for getButton() in ItemClickEvent - something like this:
t.addListener(new ItemClickListener() {
public void itemClick(ItemClickEvent event) {
if (event.getButton()==ItemClickEvent.BUTTON_RIGHT) {
// Right mouse button clicked, do greatThings!
}
}
});

AspectJ capture button clicked

I want to know whether how to capture the button clicked with AspectJ and get its parameter (eg. button name). I think for having more generalized capturing with AspectJ, it shoudl be used MouseListener so it can capture other UI elements in general!
Example:
In a GUI example I have defined 2 buttons that take some actions
public JButton btn1 = new JButton("Test1");
public JButton btn2 = new JButton("Test2");
btn1.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
//take some actions
}
}
btn2.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
//take some actions
}
}
How to capture these buttons with AspectJ, and get their parameters (eg. name)?
It is possible. I have provided two examples. The first that prints out for every JButton that has an ActionListener. The other example only prints out if a specific buttons is clicked.
Prints the text for every JButton clicked with an ActionListener:
#Pointcut("execution(* *.actionPerformed(*)) && args(actionEvent)")
public void buttonPointcut(ActionEvent actionEvent) {}
#Before("buttonPointcut(actionEvent)")
public void beforeButtonPointcut(ActionEvent actionEvent) {
if (actionEvent.getSource() instanceof JButton) {
JButton clickedButton = (JButton) actionEvent.getSource();
System.out.println("Button name: " + clickedButton.getText());
}
}
Prints the text for a specific JButton:
public static JButton j1;
#Pointcut("execution(* *.actionPerformed(*)) && args(actionEvent) && if()")
public static boolean button1Pointcut(ActionEvent actionEvent) {
return (actionEvent.getSource() == j1);
}
#Before("button1Pointcut(actionEvent)")
public void beforeButton1Pointcut(ActionEvent actionEvent) {
// logic before the actionPerformed() method is executed for the j1 button..
}
UPDATED:
You can do this in many different ways. For example add your buttons to the aspect directly. But I prefere to use a enum object between (ButtonManager in this case), so the code does not know about the aspect. And since the ButtonManager is an enum object, it is easy for the aspect to retrieve values from it.
I just tested it with a Swing button class from Oracle and it works. In the Swing class:
b1 = new JButton("Disable middle button", leftButtonIcon);
ButtonManager.addJButton(b1);
AspectJ is extremely powerful when it comes to manipulating classes, but it can not weave advises into specific objects since objects is not created at the time of weaving. So you can only work with objects at runtime and that is why I have added the addJButton(..) method above. That enables the aspect to check the advised button against a list of registered buttons.
The ButtonManager class:
public enum ButtonManager {
;
private static Collection<JButton> buttonList = new LinkedList<JButton>();
public static void addJButton(JButton jButton) {
buttonList.add(jButton);
}
public static Collection<JButton> getButtonList() {
return buttonList;
}
}
Modified pointcut and advice to only print the name of the buttons registered in the ButtonManager:
#Pointcut("execution(* *.actionPerformed(*)) && args(actionEvent) && if()")
public static boolean buttonListPointcut(ActionEvent actionEvent) {
Collection<JButton> buttonList = ButtonManager.getButtonList();
JButton registeredButton = null;
for (JButton jButton : buttonList) {
if (actionEvent.getSource() == jButton) {
registeredButton = jButton;
}
}
return registeredButton != null;
}
#Before("buttonListPointcut(actionEvent)")
public void beforeButtonListPointcut(ActionEvent actionEvent) {
JButton clickedButton = (JButton) actionEvent.getSource();
System.out.println("Registered button name: " + clickedButton.getText());
}
UPDATED 2
Okay, I believe I understand what you want. You want to listen to mouse events. That is possible. The downside is that you have to register all your GUI components that you want to listen for clicks with a mouse listener. It is not enough to register the JPanel of the JFrame with a MouseListener. So if you only have registered an ActionListener for your buttons, you also have to add a mouse listener.
I have created a quick solution that works for me. It only shows that it works. I have not tried to make the solution generic with many different GUI objects. But that should be quite easy to refactor in when you have got the basics to work.
In the Swing class:
private class MouseListener extends MouseInputAdapter {
public void mouseClicked(MouseEvent e) {}
}
In the init method of the Swing class:
MouseListener myListener = new MouseListener();
btn1.addMouseListener(myListener);
btn2.addMouseListener(myListener);
In the Aspect class:
#Pointcut("execution(* *.mouseClicked(*)) && args(mouseEvent)")
public void mouseEventPointcut(MouseEvent mouseEvent) {}
#Before("mouseEventPointcut(mouseEvent)")
public void beforeMouseEventPointcut(MouseEvent mouseEvent) {
if (mouseEvent.getSource() instanceof JButton) {
JButton clickedButton = (JButton) mouseEvent.getSource();
System.out.println("aspectJ --> mouseClicked: " + clickedButton.getText());
}
}
This results in the following output in the console:
aspectJ --> mouseClicked: Test1
I hope it helps!

Resources