Unable to update button display data - button

I am learning Kotlin and TornadoFX to program a very simple task: two buttons, one with Int 1 and other with Int 200. When I click the button one, I would like to update and display button one with Int 2 and the other with Int 201. The following program will allowed me to check the update data and all seemed to be correct. My question is why the display on the buttons would not be updated according.
Please help.
class MainView : View("Hello TornadoFX") {
val controllera: ControllerA by inject()
val i: Int? = 1
val j: Int? = 2
override val root = hbox {
button(controllera.s1.toString()) {
setOnAction {
setId(i.toString())
println(" in view button 1 s1: { ${controllera.s1.toString()} }")
controllera.temp(i)
}
}
button(controllera.s2.toString()) {
setOnAction {
setId(j.toString())
println(" in view button 2 s2: { ${controllera.s2.toString()} }")
controllera.temp(j)
}
}
label(title) {
addClass(Styles.heading)
}
}
}
class ControllerA :Controller(){
var s1: Int =1
var s2: Int = 200
fun temp(k: Int?) {
when( k) {
1 -> {println("botton ID " +k)
println(" current value of s1: $s1 ")
s1 = s1+1
println(" new value of s1 to update: $s1 ")
s2 = s2 +1
println(" new value of s2 to update: $s2 ")}
2 -> {
println("botton ID " + k)
println("current value of s2: $s2 ")}
else -> println("default")
}
}
}
test output at various location:
in view button 1 s1: { 1 } //In MainView
botton ID 1 //in ControllerA
s1: 1
new value of s1 to update: 2
new value of s2 to update: 201
in view button 2 s2: { 201 } //in MainView
botton ID 2 //in ControllerA
current value of s2: 201
Note: those updated data will not be display on button text.

You need to use observable properties and bindings. Just starting writing code without reading about the framework is not a good approach. I suggest you read the guide as a starting point. Here is your code rewritten so that each button will display and increase the corresponding number on click:
class MainView : View("Hello TornadoFX") {
val controllera: ControllerA by inject()
val myModel: MyModel by inject()
override val root = hbox {
button(myModel.s1.asString()) {
action {
controllera.inc(myModel.s1)
}
}
button(myModel.s2.asString()) {
action {
controllera.inc(myModel.s2)
}
}
label(title)
}
}
class MyModel : ViewModel() {
val s1 = SimpleIntegerProperty(1)
val s2 = SimpleIntegerProperty(200)
}
class ControllerA : Controller() {
fun inc(v: IntegerProperty) {
v.value = v.value + 1
}
}

Related

remove fragment in viewPager2 and FragmentStateAdapter is not working

I've 3 Fragment - F1, F2, and F3. I've attached them in a PagerAdaper (viewPager2). The initialization of the PagerAdapter in PagerFragment (in onCreateView) is -
binding = PagerFargmentBinding.inflate(inflater, container, false)
adapter = PagerAdapter(this)
binding?.viewPager?.adapter = adapter
binding?.viewPager?.isSaveEnabled = false
Now, My PagerAdapter Implementation -->
class PagerAdapter(
fragment: PagerFragment,
) : FragmentStateAdapter(fragment) {
private var nextFragment: BaseFragment? = null
private var baseFragment: BaseFragment = F1()
override fun getItemCount() = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> {
baseFragment
}
else -> {
nextFragment ?: DummyDialogFragment()
}
}
}
fun setNextFragment(page: Int) {
nextFragment = null
nextFragment = if (page == 1) {
F2()
} else if (page == 2) {
F3()
} else {
null
}
}
}
Now From my Pager Fragment I'm setting the next fragment -
fun navigateTo(page: Int) {
adapter.setNextFragment(page)
if (page == 0) {
binding?.viewPager?.currentItem = page
} else {
binding?.viewPager?.currentItem = 1
}
}
And From F1 fragment I'm calling this function with 1 and 2. This is working fine for the first time.
If First time, If I load F2 fragment as a nextFragment then F3 fragment is not displaying. I've tried debugging and found out that the nextFragment value is updating but somehow the added fragment is not being removed from the pager Adapter.
Is there any way so that I can add / remove fragment dynamically?
I've taken implementation idea from this block - https://intensecoder.com/android-swipe-fragments-with-viewpager2-in-kotlin/
Well after quite a bit of exploring over stackoverflow and then the help of one of my peer, I've figure out the solution. The solution is -
Actually the problem was, the createFragment() was not triggering
after the first time. So I've to override two function of
StatePagerAdapter - getItemViewType() and getItemId(). My Implementation
now -
class PagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {
private var viewType: Int = 0
override fun getItemCount() = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> {
F1()
}
1 -> {
F2()
}
else -> {
F3()
}
}
}
override fun getItemViewType(position: Int): Int {
return when (position) {
0 -> 0
else -> viewType
}
}
override fun getItemId(position: Int): Long {
return when (position) {
0 -> 0
else -> viewType.toLong()
}
}
fun setNextFragment(page: Int) {
this.viewType = page
}
}
and Calling this from pagger fragment -
fun navigateTo(page: Int) {
adapter.setNextFragment(page)
if (page == 0) {
binding?.viewPager?.currentItem = page
} else {
binding?.viewPager?.currentItem = 1
}
}
The setNextFragment() assigning the desired F2() or F3() fragment. And by the help of the override implementation on getItemId() and getItemViewType() the createFragment() is getting called each time and thus I can add / remove fragment dynamically.

JavaFX ListChangeListener: getPermutation() not working

I use ListChangeListener to listen to changes in Tab Pane.
private final TabPane tabBar = new TabPane();
...
tabBar.getTabs().addListener(this::tabsChanged);
I'm trying to listen to tab move events with the following code:
private void tabsChanged(ListChangeListener.Change<? extends Tab> change) {
while (change.next()) {
if (change.wasPermutated()) {
for (int i = change.getFrom(); i < change.getTo(); i++) {
System.out.println(i + " -> " + change.getPermutation(i));
}
}
}
}
As JavaFX documentation says:
In order to get the new position of an element, you must call:
change.getPermutation(oldIndex). Returns: the new index of the same
element.
But in my case change.getPermutation(i) always returns just i.
For example, I have 4 tabs.
Their indexes are: 0, 1, 2, 3.
Then I move the 4th tab to the first position.
I expect the following output:
0 -> 1
1 -> 2
2 -> 3
3 -> 0
But I get:
0 -> 0
1 -> 1
2 -> 2
3 -> 3
How can I make it work as I need?
As already noted in the comments: the behavior you observe is a bug just reported as JDK-8278062 - the doc and your expectation based on the doc is correct, the notification (implemented in the internal class TabObservableList) is wrong.
Normally, if we want to find the newIndex, a listChangeListener would do something like:
for (int oldIndex = c.getFrom(); oldIndex < c.getTo(); ++oldIndex) {
int newIndex = c.getPermutation(oldIndex);
...
}
To work around the issue, we could manually keep a copy of the tabs, lookup the tab at the old index and find its new index in the re-ordered tabs:
for (int oldIndex = c.getFrom(); oldIndex < c.getTo(); ++oldIndex) {
Tab tab = copy.get(oldIndex);
int newIndex = c.getList().indexOf(tab);
...
}
// update the copy
Or we could have some fun and implement a TransformationList around the original tabs that does the work for us :) It jumps in when it detects a permutation and fires the correct notification. Note that the only internal class used below is SourceChangeAdapter, we either need to relax encapsulation or c&p its content (it is doing nothing but pass on notifications on behalf of the wrapper)
public class TabObservableListWrapper extends TransformationList<Tab, Tab> {
// copy of source used to build the correct permutation
private ObservableList<Tab> copy = FXCollections.observableArrayList();
public TabObservableListWrapper(ObservableList<Tab> source) {
super(source);
updateCopy();
}
#Override
protected void sourceChanged(Change<? extends Tab> c) {
// TBD: cope with a change that has
// - a mixture of permutation and other subchanges
// - multiple subchanges of type permutation
boolean isPermutation = false;
// check if the change is a permutation
while (c.next()) {
if (c.wasPermutated()) {
isPermutation = true;
break;
}
}
c.reset();
if (isPermutation) {
beginChange();
updatePermutation(c);
endChange();
} else {
// assuming other change type notifications are correct, just delegate
fireChange(new SourceAdapterChange<>(this, c));
}
// keep copy sync'ed to source
updateCopy();
}
/**
* Converts the incorrect permutation notification from source
* into a correct one and let super fire the appropriate change.
*
* Note: this method must be called inside a begin/endChange block.
* #param c a change with a single subChange of type wasPermutated
*/
private void updatePermutation(Change<? extends Tab> c) {
c.next();
int from = c.getFrom();
int to = c.getTo();
int permSize = to - from;
int[] perm = new int[permSize];
// fill the perm
for(int i = 0; i < permSize; i++) {
int oldIndex = from + i;
Tab tab = copy.get(oldIndex);
perm[i] = c.getList().indexOf(tab);
}
nextPermutation(from, to, perm);
}
// keep copy sync'ed
private void updateCopy() {
copy.setAll(getSource());
}
// implement public methods by delegating 1:1 to source
#Override
public int getSourceIndex(int index) {
return index;
}
#Override
public int getViewIndex(int index) {
return index;
}
#Override
public Tab get(int index) {
return getSource().get(index);
}
#Override
public int size() {
return getSource().size();
}
}
To use, wrap it around a tabPane's tab list and listen to the wrapper instead of directly to original list, something like:
TabObservableListWrapper wrapper = new TabObservableListWrapper(tabPane.getTabs());
wrapper.addListener((ListChangeListener<Tab>)change -> {
while (change.next()) {
if (change.wasPermutated()) {
System.out.println("from wrapper:");
for (int oldIndex = change.getFrom(); oldIndex < change.getTo(); oldIndex++) {
System.out.println(oldIndex + " -> " + change.getPermutation(oldIndex));
}
}
}
});

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 iterate a tableView in Javafx or Scalafx?

I am quite new at Scala. I use a tableView that has the custom type "Constraint", and I need to iterate it by the row.
Here is where I have gotten so far:
class ConstraintView(val stage : Stage) {
var obs : ObservableBuffer[Constraint]=ObservableBuffer()
var listConst : List[ConstraintTrait]=List()
val checkColumn=new TableColumn[Constraint, java.lang.Boolean] {
text = ""
cellValueFactory=_.value.initialSelection.delegate
}
checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(checkColumn))
stage.title="Constraint Table View"
stage.scene=new Scene {
val tab=new TableView[Constraint](obs) {
editable=true
columns ++=List(checkColumn,
new TableColumn[Constraint, String] {
text="Constraint Name"
cellValueFactory=_.value.constraintNameProp
},
new TableColumn[Constraint, String] {
text = "Result"
cellValueFactory=_.value.resultProp
}
)
}
content=tab
}
In Java, you would do
for (Constraint c : tab.getItems()) {
// do something with c
}
or
tab.getItems().forEach(c -> {
// do something with c
});
I don't know Scala, but perhaps this is enough for you to be able to translate.

Resources