Android Question Capturing the selected item in the XCustomListView control

beelze69

Active Member
Licensed User
Longtime User
Hi!,

This is regarding XCustomListView example


Few queries.

1)Is is perfectly acceptable to send an integer (or long) value for the 'Value' Object into the

Add method of the CustomListView and capture that value in a String variable ?



I explain my requirement with an Example:

1)Assume that a control is created of CustomListView by name CLV1

Assume that the records of a database are to be displayed in the CustomListView CLV1

2) There is a layout file 'lytCLVItem' containing 2 labels lblCLVItemKey and lblCLVItemDescription

3) In the calling subroutine after capturing the values from the database (via JRDC2), assume that
the records is stored in 'res' (of DBResult) .

4) Assume that row(0) references value for 'Item key' and row(1) references value for 'ITEM Description'
where 'Item key' and 'Item Description' are the fields of a table whose data was fetched via JRDC2

5) Now the Add method for CLV1 is invoked as :

strItemKey=NumberFormat(row(0),0,0)
strItemDescription=row(1)

'Where the above strItemKey and strItemDescription are string variables declared for temporarily storing the field values as we sift through each row of the result



6) The Add method is invoked as

CLV1.Add( CreateMyListItem(CLV1.AsView.Width,80dip, strItemKey,strItemDescription), strItemKey)


NOTE: the strItemKey is a string variable being passed for Value Object..

6) The CreateMyListItem is coded as

Sub CreateMyListItem(Width as int, Height as Int, parItemKey as string, parItemDescription as string) as Panel
Dim p as B4XView=xui.CreatePanel("")
p.SetLayoutAnimated(0,0,0,Width,Height)
p.LoadLayout("lytCLVItem")
lblClvItemKey.text=parItemKey
lblCLVItemDescription.text=parItemDescription
Return P
end Sub

7) Sub CLV1_ItemClick (Index As Int, Value As Object)

dim strSelectedValue as string

strSelectedValue=Value



End Sub

My doubt:

i) whether strSelectedValue=Value is a proper assignment ..

I mean, it works for me but I am not sure as to whether I can

'assign an Object type to a String variable'.

ii) Assuming I have around 5000 such items, is it acceptable to assign this table unique key for every such item

This currently solves my problem of identifying which item was clicked so that I can do further activities on the basis of the user-selection.

But I want to know if there is a better way


8) How to write the code such that when I click on the lblClvItemKey , I should be able to get the item that has been clicked ?


Thanks.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Please use regular font and code tags.

You can put whatever you like in the Value field.

8) see the example. You need to use Sender + CLV.GetItemFromView.
 
Upvote 0

beelze69

Active Member
Licensed User
Longtime User
Please use regular font and code tags.

You can put whatever you like in the Value field.

8) see the example. You need to use Sender + CLV.GetItemFromView.
Hi Erel !

Thanks ! I am almost there ..but there is a small issue ..
Attaching the code snippet.
B4X:
Sub lblCLVItemKey_Click
    Dim index As Int = CLV1.GetItemFromView(Sender)
    Dim pnl As B4XView = CLV1.GetPanel(index)
    Dim lblkey As B4XView = pnl.GetView(0)
    Dim lblDescription As B4XView=pnl.GetView(1)
    MsgboxAsync("You selected:" & lblkey.Text,"Test")
    MsgboxAsync("You selected:" & lblDescription.Text,"Test")
End Sub

It is showing The description value for the First Message box and the Key value in the second message box , when it should be the other way around..

My doubt:

1) How to identify the correct order of the controls in the layout when the controls are of same type eg. here label ?
Where am I going wrong in the above code ? ..Ps. correct me..

2) Is it the right approach to try to get the Text value (or any property, for that matter) of 'control 1' from within the click event of another control 'control 2' via this 'Sender approach'.

For example, Here I am trying to get the Text value of the 'description control' from within the click event of 'key control'..

I require this because I want BOTH the KEY and the DESCRIPTION irrespective of whether the end-user clicks on the 'Description' Label or the 'Key' Label...


So if you say it is right then I will add a similar code in my lblCLVItemDescription_Click event also to capture both the Key and the Description ...

2.1) is this correct ?

And

2.2) is there a better way to do this ?




Thanks ..
 
Last edited:
Upvote 0

Mahares

Expert
Licensed User
Longtime User
It is showing The description value for the First Message box and the Key value in the second message box , when it should be the other way around..
Open the items layout designer and check the order in the views tree. That is the correct order you should use. You probably have the description label in the first position and the other in the second. If not, take a snapshot of the tree and post it.
2) Is it the right approach to try to get the Text value (or any property, for that matter) of 'control 1' from within the click event of another control 'control 2' via this 'Sender approach'.
Yes of course
By the way Erel asked you to use regular font. What he meant is not to use bold when you post.
 
Upvote 0

Albert Kallal

Active Member
Licensed User
ii) Assuming I have around 5000 such items, is it acceptable to assign this table unique key for every such item

This currently solves my problem of identifying which item was clicked so that I can do further activities on the basis of the user-selection.

But I want to know if there is a better way

Well, you CAN pull the values from the row by using index.

However, 5,000 rows? Gee, that number is too high. (how can one scroll that many? - it going to push the rendering really hard - too big).
But, lets leave that too high row count for another day.

So, you can actually pull the ONE row based on index, and if that display of data does include the PK, then you can pull it that way. I guess in theory then you would save the memory and resources of having that extra PK value being available with the "value" saved again.

So, assuming we used the designer to create that one row we create?
And assuming you say set the tag name of each control on that one row that you pushed into the CLV?

Then you can do this:

B4X:
Sub CustomListView1_ItemClick (Index As Int, Value As Object)
  
    Log("Row click = " & Index)
    Log("PK value - " & Value)
  
    ' get row data
    Dim Pnl As Panel = CustomListView1.GetPanel(Index)
  
    ' pull values from panel
    Log("PK id = " & GetLabel("ID",Pnl))
    Log("HotelName = " & GetLabel("HotelName",Pnl))
    Log("City Name = " & GetLabel("City",Pnl))
  
End Sub

Sub GetLabel (sTag As String,p As Panel) As String
  
    For Each vItem As View In p
        If vItem.Tag = sTag Then
            Dim l As Label = vItem
            Return l.Text
        End If
    Next
  
    Return ""

So, in above, I also went with shoving PK value into the object. It works nice.

However, by pulling the other values - then I don't have to hit the database again. I mean, after all, if the data you need is in that one row, then you can grab it as per above. I of course included the little "helper" function to get that value via tag.

However, these views really don't work well beyond say 80, maybe 200 tops is really all you want to push into that view.

The amount of rendering for each row is tends to be expensive.

I would look for a road/path in which you limit the rows loaded.

In summary?
I will say that xCustomList view is among my favorite UI choices.
(and it lets me layout and build a single row with the designer).


Regardless of the high resources for that many rows?

Yes it is rather common to use the "value" option for the PK. However, as above shows, you can quite well with relative ease pull out the values from that one row based on index.

But, it would be VERY cool if LoadLayout exposed the pony trick that it does when you load that layout (behind the scenes it auto-maps the controls to existing controls defined in your globals section. Would LOVE to know how that works, but better yet would be say:

LoadLayOutControls(MyPanel).

In other words, I would not have to use "tag", and the above helper loop, but simply do say this:

B4X:
    ' get row data
    Dim Pnl As Panel = CustomListView1.GetPanel(Index)
    LoadLayOutControls(Pnl)

    ' pull values from panel
    Log("PK id = " & lblID.Text)
    Log("HotelName = " & lblHotelName.Text)
    Log("City Name = " & lblCity.Text)

I mean, somehow, during the loadlayout process, behind code knows how to setup the values of the controls that exist in the Globals section. It would be really nice to have a command to "trigger" that process based on a given panel. This would save lots of coding, no need for tags, and better yet we could with great ease pull values from a panel/view that we just loaded, or in this case grabbed from the CLV.

I thus would not need tags, not need a helper loop function either.

Anyway, venturing off topic! As noted, you can get the row data - you just have to ensure that the controls that made up that row have a "tag" set, and then you can get those controls based on "something" such as tag name.

As for the code I used via the designer and one row?

Well, I sure you had/have something like this:

B4X:
Sub CreateRow(rst As ResultSet) As Panel
  
    Dim p As     B4XView = xui.CreatePanel("")
    p.SetLayoutAnimated(0,0,0,100%x, 45dip)

    p.LoadLayout("OneRow")
  
    lblID.Text = rst.GetInt("ID")
    lblHotelName.Text = rst.GetString("HotelName")
    lblCity.Text = rst.GetString("City")
  
    Return p

End Sub

So in above - you can see that "automatic" setting of the 3 label values I have - I did not have to write code to set those 3 labels up. But, I did have to work a bit to get them out when wanting to display after the click event. So we have a hug part as automatic - I just want the other kiss part!

Maybe I should post a new question on this issue - How to transfer values from a panel to the defined controls in Global section just like loadlayout is able to do via some magic trick!

Regards,
Albert D. Kallal
Edmonton, Alberta Canada
 
Last edited:
Upvote 0

beelze69

Active Member
Licensed User
Longtime User
Open the items layout designer and check the order in the views tree. That is the correct order you should use. You probably have the description label in the first position and the other in the second. If not, take a snapshot of the tree and post it.

Yes of course
By the way Erel asked you to use regular font. What he meant is not to use bold when you post.
Hi Mahares !

Thanks for the reply..

Attaching the snapshot of the ViewTree. The order is okay viz. 'lblCLVItemKey' followed by 'lblCLVItemDescription'. Attaching the screenshot as requested..

Actually, I checked it again.. There is no issue.. ... It is showing the Key and Description properly (since the MsgboxAsync in the above code example had 'You selected:' for both , I could not distinguish)

I.e. I replaced the highlighted 2 lines with
MsgboxAsync("Your selected key:" & lblkey.Text,"Test")
MsgboxAsync("Your selected Description:" & lblDescription.Text,"Test")

but it shows the Description value first and then the Key value !!... I mean in reverse

I mean- the code is getting evaluated in the 'reverse' of the order it has been typed in ...

Can you explain this ?


Another very peculiar behaviour I encountered ...

I removed all the code in the child item's click event and decided to go by the Main CLV_ItemClick event and noted that the click event responded VERY POORLY .. I mean I had to click the xCustomListView control many times forcefully before I got to trigger the click event at the Parent level !

I again enabled the child item's click event and noted that the 'response to the item click event was faster'...

Can you explain this ?

Thanks...
 

Attachments

  • treeSnapShot.png
    treeSnapShot.png
    21 KB · Views: 134
Last edited:
Upvote 0

beelze69

Active Member
Licensed User
Longtime User
Well, you CAN pull the values from the row by using index.

However, 5,000 rows? Gee, that number is too high. (how can one scroll that many? - it going to push the rendering really hard - too big).
But, lets leave that too high row count for another day.

So, you can actually pull the ONE row based on index, and if that display of data does include the PK, then you can pull it that way. I guess in theory then you would save the memory and resources of having that extra PK value being available with the "value" saved again.

So, assuming we used the designer to create that one row we create?
And assuming you say set the tag name of each control on that one row that you pushed into the CLV?

Then you can do this:

B4X:
Sub CustomListView1_ItemClick (Index As Int, Value As Object)

    Log("Row click = " & Index)
    Log("PK value - " & Value)

    ' get row data
    Dim Pnl As Panel = CustomListView1.GetPanel(Index)

    ' pull values from panel
    Log("PK id = " & GetLabel("ID",Pnl))
    Log("HotelName = " & GetLabel("HotelName",Pnl))
    Log("City Name = " & GetLabel("City",Pnl))

End Sub

Sub GetLabel (sTag As String,p As Panel) As String

    For Each vItem As View In p
        If vItem.Tag = sTag Then
            Dim l As Label = vItem
            Return l.Text
        End If
    Next

    Return ""

So, in above, I also went with shoving PK value into the object. It works nice.

However, by pulling the other values - then I don't have to hit the database again. I mean, after all, if the data you need is in that one row, then you can grab it as per above. I of course included the little "helper" function to get that value via tag.

However, these views really don't work well beyond say 80, maybe 200 tops is really all you want to push into that view.

The amount of rendering for each row is tends to be expensive.

I would look for a road/path in which you limit the rows loaded.

In summary?
I will say that xCustomList view is among my favorite UI choices.
(and it lets me layout and build a single row with the designer).


Regardless of the high resources for that many rows?

Yes it is rather common to use the "value" option for the PK. However, as above shows, you can quite well with relative ease pull out the values from that one row based on index.

But, it would be VERY cool if LoadLayout exposed the pony trick that it does when you load that layout (behind the scenes it auto-maps the controls to existing controls defined in your globals section. Would LOVE to know how that works, but better yet would be say:

LoadLayOutControls(MyPanel).

In other words, I would not have to use "tag", and the above helper loop, but simply do say this:

B4X:
    ' get row data
    Dim Pnl As Panel = CustomListView1.GetPanel(Index)
    LoadLayOutControls(Pnl)

    ' pull values from panel
    Log("PK id = " & lblID.Text)
    Log("HotelName = " & lblHotelName.Text)
    Log("City Name = " & lblCity.Text)

I mean, somehow, during the loadlayout process, behind code knows how to setup the values of the controls that exist in the Globals section. It would be really nice to have a command to "trigger" that process based on a given panel. This would save lots of coding, no need for tags, and better yet we could with great ease pull values from a panel/view that we just loaded, or in this case grabbed from the CLV.

I thus would not need tags, not need a helper loop function either.

Anyway, venturing off topic! As noted, you can get the row data - you just have to ensure that the controls that made up that row have a "tag" set, and then you can get those controls based on "something" such as tag name.

As for the code I used via the designer and one row?

Well, I sure you had/have something like this:

B4X:
Sub CreateRow(rst As ResultSet) As Panel

    Dim p As     B4XView = xui.CreatePanel("")
    p.SetLayoutAnimated(0,0,0,100%x, 45dip)

    p.LoadLayout("OneRow")

    lblID.Text = rst.GetInt("ID")
    lblHotelName.Text = rst.GetString("HotelName")
    lblCity.Text = rst.GetString("City")

    Return p

End Sub

So in above - you can see that "automatic" setting of the 3 label values I have - I did not have to write code to set those 3 labels up. But, I did have to work a bit to get them out when wanting to display after the click event. So we have a hug part as automatic - I just want the other kiss part!

Maybe I should post a new question on this issue - How to transfer values from a panel to the defined controls in Global section just like loadlayout is able to do via some magic trick!

Regards,
Albert D. Kallal
Edmonton, Alberta Canada
Hi Albert,

That's great.. I will try out the method shown by you..

It is a nice way of pulling the data from the child controls 'without writing the code in the click event of the child control'. So sitting from the parent I can pull the data from the child controls.

If this works fine, then I can also avoid 'sitting in the click event of one child control' and pulling the data from 'another child control' , as I have indicated in my above code snippet.

As for the 5000 rows , I will try the 'lazy-loading' technique shown by Erel ,, Never tried it but there is always a first time for everything..
 
Last edited:
Upvote 0

Albert Kallal

Active Member
Licensed User
but it shows the Description value first and then the Key value !!... I mean in reverse

I mean- the code is getting evaluated in the 'reverse' of the order it has been typed in ...

Can you explain this ?

You code is working correct. Remember, "async" code does not wait, nor halt! What is in fact is occurring is the first msgbox displays, and then the 2nd one displays on top - hiding the first one!. When you dismiss the 2nd box, the one below it already displayed now comes into view.

You can fix, or force the code to wait like this:
B4X:
    MsgboxAsync("First message","First")
    Wait For Msgbox_Result (Result As Int)

    MsgboxAsync("2nd message","2nd")
    Wait For Msgbox_Result (Result As Int)

So it can be really nice - you can display a msgbox - but the code KEEPS on running! - that is quite much the whole idea here. And this means also that code can more often take advantage of multiple cpu cores. but really at the end of the day, the idea here is we are not allowed (much) these days to write code that halts and locks up the phone or the UI. This means the phone will not crash as much, nor freeze up. (because developers are not allowed to write code that hlats and locks up program flow).

As a result of this goal, then the makers of such systems (like Android) are simply hitting developers over the head with a big frying pan and telling us:

You can't write code that blocks the UI and halts things up.

And this big trend is not limited to Android. Some months ago I was writing some JavaScript (web browser client side code). I was doing a ajax post, and I had this setting:
B4X:
$.ajax({
      async: "false",
      url: "/MyPortal/GetUserInfo",
      method: "POST",
      contentType: "application/json",
      data: -- bla bla al

etc. etc.
So in above, I am calling a web method (service). I want to get some infomration, and I want to WAIT for the above to be done, and then have the code continue. So, I have async: false. While debuggin the code in Chrome browser? The debug tools spit out this gem:

"do not use async: false - it will not be supported soon and will be removed!!!"

So, once again, even in the browser? We as developers are being pushed to NOT write code that halts the UI and does not lock up nor halt code.

So, I have to split the above code into two routines (or even 3 if you count "success" vs "failure".

Thankfully? Well in place of having split that code into two or three routines to deal with "async" code (which is VERY hard to write lots of ???).

Well, B4A (and JavaScript now) both have WAIT FOR! - and man oh man - that feature could not have come fast enough or sooner!

So while EVEN the ability to write code that halts is being taken away from us developers? Well, with the rise of AWAIT or Wait For, then the pain of this is vast reduced - if not near outright so.

Regards,
Albert D. Kallal
Edmonton, Alberta, Canada
 
Upvote 0

beelze69

Active Member
Licensed User
Longtime User
You code is working correct. Remember, "async" code does not wait, nor halt! What is in fact is occurring is the first msgbox displays, and then the 2nd one displays on top - hiding the first one!. When you dismiss the 2nd box, the one below it already displayed now comes into view.

You can fix, or force the code to wait like this:
B4X:
    MsgboxAsync("First message","First")
    Wait For Msgbox_Result (Result As Int)

    MsgboxAsync("2nd message","2nd")
    Wait For Msgbox_Result (Result As Int)

So it can be really nice - you can display a msgbox - but the code KEEPS on running! - that is quite much the whole idea here. And this means also that code can more often take advantage of multiple cpu cores. but really at the end of the day, the idea here is we are not allowed (much) these days to write code that halts and locks up the phone or the UI. This means the phone will not crash as much, nor freeze up. (because developers are not allowed to write code that hlats and locks up program flow).

As a result of this goal, then the makers of such systems (like Android) are simply hitting developers over the head with a big frying pan and telling us:

You can't write code that blocks the UI and halts things up.

And this big trend is not limited to Android. Some months ago I was writing some JavaScript (web browser client side code). I was doing a ajax post, and I had this setting:
B4X:
$.ajax({
      async: "false",
      url: "/MyPortal/GetUserInfo",
      method: "POST",
      contentType: "application/json",
      data: -- bla bla al

etc. etc.
So in above, I am calling a web method (service). I want to get some infomration, and I want to WAIT for the above to be done, and then have the code continue. So, I have async: false. While debuggin the code in Chrome browser? The debug tools spit out this gem:

"do not use async: false - it will not be supported soon and will be removed!!!"

So, once again, even in the browser? We as developers are being pushed to NOT write code that halts the UI and does not lock up nor halt code.

So, I have to split the above code into two routines (or even 3 if you count "success" vs "failure".

Thankfully? Well in place of having split that code into two or three routines to deal with "async" code (which is VERY hard to write lots of ???).

Well, B4A (and JavaScript now) both have WAIT FOR! - and man oh man - that feature could not have come fast enough or sooner!

So while EVEN the ability to write code that halts is being taken away from us developers? Well, with the rise of AWAIT or Wait For, then the pain of this is vast reduced - if not near outright so.

Regards,
Albert D. Kallal
Edmonton, Alberta, Canada
Hi Albert,

Thanks ! It worked.... Thanks for all the help..
 
Upvote 0

Albert Kallal

Active Member
Licensed User
Hi Albert,

Thanks ! It worked... but did not understand what you meant by '...but the code KEEPS on running!'... Here, I could see the first message followed by the second message and that's all.. And the first message did not show again ... Thanks for all the help..

Well, NOW we are using wait for - so the code after does not run. But in your first case without the Wait for, then both boxes were displayed, and the end of that code module was being reached. So, when I stated that both boxes were already displayed? I was talking about your first example + question, not now that we fixed this with wait for. So by using wait for, then the code AFTER wait for does not run until such time the user hits OK. Once the user does that, then the code resumes running the next line after the wait for (behind the scenes, the code is split out - and the part after the wait for then resumes. But even in this case - the wait for it not actually "waiting" and the code is not halted at that point in time. But from a coding point of view, you can for the most part assume and think that we (our code) is halted and waiting for the user to dismiss the dialog - but the code is not behind really halted.

But, yes, in your first example and question? It is very likely that both boxes were displayed in the correct order, but without a code halt, then the 2nd box was on top of the first. You only could see the one below by dismissing that 2nd box when then reveled the one hiding below - hence the magic illusion that code was running out of order when it was not.

R
Albert
 
Upvote 0

beelze69

Active Member
Licensed User
Longtime User
Well, NOW we are using wait for - so the code after does not run. But in your first case without the Wait for, then both boxes were displayed, and the end of that code module was being reached. So, when I stated that both boxes were already displayed? I was talking about your first example + question, not now that we fixed this with wait for. So by using wait for, then the code AFTER wait for does not run until such time the user hits OK. Once the user does that, then the code resumes running the next line after the wait for (behind the scenes, the code is split out - and the part after the wait for then resumes. But even in this case - the wait for it not actually "waiting" and the code is not halted at that point in time. But from a coding point of view, you can for the most part assume and think that we (our code) is halted and waiting for the user to dismiss the dialog - but the code is not behind really halted.

But, yes, in your first example and question? It is very likely that both boxes were displayed in the correct order, but without a code halt, then the 2nd box was on top of the first. You only could see the one below by dismissing that 2nd box when then reveled the one hiding below - hence the magic illusion that code was running out of order when it was not.

R
Albert
Hi Albert...

Recently posted another query today morning on the forum.


Can you give your views on this ? Ps. reply only on that thread... Thanks..
 
Upvote 0
Top