Android Question TabStrip with same layout

AHilberink

Active Member
Licensed User
Hello,

I have a strange situation. I need TabStrip with the same layout more than once (dynamic). I also need the controls, for example the ListView, to be changed per Tab.

Changing the ListView of Tab1 will always change the ListView of the last Tab.

My example:
B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

#AdditionalJar: com.android.support:support-v4

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.

    Private TabStrip1 As TabStrip
    Private Page3ListView1 As ListView
    Private Button1 As Button
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main")
    TabStrip1.LoadLayout("Page3", "PAGE 1")
    For i = 1 To 5
        Page3ListView1.AddSingleLine($"Item ${i}"$)
    Next
    TabStrip1.LoadLayout("Page3", "THIS IS PAGE 2")
    For i = 1 To 8
        Page3ListView1.AddSingleLine($"Item ${i}"$)
    Next
    TabStrip1.LoadLayout("Page3", "AND PAGE 3")
    For i = 1 To 10
        Page3ListView1.AddSingleLine($"Item ${i}"$)
    Next
    Activity.AddMenuItem("Jump to page 1", "mnu1")
    Activity.AddMenuItem("Jump to page 2", "mnu2")
    Activity.AddMenuItem("Jump to page 3", "mnu3")
End Sub

Sub mnu1_Click
    TabStrip1.ScrollTo(0, True)
End Sub

Sub mnu2_Click
    TabStrip1.ScrollTo(1, True)
End Sub

Sub mnu3_Click
    TabStrip1.ScrollTo(2, True)
End Sub

Sub TabStrip1_PageSelected (Position As Int)
    Log($"Current page: ${Position}"$)
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub Button1_Click
    For i = 1 To 5
        Page3ListView1.AddSingleLine($"Item ${i}"$)
    Next
End Sub
Try to press the button on Tab1 or Tab2. You will find the Added lines into Tab3.

How can I change this behaviour?

Best regards,
André
 

Attachments

emexes

Well-Known Member
Licensed User
I must be missing something here: so, when you click Button1, whereabouts are you expecting to find the added lines, if not Page3ListView1 ?
 

AHilberink

Active Member
Licensed User
I must be missing something here: so, when you click Button1, whereabouts are you expecting to find the added lines, if not Page3ListView1 ?
Hi Emexes,

Thanks for your reply.

Of course it would be the Page3ListView, but I need it on the active Tab. Now it is always the last Tab. This would be because it is on every Tab.

Is it possible to update the correct one (the one on the active Tab)?

Best regards,
André
 

emexes

Well-Known Member
Licensed User
Ok, I think I understand now. I can't run your sample right now, perhaps I've missed loading a recent update or something.

I was thinking the problem might be that you are creating a new ListView object with each layout that you load, but (on my computer) only Page3 had a ListView on it. Page2 has a panel and four buttons Button1..Button4, and Page1 has just a panel and no buttons at all.

So now I am thinking that perhaps you wish to move the ListView between the three tabs, depending on which tab is active. Is this correct? I'll have another crack at reading your code in the meantime ;-)
 

emexes

Well-Known Member
Licensed User
When you first look at Page3, before clicking any Button1s, how many items are in the ListView? 10? or 23? or something else?
 

emexes

Well-Known Member
Licensed User
Ah, hang on - Einstein here just noticed that the same layout (Page3) is being loaded into all three tabs. It's almost midnight here, perhaps this is a sign that the day's caffeine has finally run out.
 

AHilberink

Active Member
Licensed User
Ah, hang on - Einstein here just noticed that the same layout (Page3) is being loaded into all three tabs. It's almost midnight here, perhaps this is a sign that the day's caffeine has finally run out.
Hi Emexes,

Thanks for helping me at midnight!! You can ignore all other Pages. This was from an example of TabStrip.

I do load all Tab's with the same layout. 5 Lines at Page 1, 8 Lines at Page 2 and 10 Lines at Page 3.
When I press Button1 on Page 2, 5 additional lines are added to the Page3ListView. So far, so good.
But … the Page3ListView of Tab 3 is added and not the one on Page 2.

So, Yes: I need to move the Listview between the Tabs or something to add the Lines to the correct Lisview on the current Tab.

I hope you can help.

Best regards,
André
 

emexes

Well-Known Member
Licensed User
I think I've worked out what's going on, but before I try explain it... what do you actually want to do? eg, have the same list on three tabs, or have a different list on each tab, or perhaps something else that I haven't thought of?
 

emexes

Well-Known Member
Licensed User
Not to put words to your mouth, but... if you want three separate and independent lists, one per tab, then things are looking good. If you want the one list shared (ie repeated and identical) between all tabs, then it's probably doable but a bit more effort.
 
Last edited:

AHilberink

Active Member
Licensed User
I think I've worked out what's going on, but before I try explain it... what do you actually want to do? eg, have the same list on three tabs, or have a different list on each tab, or perhaps something else that I haven't thought of?
This is only a small example of my problem.

My project has a dynamic amount of Tabs. So I cannot define an own ListView for every Tab.
The content of the ListView is not the same for every Tab, but depends on information from my database.

Best regards,
André
 

emexes

Well-Known Member
Licensed User
This is only a small example of my problem.
Super, I prefer small problems ;-)

The content of the ListView is not the same for every Tab, but depends on information from my database.
From that I am inferring that you are after a separate and independent list for each tab, which is also super. The explanation might take me ten minutes to type up, I'll be back soon.
 

emexes

Well-Known Member
Licensed User
Firstly, I am no OOP expert. I still think (mostly) in C, and when I use B4A, I am internally translating all this object stuff back to pointers.

What I think is happening with your tabs is:

You load the first layout, and LoadLayout creates a ListView object, where an object is a bunch of variables sitting in memory somewhere. The Page3ListView1 that you've declared in Globals is actually a pointer that you can use to access the object. Page3ListView1 is NOT the actual object itself, but a way of accessing the object. The LoadLayout routine sets Page3ListView1 to point to the ListView object created when you called TabStrip1.LoadLayout("Page3",...)

When you say Page3ListView1.AddSingleLine, you are not adding a line to Page3ListView1 - you are adding a line to the object that Page3ListView1 currently refers to.

Then you load the second layout, and the TabStrip2.LoadLayout("Page3",...) routine creates ANOTHER ListView object, and points your Page3ListView1 to it. The first object still exists, and is not deleted (garbage-collected) because it is still used by the GUI manager and which can access it via (I think) TabStrip1.

Then you load the third layout, and the TabStrip3.LoadLayout("Page3",...) routine creates another (THIRD) ListView object, and points Page3ListView1 to it. Again, the previous two incarnations of the ListView still exist. So now you have THREE separate ListView objects, each is a distinct bunch of variables that are independent of each other, but you only have ONE Page3ListView1 pointer and it now points to the third/last ListView (because a pointer can only point to one thing at a time, just like a variable can only be one value at a time, eg, you can have X = 2 or X = 3 or X =4, but NOT at the same time).

Thus, anything you do using Page3ListView1, happens only to the third ListView object, ie, the one on tab 3, because that is where it was last set to point.

The ListView objects on tabs 1 and 2 still exist, but you've lost their addresses. If only you'd saved their addresses before LoadLayout overwrote them, all your problems would be solved...

;-)
 

emexes

Well-Known Member
Licensed User
In my muckaround code (well, your code, actually) this is what I did to work out wtf was happening:

Firstly, I added:

B4X:
Private ListViewOnTab1 As ListView
Private ListViewOnTab2 As ListView
Private ListViewOnTab3 As ListView
to Globals. This gives us three separate and independent pointers to ListViews. A pointer is just the address of something in a computer, like an address entry in your phone book. So far, those pointers (address entries) are blank, they don't point at anything. We can use them to save the addresses of the ListViews created by LoadLayout().

Secondly, I added a line after each LoadLayout, to save a link to (ie, point to) the ListView that LoadLayout creates and points Page3ListViewer1 to:

B4X:
TabStrip1.LoadLayout("Page3", "TAB 1")
ListViewOnTab1 = Page3ListView1
...
TabStrip2.LoadLayout("Page3", "TAB 2")
ListViewOnTab2 = Page3ListView1
...
TabStrip3.LoadLayout("Page3", "TAB 3")
ListViewOnTab3 = Page3ListView1
After all that is done, you have three tabs, with an independent list on each tab (with, from memory, 5, 8 and 10 sample lines respectively)

If you want to add to the list on Tab 1, then: ListViewOnTab1.Add...()
If you want to add to the list on Tab 2, then: ListViewOnTab2.Add...()
If you want to add to the list on Tab 3, then you can either: ListViewOnTab3.Add...()
OR if you want to make yourself crazy, you could ALSO use: Page3ListView1.Add...()
because both ListViewOnTab3 and Page3ListView1 are pointing to the same ListView (ie, the one on Tab 3)

So, there you have it. Easy (when you know how ;-)

Your next problem is that you have three Button1s (one one each tab), and they will all be calling the one routine Button1_Click. So if you change Button1_Click to now adds lines to ListViewOnTab2 rather than Page3ListView1, then you are again in the situation where clicking Button1 on any of the three tabs is going to add to the specified ListView rather than the ListView of the current tab that contains the button you just clicked.

I'm going to let you work on that issue. I haven't done it before, but my first thought is to use the Tag field of each button to distinguish between Button1s on various tabs. To set the tags, it might be possible to use something like TabStrip1.Button1.Tag = "1" or TabStrip2.Button1.Tag = 2 (if tags can be non-string). If that doesn't work, then I'd think back to how the Page3ListView1 pointer was updated by each LoadLayout to point to the ListView on the tab just loaded, and then think well maybe LoadLayout is also updating the Button1 pointer too, and try:

B4X:
TabStrip1.LoadLayout...
Button1.Tag = 1

TabStrip2.LoadLayout...
Button1.Tag = 2

TabStrip3.LoadLayout...
Button1.Tag = 3
Either way, you should be able to access the Tag of the clicked button using the Sender keyword:

B4X:
sub Button1_Click
    select case Sender.Tag
        case 1: ListViewOnTab1.Add...()
        case 2: ListViewOnTab2.Add...()
        case 3: ListViewOnTab3.Add...()
        case else: Log("There is another Button1 out there somewhere")
    end select
end sub
If Tags are numbers rather than strings, that'll probably be faster.

And lastly, remember that an array of objects is really an array of (small)(ish) pointers, NOT an array of the actual full objects themselves. Thus you can have:

B4X:
    Dim WhereDidAllMyListsGo(100) as ListView
and you have yourself 100 (initially empty) pointers (address book entries) that you can use to keep track of (up to) 100 different ListViews without needing to DIM 100 separate and differently-named ListViews, when and as the needs arise.

Right, I think you've got enough rope now to do what you need to do. Me, I need to sleep, so it's over and out from down under.

:)
 

AHilberink

Active Member
Licensed User
In my muckaround code (well, your code, actually) this is what I did to work out wtf was happening:

Firstly, I added:

B4X:
Private ListViewOnTab1 As ListView
Private ListViewOnTab2 As ListView
Private ListViewOnTab3 As ListView
to Globals. This gives us three separate and independent pointers to ListViews. A pointer is just the address of something in a computer, like an address entry in your phone book. So far, those pointers (address entries) are blank, they don't point at anything. We can use them to save the addresses of the ListViews created by LoadLayout().

Secondly, I added a line after each LoadLayout, to save a link to (ie, point to) the ListView that LoadLayout creates and points Page3ListViewer1 to:

B4X:
TabStrip1.LoadLayout("Page3", "TAB 1")
ListViewOnTab1 = Page3ListView1
...
TabStrip2.LoadLayout("Page3", "TAB 2")
ListViewOnTab2 = Page3ListView1
...
TabStrip3.LoadLayout("Page3", "TAB 3")
ListViewOnTab3 = Page3ListView1
After all that is done, you have three tabs, with an independent list on each tab (with, from memory, 5, 8 and 10 sample lines respectively)

If you want to add to the list on Tab 1, then: ListViewOnTab1.Add...()
If you want to add to the list on Tab 2, then: ListViewOnTab2.Add...()
If you want to add to the list on Tab 3, then you can either: ListViewOnTab3.Add...()
OR if you want to make yourself crazy, you could ALSO use: Page3ListView1.Add...()
because both ListViewOnTab3 and Page3ListView1 are pointing to the same ListView (ie, the one on Tab 3)

So, there you have it. Easy (when you know how ;-)

Your next problem is that you have three Button1s (one one each tab), and they will all be calling the one routine Button1_Click. So if you change Button1_Click to now adds lines to ListViewOnTab2 rather than Page3ListView1, then you are again in the situation where clicking Button1 on any of the three tabs is going to add to the specified ListView rather than the ListView of the current tab that contains the button you just clicked.

I'm going to let you work on that issue. I haven't done it before, but my first thought is to use the Tag field of each button to distinguish between Button1s on various tabs. To set the tags, it might be possible to use something like TabStrip1.Button1.Tag = "1" or TabStrip2.Button1.Tag = 2 (if tags can be non-string). If that doesn't work, then I'd think back to how the Page3ListView1 pointer was updated by each LoadLayout to point to the ListView on the tab just loaded, and then think well maybe LoadLayout is also updating the Button1 pointer too, and try:

B4X:
TabStrip1.LoadLayout...
Button1.Tag = 1

TabStrip2.LoadLayout...
Button1.Tag = 2

TabStrip3.LoadLayout...
Button1.Tag = 3
Either way, you should be able to access the Tag of the clicked button using the Sender keyword:

B4X:
sub Button1_Click
    select case Sender.Tag
        case 1: ListViewOnTab1.Add...()
        case 2: ListViewOnTab2.Add...()
        case 3: ListViewOnTab3.Add...()
        case else: Log("There is another Button1 out there somewhere")
    end select
end sub
If Tags are numbers rather than strings, that'll probably be faster.

And lastly, remember that an array of objects is really an array of (small)(ish) pointers, NOT an array of the actual full objects themselves. Thus you can have:

B4X:
    Dim WhereDidAllMyListsGo(100) as ListView
and you have yourself 100 (initially empty) pointers (address book entries) that you can use to keep track of (up to) 100 different ListViews without needing to DIM 100 separate and differently-named ListViews, when and as the needs arise.

Right, I think you've got enough rope now to do what you need to do. Me, I need to sleep, so it's over and out from down under.

:)
Hi Emexes,

THANKS A LOT. WORKS LIKE A CHARM!!!!!! This is exactly what I needed. Even a little change to my sourcecode.

My sourcecode is now:
B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

#AdditionalJar: com.android.support:support-v4

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.

    Private TabStrip1 As TabStrip
    Private Page3ListView1 As ListView
    Private Button1 As Button
    Private PageLV(100) As ListView
    Private CurrentPage As Int
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main")
    TabStrip1.LoadLayout("Page3", "PAGE 1")
    For i = 1 To 5
        Page3ListView1.AddSingleLine($"Item ${i}"$)
        PageLV(0)=Page3ListView1
    Next
    TabStrip1.LoadLayout("Page3", "THIS IS PAGE 2")
    For i = 1 To 8
        Page3ListView1.AddSingleLine($"Item ${i}"$)
        PageLV(1)=Page3ListView1
    Next
    TabStrip1.LoadLayout("Page3", "AND PAGE 3")
    For i = 1 To 10
        Page3ListView1.AddSingleLine($"Item ${i}"$)
        PageLV(2)=Page3ListView1
    Next
    Activity.AddMenuItem("Jump to page 1", "mnu1")
    Activity.AddMenuItem("Jump to page 2", "mnu2")
    Activity.AddMenuItem("Jump to page 3", "mnu3")
End Sub

Sub mnu1_Click
    TabStrip1.ScrollTo(0, True)
End Sub

Sub mnu2_Click
    TabStrip1.ScrollTo(1, True)
End Sub

Sub mnu3_Click
    TabStrip1.ScrollTo(2, True)
End Sub

Sub TabStrip1_PageSelected (Position As Int)
    Log($"Current page: ${Position}"$)
    CurrentPage=Position
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub Button1_Click
    For i = 1 To 5
        PageLV(CurrentPage).AddSingleLine($"Item ${i}"$)
    Next
End Sub
Now I am going to bring it over to my project.

Best regards,
André
 

emexes

Well-Known Member
Licensed User
Hey, I didn't spot that you already know which Tab is current thanks to TabStrip1_PageSelected, so... bonus!

First thing I saw was you were using the array-of-ListViews and I thought "hmm, this could be interesting" but you're right, that array meshes nicely with the concept of Tabs being an array of panels.

:)
 
Top