Android Question "RESOLVED" change image of a button in execution

Discussion in 'Android Questions' started by Jose Briceño, Mar 16, 2019 at 4:15 PM.

  1. Jose Briceño

    Jose Briceño Member Licensed User

    I have this code, there are several buttons with images, the idea is to press and change the image, to the selected image and when I press again to return to the main image.

    Sub Activity_Create(FirstTime As Boolean)
    Dim i As Int
    Dim pos As Int

    Activity.AddView(scvMain, 0, 0, 100%x,100%y)

    Dim pnltest As Panel
    Dim lblTest As Label
    Dim btnTest As Button
    Dim edtTest As EditText

    Dim pnl As Panel
    Dim sld As StateListDrawable
    Dim bmpTeam As BitmapDrawable
    Dim bmpSel As BitmapDrawable
    Dim bmpBackground As Bitmap
    Dim bmdBackground As BitmapDrawable
    Dim etiqueta As Label
    Dim boton2 As Button

    'Dim TipoBus As Int
    Dim Bus() As Int
    Dim Posicion() As Int
    Dim Buttons(64) As Button

    ' Asiento 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
    Bus = Array As Int(1 , 95, 315,1 ,95, 315,1 , 95, 315,1 , 95, 315, 1 , 95, 315, 1 , 95, 315, 1 , 95, 315, 1 , 95, 315, 1)
    Posicion = Array As Int(1,1,1 ,1, 100, 100,100,190,190,190, 280, 280, 280, 370, 370, 370, 460, 460, 460, 550, 550, 550, 640, 640,640,730,730,730,820,820,820,910,910)

    Activity.Background = bmdBackground

    For i=1 To TipoBus

    If pos <> Posicion(i) Then
    'salto de linea


    End If

    ' boton con imagen



    sld.AddState(sld.State_Disabled, bmpTeam )
    sld.AddState(sld.State_Pressed, bmpSel )

    btnTest.Background = sld

    pnltest.AddView(btnTest,Bus(i-1)+2%x, 1dip, 70dip, 70dip)
    Buttons(i) = btnTest '2dip


    'pnl.Height = lstChecks.Size * height
    'Activity.AddMenuItem("Display checked", "mnuChecked")
    End Sub

  2. emexes

    emexes Active Member Licensed User


    four tips, and then I'll get to what (I think) is your main problem:

    1/ You've posted in a forum "Android Questions" but not actually asked a question. On the bright side, at least you've described what you're trying to do, so you're halfway there - if you also told us what results you're getting, vs what results you're expecting, then generally people here are super-helpful. So far, you have no responses, which is probably because people are wary of putting wasted effort into answering guessed questions.

    2/ Next time, put the code between tags like this:


    so that it keeps its structure (mostly) and is more readable like this:

    For I = 1 To 100
    Righto, that's the housekeeping done, now on to some coding observations.
  3. emexes

    emexes Active Member Licensed User

    3/ You have a loop "for i = 1 to TipoBus" where i is used to index an array. This always makes me nervous, because arrays in B4A are zero-based, and using an index starting at one is usually and closely followed by an array-out-of-bounds error at the other end of the array when you try to access the "missing" last element.

    But I think you've already discovered this: I'm guessing that the "Dim Buttons(64) as Button" used to be "Dim Buttons(25) as Button". No worries - we've all been there and done that at some point ;-)

    If I am doing a too-large Dimming because I don't yet know how many I'll need, I usually do something like:

    Dim MaxNumButtons As Int = 64

    Dim Buttons(MaxNumButtons) As Button
    Dim NumButtons As Int = 0

    Do While more buttons to add
    'add another button
        If NumButtons < MaxNumButtons Then
            Buttons(NumButtons) = AnotherButton
            NumButtons = NumButtons + 
    Log("Too many buttons")
    End If
    Yes, array bounds are always checked by B4A/Java and so the error would be caught anyway, but my experience has been that explicit pessimism generally leads to fewer bugs. And yes, you can use a Try..Catch instead of doing the explicit array bounds check yourself, but... to me that feels a bit like driving through a pothole and then repairing the tyre afterwards instead of driving around or stopping before the pothole. Best not to tempt fate.

    4/ The Bus() and Posicion() arrays look misaligned. They look like Left and Top coordinates for the buttons, but Posicion() has an extra element at the beginning (ie four 1s instead of three), probably to pad it out to look like a one-based array rather than zero-based? I can live with that, although it'd be less bug-prone if the Bus() and Posicion() indexing was consistent. Also, Posicion() has a heap of extra entries that don't have a corresponding Bus() entry. Better too many than too few, but... it's just going to confuse things when you later add more buttons.
  4. emexes

    emexes Active Member Licensed User

    Righto, now to your actual probable problem. I'm guessing you expect to see 25 buttons on the screen, but you only see one. If this is the case:

    Variables and array elements in B4A are not the actual objects - they are pointers to the objects. It is entirely plausible to have multiple B4A variables pointing to the same object, and that is what you (unexpectedly) have - all of the Buttons() elements are pointing to the same object, aka btnTest, which has only been created (Dimmed) once.

    So your code currently does something like this, where a single button is created:

    Dim NumButtons As Int = 25
    Dim Buttons(NumButtons) As Button    'is an array of (empty) pointers to objects, NOT an array of objects

    Dim TestButton as Button    'creates a new Button object and makes TestButton point to it

    For I = 0 to NumButtons - 1
    'configure button per I
        TestButton.Tag ="Call me Button " & (I+1)
        TestButton.Left = (I 
    Mod 3) * 100
        TestButton.Top = 
    Floor(I / 3) * 100

        Buttons(I) = TestButton    
    'Button(I) points to same SINGLE object that TestButton variable always points to

    whereas what you were probably hoping for was 25 individual buttons, which is easily done by moving the make-a-new-Button-object code to inside the For loop eg:

    Dim NumButtons As Int = 25
    Dim Buttons(NumButtons) As Button    'is an array of (empty) pointers to objects, NOT an array of objects

    For I = 0 to NumButtons - 1
    Dim TestButton as Button    'creates a new Button object and makes TestButton point to it

    'configure button per I
        TestButton.Tag = I
        TestButton.Left = (I 
    Mod 3) * 100
        TestButton.Top = 
    Floor(I / 3) * 100

        Buttons(I) = TestButton    
    'Button(I) points to same NEW object that TestButton variable CURRENTLY points to
    Note that I have reverted to zero-based array indexing, ie 0..24 rather than 1..25, and that the tags have thus changed also (which should be no problem, assuming you are using the tag to index back to the array).

    Zero-based indexing isn't all bad, eg, it simplifies the Left and Top calculations above. If the array was one-based, you'd be adding and subtracting ones all over the place, which usually takes a few goes to get right (and sometimes not even then).
    Last edited: Mar 16, 2019 at 11:08 PM
  5. Jose Briceño

    Jose Briceño Member Licensed User

    Thanks for answering. but the code that shows you well shows the buttons the problem I have is when I click on the buttons do not change the image, only, it does when pressing but then I return to the previous image ... the idea is to keep the pressed image ...
  6. emexes

    emexes Active Member Licensed User

    No worries :)

    I don't quite understand this bit (but your English is 1000x better than my Italian)

    I think it means that your 25 buttons are all displayed on the screen. I'm kinda intrigued as to how this is happening, given that the code looks like all 25 buttons in the array are pointing to the same object that is only Dimmed once, but hey: if it's working, it's working!

    I think what might be happening here is that you are changing the not-being-pressed and the being-pressed images of a momentary button.

    Sorry, just got distracted for a long time. Banged up quick sample in B4J. Assume you are implementing some kind of control panel and want to have buttons that act like physical press-on press-off toggle battons, with an indicator of whether they are currently on or off, eg a LED inside the button.

    I've used the button text to indicate the current state of each button. Changing the button imagery rather than the text should be straightforward, but I don't have any suitable images, and I do have other tasks piling up, so I'll leave that project for you ;-) If you get stuck for more than an hour, post back here and I'll put in the extra effort.


    #Region Project Attributes 
    #MainFormWidth: 600
    #MainFormHeight: 600 
    #End Region

    Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Dim NumButtons As Int = 32
    Dim Button(NumButtons) As Button    'initially this is an array of empty pointers
    Dim ButtonState(NumButtons) As Int    'using momentary button as toggle button = need to save current state
    End Sub

    Sub AppStart (Form1 As Form, Args() As String)
        MainForm = Form1
    'MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.

    Dim NumCols As Int = 3
    Dim NumRows As Int = Ceil(NumButtons / NumCols)
    Dim NewPadding As Double = MainForm.Width * 0.02    'boldly assuming square pixels
        Dim NewWidth As Double = (MainForm.Width - NewPadding * (NumCols + 1)) / NumCols
    Dim NewHeight As Double = (MainForm.Height - NewPadding * (NumRows + 1)) / NumRows
    Dim NewLeft, NewTop As Double
    Dim ValveLocations() As String = Array As String("Upper""Lower""Middle""Front""Back""Side")
    For I = 0 To NumButtons - 1
    Dim NewButton As Button    'create another button
            Button(I) = NewButton    'and save (pointer to) it in array so that we can access it later
    'at this point, NewButton and Button(I) variables both point to the one/same object,
            'thus it doesn't matter which we use to alter the button,
            NewLeft = NewPadding + (I 
    Mod 3) * (NewPadding + NewWidth)
            NewTop = NewPadding + 
    Floor(I / 3) * (NewPadding + NewHeight)
    Log(I & " " & NewLeft & " " & NewTop & " " & NewWidth & " " & NewHeight)
            MainForm.RootPane.AddNode(NewButton, NewLeft, NewTop, NewWidth, NewHeight)    
    'and show on screen
    'and at this point, now there are three pointers to the one/same object,
            'so even though we think we've handed it over to RootPane,
            'we still have access to it via NewButton and Button(I)
            NewButton.Text = ValveLocations(I 
    Mod 6) & " Valve " & (I + 1)    'number using human one-based rather than computer zero-based
            NewButton.Tag = I    'index into Button array so that we don't have to search for it
    For I = 0 To NumButtons - 1
            ButtonState(I) = 
    Min((I / 2Mod 31)    'demo = random on/off state
    End Sub

    'Return true to allow the default exceptions handler to handle the uncaught exception.
    Sub Application_Error (Error As Exception, StackTrace As StringAs Boolean
    Return True
    End Sub

    Sub AnyButton_Click
    Dim B As Button = Sender
    Log("righto, who's the joker that pressed Button(" & B.Tag & ") aka " & B.Text & " ???")
    Dim I As Int = B.Tag    'index into Button array
        ButtonState(I) = (ButtonState(I) + 
    1Mod 4    'advance through states 0, 1, 2, 3, 0...

    End Sub

    Sub RefreshButtonAppearance(B As Button)    'or could pass it an Index into Button()

    Dim I As Int = B.Tag    'index into Button array
    'this is where you paint the button with whatever imagery you like
        'for each of the button's various states (normal toggle button = 2 states,
        'but you can have more if you wish, eg 4 here:
    Select Case ButtonState(I)
    Case 0
                B.Text = 
    "Valve " & (I + 1) & " is OFF"
    Case 1
                B.Text = 
    "Valve " & (I + 1) & " is ON"
    Case 2
                B.Text = 
    "Valve " & (I + 1) & " is AUTO"
    Case 3
                B.Text = 
    "[LOCKED OUT]"    '
    Case Else
                B.Text = 
    "[ERROR " & ButtonState(I) & "]"    'just in case...
    End Select
    End Sub
  7. emexes

    emexes Active Member Licensed User

    Then I thought, ah, stuff it, I'll just do it. And I found out that B4J Buttons don't have the background image thing. Well, they probably do, I've just never used it in B4J.

    No worries, though, it looked like you were on the right track anyway, but derailed by the button not being a toggle-on-toggle-off button.

    What I'd try is (merging the best bits of your code and mine ;-) :

    'in globals:

    Dim NumButtonStates As Int = 4
    Dim ButtonStateSld(NumButtonStates) As StateListDrawable

    'in startup code:

    Dim ButtonStateImageFiles() As String = Array As String("asiento.png""asiento_sel.png""state2.png""state3.png")

    For I = 0 To NumButtonStates - 1
    Dim ButtonStateBitmap As BitmapDrawable
    LoadBitmap(File.DirAssets, ButtonStateImageFiles(I))

    Dim sld As StateListDrawable

        sld.AddState(sld.State_Disabled, ButtonStateBitmap)
        sld.AddState(sld.State_Pressed, ButtonStateBitmap)

        ButtonStateSld(I) = sld    
    'save for use in refresh routine

    'in RefreshButtonAppearance():

    Select Case ButtonState(I)    'or just: If ButtonState(I) < NumButtonStates Then
        Case 0123:
    Button(I).Background = ButtonStateSld(ButtonState(I))

    Case Else:
    'whatever you want to do if ButtonState is invalid (shouldn't happen, but...)
            Button(I).Background = ButtonStateSld(sld_to_use_if_ButtonState_is_invalid)

    End Select
  8. Jose Briceño

    Jose Briceño Member Licensed User

    this code loads all the seats well, and when you press a button it changes it to yellow ... now you only need to put an image when pressing.

    For i=1 To TipoBus 
    Dim TestButton As Button
    If pos <> Posicion(i) Then       
    'salto de linea

    End If
    ' boton con imagen
        sld.AddState(sld.State_Disabled, bmpTeam ) 
        sld.AddState(sld.State_Pressed, bmpSel )
        TestButton.Background = sld

        Buttons(i) =  TestButton       
    'pnl.Height = lstChecks.Size * height
        'Activity.AddMenuItem("Display checked", "mnuChecked")
    End Sub

    Sub Activity_Resume

    End Sub

    Sub TestButton_Click
    Dim Send As Button
    Dim n As Int
    ToastMessageShow("Botón pulsadoooo nº: " &Send.tag , False)
    End Sub

    I still have this code but the idea is to put an image by clicking on the buttons Thank you
    Last edited: Mar 17, 2019 at 8:16 PM
  9. emexes

    emexes Active Member Licensed User


    That looks like an amazing home cinema setup you've got going there!!! Once you get your management app going, perhaps you should get in touch with this guy:

    or is there already some worldwide organisation for home cinema buffs???

    Nextly, partly out of curiousity and partly because it'd be useful to know:

    Are you writing in Spanish or in English? I just noticed today that your post is in Spanish when I view it in my email, and in English when I view it through the web interface. It looks like the forum software is automatically translating for us. I am impressed, but also mildly embarrassed that it took me so long to notice.

    I am writing in English - does this automatically come up as Spanish on your screen?
  10. emexes

    emexes Active Member Licensed User

    Righto, on to programming matters :)

    1/ Is your app now working the way that you want?

    2/ If it is not, here are some thoughts I had:

    2a/ Does it *have* to be a button? An ImageView and a Label also have Click (and Long Click) events. These might be better for your purpose.

    2b/ Speaking of Long Click - you could use normal click to do one thing to the seat (allocate/deallocate? clear request?) and a long click to do something else, like bring up a menu of other less-common options.

    2c/ Could you put a label over a seat image, so that you can see the seat image under the text? The Material Icons and Awesome Fonts have characters that could represent: seat occupied, reserved, service request, patron seated.

    2d/ Presumably each installation of your app will be used with a different seating layout, so your use of an internal minidatabase (arrays) to store the location and numbering of the seats is a good plan, rather than the automatic grid layout that I had going.

    2e/ A cheap way of getting call buttons going might be those Bluetooth iTag keyfinder things - they're like $3 on Ebay, and have a button that can be read. The downsides that I can immediately see are: (i) they beep when the button is pressed (open up and cut wire to buzzer might fix that), and the button seems to only be readable when the Android device is connected to the iTag, which might limit the number of iTags that can be used in one setting.

    Chew those over, give it a burl, let me know how it goes. I'm itching to code some of it, but I've got plenty of other stuff to keep me occupied elsewhere, so no worries if you want to keep all the fun to yourself :)
  11. emexes

    emexes Active Member Licensed User

    Ignore the cinema references: I just noticed the wheels.

    Or is it a mobile cinema?!?!

  12. klaus

    klaus Expert Licensed User

    Instead of Buttons use ToggleButtons for this, they are made for what you want.
    Use the StateListDrawable and BitmapDrawables with two bitmaps for each button and use the CheckedChange event instead of the Click event.
    Erel had written a StateListDrawable example for ToggleButtons, it uses colors, but you have to replace the the ColorDrawables by BitmapDrawables.
    1. As already suggested use For i = 0 To TipoBus - 1 instaed of For i = 1 To TipoBus, arrays are 0 indexed.
    2. Define the two bitmaps outsides the For / Next loop, loading them only once.
    Peter Simpson likes this.
  13. emexes

    emexes Active Member Licensed User

    Good point. I didn't suggest this because it looked like the use case might grow to more than 2 states, in which case the standard button with the Click event seemed better.

    +1 generally, but what I also noted was that the array in question was psuedo-1-based, because there was some index calculation referring to the previous element, and B4A/Java/C doesn't allow for negative-based arrays either.

    BASIC FTW here: a nice feature of most BASICs is that you can DIM (A to B) where A and B can be any integers, including negative (well, as long as A < B, I guess: having said that, I've never actually tried reversing the bounds - how boring am I?!?!) I do miss this flexibility, eg DIM Year(1980 to 2030) or DIM Temperature(-30 to 60) or DIM Direction$(-1 to 1)

    +1 (assuming they are constant)
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice