for (int i = 1; i <= 100; ++i) {
ageList.add(i);
}
DefaultComboBoxModel<Integer> modelAge = new DefaultComboBoxModel<Integer>();
for (Integer i : ageList) {
modelAge.addElement(i);
}
JComboBox<Integer> ageEntries = new JComboBox<Integer>();
ageEntries.setModel(modelAge);
ageEntries.addItemListener(new ageListener());
class ageListener implements ItemListener{
public void itemStateChanged(ItemEvent event) {
if (event.getStateChange() == ItemEvent.SELECTED) {
System.out.println("Selected:" + ItemEvent.SELECTED);
}
}
}
Problem: When I choose an age, it prints out 1, regardless of the age I've choosed. For example, if I choose the number 7, it prints out 1. If I choose 56, it prints out 1. Any ideas? I'm stumped.
Use this to retrieve the selected item ..
((JComboBox)event.getSource()).getSelectedItem();
EDIT: should be ((JComboBox<Integer>)event.getSource()).getSelectedItem(); as you are using generics. And "Yes" this will come inside your event method.
EDIT 2: you are getting the warning because we cannot determine whether JComboBox should have a Generic parameter. Compiler raises this warning because it thinks its unsafe to do such casting.
#SuppressWarnings("unchecked")
((JComboBox<Integer>)event.getSource()).getSelectedItem();
System.out.println("Selected:" + ItemEvent.SELECTED);
That is not the selected element you are printing here, but just some internal event type code (to mark this as a selection event). It is a constant value defined by the ItemEvent class.
Try event.getItem(). That should return the selected item.
ItemEvent.SELECTED is a constant representing the flag that says 'an item was selected'. You need to use the other methods of the ItemEvent object to extract your original combo box and find the selected item...
check here: http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/event/ItemEvent.html#SELECTED
ItemEvent.SELECTED is a constant, below is the source of ItemEvent.java.
package java.awt.event;
public class ItemEvent extends AWTEvent {
......
/**
* This state-change value indicates that an item was selected.
*/
public static final int SELECTED = 1;
......
}
Related
Linking in from Sort rows in JFace Treeviewer where I asked #greg-449 why my comparator doesn't work when (like the question on that page) I just wanted to sort by the label provider.
My TreeViewer is somewhat extended with some generic features used in my application. There are then at 3 derivations extending it, but essentially the example below of it being an abstract class is because it's not intended to be directly instantiated, and the rest of the Eclipse plugin code must choose a concrete implementation for various view parts. However, I need sorting functionality in all of them, so this is probably where it should go.
I anonymized the code below, but it is in essence the abstract wrapper around TreeViewer which in all derivations has 3 columns. The first column (index 0) is always a tree of some type, so the nodes are expandable and this produces more visible rows of data in the 2nd and 3rd columns (indexes 1 and 2). These columns contain text data only.
As such, what I hope to achieve is a sortable control, where clicking header column with index 0 will clear any sorting and render the data as it was initially loaded, while clicking any other headers will do the following:
Sort Ascending (UP) if it was NOT already the sort column
Invert sort direction if same sort column is repeatedly clicked
Here's what I have tried, starting with the ViewerComparator class mentioned in the link at he top of this post:
public abstract class MyTreeViewer extends TreeViewer {
public static final int ACTIVE_TOTAL_COLUMN_WIDTH = 120;
public static final int FIRST_COLUMN_WIDTH = 180;
private static final String SPECIAL_FIELD = "specialField";
private TreeColumn sortColumn = null;
final RMService rmService;
final PService pService;
public MyTreeViewer(Composite parent) {
this(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION);
}
public MyTreeViewer(Composite parent, int style) {
super(parent, style);
this.rmService = UIActivator.getDefault().getInjector().getInstance(RMService.class);
this.pService = UIActivator.getDefault().getInjector().getInstance(PService.class);
this.setUseHashlookup(true);
this.addSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(SelectionChangedEvent event) {
if (!event.getSelection().isEmpty()) {
Tree tree = getTree();
if (tree != null) {
List<MyTreeItem> treeItems = Lists.newArrayList();
for (int i = 0; i < tree.getSelectionCount(); i++) {
TreeItem item = tree.getSelection()[i];
Object obj = item.getData();
if (obj instanceof MyTreeItem) {
treeItems.add((MyTreeItem) obj);
}
}
handleSelectionChanged(treeItems);
}
}
}
});
this.setComparator(new ViewerComparator());
}
protected abstract void handleSelectionChanged(Collection<MyTreeItem> treeItemList);
public void initTree(List<ViewField> fields, IMemento memento) {
TableLayout tableLayout = new TableLayout();
Tree tree = getTree();
for (ViewField field : fields) {
TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
if (SystemPropertiesLoader.OPERATING_SYSTEM_NAME.equalsIgnoreCase(IConstants.LINUX_OS)) {
column.getColumn().setResizable(false);
column.getColumn().setMoveable(false);
} else {
column.getColumn().setResizable(true);
column.getColumn().setMoveable(true);
}
column.getColumn().setData(SPECIAL_FIELD, field);
column.getColumn().setText(field.getFieldName());
tableLayout.addColumnData(new ColumnPixelData(field.getWidth(), false));
column.getColumn().addSelectionListener(new SelectionListener() {
#Override
public void widgetSelected(SelectionEvent selectionEvent) {
if (selectionEvent.getSource() instanceof TreeColumn) {
TreeColumn column = (TreeColumn)selectionEvent.getSource();
Tree tree = getTree();
if (column.getText().equalsIgnoreCase("") && sortColumn != null) {
// file column clicked - use it to clear any sort order
sortColumn = null;
tree.setSortColumn(sortColumn);
tree.setSortDirection(0);
refresh();
} else {
sortColumn = column;
tree.setSortColumn(sortColumn);
tree.setSortDirection(invertSortDirection(tree.getSortDirection()));
refresh();
}
}
}
#Override
public void widgetDefaultSelected(SelectionEvent selectionEvent) {
// not currently used, but required as part of implementation of SelectionListener interface
}
});
}
tree.setLayout(tableLayout);
tree.setLinesVisible(false);
tree.setHeaderVisible(true);
tree.layout(true);
// prevent expanding/collapsing tree item on dbl click
tree.addListener(SWT.MeasureItem, new Listener() {
#Override
public void handleEvent(Event event) {
// nothing
}
});
}
private int invertSortDirection(int sortDirection) {
if (sortDirection != SWT.UP && sortDirection != SWT.DOWN) {
return SWT.UP;
} else if (sortDirection == SWT.UP) {
return SWT.DOWN;
}
return SWT.UP;
}
#Override
public void refresh() {
super.refresh();
}
#Override
public void refresh(boolean updateLabels) {
super.refresh(updateLabels);
}
}
I inherited this code, so there are some peculiar things that fixed bugs that I won't touch without knowing it won't produce a regression in QA, such as the crude way preventing expanding/collapsing tree item on double-click is implemented. In fact, the only part of this particular code I amended thus far is the insertion of the addSelectionListener closure for handling column header clicks, and the invertSortDirection method.
What happens when I run this and click on the headers is as I expect. I see the UI caret indicating the sort direction on column index 1 or 2, but I do not see the data sorted. Clicking the header of column index 0 will clear the sort column and the order. If the data was sorted, I'd like the viewer to refresh in the UI to its original loaded state before any column ordering is requested.
On the prior question (linked at top), I interpreted that if sorting by label text was required, I should just add the this.setComparator(new ViewerComparator()); line. I've no idea what I would override or change if I have to write a class that extends ViewComparator.
None of the derived classes from the above code implement a listener on a column. I can trace the code, and the above code for handling header clicks does execute.
So, do I need to extend ViewComparator and what should I be changing in it to get the desired behaviour, if I do?
(I can also probably do away with the TreeColumn sortColumn field since the tree itself 'remembers' this. The Google Guice injected services are used by derivations of this abstract tree viewer)
UPDATE 1:
My intention was to show a derived class of this generic viewer defined above, but after I examined this, it was clear it shows little of use for the current issue of why the sort does not occur.
I had found what I thought was the 'smoking gun' of why the sort does not occur in the custom label provider itself from one of my predecessors that I've inherited the project from. Here's the label provider:
public class MyCustomLabelProvider extends LabelProvider implements ITableLabelProvider {
final IDecoratorManager decorator;
public MyCustomLabelProvider() {
decorator = PlatformUI.getWorkbench().getDecoratorManager();
}
#Override
public Image getImage(Object element) {
return super.getImage(element);
}
#Override
public String getText(Object element) {
return null;
}
#Override
public Image getColumnImage(Object element, int columnIndex) {
if (element instanceof MyTreeItem) {
if (columnIndex == 0) {
final MyTreeItem item = (MyTreeItem)element;
switch (item.getType()) {
// snipped cases returning different images
}
}
}
return null;
}
#Override
public String getColumnText(Object element, int columnIndex) {
if (element instanceof MyTreeItem) {
return showColumns((MyTreeItem) element, columnIndex);
}
return null;
}
private String showColumns(MyTreeItem element, int columnIndex) {
if (columnIndex == 0) {
return element.getName();
}
if (columnIndex == 1) {
return String.valueOf(element.getCustomProperty1());
}
if (columnIndex == 2) {
return String.valueOf(element.getCustomProperty2());
}
return "";
}
}
Via tracing the ViewerComparator code, the program eventually calls getText which always returning null.
The ViewerComparator transpires only to attempt to grab the label text, which due to the above is null, which it amends to an empty String. It then uses the Java String compareTo method for the comparison. Since both are "", there is no comparison result to signal the elements order needs to be swapped.
I wondered about changing the getText method to somehow obtain the original column index of the clicked column and to have logic in it to determine which property to read from my underlying data object used to populate a row in the viewer. For me, this transpired to not work because the underlying object I used has non-String properties that are used to populate 2 of the 3 columns.
User greg-449 had indicated in comments I would need to extend and override the ViewerComparator to make my own version, but until I got this far, or until he stated ...
The standard ViewerComparator only supports one column
... it had not been clear why. The originally linked post doesn't have that clarification. Or at least, not at time of writing this.
At the point he mentioned that, I had not indicated the issue as resolved, just that I thought I had found the issue. I had simply started with the ViewerComparator, traced running code, and found the reason the existing comparator could not re-order the items, assuming that would likely be the end of it once I update code.
I would go further to what greg-449 said, in that even if you have a single column, the default ViewerComparator will not support comparing elements where the property of the underlying data object is not a Java String. You need to implement your own comparator for that. Thus is became clear why greg-449 suggested that at the outset of me stating I had 3 columns - but I remained confused because I simply thought I had text data on the 2 columns I wanted to be sortable, even if that text is actually converted from an int. I just thought the linked post I read was probably apt because it mentioned sorting on text, without mentioning the limitation of a single column, or Java String data types.
So moving on, I have now got my code working as I wanted. More importantly, I did not have to change the getText method, since this was called specifically by the default ViewerComparator and I found an approach to follow where this wasn't needed. I shall be posting these in the answer.
So in addition to my updates in the question...
I tried to find the example of a different comparator that greg-449 referenced, but the specific package was not available in my environment if I tried to import it.
I then searched for a means to writing a custom comparator and found a Vogella post:
https://www.vogella.com/tutorials/EclipseJFaceTable/article.html#sort-content-of-table-columns
I noticed that the column index issue I mentioned above was handled by creating a handler per column in the Vogella code. I mostly copied it, with slight changes to how it performs the comparison to meet my requirements:
public class MyCustomViewerComparator extends ViewerComparator {
private int propertyIndex;
private static final int DESCENDING = 1;
private int direction;
public MyCustomViewerComparator() {
this.propertyIndex = 0;
direction = DESCENDING;
}
public int getDirection() {
return direction == 1 ? SWT.DOWN : SWT.UP;
}
public void setColumn(int column) {
if (column == this.propertyIndex) {
// Same column as last sort; toggle the direction
direction = 1 - direction;
} else {
// New column; do an ascending sort
this.propertyIndex = column;
direction = DESCENDING;
}
}
#Override
public int compare(Viewer viewer, Object e1, Object e2) {
MyTreeItem p1 = (MyTreeItem) e1;
MyTreeItem p2 = (MyTreeItem) e2;
int rc = 0;
switch (propertyIndex) {
case 1:
rc = p1.getCustomProperty1() - p2.getCustomProperty1();
break;
case 2:
rc = p1.getCustomProperty2() - p2.getCustomProperty2();
break;
default:
rc = 0;
}
// If descending order, flip the direction
if (direction == DESCENDING) {
rc = -rc;
}
return rc;
}
}
As can be seen, we have a standard compare function that returns a negative number, zero when matching, or a positive number, that indicates which order two items should be in. Or as the standard JavaDoc states:
the value {#code 0} if the argument string is equal to
* this string; a value less than {#code 0} if this string
* is lexicographically less than the string argument; and a
* value greater than {#code 0} if this string is
* lexicographically greater than the string argument.
Just in my version, the compare is using int values from my data object instead.
In addition to this, if the user clicked the column header of the column that by default is column index 0, I wanted it to clear any sort and return to the format the viewer was initially loaded as.
To achieve this, I simply changed the SelectionListener I originally used in MyTreeViewer (see second code listing in question) for each column, and made it use the following from the Vogella example (again tweaked):
private SelectionAdapter getSelectionAdapter(final TreeColumn column,
final int index) {
SelectionAdapter selectionAdapter = new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
comparator.setColumn(index);
if (index == 0) {
viewer.getTree().setSortDirection(0);
viewer.getTree().setSortColumn(null);
} else {
int dir = comparator.getDirection();
viewer.getTree().setSortDirection(dir);
viewer.getTree().setSortColumn(column);
}
viewer.refresh();
}
};
return selectionAdapter;
}
The if (index == 0) check in the above, clears the sorting for me when true, otherwise sorts on the whichever other column header has been clicked. Since the column index pertains to how the columns were set up, they are not affected by having movable columns in the UI. You can change the visible column ordering without deleterious effects.
From what I've learnt, it's only the logic for comparing elements that is instrumental and would need overriding in a custom comparator. Outside of this, the only thing to recognize is you then want the comparator specifically wired to each column, by implementing the SelectionAdapter interface and adding this to the column, while also being able to track the column indexes. The Vogella link does show this. Basically you will see on the comparator that there exists a setColumn method, and this is used in the SelectionAdapter to set the column index to be tested in the compare.
I've implemented a ComboBox where its list is filtered by the input in the ComboBox TextField. It works as you might expect a filter of such a control to work. Every item in the list that starts with the input text is shown in the list.
I just have one small issue. If I select an item from the list, and then try to remove the last character in the textfield, nothing happens. If I select an item from the list, and then try to remove any other character than the last, the whole string gets removed. Both of these problems occur only if this is the first thing I do in the ComboBox. If I write something in the combo box first, or if I select an item for the second time, none of the issues described occurs.
What's really strange to me is that these problems seem to be caused by the predicate being set (if I comment out the invoking of setPredicate, everything works fine). This is strange since I think that should only affect the list that the predicate is being set for. It shouldn't affect the rest of the ComboBox.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class TestInputFilter extends Application {
public void start(Stage stage) {
VBox root = new VBox();
ComboBox<ComboBoxItem> cb = new ComboBox<ComboBoxItem>();
cb.setEditable(true);
cb.setConverter(new StringConverter<ComboBoxItem>() {
#Override
// To convert the ComboBoxItem to a String we just call its
// toString() method.
public String toString(ComboBoxItem object) {
return object == null ? null : object.toString();
}
#Override
// To convert the String to a ComboBoxItem we loop through all of
// the items in the combobox dropdown and select anyone that starts
// with the String. If we don't find a match we create our own
// ComboBoxItem.
public ComboBoxItem fromString(String string) {
return cb.getItems().stream().filter(item -> item.getText().startsWith(string)).findFirst()
.orElse(new ComboBoxItem(string));
}
});
ObservableList<ComboBoxItem> options = FXCollections.observableArrayList(new ComboBoxItem("One is a number"),
new ComboBoxItem("Two is a number"), new ComboBoxItem("Three is a number"),
new ComboBoxItem("Four is a number"), new ComboBoxItem("Five is a number"),
new ComboBoxItem("Six is a number"), new ComboBoxItem("Seven is a number"));
FilteredList<ComboBoxItem> filteredOptions = new FilteredList<ComboBoxItem>(options, p -> true);
cb.setItems(filteredOptions);
InputFilter inputFilter = new InputFilter(cb, filteredOptions);
cb.getEditor().textProperty().addListener(inputFilter);
root.getChildren().add(cb);
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch();
}
class ComboBoxItem {
private String text;
public ComboBoxItem(String text) {
this.text = text;
}
public String getText() {
return text;
}
#Override
public String toString() {
return text;
}
}
class InputFilter implements ChangeListener<String> {
private ComboBox<ComboBoxItem> box;
private FilteredList<ComboBoxItem> items;
public InputFilter(ComboBox<ComboBoxItem> box, FilteredList<ComboBoxItem> items) {
this.box = box;
this.items = items;
}
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String value = newValue;
// If any item is selected we get the first word of that item.
String selected = box.getSelectionModel().getSelectedItem() != null
? box.getSelectionModel().getSelectedItem().getText() : null;
// If an item is selected and the value of in the editor is the same
// as the selected item we don't filter the list.
if (selected != null && value.equals(selected)) {
items.setPredicate(item -> {
return true;
});
} else {
items.setPredicate(item -> {
if (item.getText().toUpperCase().startsWith(value.toUpperCase())) {
return true;
} else {
return false;
}
});
}
}
}
}
Edit: I've tried to override the key listeners in a desperate attempt to solve the issue:
cb.getEditor().addEventFilter(KeyEvent.KEY_PRESSED, e -> {
TextField editor = cb.getEditor();
int caretPos = cb.getEditor().getCaretPosition();
StringBuilder text = new StringBuilder(cb.getEditor().getText());
// If BACKSPACE is pressed we remove the character at the index
// before the caret position.
if (e.getCode().equals(KeyCode.BACK_SPACE)) {
// BACKSPACE should only remove a character if the caret
// position isn't zero.
if (caretPos > 0) {
text.deleteCharAt(--caretPos);
}
e.consume();
}
// If DELETE is pressed we remove the character at the caret
// position.
else if (e.getCode().equals(KeyCode.DELETE)) {
// DELETE should only remove a character if the caret isn't
// positioned after that last character in the text.
if (caretPos < text.length()) {
text.deleteCharAt(caretPos);
}
}
// If LEFT key is pressed we move the caret one step to the left.
else if (e.getCode().equals(KeyCode.LEFT)) {
caretPos--;
}
// If RIGHT key is pressed we move the caret one step to the right.
else if (e.getCode().equals(KeyCode.RIGHT)) {
caretPos++;
}
// Otherwise we just add the key text to the text.
// TODO We are currently not handling UP/DOWN keys (should move
// caret to the end/beginning of the text).
// TODO We are currently not handling keys that doesn't represent
// any symbol, like ALT. Since they don't have a text, they will
// just move the caret one step to the right. In this case, that
// caret should just hold its current position.
else {
text.insert(caretPos++, e.getText());
e.consume();
}
final int finalPos = caretPos;
// We set the editor text to the new text and finally we move the
// caret to its new position.
editor.setText(text.toString());
Platform.runLater(() -> editor.positionCaret(finalPos));
});
// We just consume KEY_RELEASED and KEY_TYPED since we don't want to
// have duplicated input.
cb.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, e -> {
e.consume();
});
cb.getEditor().addEventFilter(KeyEvent.KEY_TYPED, e -> {
e.consume();
});
Sadly, this doesn't fix the issue either. If I e.g. choose the "Three is a number" item and then try to remove the last "e" in "Three", this is the values that the text property will switch between:
TextProperty: Three is a number
TextPropery: Thre is a number
TextPropery:
So it removes the correct character at first, but then it removes the whole String for some reason. As mentioned before, this happens only because the predicate has been set, and it only happens when I do the first input after I've selected an item for the first time.
Jonatan,
As Manuel stated one problem is that setPredicate() will trigger your changed() method twice since you are changing the combobox model, however the real problem is that the combobox will overwrite the editor values with whatever values seems fit. Here is the explanation to your symptoms:
If I select an item from the list, and then try to remove the last
character in the textfield, nothing happens.
In this case, the deleting of the last char is actually happening however the first call to setPredicate() matches one possible item (exactly the same item that you deleted the last char of) and changes the combobox contents to only one item. This causes a call where the combobox restores the editor value with the current combobox.getValue() string giving the illusion that nothing happens. It also causes a second call to your changed() method, but at this point the editor text is already changed.
Why do this only happen the first time, but then never again?
Good question! This happens only once, because you are modifying the entire underlying model of the combobox once (which as explained before, triggers the second call to the changed() method).
So after the previous scenario happens if you click the dropdown button (right arrow) you will see you only have one item left, and if you try to delete one character again you will still have that same item left, that is, the model (contents of the combobox) hasn't change, because the setPredicate() will still match the same contents, therefore not causing a markInvalid() call in the TextInputControl class since the contents didn't actually change which means not restoring the item string again (If you want to see where the textfield is actually restored the first time see the ComboBoxPopupControl.updateDisplayNode() method with the JavaFX sources).
If I select an item from the list, and then try to remove any other
character than the last, the whole string gets removed.
In your second scenario nothing matches the first setPredicate() call (NO items to match your startsWith condition) which deletes all the items int the combobox removing your current selection and the editor string too.
TIP: Try and make sense of this for yourself, toggle a breakpoint inside the changed() method to see how many times this is entering and why (JavaFX source is needed if you want to follow the ComboBox and it's components behaviors)
Solution:
If you want to keep using your ChangeListener, you can simply attack your main problem (which is the Editor contents being replaced after the setPredicate call) by restoring the text in the editor after the filtering:
class InputFilter implements ChangeListener<String> {
private ComboBox<ComboBoxItem> box;
private FilteredList<ComboBoxItem> items;
public InputFilter(ComboBox<ComboBoxItem> box, FilteredList<ComboBoxItem> items) {
this.box = box;
this.items = items;
}
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String value = newValue;
// If any item is selected we get the first word of that item.
String selected = box.getSelectionModel().getSelectedItem() != null
? box.getSelectionModel().getSelectedItem().getText() : null;
// If an item is selected and the value of in the editor is the same
// as the selected item we don't filter the list.
if (selected != null && value.equals(selected)) {
items.setPredicate(item -> {
return true;
});
} else {
// This will most likely change the box editor contents
items.setPredicate(item -> {
if (item.getText().toUpperCase().startsWith(value.toUpperCase())) {
return true;
} else {
return false;
}
});
// Restore the original search text since it was changed
box.getEditor().setText(value);
}
//box.show(); // <-- Uncomment this line for a neat look
}
}
Personally I've done this before with KeyEvent handlers in the past (to avoid multiple calls to my code in the changed() event), however you can always use a Semaphore or your favorite class from the java.util.concurrent class to avoid any unwanted re-entrance to your method, if you feel you start to need it. Right now, the getEditor().setText() will always tail restore the correct value even if the same method bubbles down two or three times.
Hope this helps!
Setting a predicate will trigger your ChangeListener, because you're changing the ComboBox-Items and therefore the cb-editor's text-value. Removing the Listener and Re-Adding it will prevent those unexpected Actions.
I added three Lines to your change(...) - Method.
Try out, if this is a fix for your problem.
Info: I only used your first block of code
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String value = newValue;
// If any item is selected we get the first word of that item.
String selected = box.getSelectionModel().getSelectedItem() != null
? box.getSelectionModel().getSelectedItem().getText() : null;
box.getEditor().textProperty().removeListener(this); // new line #1
// If an item is selected and the value of in the editor is the same
// as the selected item we don't filter the list.
if (selected != null && value.equals(selected)) {
items.setPredicate(item -> {
return true;
});
} else {
items.setPredicate(item -> {
if (item.getText().toUpperCase().startsWith(value.toUpperCase())) {
return true;
} else {
return false;
}
});
box.getEditor().setText(newValue); // new line #2
}
box.getEditor().textProperty().addListener(this); // new line #3
}
I'm writing a program to calculate GPA. It consists of several panels. The first panel
tells the user to specify the number of courses so that Comboboxes (gradeCombo),(hourCombo) and Textfields will be added dynamically to the second panel . Everything is fine to this point but the problem is with the listeners. In early stages, I registered the event listeners for these comboboxes individually for every element in the array and it resulted in 900 lines of codes, but it worked fine and all my results were correct. To enhance my code I'm trying to write a for loop for registering the events for the comboboxes and so far I couldn't succeed.
I tried to write the handling code as anonymous inner class and as separate inner class, Here is my last try:
for(i = 0; i<courseN;i++)
{
hourCombo[i].addItemListener(new HoursHandler());
gradeCombo[i].addItemListener(new GradeHandler());
}
public class HoursHandler implements ItemListener
{
public void itemStateChanged(ItemEvent event)
{
if(event.getStateChange()==ItemEvent.SELECTED)
{
String hour;
hour = (String) hourCombo[i].getSelectedItem();
currentHour[i]=Integer.parseInt(hour);
aquiredHours=aquiredHours+currentHour[i] prevHour[i];
prevHour[i]=currentHour[i];
}
}
}
public class GradeHandler implements ItemListener
{
public void itemStateChanged(ItemEvent event)
{
if(event.getStateChange()==ItemEvent.SELECTED)
{
String grade;
grade=(String) gradeCombo[i].getSelectedItem();
switch(grade)
{
case "A+":
currentPoint[i]=5*currentHour[i];
break;
case "A":
currentPoint[i]= 4.75 * currentHour[i];
break;
case "B+":
currentPoint[i]= 4.5 * currentHour[i];
break;
case "B":
currentPoint[i]= 4 * currentHour[i];
break;
case "C+":
currentPoint[i]= 3.5 * currentHour[i];
break;
case "C":
currentPoint[i]= 3 * currentHour[i];
break;
case "D+":
currentPoint[i]= 2.5 * currentHour[i];
break;
case "D":
currentPoint[i]= 2 * currentHour[i];
break;
case "F":
currentPoint[i]= 1 * currentHour[i];
break;
}
aquiredPoints=aquiredPoints+currentPoint[i]-prevPoint[i];
prevPoint[i]=currentPoint[i];
}
}
}
I get a NullPointerException for this statement:
hour = (String) hourCombo[i].getSelectedItem();
and everything goes wrong, none of my variables is updated and I cannot calculate the GPA..
It's hard to tell from the posted code what is wrong there. However, I assume that i is declared as an instance variable. In this case, the loop for(i = 0; i<courseN;i++) will change the value of this instance variable. Afterwards, all the listeners will internally use i with the last value that it received in the for-loop.
To circumvent this, you can declare an instance variable for each listener instance. So you can change your listener classes like this:
public class HoursHandler implements ItemListener
{
private final int index;
HoursHandler(int index)
{
this.index = index;
}
#Override
public void itemStateChanged(ItemEvent event)
{
// Use the "index" here:
String hour = (String) hourCombo[index].getSelectedItem();
currentHour[index]=Integer.parseInt(hour);
...
}
}
(similarly, introduce such an index for the GradeHandler).
Then, when you create the listeners, you can pass to each instance the index that it refers to:
// Note: "i" is declared here, and should NO longer
// be an instance variable!
for(int i = 0; i<courseN;i++)
{
hourCombo[i].addItemListener(new HoursHandler(i)); // Use "i" as "index"
gradeCombo[i].addItemListener(new GradeHandler(i)); // Use "i" as "index"
}
I assume that there might be some more elegant solutions, but this is one possible solution, solely based on the code that you provided.
It appears that i is a member field in your outer class. When your listener is executed, and your listener
hour = (String) hourCombo[i].getSelectedItem();
this statement will use whatever value i happens to be in this outer class. And after you execute your for loop, i will probably be equal to courseN, unless there's something else changing it somewhere else. In any case, it doesn't use the value that i held when you set up the listener, because you did nothing to tell it to use that value.
A simple way to fix this is to construct your listeners by giving it the index you want them to use:
public class HoursHandler implements ItemListener
{
private final int index;
public HoursHandler(int index) {
this.index = index;
}
public void itemStateChanged(ItemEvent event)
{
if(event.getStateChange()==ItemEvent.SELECTED)
{
String hour;
hour = (String) hourCombo[index].getSelectedItem();
and use index instead of i everywhere in the listener. When you construct the listener, it will store the index you want, and then the code will use that instead of the current value of i. Similarly for GradeHandler. Then
for(i = 0; i<courseN;i++)
{
hourCombo[i].addItemListener(new HoursHandler(i));
gradeCombo[i].addItemListener(new GradeHandler(i));
}
Note that the index you want the listeners to use is now passed as a parameter to the listeners' constructors.
EDITED to use final on the index member--plagiarized from Marco13's good idea.
So I'm having trouble figuring out how to update a TextArea with information that I submit from an generics arraylist. As of now the program creates a new Order:
Order d1 = new Order();
Then the user selects some data and pushes an add button, and the order is added to a TextArea. The problem I have is that I have to add the order to the correct spot in the list and update it each time. I"m only sorting it by one item. I'm not really sure how to do that using the CompareTo method.
public void actionPerformed(ActionEvent event)
{
ArrayList<Drink> DrinkArray = new ArrayList<Drink>();
if (event.getSource() == addcoffeeButton)
{
String coffeesize = (String) sizecoffeelist.getSelectedItem();
double coffeeprice = Double.parseDouble(pricecoffeeTextfield.getText());
String coffeetype = (String) cuptypecoffeelist.getSelectedItem();
String coffeecaffeine = (String) caffeineList.getSelectedItem();
String coffeeroom = (String) roomforcreamList.getSelectedItem();
String coffeeadditional = additionalflavorList.getText();
if ((coffeeadditional.isEmpty()))
coffeeadditional = "No Additional Flavor";
Drink d1 = new Coffee(coffeesize, coffeeprice, coffeetype, coffeecaffeine, coffeeroom, coffeeadditional);
DrinkArray.add(d1);
orderTextArea.append(d1);
So I would have to add the drink to the correct spot before adding it to the array and printing to the text area, but I'm not quite sure how to do that.
I'll assume that Drink implements Comparable. Look at the javadocs if you don't know what that means.
If that's true, you can do this:
List<Drink> drinks = new ArrayList<Drink>();
// add Drinks
Collections.sort(drinks); // now they're sorted according to your Comparable.
You can also instantiate a Comparator and pass it to the sorts method.
Something like this (make the getValue() function whatever you want):
public class DrinkComparator implements Comparator<Drink> {
public int compare(Drink d1, Drink d2) {
if (d1.getValue() < d2.getValue()) {
return -1;
} else if (d1.getValue() > d2.getValue()) {
return 1;
} else {
return 0;
}
}
public boolean equals(Object obj) {
return this.compare(this, (Drink)obj) == 0;
}
}
You basically need to pre-determine the insertion point where the "object" would be inserted...
Take a look at Collections.binarySearch(List<T>, T)
From the Java Docs
Returns:
the index of the search key, if it is contained in the list;
otherwise, (-(insertion point) - 1). The insertion point is defined as
the point at which the key would be inserted into the list: the index
of the first element greater than the key, or list.size() if all
elements in the list are less than the specified key. Note that this
guarantees that the return value will be >= 0 if and only if the key
is found.
I'm trying to find a way to pick what an array stores based on which button the user presses on a GUI.
Obviously this will not compile due to the variable name being the same.
The calculations are performed outside of the loop but use "values". I just want the user to be able to determine what values are set in the array based on what button they press. The obvious issue is not being able to use the name "values" twice, which is where I am having a problem as I have a for loop that requires the variable "values" and I don't want to have to be re adding the code several times for each data set when there is most likely an easy workaround that I am currently not seeing.
Just pull the declaration out:
double[] Xvalues = null;
if (e.getSource() == X1btn) {
Xvalues = new double[]{2001,350,799,1004};
}
else if (e.getSource() == X2btn) {
Xvalues = new double[]{5,62,28,500};
}
A better approach would be to subclass JButton and associate a set of values with each instance. To retrieve the button's values, include an accessor.
Example
public final class JArrayButton extends JButton{
private final double[] values;
public JArrayButton(double[] values){
this.values = values;
}
// ... other stuff (e.g. constructors)
public final double[] getValues(){
return values;
}
}
What if you create the array ourside the loop.
double Xvalues[] = new Xvalues[5]; //or whatever size you want
and then use if/else statement
if (e.getSource() == X1btn) {
Xvalues ={2001,350,799,1004};
} else if (e.getSource() == X2btn) {
Xvalues={5,62,28,500};
}