listview in TornadoFX displaying duplicated items when using cache-form - kotlin

I'm loading JSON data from internet, capturing data about items, like name, author and imageurl. Then I want to load them one under the other so I put them into the listview. I only add them once as a custom class, that only holds those variables.
I'm having a problem with those values duplicating and not appearing as they should. For example, it would load first 5 items(out of 20) and it would repeat them over the remaining 15. I don't understand why that's happening, also tried with looping over listview's items array and printing them out and they are all different, also tried doing refresh() on them, but it doesn't seem to change anything at all.
I'm adding a code that I use to create the listview and the piece that I'm using to fill it in.
val lv = listview<Item>{
anchorpaneConstraints {
topAnchor = 0.0
bottomAnchor = 0.0
leftAnchor = 0.0
rightAnchor = 0.0
}
cellFormat {
graphic = cache {
form {
fieldset {
hbox {
spacing = 10.0
println(it.name)
println(it.author)
println(it.imgurl)
println(it.desc)
imageview {
image = Image(it.imgurl)
prefWidth(256.0)
prefHeight(256.0)
}
vbox {
field("Name") {
label(it.name)
}
field("Author") {
label(it.author)
}
field("Description") {
label {
text = it.desc
wrapWidth = 150
}
}
}
}
}
}
}
}
}
val tmpItems = items.clone() as ArrayList<JsonObject>()
val arr = ArrayList<Item>()
for (m in tmpItems) {
arr.add(
Item(
m["name"].toString(),
m["author"].toString(),
m["desc"].toString(),
m["imgUrl"].toString()
)
)
}
lv.items.addAll(arr)
I expected the output to be 20 unique items, as that's what's in the lv.items, but the shown result is 5 unique items repeated over 20 lines.

When using cache you need to specify a unique id for each item, so the framework knows how to retrieve the cached ui elements for the currently displayed item in a given table cell. This is explained in detail in the javadoc for the cache function.
If you have an id field in your item, you can use that for example:
cache(rowItem.id) { }
You could even use the value for the cell, if that's unique:
cache(it) { }

Related

Optimise Kotlin code for button clicked state

I want to change the button background when the button clicked, the function is work by using this code
bank1.setOnClickListener {
bank1.setBackgroundResource(R.drawable.selected_btn_border_blue_bg);
bank2.setBackgroundResource(R.drawable.default_option_border_bg);
bank3.setBackgroundResource(R.drawable.default_option_border_bg);
bank4.setBackgroundResource(R.drawable.default_option_border_bg);
}
bank2.setOnClickListener {
bank2.setBackgroundResource(R.drawable.selected_btn_border_blue_bg);
bank1.setBackgroundResource(R.drawable.default_option_border_bg);
bank3.setBackgroundResource(R.drawable.default_option_border_bg);
bank4.setBackgroundResource(R.drawable.default_option_border_bg);
}
bank3.setOnClickListener {
bank3.setBackgroundResource(R.drawable.selected_btn_border_blue_bg);
bank2.setBackgroundResource(R.drawable.default_option_border_bg);
bank1.setBackgroundResource(R.drawable.default_option_border_bg);
bank4.setBackgroundResource(R.drawable.default_option_border_bg);
}
bank4.setOnClickListener {
bank4.setBackgroundResource(R.drawable.selected_btn_border_blue_bg);
bank2.setBackgroundResource(R.drawable.default_option_border_bg);
bank3.setBackgroundResource(R.drawable.default_option_border_bg);
bank1.setBackgroundResource(R.drawable.default_option_border_bg);
}
But it kinda hardcoded, and make it to so many lines, any way to make the code shorter?
I would keep a variable that keeps track of the selected one like
private var selectedBank: View? = null
And then do
arrayOf(bank1, bank2, bank3, bank4).forEach {
it.setOnClickListener {
selectedBank?.setBackgroundResource(R.drawable.default_option_border_bg)
selectedBank = it
it.setBackgroundResource(R.drawable.selected_btn_border_blue_bg)
}
}
you only need to deselected the previous selected one

Some basic questions about Kotlin

I am trying to learn Kotlin and TornadoFX, and I am working from this repository
The idea currently is that the app will show three rows of 7 buttons. Each button represents a letter which is either known or unknown. If the letter is known, it is displayed on the button. If the letter is unknown, the button displays the index (0-20).
I did this by creating (using Java thinking I'm sure) a global array with 21 elements. Each element starts out null. I defined this in the main app, using the companion construct:
class InteractiveClientApp : App(MainView::class) {
companion object {
var knownLetters = arrayOfNulls<String>(21)
}
Then in the App constructor, I initialized a few random elements with values, just to see if this would work (it doesn't).
override fun init() {
knownLetters[4] = "T"
knownLetters[9] = "G"
knownLetters[17] = "Z"
}
Then in the LettersGridView I use forEachIndexed on the knownLetters array, so I would have access to the element from the array and the index for it.
import jcn.deduce.client.InteractiveClientApp.Companion.knownLetters
class LettersGridView : View() {
override val root = gridpane {
knownLetters.forEachIndexed { index, element ->
if (index == 0 || index % 7 == 0) {
row {
button(element?.toString() ?: index.toString()) {
useMaxWidth = true
gridpaneConstraints {
marginBottom = 10.0
}
}
}
} else {
button(element?.toString() ?: index.toString()) {
useMaxWidth = true
gridpaneConstraints {
marginBottom = 10.0
}
}
}
}
}
}
Whats actually happening is three buttons appear instead of the expected 21, and the value on the button is always the index, never a letter value. Also the indexes shown are 20, 7 and 14. Not the three I used when setting elements in the array. So I am missing something there.
Also I think I am not understanding this bit correctly:
button(element?.toString() ?: index.toString()) {
useMaxWidth = true
gridpaneConstraints {
marginBottom = 10.0
}
}
What I am trying to say there is "If the element value is not null, use the element value, otherwise use the string value of index. This isn't working, because the buttons only ever have indexes on them, never letters.
I notice if I leave off the .toString() on element, I get an error that button expects String, not string?. Which seems somewhat similar to Java and String vs Optional < String >. However, when I add the toString(), I get an IDE warning that the toString() is redundant.
If I take off the trailing ? altogether, I get a clean compile, but still only three buttons render, and their labels are index, not element.
So I am pretty sure I got lost somewhere along the way, can anyone explain why my program isn't working?
Also, when I am debugging the app, I always wind up with two processes. I don't understand why, but this is what IntelliJ looks like:
Is this normal?
Your init function is working, you can confirm this by changing the initialisation of entry 20, 7 or 14 to a letter and you should see a letter appear when you next run it.
As for your main issue, the reason you are seeing 20, 7 and then 14 is because in this section:
if (index == 0 || index % 7 == 0) {
row {
button(element?.toString() ?: index.toString()) {
useMaxWidth = true
gridpaneConstraints {
marginBottom = 10.0
}
}
}
}
You are adding a row with a single button, these buttons will be 0, 7 and 14 (since they are all == 0 % 7). This means you'll only ever add three rows, each with one button on. You might be confused as to why it says 20 instead of 0... Well this is because the next section:
else {
button(element?.toString() ?: index.toString()) {
useMaxWidth = true
gridpaneConstraints {
marginBottom = 10.0
}
}
}
Is also adding buttons, but not onto any row (notice how these buttons aren't inside a row { } lambda). This means all these buttons will get added to the gridpane on top of eachother, including that first 0 button. The last button to be added is 20, hence why you see 20, it is covering the 0!
Here's an example of how to approach this problem:
val rowSize = 7
val rows = knownLetters.toList().chunked(rowSize)
rows.forEachIndexed { rowIndex, elements ->
row {
elements.forEachIndexed { buttonIndex, element ->
val displayIndex = rowSize * rowIndex + buttonIndex
button("${element ?: displayIndex}") {
useMaxWidth = true
gridpaneConstraints {
marginBottom = 10.0
}
}
}
}
}
This takes the Kotlin libraries' "chunked" method to divide your 21 size array into three lists of size 7. You can then loop through (you do have to piece back together the display index with this approach) creating a new row for each list (3 lists makes 3 rows), whilst creating your buttons in a nested loop inside the row's lambda.
The key thing to take away here is that not all your buttons are being added within a row { } lambda.
As for the double process issue, I do not have this issue if I run the app using a main method like so:
fun main(args: Array<String>) {
launch<InteractiveClientApp>(args)
}
Hope this helps!

how to show different value of single store in 2 combo box?

I have Problem with Store in ExtJS 4. In my application I want to use two combo. A store values are displaying in both combo. But the problem is the value selected in first combo should not get shown in next combo. Due to single store I'm unable to make it happen..What should have to do ?
This Works for me
listeners : {
expand: function() {
boxerListStore.filter(function(r) {
var value = r.get('id');
var getValue = Ext.getCmp('boxer2').getValue();
return (value != getValue);
});
}
}
You can use select event of first combo like -
listeners: {
select: function(combo, records, eOpts){
Ext.getCmp('secondCombo').select(records[0]);
}
}

How to access item in a ListDelegates / ListView outside of it?

I am new to Qt / QML coding and I am facing one problem with respect to accessing the elements in a listdelegate in a listview.
For Example if my Qml looks like this
Item
{
id: item_id
property int focus_id: 0
function setFocusImageSource() {}
ListView
{
id: listView_Id
delegate: listViewdelegate
model: listModeldata
}
Component
{
id: listViewdelegate
Rectangle
{
id: rectangle_id
Image
{
id: focus_Image
source: x.x
}
}
}
ListModel
{
id: listModeldata
/*elements*/
}
}
Now the basic functionality of the list view is working fine with my code ( not the above one ) how ever when I do specific operation I need to change the focusing Image . I want to change it using the function "setFocusImageSource()" . I have tried setting the image source directly using focus_Image.source = "xx" .
Is it like the Image inside the Rectangle component is local to the delegate and cannot be accessed from ITEM tag. If so How can I set the image from the function mention above.
Thanks in advance.
Chand.M
A counterpart of QML Component in C++ is a class. As you know you can change members' values only in class' instances - objects. The same is true for Components too: you can not change anything in Component - only in its instances. There are two possibilities to solve you problem:
Bind properties of listViewdelegate to some property outside of it: property of item_id or listView_Id or something else.
Bind properties of listViewdelegate to some property of the element of listModeldata.
Examples:
Image {
id: focus_Image
source: x.x // defualt value
Connections {
target: item_id
onFocus_idChanged: {
if ( /* some logic if needed */ ) {
focus_Image.source = xx;
}
}
}
}
or
Image {
id: focus_Image
source: {
// inidicator is a property of the element of listModeldata
if (indicator) {
return xx;
}
}
}

Search in dijit.Tree

In one of my projects I use a dijit.Tree control. I need to add a search to the tree and show only those nodes/leafs which have the searched term in them. However I can't seem to figure out how that can be achieved. Could anybody please help me?
im not entirely sure that your question entirely but it should give hint whereas to go.
Lets use reference documentation example as offset, there is 1) a store 2) a model and 3) the tree
var store = new ItemFileReadStore({
url: "{{dataUrl}}/dijit/tests/_data/countries.json"
});
var treeModel = new ForestStoreModel({
store: store,
query: {"type": "continent"}, // note, this bit
rootId: "root",
rootLabel: "Continents",
childrenAttrs: ["children"]
});
new Tree({
model: treeModel
}, "treeOne");
Interpret the above as such; You have loaded all known countries and continents but 'user' has selected only to show continents by using query on the model - and the hierachy is then represented in a tree structure.
You want a textbox with searching capeabilities, so we hook into onChange
new dijit.form.TextBox({
onChange: function() {
...
}
});
First bit, getting variables
var searchByName = this.get("value");
var oQuery = treeModel.query;
Next, set a new query on the model - preserving the old ones with an object mixin
treeModel.query = dojo.mixin(oQuery, { name: '*'+searchByName+'*' });
Last, notify the model and its tree that changes has occurred - and requery the visible items.
treeModel._requeryTop();
NB If the top-level item (for ForestModel) is not visible, none of its child elements will show, even if the search-string matches those. (Examplewise, Alabama is not shown if US Continent is not matched by query)
EDIT
As OP has the agenda to go by the 'NB', this may not fit needs 100% but its what dojo offers with dijit.Tree.. As it will get rather a lengthy process to recode the model/store queries to include parentbranches up until root i will not do this here - but there are a few tricks still ;)
var tree = new dijit.Tree( {
/**
* Since TreeNode has a getParent() method, this abstraction could be useful
* It sets the dijit.reqistry id into the item-data, so one l8r can get parent items
* which otherwise only can be found by iterating everything in store, looking for item in the parent.children
*
*/
onLoad : function() {
this.forAllNodes(function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
},
/* recursive iteration over TreeNode's
* Carefull, not to make (too many) recursive calls in the callback function..
* fun_ptr : function(TreeNode) { ... }
*/
forAllNodes : function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
}
});
(non-tested, but might just work) Example:
// var 'tree' is your tree, extended with
tree.forAllNodes = function(parentTreeNode, fun_ptr) {
parentTreeNode.getChildren().forEach(function(n) {
fun_ptr(n);
if(n.item.children) {
n.tree.forAllNodes(fun_ptr);
}
})
};
// before anything, but the 'match-all' query, run this once
tree.forAllNodes(tree.rootNode, function(node) {
// TreeNode.item <-- > store.items hookup
node.item._NID = node.domNode.id
});
// hopefully, this in end contains top-level items
var branchesToShow = []
// run fetch every search (TextBox.onChange) with value in query
tree.model.store.fetch(query:{name:'Abc*'}, onComplete(function(items) {
var TreeNode = null;
dojo.forEach(items, function(item) {
TreeNode = dijit.byId(item._NID+'');
while(TreeNode.getParent()
&& typeof TreeNode.getParent().item._RI == 'undefined') {
TreeNode = TreeNode.getParent();
}
branchesToShow.push(TreeNode.item);
});
}});
// Now... If a success, try updating the model via following
tree.model.onChildrenChange(tree.model.root, branchesToShow);