Android Question Views: How to keep track of them with pointers?

rgarnett1955

Active Member
Licensed User
Longtime User
Hi

I have implemented a map with a sparse matrix of cells using label views; approx 1000 of them. I did it this way as the map is of arbitrary shaped vegetable gardens with different crops . Hence sparse rather than rectangular matrix. Each cell can represent an arbitrary area and they don't have to be square. This makes it very versatile for different types of crop arrangements, Although within any single map all cells must be there same dimensions. But you can have multiple maps.

It works very well and is quite fast. The attributes of each cell are recovered from a data base with and ID and the xy co'ords of the cells. Each cell has an attribute such as path, soil, compost bins etc and soil cells are set to cultivatable so that a crop ID can be assigned to the cell. To speed things up I store the attributes in a B4XMap. I store the ID of each cells label in its Tag as an integer. I use this to key into the database and B4XMaps.

All good. To change a cell click on it and set the pars. That's fast, because I can recover the cell ID from the Senders Tag.

However if i want to do a bulk update or programmatically change a particular cell based on its ID I have to iterate over all of the cell labels to find the one I want to change. This is OK, but it seems highly inefficient as you have to search the entire panel to find the label view you want. Is there any way for finding or "addressing" a view such as a label without having to search for it.

Question 1: Is it possible to set up a table at run-time that keeps the "address" of each of the graphics objects so you don't have to search?

Question 2: Is their a better way of finding the cell's label that I want without searching over and over again every time I want to make a change?

I used labels on a 2D scrollable panel rather than an image because I was too lazy to learn how to use images. I know the correct answer is to use an image as you can recover the xy value, but that is complicated as when you zoom an image you have to be careful to adjust the coordinates to recover the correct cell attributes from the DB. You don't have this trouble with labels representing cells as the label xy values are stored against the cell ID. Now I understand that an image can have very big pixels so that each pixel could represent a cell, but then you have the problem of not having a border around each cell. Of course you could then overlay coarse image with a high res image with transparent pixels and opaque grid lines and it you kept your ratios as integers you could probably keep them registered when zooming.

Any comments or ideas would be appreciated.

Best regards
Rob
 

Attachments

  • GardenBuddyMap.png
    GardenBuddyMap.png
    138.2 KB · Views: 97

Brian Dean

Active Member
Licensed User
Longtime User
I don't think that your users will notice whatever method you use, but I know what you mean - there must be a best way to do these things. I thought that I understood your problem until I read this ...
I know the correct answer is to use an image ...
I don't know why this is relevant if you are trying to "change a particular cell based on its ID". Unless you have derived the ID from some x-y position coordinates. But in that case you can find the coordinates relative to the container panel, I would have thought; you don't need to use an image.

Either way, it seems to me that you will probably end up by creating an index of your IDs and doing a binary search, or creating an x-y tree of your label positions and a "closest point" search. But as I say, I am not sure that I understand the question.
 
Last edited:
Upvote 0

rgarnett1955

Active Member
Licensed User
Longtime User
Hi Brian,

Just to explain the the problem again, but I will stick to the case where I am using labels as the visual elements to create the map. (Not images/canvas's)

- I create a map table in a db where each map entry (row) represents a cell on the map and it has has the following:

- ID (Identifer Integer)
- cellX (x position of top LH corner of cell)
- cellY (y position of top LH Corner of cell
- Other attributes (Color, cropID, etc)

I read this table and for a each row I produce an entry in a B4Xtable with the attributes and I create a cell on a display scroll 2D panel:

Method to add a cell to the scroll panel and b4XMap:
'======================================================================================================
Public Sub setMapToDB(currentCellSizeArg As cell_Size_t, topOffsetN As Int, leftOffsetN As Int)

    sv2D.Panel.Height = sv2D.Height * 20
    sv2D.Panel.Width  = sv2D.Width * 20

    Dim cellBorderColor As Int = 0xFF989898
    
    ' Get the map cell ID, xy coordinates and cell attributes from sqLite DB
    Dim mapTable As List = Starter.dbGB.getMap(1)
    
    'For each of the map DB entries add these to a B4XMap (cellAttributesMap) and create
    ' a cell (Label) with in the ScrollView2D panel sv2D. Set all cell events to "cellEvent"
    For Each row() As Object In mapTable
        Dim cell As Label
        cell.Initialize("cellEvent")
        
        Dim cd As ColorDrawable
        
        Dim cellProps As cell_t
        cellProps.Initialize

        cellProps.id             = row(0) 'DB Prim Key         As Int Col      = "ID"
        cellProps.x              = row(1) 'DB cell X pos.     As Int Col      = "cellX"
        cellProps.y              = row(2) 'DB cell y pos      As Int Col      = "cellY"
        cellProps.surfaceType     = row(3) 'DB surface type   As Int Col      = "surfaceType"
        
        cell.Tag                 = cellProps.id  'cellProps
    
        'Handle Nulls for crop ID
        Try
            If row(4) = Null Then
                cellProps.cropID     = -1
            Else
                cellProps.cropID     = row(4) 'DB crop ID   As Int Col      = "cropID"
            End If
        Catch
            Log(LastException  & "  ID = " & row(0) & "  Crop ID = " & row(4))
        End Try

        cellProps.backColorVal      = row(5) 'DB cell background color   As Int Col = "backColorVal"
        cellProps.textColorVal      = row(6) 'DB cell text color   As Int Col          = "backColorVal"
        
        If  0 = row(7) Then cellProps.canCultivate = False Else cellProps.canCultivate = True
        
        cellProps.editMode         = False
        cell.Enabled            = False
        cell.Width                 = currentCellSizeArg.width
        cell.Height             = currentCellSizeArg.height
        cd.Initialize2(cellProps.backColorVal, 0, 1, cellBorderColor)
        cell.Background = cd
        cellAttributesMap.Put(cell.Tag, cellProps)

        'Add the cell to the sv2D panel
        sv2D.Panel.AddView(cell, cell.Width * (row(1) + leftOffsetN) , cell.Height * (row(2) + topOffsetN), cell.Width,  cell.Height)

    Next
    
    'Set the scroll position to register the cells to the top/LH corner of the scroll panel
    Sleep(0)
    sv2D.HorizontalScrollPosition = currentCellSizeArg.width  * leftOffsetN
    sv2D.VerticalScrollPosition   = currentCellSizeArg.height * topOffsetN
End Sub

Now suppose I want to set the cell representing ID = 101 with a new color programatically with the new background color at present I do the following:

Search and set color code (to set a single Cell Label Color):
'======================================================================================================
Public Sub setCellBackColor(idArg as Int, colArg As Int)
        For Each v As View In sv2D.Panel.GetAllViewsRecursive
            If v Is Label Then
            if v.Tag = idArg then
                v.Color = colArg  'pseudocode'
        End If
    Next
End Sub

Now if I want to do a "flood fill" of color I have to find each of the cell Labels using a list of id's using the "search and set color" code. If I have 100 cells that I want to change I have to iterate over the cell array 100 times. If I have 1000 cells then that means I examine each cell 100 times. That's 100,000 transactions; not efficient.

Now I know that each of these cells along with their tags are stored persistently as objects in memory as they are globals, but I don't know how to find them without doing a search. What I would like to to is put the "address" of the cell in my B4XMap so all I do is change a cell color is get the cell Label address from the B4Xtable and refer to the cell via this "address"

If I had created the cells in a layout with each having a separate name i.e. lblCell_1, lblCell_2, lblCell3, ... and thus can refer to them statically in code to set their color:

B4X:
lblCell_2.Color = 0xFF349876


If I did this I still have the problem that I can't store these names in a table and call them up by reference to that table. i.e

id cellName cellAddress
1 lblCell_1 2345
2 lblCell_2 6367
3 lblCell_3 99
...


B4X:
Dim v as Label
Dim a as tblObj
Dim a as Int
 
a = B4XTable.Get(2)

v = object(a.cellAddress)

v.Color = myColor


I hope this explains my question better.

To summarize:

I want to change a number of cells (Label Views)
I have to iterate over them to find the one I want based on the Tag I have given it.
I now have the cell address in the "v" variable and can operate on the v object.

I have to do this for each of the cells I want to change.

I guess the questions are;

  1. Can I store all of my labels as pointers in an array and retrieve and operate on them based on an index into that array?
  2. How would I do this if it were possible?

I am a vanilla C prgrammer without much object oriented programming experince and I just don't understand how this stuff works. In C you can always keep track of data structures you build including graphics structures, but in object oriented codeing the graphics objects seem to disappear into memory arbitrarily and then they must be searched for if you want to change them. Yet if you click on them you get the sender address back. Also if you give them an explicit variable name then you can go straight to them. Weird!!

best regards
Rob
 
Upvote 0

Brian Dean

Active Member
Licensed User
Longtime User
What I would like to to is put the "address" of the cell in my B4XMap . . .

I think that we can do that. First of all, a bit of background about objects which I hope is not too elementary. As far as I know objects are essentially the same in all languages. When you create an instance of an object you get back the memory address of that object instance - that is, an integer; a simple, robust value. In languages that allow casting (certainly in B4X) you can cast an object to an integer. That is how the Tag works in a View; it can hold any type of object because what it really holds is the pointer to that object.

So the following is certainly possible ...
B4X:
    Type cell_t (ID As Int, x As Int, y As Int, lbl As Label, etc1 As String, etc2 As Float)

    Dim data As cell_t

    Dim cells As Map
    cells.Initialize
    cells.Put(data.ID, data)

Now when you retrieve a record using a given ID you have access to the label as well. This is essentially what I said in my first reply about using a binary search, although a Map uses hashing. Either way you are testing a handful of table entries instead of iterating through the whole table. I haven't used a B4XTable but I see that it uses a List as input so the same principle applies.

Can I store all of my labels as pointers in an array and retrieve and operate on them based on an index into that array?
Yes you can, but I don't think that is necessary - just store the pointers themselves in your base table instead of an array index.

How would I do this if it were possible?
There must be some point where the ID, your data record and the label come together. For instance ...
B4X:
    For Each c as cell_t in datalist
        ... ...
        Dim lbl as Label
        lbl.Initialise("cell")
        pnl.AddView(lbl, c.x, c.y, wide, high)
        lbl.Tag = c.ID
        c.lbl = lbl
        ... ... ...

I hope that this is clear, but if not come back with any questions. Good luck.
 
Last edited:
Upvote 0

rgarnett1955

Active Member
Licensed User
Longtime User
Hi Brian,

Thanks for that. After I replied to you as often happens, framing the question for the reply gave me the answer pretty much as you described so I added the label to my cell atrributes map as a label type. I then ran it, but is didn't seem to work, however I went back to just searching for the labels and that didn't work either as when I added a new cell it didn't show up in the display. But then I realised I had forgotten to add it to the sv@2D scrollbar ; sv2D.AddView(cell,...). When I did this It worked with the old search method so I suspect that if I use your suggestion and remember to add the view it will work. So I will have another go at it.

I will let you know how I get on.

Best regards
Rob
 
Upvote 0

rgarnett1955

Active Member
Licensed User
Longtime User
I think that we can do that. First of all, a bit of background about objects which I hope is not too elementary. As far as I know objects are essentially the same in all languages. When you create an instance of an object you get back the memory address of that object instance - that is, an integer; a simple, robust value. In languages that allow casting (certainly in B4X) you can cast an object to an integer. That is how the Tag works in a View; it can hold any type of object because what it really holds is the pointer to that object.

So the following is certainly possible ...
B4X:
    Type cell_t (ID As Int, x As Int, y As Int, lbl As Label, etc1 As String, etc2 As Float)

    Dim data As cell_t

    Dim cells As Map
    cells.Initialize
    cells.Put(data.ID, data)

Now when you retrieve a record using a given ID you have access to the label as well. This is essentially what I said in my first reply about using a binary search, although a Map uses hashing. Either way you are testing a handful of table entries instead of iterating through the whole table. I haven't used a B4XTable but I see that it uses a List as input so the same principle applies.


Yes you can, but I don't think that is necessary - just store the pointers themselves in your base table instead of an array index.


There must be some point where the ID, your data record and the label come together. For instance ...
B4X:
    For Each c as cell_t in datalist
        ... ...
        Dim lbl as Label
        lbl.Initialise("cell")
        pnl.AddView(lbl, c.x, c.y, wide, high)
        lbl.Tag = c.ID
        c.lbl = lbl
        ... ... ...

I hope that this is clear, but if not come back with any questions. Good luck.

Hi Brian,

Works fine.

Nest regards
Rob
 
Upvote 0
Top