I have a program with a ComboBox which changes a view. If there are unsaved edits and the user changes the ComboBox, a dialog warns them if they're happy to proceed, and if yes it changes the view... BUT if they say no, I want the ComboBox to revert back to the previous value.
I'm trying to only post code relevant to the problem to be succinct... the warning dialog code happens elsewhere (in my viewController class) and the current group is stored in a class my whole program can see (globalFields).
Here is the code, and as such the problem:
#FXML
private void handleClassesComboBox(ActionEvent event) {
if (classesComboBox.getSelectionModel().getSelectedItem() != null) {
if (viewController.ifUnsavedChangesUserHappyToLose()) {
globalFields.setCurrentGroup(classesComboBox.getSelectionModel().getSelectedItem());
setView();
} else{
classesComboBox.setValue(globalFields.getCurrentGroup());
}
}
So if they are NOT happy to lose changes, I want to revert to the previous selection, but of course this causes a loop as this handleClassesComboBox method is triggered again due to the change.
I'm sure it's obvious, but I can't work out the logic to revert back without the dialog looping over and over.
Also it's my first question here so if I've missed anything or explained the obvious let me know!!
Thanks!
Vin
You could just set a flag:
private boolean checkUserChange = true ;
and then
#FXML
private void handleClassesComboBox(ActionEvent event) {
if (classesComboBox.getSelectionModel().getSelectedItem() != null) {
if (checkUserChange && viewController.ifUnsavedChangesUserHappyToLose()) {
globalFields.setCurrentGroup(classesComboBox.getSelectionModel().getSelectedItem());
setView();
} else{
checkUserChange = false ;
classesComboBox.setValue(globalFields.getCurrentGroup());
checkUserChange = true ;
}
}
}
Related
Hello fellow coders of the night,
I am stuck with a moral dilemma (well not moral, but mostly i don't know what to do).
Suppose I have one button that can do several actions, depending on the menu item which is chosen.
Basically, I've imagined this
private void menuButtonActionPerformed(ActionEvent b)
ActionEvent a
if(a.getSource()==menuItem)
if(b.getSource()==button)
do this and that
Is this the correct way to do this? because if it is I'd have to add ActionListeners on the menuItem but I get stuck with some stupid error code somewhere!
Thanks in advance for helping me!
Post Scriptum : #David, I've tried this, however the initial condition isn't verified.
private void buttonValidateActionPerformed(java.awt.event.ActionEvent evt)
ActionListener l = (ActionEvent e) -> {
if(e.getSource()==menuItemAdd)
{
System.out.println("eureka!");
buttonSearch.setEnabled(false);
if (evt.getSource()==buttonValidate)
{
DataTransac dt = new DataTransac();
dt.addCoders("...");
}
}
if(e.getSource()==itemDelete)
{
DataTransac dt = new DataTransac();
dt.deleteCoders("...");
}
};
menuItemAdd.addActionListener(l);
itemDelete.addActionListener(l);
That won't work; your listener will get a different invocation for each time the listener is used -- so the event source will be either a button or a menu item for a single invocation.
You'll need to respond to the menu item with one ActionListener that stores state, and then separately handle the button action. You could do this with one listener, but I wouldn't; I'd do this:
private MenuItem selected;
private class MenuItemListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
// if you really want to have one listener for multiple menu items,
// continue with the .getSource() strategy above, but store some
// state outside the listener
selected = (MenuItem)event.getSource();
// you could alternatively have a different listener for each item
// that manipulates some state
}
}
private class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
// take conditional action based on selected menu item, as you describe
// in the question
}
}
void setup() {
JMenuItem first = /* ... */;
JMenuItem second = /* ... */;
MenuItemListener listener = new MenuItemListener();
first.addActionListener(listener);
second.addActionListener(listener);
JButton button = /* ... */;
button.addActionListener(buttonListener);
}
Generally speaking this is the preferred approach -- use a different listener for each semantic action, rather than one that introspects the source. Your code will be cleaner, simpler, and easier to understand.
For the same reasons, some people prefer to use anonymous classes for Java event listeners. Here's a Gist that shows several syntaxes: https://gist.github.com/sfcgeorge/83027af0338c7c34adf8. I personally prefer, if you are on Java 8 or higher:
button.addActionListener( event -> {
// handle the button event
} );
I'm writing a simple calculator app with JavaFX. Every keyboard event is successfully captured except the ENTER key. The EQUALS ("=") is the only hardware key that will trigger the calculation function. I have scoured every question on this site that even remotely deals with with keyboard event handling, I have pored over the documentation on Oracle's site to no avail. below is are some excerpts from my code (I left out most of the lines that check every possible KeyCode for brevity):
#FXML
private void handleKeyboardEvent(KeyEvent event) {
KeyCode keyCode = event.getCode();
if (event.getCharacter().equals("\r") || event.getCharacter().equals("\n")) {
onEqualsButtonPressed();
}
switch (keyCode) {
case ESCAPE:
textFieldDisplayEquation.clear();
textFieldDisplayResult.clear();
break;
case EQUALS:
if (event.isShiftDown()) {
if (textFieldDisplayEquation.getText().length() == 0) {
textFieldDisplayEquation.clear();
break;
} else {
textFieldDisplayEquation.appendText(" + ");
break;
}
} else {
onEqualsButtonPressed();
break;
}
case ENTER:
onEqualsButtonPressed();
break;
}
}
when the ENTER key is pressed, the text previously entered into the TextField simply vanishes.
As I said, every other keyboard onKeyPress event is successfully captured and the calculator works perfectly (including clicking GUI buttons)! This is driving me crazy. Any advice would be appreciated.
Ok. It seems that whenever the two textfields are cleared, you have to tell the GridPane to request focus. Having done that, the ENTER key works every time
Below are the relevant code snippets:
#FXML
private GridPane root;
#FXML
private void onButtonClearClicked() {
labelMessage.setText("");
textFieldDisplayEquation.clear();
textFieldDisplayResult.clear();
root.requestFocus();
}
Thank you to everyone who took the time to answer or comment!
I am writing a JavaFX app where a series of messages appear in a TableView. When a new message appears, its row in the table should be highlighted, meaning its background color should be orange or something. Once the user clicks it, the background color should clear, acknowledging the message was read. Should be simple.
I've done enough research to realize that I need to use a rowFactory to set or clear a row's background. But I'm struggling with the mechanics of setRowFactory(). The documentation on Oracle is over my head, and every example I pull up online seems radically different than the last one.
Here's what I have:
public class Message {
private boolean readOnce;
private int date;
private String msg;
public Message(int date, String msg, String msg2){
this.readOnce = false;
this.date = date;
this.msg = msg;
}
public boolean isReadOnce() {
return readOnce;
}
public void setReadOnce(){
readOnce = true;
}
// ...and more standard getters & setters here...
}
The TableView is set up in the main controller:
#FXML TableView<Message> messageTable;
#FXML TableColumn<Message, Integer> Col1;
#FXML TableColumn<Message, String> Col2;
ObservableList<Message> tableItems;
// ...
// Setting up the Table:
PropertyValueFactory<Message, Integer> dateProperty = new PropertyValueFactory<Message, Integer>("date");
PropertyValueFactory<Message, String> msgProperty = new PropertyValueFactory<Message, String>("msg");
Col1.setCellValueFactory( dateProperty );
Col2.setCellValueFactory( msgProperty );
messageTable.setItems( tableItems );
// If we click an item in the table: messageTable.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
System.out.println("Troubleshoot: You clicked: "+newSelection.getMsg());
newSelection.setReadOnce(true);
}
});
And if I want to add a new message to the table, I just add it into the observable list:
public void addMsg(int num, String msg){
tableItems.add(new Message(num, msg));
}
So far, pretty easy. But I'm all thumbs when it comes to the rowFactory:
messageTable.setRowFactory(messageTable -> {
TableRow<Message> row = new TableRow<>();
ObjectProperty<Message> opMsg = row.itemProperty();
Message tmpMsg = opMsg.get();
if(!tmpMsg.isReadOnce()){
row.getStyleClass().add("highlight-message"); // defined in CSS
} else {
row.getStyleClass().add("clear-message"); // defined in CSS
}
return row;
});
To be very honest, I have no idea what I'm doing here. I understand that the rowFactory takes in the entire table and regenerates each row one-by-one. What I don't understand is how does the RowFactory code examine each Message in the table and how can I access them? Originally I thought these line might allow me to see the Message within the row:
TableRow<Message> row = new TableRow<>();
ObjectProperty<Message> opMsg = row.itemProperty();
Message tmpMsg = opMsg.get();
But when I debug the code, tmpMsg == NULL. So that's a big fat dead end.
Anyone see what I'm doing wrong? I've been researching this for about a week, getting absolutely no-where. Any help anyone can offer is wildly appreciated.
Many thanks,
-RAO
TableRows are created by TableView to fill it's viewport and contain TableCells. At the time they are created the item property still contains the default value null. You could register a listener to that property but usually I prefer overriding the updateItem method of a cell.
Also using PseudoClass is simpler than using style classes. New items can be assigned to a row; this could result in the same style class being added multiple times and even both style classes could be added to the same cell. PseudoClasses however can be switched on/of without the need to take care of removing other classes.
final PseudoClass highlightMessage = PseudoClass.getPseudoClass("highlight-message");
messageTable.setRowFactory(messageTable -> new TableRow<Message>() {
{
selectedProperty().addListener((o, oldVal, newVal) -> {
if (newVal) {
Message item = getItem();
if (item != null) {
item.setReadOnce();
pseudoClassStateChanged(highlightMessage, false);
}
}
});
}
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
pseudoClassStateChanged(highlightMessage, item != null && !item.isReadOnce());
}
});
In a CSS stylesheet you could use rules like this:
.table-row-cell:filled {
/* style for non-highlighted rows */
}
.table-row-cell:filled:highlight-message {
/* style for highlighted rows */
}
Note that this does not allow you to programmatically alter the read state. It updates the state on selecting a cell. You could add a BooleanProperty to Message or use a ObservableSet to store the highlighted messages and update the state of cells from a listener if you need to programmatically update the readOnce property. In the latter case you do not need to store a readOnce property in the Message itself...
Background:
According to the accepted answer of this question, a TextArea has an underlying Text node that contains the text and acts as its bounding box. It is useful for calculating the visual (pre-transform) height of the text, even when the text wraps around.
The Text node is not exposed in the public JavaFX 8 API, but it can be gotten via .lookup(). Moreover, it seems it is not initialized till after the Scene is rendered. The above said answer shows how to get it successfully by listening to the Scene.
Problem:
I have taken the logic of the listener, which works, and implemented it as a binding instead. But the binding does not work, and I cannot understand why. The value of the property updated by the listener is successfully set to the new Text node, while the value of the binding is always null because it is not detecting the change of Scene from null to a Scene object.
public ObjectExpression<Text> getObservableTextNode(TextArea textArea) {
// The underlying text node can be looked up only after applying CSS when there is a scene.
// For that reason we return it as an observable.
// Way #1: Listener
ObjectProperty<Text> property = new SimpleObjectProperty<>();
textArea.sceneProperty().addListener((observableNewScene, oldScene, newScene) -> {
if (newScene != null) {
textArea.applyCss();
Node text = textArea.lookup(".text");
property.set((Text) text);
} else {
property.set(null);
}
});
return property;
// Way #2: Binding - does not update
ObjectBinding<Text> ob = Bindings.createObjectBinding(() -> {
if (textArea.sceneProperty().get() != null) {
textArea.applyCss();
return (Text) textArea.lookup(".text");
}
return null;
}, textArea.sceneProperty());
return ob;
}
As far as I can tell, the logics of the listener and binding are the same. Why then is there a difference?
I have a requirement in my program that the object bound (from ViewModel) in a Combobox is updated as soon as an item is selected in the combobox. Currently, the object only updates once the edit is committed by either pressing Enter or leaving the cell. The user does not want the extra step.
My thought would be to have the act of selecting an item in the combobox trigger the CommitEdit() method and then CancelEdit(). However, I cannot seem to find a way to hook into the SelectionChanged event for the DataGridComboBoxColumn as it is not available.
Other suggestions have been to listen in the viewmodel for a property change event but the property is not changed until the Cell Edit is finished.
Can anyone think of a way to cause the selection of a new item (index) in a DataGridCombobox to close the edit of the cell as if the user pressed Enter or left the cell?
NOTE: I cannot use .NET 4.5 due to customer limitations.
I've had similar issue but i just found out the solution using attached property, This may not exactly fix your problem but it will help in datagrid selection changed issue.
Below is the attached property and handler methods
public static readonly DependencyProperty ComboBoxSelectionChangedProperty = DependencyProperty.RegisterAttached("ComboBoxSelectionChangedCommand",
typeof(ICommand),
typeof(SpDataGrid),
new PropertyMetadata(new PropertyChangedCallback(AttachOrRemoveDataGridEvent)));
public static void AttachOrRemoveDataGridEvent(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
DataGrid dataGrid = obj as DataGrid;
if (dataGrid != null)
{
if (args.Property == ComboBoxSelectionChangedProperty)
{
dataGrid.SelectionChanged += OnComboBoxSelectionChanged;
}
}
else if (args.OldValue != null && args.NewValue == null)
{ if (args.Property == ComboBoxSelectionChangedProperty)
{
dataGrid.SelectionChanged -= OnComboBoxSelectionChanged;
}
}
}
private static void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs args)
{
DependencyObject obj = sender as DependencyObject;
ICommand cmd = (ICommand)obj.GetValue(ComboBoxSelectionChangedProperty);
DataGrid grid = sender as DataGrid;
if (args.OriginalSource is ComboBox)
{
if (grid.CurrentCell.Item != DependencyProperty.UnsetValue)
{
//grid.CommitEdit(DataGridEditingUnit.Row, true);
ExecuteCommand(cmd, grid.CurrentCell.Item);
}
}
}
SpDataGrid is the custom control that i inherited from data grid.
I added below style in generic.xaml as i use the resourcedictionary for style (you can certainly add inside the datagrid).
<Style TargetType="{x:Type Custom:SpDataGrid}">
<Setter Property="Custom:SpDataGrid.ComboBoxSelectionChangedCommand" Value="{Binding ComboBoxSelectionChanged}"/>
</Style>
ComboBoxSelectionChanged is the command in my viewmodel. OnComboBoxSelectionChanged i commented the commitedit because in my case the values were already updated.
Let me know if anything is not clear or any questions. Hope this helps.