I have created a Treeview (javafx), it looks like:
I want now, that only the "Tour"-TreeItems be selectable.
But I don't know how.
I have tried it with a ChangeListener, but I can only with it refresh the content of a Tab (TabPane)...the refresh works fine...but the "Delivery"-TreeItems can be selected :(
code:
public void showTours(List<Tour> pTours) {
treeViewPane.getSelectionModel().selectedItemProperty().addListener(treeItemChangeListener);
TreeItem tTreeRoot = new TreeItem<>("Root", new ImageView(Icons.getIcon24("truck_blue.png")));
tTreeRoot.setExpanded(true);
treeViewPane.setRoot(tTreeRoot);
for (Tour tTour : pTours) {
TreeItem<Object> tTourItem = new TreeItem<>(tTour);
tTreeRoot.getChildren().add(tTourItem);
if (tTour.getDeliveries() != null) {
for (Delivery tDelivery : tTour.getDeliveries()) {
TreeItem<Object> tDeliveryItem = new TreeItem<>(tDelivery);
tTourItem.getChildren().add(tDeliveryItem);
}
}
}
}
private final ChangeListener<TreeItem> treeItemChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null && newValue.getValue() instanceof Tour){
Tour selectedTour = (Tour) newValue.getValue();
reloadTabContent(selectedTour);
}
};
I hope you can help me.
If you can show me example code, I will be really happy :)
Thank you
Modifying the selection behavior in any controls in JavaFX seems to be a bit of a pain; but the "proper" way to do this is to define a custom selection model for the tree. The easiest way to do this is to wrap the default selection model, and delegate the method calls to it, vetoing selection if the selection index is for an item which shouldn't be selected.
It's a good idea to select something whenever possible when a select method is called, as otherwise keyboard navigation will break.
Here is an implementation:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class CustomTreeSelectionModelExample extends Application {
#Override
public void start(Stage primaryStage) {
TreeItem<Object> root = new TreeItem<>("Root");
for (int i = 1 ; i <= 5 ; i++) {
TreeItem<Object> item = new TreeItem<>(new Tour("Tour "+i));
for (int j = 1 ; j <= 5; j++) {
Delivery delivery = new Delivery("Delivery "+j);
item.getChildren().add(new TreeItem<>(delivery));
}
root.getChildren().add(item);
}
TreeView<Object> tree = new TreeView<>();
tree.setSelectionModel(new TourSelectionModel(tree.getSelectionModel(), tree));
tree.setRoot(root);
primaryStage.setScene(new Scene(new BorderPane(tree), 400, 400));
primaryStage.show();
}
public static class TourSelectionModel extends MultipleSelectionModel<TreeItem<Object>> {
private final MultipleSelectionModel<TreeItem<Object>> selectionModel ;
private final TreeView<Object> tree ;
public TourSelectionModel(MultipleSelectionModel<TreeItem<Object>> selectionModel, TreeView<Object> tree) {
this.selectionModel = selectionModel ;
this.tree = tree ;
selectionModeProperty().bindBidirectional(selectionModel.selectionModeProperty());
}
#Override
public ObservableList<Integer> getSelectedIndices() {
return selectionModel.getSelectedIndices() ;
}
#Override
public ObservableList<TreeItem<Object>> getSelectedItems() {
return selectionModel.getSelectedItems() ;
}
#Override
public void selectIndices(int index, int... indices) {
List<Integer> indicesToSelect = Stream.concat(Stream.of(index), IntStream.of(indices).boxed())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.collect(Collectors.toList());
if (indicesToSelect.isEmpty()) {
return ;
}
selectionModel.selectIndices(indicesToSelect.get(0),
indicesToSelect.stream().skip(1).mapToInt(Integer::intValue).toArray());
}
#Override
public void selectAll() {
List<Integer> indicesToSelect = IntStream.range(0, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.boxed()
.collect(Collectors.toList());
if (indicesToSelect.isEmpty()) {
return ;
}
selectionModel.selectIndices(0,
indicesToSelect.stream().skip(1).mapToInt(Integer::intValue).toArray());
}
#Override
public void selectFirst() {
IntStream.range(0, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
#Override
public void selectLast() {
IntStream.iterate(tree.getExpandedItemCount() - 1, i -> i - 1)
.limit(tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
#Override
public void clearAndSelect(int index) {
int toSelect = index ;
int direction = selectionModel.getSelectedIndex() < index ? 1 : -1 ;
while (toSelect >= 0 && toSelect < tree.getExpandedItemCount() && ! (tree.getTreeItem(toSelect).getValue() instanceof Tour)) {
toSelect = toSelect + direction ;
}
if (toSelect >= 0 && toSelect < tree.getExpandedItemCount()) {
selectionModel.clearAndSelect(toSelect);
}
}
#Override
public void select(int index) {
int toSelect = index ;
int direction = selectionModel.getSelectedIndex() < index ? 1 : -1 ;
while (toSelect >= 0 && toSelect < tree.getExpandedItemCount() && ! (tree.getTreeItem(toSelect).getValue() instanceof Tour)) {
toSelect = toSelect + direction ;
}
if (toSelect >= 0 && toSelect < tree.getExpandedItemCount()) {
selectionModel.select(toSelect);
}
}
#Override
public void select(TreeItem<Object> obj) {
if (obj.getValue() instanceof Tour) {
selectionModel.select(obj);
}
}
#Override
public void clearSelection(int index) {
selectionModel.clearSelection(index);
}
#Override
public void clearSelection() {
selectionModel.clearSelection();
}
#Override
public boolean isSelected(int index) {
return selectionModel.isSelected(index);
}
#Override
public boolean isEmpty() {
return selectionModel.isEmpty();
}
#Override
public void selectPrevious() {
int current = selectionModel.getSelectedIndex() ;
if (current > 0) {
IntStream.iterate(current - 1, i -> i - 1).limit(current)
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
}
#Override
public void selectNext() {
int current = selectionModel.getSelectedIndex() ;
if (current < tree.getExpandedItemCount() - 1) {
IntStream.range(current + 1, tree.getExpandedItemCount())
.filter(i -> tree.getTreeItem(i).getValue() instanceof Tour)
.findFirst()
.ifPresent(selectionModel::select);
}
}
}
public static class Tour {
private final String name ;
public Tour(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
#Override
public String toString() {
return getName();
}
}
public static class Delivery {
private final String name;
public Delivery(String name) {
this.name = name;
}
public String getName() {
return name;
}
#Override
public String toString() {
return getName();
}
}
public static void main(String[] args) {
launch(args);
}
}
I modified the selection-model that James_D posted by making it a bit more generic so that you can specify a custom filter. The implementation is:
public class FilteredTreeViewSelectionModel<S> extends MultipleSelectionModel<TreeItem<S>> {
private final TreeView<S> treeView;
private final MultipleSelectionModel<TreeItem<S>> selectionModel;
private final TreeItemSelectionFilter<S> filter;
public FilteredTreeViewSelectionModel(
TreeView<S> treeView,
MultipleSelectionModel<TreeItem<S>> selectionModel,
TreeItemSelectionFilter<S> filter) {
this.treeView = treeView;
this.selectionModel = selectionModel;
this.filter = filter;
selectionModeProperty().bindBidirectional(selectionModel.selectionModeProperty());
}
#Override
public ObservableList<Integer> getSelectedIndices() {
return this.selectionModel.getSelectedIndices();
}
#Override
public ObservableList<TreeItem<S>> getSelectedItems() {
return this.selectionModel.getSelectedItems();
}
private int getRowCount() {
return this.treeView.getExpandedItemCount();
}
#Override
public boolean isSelected(int index) {
return this.selectionModel.isSelected(index);
}
#Override
public boolean isEmpty() {
return this.selectionModel.isEmpty();
}
#Override
public void select(int index) {
// If the row is -1, we need to clear the selection.
if (index == -1) {
this.selectionModel.clearSelection();
} else if (index >= 0 && index < getRowCount()) {
// If the tree-item at the specified row-index is selectable, we
// forward select call to the internal selection-model.
TreeItem<S> treeItem = this.treeView.getTreeItem(index);
if (this.filter.isSelectable(treeItem)) {
this.selectionModel.select(index);
}
}
}
#Override
public void select(TreeItem<S> treeItem) {
if (treeItem == null) {
// If the provided tree-item is null, and we are in single-selection
// mode we need to clear the selection.
if (getSelectionMode() == SelectionMode.SINGLE) {
this.selectionModel.clearSelection();
}
// Else, we just forward to the internal selection-model so that
// the selected-index can be set to -1, and the selected-item
// can be set to null.
else {
this.selectionModel.select(null);
}
} else if (this.filter.isSelectable(treeItem)) {
this.selectionModel.select(treeItem);
}
}
#Override
public void selectIndices(int index, int... indices) {
// If we have no trailing rows, we forward to normal row-selection.
if (indices == null || indices.length == 0) {
select(index);
return;
}
// Filter indices so that we only end up with those indices whose
// corresponding tree-items are selectable.
int[] filteredIndices = IntStream.concat(IntStream.of(index), Arrays.stream(indices)).filter(indexToCheck -> {
TreeItem<S> treeItem = treeView.getTreeItem(indexToCheck);
return (treeItem != null) && filter.isSelectable(treeItem);
}).toArray();
// If we have indices left, we proceed to forward to internal selection-model.
if (filteredIndices.length > 0) {
int newIndex = filteredIndices[0];
int[] newIndices = Arrays.copyOfRange(filteredIndices, 1, filteredIndices.length);
this.selectionModel.selectIndices(newIndex, newIndices);
}
}
#Override
public void clearAndSelect(int index) {
// If the index is out-of-bounds we just clear and return.
if (index < 0 || index >= getRowCount()) {
clearSelection();
return;
}
// Get tree-item at index.
TreeItem<S> treeItem = this.treeView.getTreeItem(index);
// If the tree-item at the specified row-index is selectable, we forward
// clear-and-select call to the internal selection-model.
if (this.filter.isSelectable(treeItem)) {
this.selectionModel.clearAndSelect(index);
}
// Else, we just do a normal clear-selection call.
else {
this.selectionModel.clearSelection();
}
}
#Override
public void selectAll() {
int rowCount = getRowCount();
// If we are in single-selection mode, we exit prematurely as
// we cannot select all rows.
if (getSelectionMode() == SelectionMode.SINGLE) {
return;
}
// If we only have a single index to select, we forward to the
// single-index select-method.
if (rowCount == 1) {
select(0);
}
// Else, if we have more than one index available, we construct an array
// of all the indices and forward to the selectIndices-method.
else if (rowCount > 1) {
int index = 0;
int[] indices = IntStream.range(1, rowCount).toArray();
selectIndices(index, indices);
}
}
#Override
public void clearSelection(int index) {
this.selectionModel.clearSelection(index);
}
#Override
public void clearSelection() {
this.selectionModel.clearSelection();
}
#Override
public void selectFirst() {
Optional<TreeItem<S>> firstItem = IntStream.range(0, getRowCount()).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
firstItem.ifPresent(this.selectionModel::select);
}
#Override
public void selectLast() {
int rowCount = getRowCount();
Optional<TreeItem<S>> lastItem = IntStream.iterate(rowCount - 1, i -> i - 1).
limit(rowCount).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
lastItem.ifPresent(this.selectionModel::select);
}
private int getFocusedIndex() {
FocusModel<TreeItem<S>> focusModel = this.treeView.getFocusModel();
return (focusModel == null) ? -1 : focusModel.getFocusedIndex();
}
#Override
public void selectPrevious() {
int focusIndex = getFocusedIndex();
// If we have nothing selected, wrap around to the last index.
int startIndex = (focusIndex == -1) ? getRowCount() : focusIndex;
if (startIndex > 0) {
Optional<TreeItem<S>> previousItem = IntStream.iterate(startIndex - 1, i -> i - 1).
limit(startIndex).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
previousItem.ifPresent(this.selectionModel::select);
}
}
#Override
public void selectNext() {
// If we have nothing selected, starting at -1 will work out correctly
// because we'll search from 0 onwards.
int startIndex = getFocusedIndex();
if (startIndex < getRowCount() - 1) {
Optional<TreeItem<S>> nextItem = IntStream.range(startIndex + 1, getRowCount()).
mapToObj(this.treeView::getTreeItem).
filter(this.filter::isSelectable).
findFirst();
nextItem.ifPresent(this.selectionModel::select);
}
}
}
I changed the selectIndex(int) method as this method should just forward the index-based selection to its internal selection-model if the filter permits. I disagree with the while loop logic as you explicitly pass the index to be selected to this method in the hopes that it can select it. The expected behaviour should be that it should ignore the select if the filter doesn't allow it. I also fleshed out the method by adding a catch for the index == -1 case as we need to clear selection when this happens.
The select(TreeItem) method was also changed quite a bit by checking for a null parameter and handling this separately so that if we are in single-selection mode we need to clear the selection, otherwise we call select(null) so that the internal selection-model handles it correctly. If we do have a tree-item we just check against filter and pass through to the internal selection-model.
The selectIndices(int, int[]) method is also different in that it should handle the case where the indices-array could be null or of length 0. If this is the case the select(index) method should be called.
I implemented the clearAndSelect(int) method a bit differently compared to the other approach. I do the boundary checks at the beginning to see if we need to call clearSelection() immediately. Else, I check if the TreeItem at the index is selectable via the filter. If it is we forward to the internal selection-model, else we just clear. I also disagree with the while-loop approach here that was done in the other implementation.
There is actually a bug with the selectPrevious() and selectNext() methods of James_D's implementation. If nothing is selected you need to snap to the last index when calling selectPrevious(). The opposite is true for selectFirst() where you need to snap to the first index if nothing is selected. You then work from these new indices to find the first item that is permitted by the filter. You also need to work with the focus-index and not the selected-index. You can see this behaviour if you look at the MultipleSelectionModelBase class for reference.
The TreeItemSelectionFilter is specified as:
public interface TreeItemSelectionFilter<S> {
public boolean isSelectable(TreeItem<S> treeItem);
}
For your particular case you can then wire it all together as:
....
MultipleSelectionModel<TreeItem<Object>> selectionModel = tree.getSelectionModel();
TreeItemSelectionFilter<Object> filter = treeItem -> treeItem.getValue() instanceof Tour;
FilteredTreeViewSelectionModel<Object> filteredSelectionModel = new FilteredTreeViewSelectionModel<>(tree, selectionModel, filter);
tree.setSelectionModel(filteredSelectionModel);
....
I've uploaded the source-code of an example application here so that you can easily test the behavior of the FilteredTreeViewSelectionModel for yourself. Compare it with the default selection-model and see if you are satisfied with the behavior.
Related
I want to create three(3) sets of Combobox (Year, Month, Day).
The Combobox Day should only be enabled until the Combobox Month and Year were filed correctly, and values should be synchronized based on the given month and year. (This means that it should check for leap years).
Here is what I have so far, I have a hint that I should use bindings and/or listeners to do this but struggle to do so.
public class Testing extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final JFXComboBox<Month> cbMonths = new JFXComboBox<>();
final JFXComboBox<Integer> cbYears = new JFXComboBox<>();
final JFXComboBox<Integer> cbDays = new JFXComboBox<>();
// Month Values
cbMonths.getItems().setAll(Month.values());
// Year Values
Calendar calendar = Calendar.getInstance();
for (int i = calendar.get(Calendar.YEAR) ;
i >= (calendar.get(Calendar.YEAR) -35) ; i--)
{
cbYears.getItems().add(i);
}
// NOTE: will cause NPE
// I want to insert this code only when cbMonth and cbYears has a value
YearMonth numberOfDays = YearMonth.of(cbYear.getValue(), cbMonth.getValue());
for (int i = 1 ; i >= numberOfDays.lengthOfMonth() ; i ++) {
cbDays.getItems().add(i);
}
final HBox root = new HBox(cbMonth, cbYear, cbDays);
root.setAlignment(Pos.CENTER);
root.setSpacing(10.0);
root.setPadding(new Insets(10, 10, 10, 10));
Scene scene = new Scene(root, 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
EDIT
Having a lack of time, I tried other options.
OPTION 1:
As #Zephyr points out, I switch to a date picker and set it to editable. I tried to override some of its default settings to come up with my desire output. But I notice that whenever I use TextFormatter I was unable to pick dates on the DatePicker choice box. Here is the sample code
public class DatePickerFinal extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final String DATE_REGEX = "(0[1-9]|1[012])\\s(0[1-9]|[12][0-9]|3[01])\\s((19|2[0-9])[0-9]{2})";
final DateTimeFormatter SHOW_DATE = DateTimeFormatter.ofPattern("MMMM dd, yyyy", Locale.getDefault());
final DateTimeFormatter ENTER_DATE = DateTimeFormatter.ofPattern("MM dd yyyy", Locale.getDefault());
final LocalDate TODAY = LocalDate.now();
final JFXDatePicker DATE_PICKER = new JFXDatePicker();
// Disable some dates
DATE_PICKER.setDayCellFactory(new Callback<DatePicker, DateCell>() {
#Override
public DateCell call(DatePicker datePicker) {
return new DateCell() {
#Override
public void updateItem(LocalDate localDate, boolean b) {
super.updateItem(localDate, b);
setDisable(b || localDate.compareTo(TODAY) > 0 || localDate.compareTo(TODAY.minusYears(45)) < 0);
}
};
}
});
// Add StringConverter to make it more readable,
// and also rejecting disable dates inputted by the user
DATE_PICKER.setConverter(new StringConverter<LocalDate>() {
#Override
public String toString(LocalDate localDate) {
if (localDate == null) {
return "";
} else if (localDate.isAfter(TODAY) || localDate.isBefore(TODAY.minusYears(45))) {
return "";
} else {
return SHOW_DATE.format(localDate);
}
}
#Override
public LocalDate fromString(String s) {
return (s == null || s.isEmpty()) ? null : LocalDate.parse(s, ENTER_DATE);
}
});
// Then I want to manage user input so that they can only enter digits to the date picker
// then format it accordingly.
DATE_PICKER.getEditor().setTextFormatter(new TextFormatter<Object>(change -> {
String enteredText = change.getText();
if((enteredText.matches("[\\d]+")) || change.isDeleted()) {
final int oldTextLength = change.getControlText().length();
int newTextLength = change.getControlNewText().length();
if (newTextLength < oldTextLength) return change;
switch (newTextLength) {
case 2 :
case 5 :
StringBuilder stringBuilder = new StringBuilder(enteredText);
stringBuilder.append(" ");
change.setText(stringBuilder.toString());
newTextLength++;
break;
case 11 :
return null;
}
change.setCaretPosition(newTextLength);
change.setAnchor(newTextLength);
return change;
}
return null;
}));
// Add some validators where if the user input was valid or not. The below code was still in progress though.
RequiredFieldValidator requiredFieldValidator = new RequiredFieldValidator();
requiredFieldValidator.setMessage("Field Should Not Be Empty");
RegexValidator regexValidator = new RegexValidator("MM DD YYYY");
regexValidator.setRegexPattern(DATE_REGEX);
DATE_PICKER.setValidators(regexValidator);
DATE_PICKER.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean t1) {
if (t1) {
DATE_PICKER.validate();
}
}
});
DATE_PICKER.getEditor().textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observableValue, String s, String t1) {
if (!DATE_PICKER.getEditor().getText().matches(DATE_REGEX)) {
DATE_PICKER.validate();
}
}
});
VBox root = new VBox(20, DATE_PICKER, new JFXButton("Button"));
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 300, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Aside from being editable, I also want the user to be able to just click and/or pick dates from the choice box. I hope someone could point me in the right direction :)
With #kleopatra's help. My solution is to create a class responsible for parsing the date selected by the user on the DatePickers default choice box. Furthermore, the date picker is set to editable so that the user can also edit it manually. However, there is a restriction where a user can ONLY insert numerical value when editing manually, also I wanted to make sure that the user should input only valid dates.
MCVE
public class DatePickerFinal extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final String DATE_OF_BIRTH_REGEX
= "(0[1-9]|1[012])\\s(0[1-9]|[12][0-9]|3[01])\\s((19|2[0-9])[0-9]{2})";
final DateTimeFormatter showingDateFormat = DateTimeFormatter.ofPattern("MMMM dd, yyyy", Locale.getDefault());
final DateTimeFormatter inputtedDateFormat = DateTimeFormatter.ofPattern("MM dd yyyy", Locale.getDefault());
final LocalDate dateToday = LocalDate.now();
final JFXDatePicker datePicker = new JFXDatePicker();
// Disable some dates
datePicker.setDayCellFactory(new Callback<DatePicker, DateCell>() {
#Override
public DateCell call(DatePicker datePicker) {
return new DateCell() {
#Override
public void updateItem(LocalDate localDate, boolean b) {
super.updateItem(localDate, b);
setDisable(b || localDate.compareTo(dateToday) > 0 || localDate.compareTo(dateToday.minusYears(45)) < 0);
}
};
}
});
// Add StringConverter to make it more readable,
// and also rejecting disable dates inputted by the user
datePicker.setConverter(new StringConverter<LocalDate>() {
#Override
public String toString(LocalDate localDate) {
if (localDate == null) {
return "";
} else if (localDate.isAfter(dateToday) || localDate.isBefore(dateToday.minusYears(45))) {
return "";
} else {
return showingDateFormat.format(localDate);
}
}
#Override
public LocalDate fromString(String s) {
return (s == null || s.isEmpty()) ? null : LocalDate.parse(s, inputtedDateFormat);
}
});
// Add a validator
RequiredFieldValidator requiredFieldValidator = new RequiredFieldValidator();
requiredFieldValidator.setMessage("Enter with the format\nMM DD YYYY");
datePicker.setValidators(requiredFieldValidator);
// Format the user's input field
datePicker.getEditor().setTextFormatter(new TextFormatter<>(change -> {
String textEntered = change.getText();
DateValidator validator;
if (change.isContentChange()) {
validator = new DateValidator(change.getControlNewText(), showingDateFormat);
if (!validator.isValid()) {
datePicker.validate();
} else {
datePicker.resetValidation();
return change;
}
if (textEntered.matches("\\D+")) {
return null;
} else {
final int oldLength = change.getControlText().length();
int newLength = change.getControlNewText().length();
if (newLength < oldLength) return change;
if (newLength == 2 || newLength == 5) {
change.setText(textEntered + " ");
newLength++;
} else if (newLength == 11) {
validator = new DateValidator(change.getControlNewText(), inputtedDateFormat);
if (!validator.isValid()) {
return null;
} else {
datePicker.resetValidation();
}
}
change.setCaretPosition(newLength);
change.setAnchor(newLength);
}
}
return change;
}));
datePicker.focusedProperty().addListener((observableValue, wasFocused, isFocused) -> {
if (isFocused) {
Platform.runLater(()-> {
datePicker.validate();
datePicker.getEditor().selectAll();
});
} else {
datePicker.resetValidation();
}
});
datePicker.getEditor().textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observableValue, String s, String t1) {
if (t1.matches(DATE_OF_BIRTH_REGEX)) {
datePicker.resetValidation();
}
}
});
// Show picker choice box on MouseEvent
datePicker.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseEvent -> {
datePicker.show();
});
VBox root = new VBox(50, datePicker, new JFXButton("Button"));
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 300, 120);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class DateValidator {
DateTimeFormatter formatter;
String date;
DateValidator (String date, DateTimeFormatter formatter) {
this.date = date;
this.formatter = formatter;
}
public boolean isValid() {
try {
LocalDate.parse(this.date, this.formatter);
} catch (Exception e) {
return false;
}
return true;
}
}
}
I want to have a TableCell with a custom graphic that animates on value change, where the animation type depends on the nature of the change, so I need to know the previous value to compare to the current one.
Here's your typical custom table cell (Kotlin code):
class MyTableCell<S, T> : TableCell<S, T>() {
override fun updateItem(item: T?, empty: Boolean) {
if (empty || field == null) {
text = null
graphic = null
} else {
// need to get the old value here
}
}
I see that the super method in javafx/scene/control/TableCell.java does know the old value and uses it compare it to the current one, but the override only gets the newValue:
private void updateItem(int oldIndex) {
...
final T oldValue = getItem();
...
final T newValue = currentObservableValue == null ? null : currentObservableValue.getValue();
...
if (oldIndex == index) {
if (!isItemChanged(oldValue, newValue)) {
...
}
...
}
...
updateItem(newValue, false); // sadly, `oldValue` is not passed
I can only think of an ugly workaround, so I wonder if there is some idiomatic way to get the old cell value?
Here is a sample app:
import javafx.application.Application
import javafx.beans.property.SimpleDoubleProperty
import javafx.collections.FXCollections
import javafx.scene.Scene
import javafx.scene.control.*
import javafx.scene.control.cell.PropertyValueFactory
import javafx.stage.Stage
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import tornadofx.*
class Foo {
val barProperty = SimpleDoubleProperty()
var bar: Double
get() = barProperty.get()
set(value) = barProperty.set(value)
}
class FooApp: Application() {
override fun start(primaryStage: Stage) {
val foos = FXCollections.observableArrayList(
Foo().apply { bar = 42.0 }
)
val table = TableView<Foo>(foos)
val barColumn = TableColumn<Foo, Double>("Bar")
barColumn.cellValueFactory = PropertyValueFactory<Foo, Double>("bar")
barColumn.setCellFactory {
FooTableCell<Foo, Double> { "%.2f".format(it) }
}
table.columns.add(barColumn)
val scene = Scene(table, 400.0, 200.0)
primaryStage.scene = scene
primaryStage.title = "Table Cell"
primaryStage.show()
launch {
while (isActive) {
delay(500)
val oldFoo = foos[0]
// Replacing the old Foo instance with a new one,
// updating the value of the `bar` field:
foos[0] = Foo().apply {
bar = oldFoo.bar - 1.0 + Math.random() * 2.0
}
// because a change to a field cannot be detected by an observable list
// and so does not propagates to the table. This won't result in
// a visible change:
// foos[0].bar = foos[0].bar - 1.0 + Math.random() * 2.0
}
}
}
}
class FooTableCell<S, T>(private val format: (T) -> String) : TableCell<S, T>() {
init {
contentDisplay = ContentDisplay.GRAPHIC_ONLY
itemProperty().addListener(ChangeListener { obs, oldItem, newItem ->
if (newItem != null && oldItem != null && newItem != oldItem) {
// This is never true.
println("!!! Old: $oldItem, New: $newItem")
} else {
println("Change listener:\nOld: $oldItem, New: $newItem\n")
}
})
}
override fun updateItem(item: T?, empty: Boolean) {
val oldItem = this.item
super.updateItem(item, empty)
if (item != null && oldItem != null && item != oldItem) {
// This is never true.
println("!!! Old: $oldItem, New: $item")
} else {
println("updateItem:\nOld: $oldItem, New: $item\n")
}
if (empty || item == null) {
graphic = null
text = null
} else if (tableRow != null) {
val cell = this
graphic = Label().apply {
textProperty().bindBidirectional(cell.textProperty())
}
text = format(item)
}
}
}
fun main(args: Array<String>) {
Application.launch(FooApp::class.java, *args)
}
The actual value of the item property is changed by the default implementation of the updateItem() method, so just get the value before calling the default implementation:
public class MyTableCell<S,T> extends TableCell<S,T> {
#Override
protected void updateItem(T item, boolean empty) {
T oldItem = getItem();
super.updateItem(item, empty) ;
// ...
}
}
Alternatively, you can just register a change listener with the itemProperty():
public class MyTableCell<S,T> extends TableCell<S,T> {
public MyTableCell() {
itemProperty().addListener((obs, oldItem, newItem) -> {
// do animation here...
});
}
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
// other functionality here...
}
}
Here is a SSCCE demonstrating both techniques:
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class TableCellWithChange extends Application {
public static class ChangeAwareCell<S,T> extends TableCell<S,T> {
public ChangeAwareCell() {
itemProperty().addListener((obs, oldItem, newItem) -> {
System.out.printf("In listener, value for %s changed from %s to %s%n", getTableRow().getItem(), oldItem, newItem);
});
}
#Override
protected void updateItem(T item, boolean empty) {
T oldItem = getItem();
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item.toString());
System.out.printf("Change in %s from %s to %s %n", getTableView().getItems().get(getIndex()), oldItem, item);
}
}
}
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
TableColumn<Item, String> itemCol = column("Item", Item::nameProperty);
table.getColumns().add(itemCol);
TableColumn<Item, Number> valueCol = column("Value", Item:: valueProperty);
table.getColumns().add(valueCol);
valueCol.setCellFactory(tc -> new ChangeAwareCell<>());
TableColumn<Item, Void> changeCol = new TableColumn<>();
changeCol.setCellFactory(tc -> new TableCell<>() {
private Button incButton = new Button("^");
private Button decButton = new Button("v");
private HBox graphic = new HBox(2, incButton, decButton);
{
incButton.setOnAction(e -> {
Item item = getTableRow().getItem();
item.setValue(item.getValue()+1);
});
decButton.setOnAction(e -> {
Item item = getTableRow().getItem();
item.setValue(item.getValue()-1);
});
}
#Override
protected void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(graphic);
}
}
});
table.getColumns().add(changeCol);
Random rng = new Random();
for (int i = 1 ; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(100)));
}
Scene scene = new Scene(table);
primaryStage.setScene(scene);
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String text, Function<S, ObservableValue<T>> property) {
TableColumn<S,T> col = new TableColumn<>(text);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(150);
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
#Override
public String toString() {
return getName();
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
Of course, these items will also change, e.g. when the user scrolls around the table, or if the cells are reused in other ways; so this might not be exactly when you want. You might want to add a listener to the appropriate property in the model instead. The simplest way to do this is probably to store a reference to the actual property from the model in the cell, and update that reference when the cell is updated:
public static class ChangeAwareCell<S,T> extends TableCell<S,T> {
private Function<S, ObservableValue<T>> property ;
private ObservableValue<T> lastObservableValue ;
private ChangeListener<T> listener = (obs, oldValue, newValue) -> valueChanged(oldValue, newValue);
public ChangeAwareCell(Function<S, ObservableValue<T>> property) {
this.property = property ;
}
private void valueChanged(T oldValue, T newValue) {
System.out.printf("Value changed from %s to %s %n", oldValue, newValue);
}
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (lastObservableValue != null) {
lastObservableValue.removeListener(listener);
}
if (empty) {
setText(null);
} else {
lastObservableValue = property.apply(getTableRow().getItem());
lastObservableValue.addListener(listener);
setText(item.toString());
}
}
}
And of course make the corresponding change:
valueCol.setCellFactory(tc -> new ChangeAwareCell<>(Item::valueProperty));
I need to limit interval of the text property of a text field
int maxLength = 64;
int minLength = 0;
txtSeuil.textProperty().addListener((v, oldValue, newValue) -> {
if (!newValue.matches("\\d*")) {
txtSeuil.setText(newValue.replaceAll("[^\\d*{1,2}]", ""));
if (txtSeuil.getText().length() > maxLength || txtSeuil.getText().length() < minLength) {
String s = txtSeuil.getText().substring(0, maxLength);
txtSeuil.setText(s);
}
}
});
the field does accept only numbers but any number, not just the interval values
If I have understood correctly, you're trying to implement a number field that only allows values within the interval [0, 64]. According to this answer, TextFormatter is the recommended way to accomplish such a functionality. Have a look at this MWE which should solve your problem:
public class RestrictedNumberFieldDemo extends Application {
public static void main(String[] args) {
launch();
}
#Override
public void start(Stage primaryStage) {
TextField numField = new TextField();
numField.setTextFormatter(new TextFormatter<Integer>(change -> {
// Deletion should always be possible.
if (change.isDeleted()) {
return change;
}
// How would the text look like after the change?
String txt = change.getControlNewText();
// There shouldn't be leading zeros.
if (txt.matches("0\\d+")) {
return null;
}
// Try parsing and check if the result is in [0, 64].
try {
int n = Integer.parseInt(txt);
return 0 <= n && n <= 64 ? change : null;
} catch (NumberFormatException e) {
return null;
}
}));
primaryStage.setScene(new Scene(numField));
primaryStage.show();
}
}
To solve your problem you can implement custom Filter for TextFormatter. It will allow to enter numbers only and restrict length of string. Here is snippet which show how it can works:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.stage.Stage;
public class Main5 extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField();
textField.setTextFormatter(new TextFormatter<Integer>(change -> {
if (!change.getText().isEmpty()) {
return change.getText().matches("\\d+") && change.getControlNewText().length() <= 5 ? change : null;
}
return change;
}));
primaryStage.setScene(new Scene(textField));
primaryStage.show();
}
}
Your problem is:
The length check is not done, if the regex matches. But the text can be a sequence of digits and be to long.
You need to do those checks independent of each other.
Also you're setting a new value in some cases which triggers the same checks again. Of course they will result in the String being evaluates as a valid input, but you could prevent checking again by introducing a field in the ChangeListener that stores, whether the listener is currently being executed:
txtSeuil.textProperty().addListener(new ChangeListener<String>() {
private boolean validating = false;
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!validating) {
validating = true;
String newText = newValue;
boolean modify = false;
if (!newValue.matches("\\d*")) {
newText = newValue.replaceAll("[^\\d*{1,2}]", "");
modify = true;
}
if (newText.length() > maxLength || newText.length() < minLength) {
newText = newText.substring(0, maxLength);
modify = true;
}
if (modify) {
txtSeuil.setText(newText);
}
validating = false;
}
}
});
I have TableView with column inside it that must only accept numbers.
and I added onMouseClickListener to enter edit mode on the mouse click instead of double click on the cell
I want a way to not allowing the user to enter any character except numbers. My code is:
Callback<TableColumn<DailyDetails, String>, TableCell<DailyDetails, String>> defaultCellFactory
= TextFieldTableCell.<DailyDetails>forTableColumn();
dailyCredit.setCellFactory(column -> {
TableCell<DailyDetails, String> cell = defaultCellFactory.call(column);
cell.setOnMouseClicked(e -> {
if (!cell.isEditing() && !cell.isEmpty()) {
cell.getTableView().edit(cell.getIndex(), column);
}
});
return cell;
});
I implemented Table cell from the scratch:
class NumberCell extends TableCell<DailyDetails, String> {
private TextField textField;
public NumberCell() {
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(String.valueOf(getItem()));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(getString());
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
private void createTextField() {
textField = new TextField(getString());
//textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.lengthProperty().addListener(new ChangeListener<Number>(){
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (newValue.intValue() > oldValue.intValue()) {
char ch = textField.getText().charAt(oldValue.intValue());
// Check if the new character is the number or other's
if (!(ch >= '0' && ch <= '9' )) {
// if it's not number then just setText to previous one
textField.setText(textField.getText().substring(0,textField.getText().length()-1));
}
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
Callback<TableColumn<DailyDetails, String>,
TableCell<DailyDetails, String>> cellFactory
= (TableColumn<DailyDetails, String> p) -> new NumberCell();
dailyDebit.setCellFactory(cellFactory);
the problem is i lost the on mouse listener cell.setOnMouseClicked!!!
how do i get the cell again to assign the listener ???
Just for driving the new api into everybody's brain: a full example with a slightly different TextFormatter (than in the other answer) that is Locale-aware and (dirtily!) hooked into core TextFieldTableCell, can be used in any custom editing TableCell as well:
/**
* Example of how-to use a TextFormatter in a editing TableCell.
*/
public class CellFormatting extends Application {
private Parent getContent() {
ObservableList<IntData> data = FXCollections.observableArrayList(
new IntData(1), new IntData(2), new IntData(3)
);
TableView<IntData> table = new TableView<>(data);
table.setEditable(true);
TableColumn<IntData, Integer> column = new TableColumn<>("Data");
column.setCellValueFactory(new PropertyValueFactory("data"));
// core default: will throw exception on illegal values
// column.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
NumberFormat format = NumberFormat.getIntegerInstance();
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.isContentChange()) {
ParsePosition parsePosition = new ParsePosition(0);
// NumberFormat evaluates the beginning of the text
format.parse(c.getControlNewText(), parsePosition);
if (parsePosition.getIndex() == 0 ||
parsePosition.getIndex() < c.getControlNewText().length()) {
// reject parsing the complete text failed
return null;
}
}
return c;
};
column.setCellFactory(c -> new ValidatingTextFieldTableCell<>(
// note: each cell needs its own formatter
// see comment by #SurprisedCoconut
new TextFormatter<Integer>(
// note: should use local-aware converter instead of core!
new IntegerStringConverter(), 0,
filter)));
table.getColumns().add(column);
VBox box = new VBox(table);
return box;
}
/**
* TextFieldTableCell that validates input with a TextFormatter.
* <p>
* Extends TextFieldTableCell, accesses super's private field reflectively.
*
*/
public static class ValidatingTextFieldTableCell<S, T> extends TextFieldTableCell<S, T> {
private TextFormatter<T> formatter;
private TextField textAlias;
public ValidatingTextFieldTableCell() {
this((StringConverter<T>)null);
}
public ValidatingTextFieldTableCell(StringConverter<T> converter) {
super(converter);
}
public ValidatingTextFieldTableCell(TextFormatter<T> formatter) {
super(formatter.getValueConverter());
this.formatter = formatter;
}
/**
* Overridden to install the formatter. <p>
*
* Beware: implementation detail! super creates and configures
* the textField lazy on first access, so have to install after
* calling super.
*/
#Override
public void startEdit() {
super.startEdit();
installFormatter();
}
private void installFormatter() {
if (formatter != null && isEditing() && textAlias == null) {
textAlias = invokeTextField();
textAlias.setTextFormatter(formatter);
}
}
private TextField invokeTextField() {
Class<?> clazz = TextFieldTableCell.class;
try {
Field field = clazz.getDeclaredField("textField");
field.setAccessible(true);
return (TextField) field.get(this);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
public static class IntData {
IntegerProperty data = new SimpleIntegerProperty(this, "data");
public IntData(int value) {
setData(value);
}
public void setData(int value) {
data.set(value);
}
public int getData() {
return data.get();
}
public IntegerProperty dataProperty() {
return data;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
BTW, the formatter is re-used from another question where the task at hand was to restrict input into a Spinner.
Use a TextFormatter on the TextField like this:
TextFormatter<String> formatter = new TextFormatter<String>( change -> {
change.setText(change.getText().replaceAll("[^0-9.,]", ""));
return change;
});
textField.setTextFormatter(formatter);
Works with Java8u40 upwards. Use e. g. the TableView example from the Oracle site as base.
I have a fragment that has a SearchView. When I click in the search box the keyboard appears. I enter a search term and press "Enter" the keyboard disappears and I get my results page. When I choose an item in my list of results I am taken back to the search fragment and the keyboard reappears. How to I not have the keyboard reappear? Here is part of my code:
public class LocationFragment extends ListFragment implements OnItemLongClickListener, OnClickListener{
// ===========================================================
// Constants
// ===========================================================
private static final int BTN_ID = 0x5f000001;
private static final String DEBUG_TAG = "Tablet/LocationFragment";
// ===========================================================
// Fields
// ===========================================================
private int pos;
private int mPositionToDelete = 0;
private FragmentListener listener;
private Activity mActivity;
// ===========================================================
// Lifecycle
// ===========================================================
#Override
public void onAttach(Activity activity) {
mActivity = activity;
super.onAttach(activity);
try {
listener = (FragmentListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement Fragment");
}
}
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
View v = inflater.inflate(R.layout.locations_tile, null);
SearchManager sm = (SearchManager)getActivity().getSystemService(Context.SEARCH_SERVICE);
final SearchView sv = (SearchView)v.findViewById(R.id.search_view);
sv.setSearchableInfo(sm.getSearchableInfo(getActivity().getComponentName()));
sv.setSubmitButtonEnabled(true);
consumeKeyUp(sv);
return v;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final ListView lv = getListView();
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
lv.setItemChecked(pos, true);
lv.setSelector(R.xml.location_select_gradient);
lv.setCacheColorHint(0x00000000);
lv.setSelector(R.drawable.list_item_selector);
lv.setOnItemLongClickListener(this);
lv.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch(keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
// Log.i(DEBUG_TAG, "onkeyDown");
return !(lv.getSelectedItemPosition() < lv.getCount() - 1);
case KeyEvent.KEYCODE_DPAD_UP:
// Log.i(DEBUG_TAG, "onkey up");
if(lv.getSelectedItemPosition() == 0) {
// Log.i(DEBUG_TAG, " requesting for search focused");
return LocationFragment.this.getView().findViewById(R.id.search_view).requestFocus();
} else return false;
case KeyEvent.KEYCODE_DPAD_RIGHT:
Log.i(DEBUG_TAG, "KEYCODE_DPAD_RIGHT");
return true;
}
return false;
}
return false;
}
});
ImageView closeBtn = (ImageView) getView().findViewById(R.id.locationsCloseButton);
closeBtn.setOnClickListener(this);
closeBtn.setFocusable(true);
closeBtn.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
v.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
Log.i(DEBUG_TAG, "keypad Center");
return false;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
return !(KeyEvent.KEYCODE_DPAD_DOWN == keyCode);
}
return false;
}
});
} else {
v.setOnKeyListener(null);
}
}
});
}
You could trying clearing the search view focus in your list click callback, e.g. sv.clearFocus()
I grab the search view to do this in OnCreateOptionsMenu using getActionView() on the MenuItem.
Incidentally I also call the following on my search view in onCreateOptionsMenu
final InputMethodManager imm = (InputMethodManager) getActivity().getSystemServiceContext.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT);
To be honest though I find the whole are of soft input keyboards a bit of a black art
private SearchView mSearchView;
...
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search_menu, menu);
// Associate search able configuration with the SearchView
android.app.SearchManager searchManager = (android.app.SearchManager) getSystemService(Context.SEARCH_SERVICE);
mSearchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
mSearchView.setIconifiedByDefault(false);
return super.onCreateOptionsMenu(menu);
}
private result() {
mSearchView.clearFocus();
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
}
don't use startSearch or onSearchRequested