Spinner keeps last selected item - android-fragments

I have a spinner with an array adapter. The spinner is populated inside a fragment onCreateView().
spinner.setSelection(0)
spinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) {
}
override fun onNothingSelected(var1: AdapterView<*>?) {
}
}
Whenever I get back to the fragment and the spinner is created, the last selected item is selected when onItemSelected() is called automatically and ignoring the spinner.setSelection(0) call.
I have put many logs to see what is going, but I cannot understand why the lately selected item is the one being selected by default and not the one at position 0.

I solved the issue by setting a click listener on the drop down view and basically do the same stuff I was doing with the OnItemSelectedListener.
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = SpinnerItemChartDropdownBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
val item = getItem(position)
val root = binding.root
bindDropdown(root, item)
binding.setClickListener {
listener.onChartRangeSelected(item)
}
return root
}
One important stuff. You need to do something like this, to dismiss the drop down view after an item has been selected:
fun hideSpinnerDropDown(spinner: Spinner) {
try {
val method: Method = Spinner::class.java.getDeclaredMethod("onDetachedFromWindow")
method.isAccessible = true
method.invoke(spinner)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}

Related

Reorder and Move NSTableView Row with NSPasteboard

I have an NSTableView where I can drag and drop table rows to reorder them. This works by setting a drag type in my view controller:
#IBOutlet weak var tableView: NSTableView!
let dragType = NSPasteboard.PasteboardType(rawValue: "myapp.task")
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerForDraggedTypes([dragType])
}
...and then implementing the reordering with these table delegate methods:
//Start drag
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: dragType)
return item
}
//Verify proposed drop
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
}else{
return []
}
}
//Accept drop of one or multiple rows
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
var oldIndexes = [Int]()
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) { dragItem, _, _ in
if let str = (dragItem.item as! NSPasteboardItem).string(forType: self.dragType), let index = Int(str) {
oldIndexes.append(index)
}
}
//Do a bunch of logic to reorder the table rows...
}
Now, in addition to reordering my table rows, I want to be able to drag a row and drop it somewhere else in my app--sort of like moving the row to a different place.
I have a custom NSView set up as the drag destination for this, and I can drag a table row and the custom view reacts appropriately with a table row dragged over it:
class MyCustomView: NSView{
required init?(coder: NSCoder) {
super.init(coder: coder)
let taskDragType = NSPasteboard.PasteboardType(rawValue: "myapp.task")
registerForDraggedTypes([taskDragType])
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
//...
}
override func draggingExited(_ sender: NSDraggingInfo?) {
//...
}
}
But the part I'm unclear on is how to get the table row, and its associated object set as a property on the NSTableCellView, when the drop occurs:
//This is another method in MyCustomView
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
guard let items = draggingInfo.draggingPasteboard.pasteboardItems else{ return false }
for item in items{
print("---")
print(item) //<-- NSPasteboardItem
let index = item.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "myapp.task"))
print(index) //<-- Index of the table row
//How can I also get the task object associated with the row?
}
}
I can get the index of the row, but what I need is the entire object from the row's data source so I can take action on the object it represents. My suspicion is that I need to change how I'm using pasteboardWriterForRow to put my object on the pasteboard, but I'm unsure how to do that.
How can I pass both the row index and the object to the pasteboard?
Soon after posting this, I decided to try something crazy, and it turns out I found a way to make this a lot simpler. It seems that NSPasteboard is really only necessary if you need to get stuff into and out of your app. Since I am just moving something from one part of my app to another, I can use the drag and drop delegate methods as events and handle the data myself.
First, I set up a global array for adding dragged task objects:
var draggedTasks = [Task]()
Whenever a task is dragged from my NSTableView, I add them to the array in the aforementioned delegate method where dragging starts:
//Start drag
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
//Queue tasks for moving to phases or projects
draggedTasks.append(tasks[row])
//Queue row for reordering
let item = NSPasteboardItem()
item.setString(String(row), forType: dragType)
return item
}
Then where I accept the drop in MyCustomView, I take action on the draggedTasks array:
//Save dropped tasks
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
//Do stuff to draggedTasks based on the context of where they are dropped
return true
}
This is much simpler than going down the NSPasteboard route. 🙂

Async Loading of a TreeView

Hey I am very new to tornadofx struggeling with async loading of data for the treeview. I am loading categories from a rest endpoint, which I want to show in there.
It seems like there's no direct data binding to the children.
when using 'bindChildren' I can provide the observable list, but I have to convert them into Node's. which then would make the populate block kind of obsolete.
What's the recommended way of doing this? I cannot find anything about this.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
val sourceProperty = SimpleListProperty<Category<*>>()
fun loadData() {
// load items for treeview into 'newItems'
sourceProperty.value = newItems
}
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty.value
else -> parent.value.subcategories
}
}
}
}
Assuming that on a button click I call viewmodel.loadData(), I would expect the TreeView to update as soon as there's some new data. (If I would've found a way to bind)
I've never had to use bindChildren for TornadoFX before and your use of async isn't very relevant to what I think is your primary problem. So, admittedly, this question kind of confused me at first but I'm guessing you're just wondering why the list isn't appearing in your TreeView? I've made a test example with changes to make it work.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//Just a dummy category
class ChildCategory(override val name: String) : Category<ChildCategory> {
override val id = name
override val subcategories: List<ChildCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
//filled with dummy data
val sourceProperty = SimpleListProperty<Category<*>>(listOf(
ChildCategory("Categorya"),
ChildCategory("Categoryb"),
ChildCategory("Categoryc"),
ChildCategory("Categoryd")
).asObservable())
fun loadData() {
sourceProperty.asyncItems {
//items grabbed somehow
listOf(
ChildCategory("Category1"),
ChildCategory("Category2"),
ChildCategory("Category3"),
ChildCategory("Category4")
).asObservable()
}
}
}
class TestView : View() {
val model: CategoryViewModel by inject()
override val root = vbox(10) {
button("Refresh Items").action {
model.loadData()
}
add(createTreeView(model.sourceProperty))
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty
else -> parent.value.subcategories
}
}
}
}
}
There are 2 important distinctions that are important.
1. The more relevant distinction is that inside the populate block, root -> listProperty is used instead of root.listProperty.value. This will make your list appear. The reason is that a SimpleListProperty is not a list, it holds a list. So, yes, passing in a plain list is perfectly valid (like how you passed in the value of the list property). But now that means the tree view isn't listening to your property, just the list you passed in. With that in mind, I would be considerate over the categories' subcategory lists are implemented as well.
2. Secondly, notice the use of asyncItems in the ViewModel. This will perform whatever task asynchronously, then set the items to list on success. You can even add fail or cancel blocks to it. I'd recommend using this, as long/intensive operations aren't supposed to be performed on the UI thread.

TableView cell request Focus

I'm new in JavaFX and have the following issue:
I have a tableview inside a BorderPane. I want it to focus on the last row/1st column when it's loaded. I have tried the following:
requestfocus()
scrollTo()
focusModel.focus()
selectionModel.select()
What happens is that the cell I want is indeed blue (as if it was selected) but the first cell has a blue border. So, when I try to use the arrow keys, the selected cell moves to the first row.
BTW, I'm using TornadoFX.
Any ideas?
Thanks in advance!
class CashflowTab : View() {
override val root: HBox by fxml()
private val mController : CashflowController by inject()
private val mainView : MainView by inject()
// Get the buttons
private val buttonCashflow : Button by fxid("btnCashflow")
init {
// Setup the buttons
buttonCashflow.action {
setupCashflowTable()
}
}
/** Displays the TableView for the Cashflow */
private fun setupCashflowTable() {
var initialFocus = true
// List of entries for the category ComboBox
val categoryList = mController.getCashFlowCategoryList()
// Create the table
val cashTable = tableview<CashEntry>(mController.getCashEntryList()) {
isEditable = true
column(Constants.COL_COUNT, CashEntry::countProperty)
column(Constants.COL_DATE, CashEntry::dateProperty).makeEditable(LocaleDateConverter())
column(Constants.COL_INCOME, CashEntry::incomeProperty).makeEditable(CurrencyConverter())
column(Constants.COL_EXPENSES, CashEntry::expensesProperty).makeEditable(CurrencyConverter())
column(Constants.COL_PROFIT, CashEntry::profitProperty).converter(CurrencyConverter())
column(Constants.COL_TOTAL_PROFIT, CashEntry::totalProfitProperty).converter(CurrencyConverter())
column(Constants.COL_COMMENTS, CashEntry::commentsProperty).makeEditable()
column(Constants.COL_CATEGORY, CashEntry::categoryProperty).useComboBox(categoryList)
// Scroll to and focus on the last cell on startup
if (initialFocus) {
val lastRow = mController.getCashEntryList().size - 1
requestFocus()
scrollTo(lastRow)
focusModel.focus(lastRow)
selectionModel.select(lastRow)
initialFocus = false
}
onEditCommit {entry ->
// Update the list
mController.updateCashEntryList(entry)
// Move to the next cell
requestFocus()
focusModel.focusRightCell()
#Suppress("UNCHECKED_CAST")
selectionModel.select(focusModel.focusedCell.row, focusModel.focusedCell.tableColumn as TableColumn<CashEntry, *>)
}
enableCellEditing()
// Enable edit on key typed
addEventHandler(KeyEvent.KEY_PRESSED) {keyEvent ->
if (keyEvent.code.isDigitKey || keyEvent.code.isLetterKey) {
if (editingCell == null) {
val currentSelectedCell = selectedCell
if (currentSelectedCell != null && currentSelectedCell.tableColumn.isEditable) {
edit(currentSelectedCell.row, currentSelectedCell.tableColumn)
}
}
}
}
}
// Add the table to the view
mainView.root.center = cashTable
cashTable.tableMenuButtonVisibleProperty()
// Ensure no other node can get focus
cashTable.focusedProperty().onChange {
val focusOwner = currentStage?.scene?.focusOwnerProperty()?.value
// Check if the focus owner is the table or a cell
if (focusOwner !is TableView<*> && focusOwner !is TextField) {
cashTable.requestFocus()
}
}
}
}
You should use
Platform.runLater(() -> {
requestFocus();
scrollTo(lastRow);
...
});
to update the GUI.

How to get SQLite Id from RecycleView adapter in Kotlin?

Sorry, I'm bad in English. I'm beginner in Android Studio.
I building same CREATE, UPDATE AND DELETE application in Android Studio, using custom RecycleView. But, I confused how to get row data when recycleview item clicked.
Can I get visible text in RecycleView item or not? Or can I get that data directly from SQLite.
How to get SQLite ID from this adapter?
package gh.alwathan.sipinsyar
import android.support.v7.widget.PopupMenu
import android.support.v7.widget.RecyclerView
import android.view.*
import android.widget.TextView
import android.widget.Toast
class NasabahAdapter(val userList: ArrayList<Nasabah>) : RecyclerView.Adapter<NasabahAdapter.ViewHolder>() {
//this method is returning the view for each item in the list
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NasabahAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_nasabah, parent, false)
return ViewHolder(v)
}
//this method is binding the data on the list
override fun onBindViewHolder(holder: NasabahAdapter.ViewHolder, position: Int) {
holder.bindItems(userList[position])
holder.itemView.setOnLongClickListener {
//holder.nametxt.setText(players.get(position).getName());
//creating a popup menu
val popup = PopupMenu(holder.itemView.getContext(), holder.itemView, Gravity.RIGHT)
//inflating menu from xml resource
popup.inflate(R.menu.nasabah_item)
//adding click listener
popup.setOnMenuItemClickListener(PopupMenu.OnMenuItemClickListener { item: MenuItem? ->
when (item!!.itemId) {
R.id.nasabah_id-> {
// GET SQLITE ID FROM HERE
}
}
true
})
//displaying the popup
popup.show()
true
}
}
//this method is giving the size of the list
override fun getItemCount(): Int {
return userList.size
}
//the class is hodling the list view
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(user: Nasabah) {
val textViewName = itemView.findViewById(R.id.nasabah_id) as TextView
val textViewLastName = itemView.findViewById(R.id.nasabah_nama) as TextView
textViewName.text = user.id
textViewLastName.text = user.nama
}
}
}
data class Nasabah(val id: String, val nama: String)

JavaFX8: How to create listener for selection of row in Tableview?

I currently have two tableviews in one screen, which results in both TableViews have rows which the user can select.
Now I want only one row to be selected at the same time (doesn't matter which TableView it is selected from). I was thinking about some kind of listener which deselects the other row when a row is selected. This is my initial setup:
Step 1
Search for a way to bind a method to the selection of a row (there is not something like tableview.setOnRowSelected(method))
Step 2
Create the method which acts like a kind of listener: when a row is selected, deselect the other row (I know how to do this part)
Class1 selectedObject1 = (Class1)tableview1.getSelectionModel().getSelectedItem();
Class2 selectedObject2 = (Class2)tableview2.getSelectionModel().getSelectedItem();
if(selectedObject1 != null && selectedObject2 != null) {
tableview1.getSelectionModel().clearSelection();
}
So, step one is the problem. I was thinking of an observable list on which a listener can be created, and then add the selected row to the list. When this happens, the listener can call the method.
Anyone any clue how to make this?
Any help is greatly appreciated.
The selectedItem in the selection model is an observable property, so you should be able to achieve this with:
tableview1.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
tableview2.getSelectionModel().clearSelection();
}
});
tableview2.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
if (newSelection != null) {
tableview1.getSelectionModel().clearSelection();
}
});
My solution would be creating custom cell factory for table and set it for each table columns.
Callback<TableColumn<..., ...>, TableCell<..., ...>> value = param -> {
TextFieldTableCell cell = new TextFieldTableCell<>();
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
//your code
}
);
return cell;
};
packageName.setCellFactory(value);
table1.column1.setCellFactory();
table2.column1.setCellFactory();
...
I use it for deleting the chosen row.
public void ButtonClicked()
{
ObservableList<Names> row , allRows;
allRows = table.getItems();
row = table.getSelectionModel().getSelectedItems();
row.forEach(allRows::remove);
}
This question helped me but during experiment in javafx and jfoenix this also works for me.
deleteSingle.addEventHandler(MouseEvent.MOUSE_CLICKED, (e) -> {
StringProperty selectedItem = table.getSelectionModel().getSelectedItem().getValue().link1;
System.out.println("That is selected item : "+selectedItem);
if (selectedItem.equals(null)) {
System.out.println(" No item selected");
} else {
System.out.println("Index to be deleted:" + selectedItem.getValue());
//Here was my database data retrieving and selectd
// item deleted and then table refresh
table.refresh();
return;
}
});
In case you need not only the row, but the x|y position of the table cell, do this:
table.getFocusModel().focusedCellProperty().addListener(
new ChangeListener<TablePosition>() {
#Override
public void changed(ObservableValue<? extends TablePosition> observable,
TablePosition oldPos, TablePosition pos) {
int row = pos.getRow();
int column = pos.getColumn();
String selectedValue = "";
if (table.getItems().size() > row
&& table.getItems().get(row).size() > column) {
selectedValue = table.getItems().get(row).get(column);
}
label.setText(selectedValue);
}
});
In this example, I am using a "classic" TableView with List<String> as column model. And, of course, that label is just an example from my code.

Resources