Android Question How to get text of a line in label?

RB Smissaert

Well-Known Member
Licensed User
Longtime User
How can I read a particular line in a multi-line label? There are no linebreaks, so the lines are set by the label.

RBS
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Didn't know about BBLabel and I think it would add unjustified complexity to solve a small problem
I would have chosen BBLabel or BBCodeView if I needed to make such measurements. Measuring text is not a small problem.

Here is the relevant code for anyone who is interested:
B4X:
For Each line As BCTextLine In BBCodeView1.Paragraph.TextLines
    Dim sb As StringBuilder
    sb.Initialize
    For Each un As BCUnbreakableText In line.Unbreakables
        For i = 0 To un.NotFullTextChars.Length - 1
            sb.Append(un.NotFullTextChars.Buffer(un.NotFullTextChars.StartIndex + i))
        Next
    Next
    Log(sb.ToString)
Next
Note that some characters will be missing if there are unbreakable words with multiple styles. Only the text of the first style in each word will appear.
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I would have chosen BBLabel or BBCodeView if I needed to make such measurements. Measuring text is not a small problem.

Here is the relevant code for anyone who is interested:
B4X:
For Each line As BCTextLine In BBCodeView1.Paragraph.TextLines
    Dim sb As StringBuilder
    sb.Initialize
    For Each un As BCUnbreakableText In line.Unbreakables
        For i = 0 To un.NotFullTextChars.Length - 1
            sb.Append(un.NotFullTextChars.Buffer(un.NotFullTextChars.StartIndex + i))
        Next
    Next
    Log(sb.ToString)
Next
Note that some characters will be missing if there are unbreakable words with multiple styles. Only the text of the first style in each word will appear.

OK, thanks.
Not sure I will have use for it other than with this rare case of char 9632, but will have a look into using BBLabel.

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
OK, thanks.
Not sure I will have use for it other than with this rare case of char 9632, but will have a look into using BBLabel.

RBS

Had a look at changing the particular plain label to a BBLabel, but it looks quite a big job as lots of properties/methods are different.
I take it I can't use a dummy BBLabel to measure the text height and apply the result to the plain label?

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
I think I can do this by using an edittext instead of a label as the dummy.
Used some Java code posted by Klaus to set the cursor and get the cursor Y position.

B4X:
'Selects the text between the two indexes.
Sub SetSelection(edt As EditText, StartIndex As Int, EndIndex As Int)
    Dim jo = edt As JavaObject
    jo.RunMethod("setSelection", Array As Object(StartIndex, EndIndex))
End Sub

'gets the x and y coordinates of the cursor in an EditText view
Sub GetEditTextXYCursor(edt As EditText) As Int()
    
    Dim joEditText As JavaObject
    Dim joLayout As JavaObject
    Dim  PaddingLeft As Int
    Dim PaddingTop As Int
    Dim ScrollY As Int
    Dim Pos As Int
    Dim Line As Int
    Dim LineBaseline As Int
    Dim  xy(2) As Int

    joEditText = edt
    PaddingLeft = joEditText.RunMethod("getPaddingLeft", Null)
    PaddingTop = joEditText.RunMethod("getPaddingTop", Null)
    ScrollY = joEditText.RunMethod("getScrollY", Null)
    Pos = joEditText.RunMethod("getSelectionStart", Null)
    joLayout = joEditText.RunMethod("getLayout", Null)
    Line = joLayout.RunMethod("getLineForOffset", Array As Object(Pos))    'line number
    LineBaseline = joLayout.RunMethod("getLineBaseline", Array As Object(Line))

    xy(0) = joLayout.RunMethod("getPrimaryHorizontal", Array As Object(Pos)) + PaddingLeft
    xy(1) = LineBaseline + PaddingTop - ScrollY    'base line
    
    Return xy
    
End Sub

Sub GetEditTextLines(edt As EditText, strText As String, oCS As CSBuilder) As String()
    
    Dim i As Int
    Dim xy() As Int
    Dim iYOld As Int
    Dim lstLineStarts As List
    
    If oCS.IsInitialized Then
        strText = oCS.ToString
    End If
    
    lstLineStarts.Initialize
    
    For i = 0 To strText.Length
        SetSelection(edt, i, i)
        xy = GetEditTextXYCursor(edt)
        If xy(1) > iYOld Then
            lstLineStarts.Add(i)
            Log("GetEditTextLines, i: " & i)
        End If
        iYOld = xy(1)
    Next
    
    Dim arrLines(lstLineStarts.Size) As String
    
    arrLines(0) = strText.SubString2(0, lstLineStarts.Get(1))
    Log(0 & " _ " & arrLines(0))
    
    For i = 1 To lstLineStarts.Size - 1
        If i < lstLineStarts.Size - 1 Then
            arrLines(i) = strText.SubString2(lstLineStarts.Get(i), lstLineStarts.Get(i + 1))
        Else
            arrLines(i) = strText.SubString2(lstLineStarts.Get(i), strText.Length)
        End If
        Log(i & " _ " & arrLines(i))
    Next
    
    Return(arrLines)
    
End Sub

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Have tested this now and this does indeed work (getting the text height from the cursor position instead of using sUtils.MeasureMultilineTextHeight) and gives me better results when the text contains for example character 9632.

B4X:
Public Sub MeasureMultilineTextHeightLBL(oParent As Activity, lbl As Label, strText As String, oCSB As CSBuilder) As Int
    
    Dim Left, Top, Right, Bottom As Int
    Dim edtDummy As EditText
    Dim lstLinePos As List
    Dim Ht As Int
    Dim jo = lbl As JavaObject
    
    'get padding
    Left = jo.RunMethod("getPaddingLeft", Null)
    Top = jo.RunMethod("getPaddingTop", Null)
    Right = jo.RunMethod("getPaddingRight", Null)
    Bottom = jo.RunMethod("getPaddingBottom", Null)
    
    'setup temp edittext, same parameters, but twice the height
    edtDummy.Initialize("")
    oParent.AddView(edtDummy, 0, lbl.Height * -2dip, lbl.Width, lbl.Height * 2)
    edtDummy.TextSize = lbl.TextSize
    edtDummy.Typeface = lbl.Typeface
    edtDummy.Padding = Array As Int(Left,Top,Right,Bottom)
    
    If oCSB.IsInitialized Then
        edtDummy.Text = oCSB
    Else
        edtDummy.Text = strText
    End If
    
    DoEvents 'ignore warning, this is needed
    lstLinePos = GetEditTextLinePositions(edtDummy, strText, oCSB)
    
    edtDummy.RemoveView
    
    Ht = lstLinePos.Get(lstLinePos.Size  - 1) + Bottom
    Return Ht
    
End Sub

Sub GetEditTextLinePositions(edt As EditText, strText As String, oCS As CSBuilder) As List
    
    Dim i As Int
    Dim xy() As Int
    Dim iYOld As Int
    Dim lstLines As List
    
    If oCS.IsInitialized Then
        strText = oCS.ToString
    End If
    
    lstLines.Initialize
    
    For i = 0 To strText.Length
        SetSelection(edt, i, i)
        xy = GetXYCursor(edt)
        If i = 0 Then
            iYOld = xy(3)
        End If
        If xy(1) > iYOld Or i = strText.Length Then
            lstLines.Add(xy(3))
            iYOld = xy(3)
        End If
    Next
    
    Return(lstLines)
    
End Sub

'Posted by Klaus'
Sub GetXYCursor(edt As EditText) As Int() 'ignore warning
    
    Dim joEditText As JavaObject
    Dim joLayout As JavaObject
    Dim PaddingLeft As Int
    Dim PaddingTop As Int
    Dim Pos As Int
    Dim Line As Int
    Dim Column As Int 'ignore warning
    Dim LineBaseline As Int
    Dim LineAscent As Int 'ignore warning
    Dim LineDescent As Int 'ignore warning
    Dim LineTop As Int
    Dim LineBottom As Int
    Dim LineCount As Int 'ignore warning
    Dim LineStart As Int
    Dim ScrollX As Int 'ignore warning
    Dim ScrollY As Int
    
    Dim xy(4) As Int
    
    joEditText = edt
    PaddingLeft = joEditText.RunMethod("getPaddingLeft", Null)
    PaddingTop = joEditText.RunMethod("getPaddingTop", Null)
    ScrollX = joEditText.RunMethod("getScrollX", Null)
    ScrollY = joEditText.RunMethod("getScrollY", Null)
    Pos = joEditText.RunMethod("getSelectionStart", Null)
    joLayout = joEditText.RunMethod("getLayout", Null)
    Line = joLayout.RunMethod("getLineForOffset", Array As Object(Pos))
    LineBaseline = joLayout.RunMethod("getLineBaseline", Array As Object(Line))
    LineBottom = joLayout.RunMethod("getLineBottom", Array As Object(Line))
    LineTop = joLayout.RunMethod("getLineTop", Array As Object(Line))
    LineAscent = joLayout.RunMethod("getLineAscent", Array As Object(Line))
    LineDescent = joLayout.RunMethod("getLineDescent", Array As Object(Line))
    LineCount = joLayout.RunMethod("getLineCount", Null)
    LineStart = joLayout.RunMethod("getLineStart", Array As Object(Line))
    Column = Pos - LineStart
    
    xy(0) = joLayout.RunMethod("getPrimaryHorizontal", Array As Object(Pos)) + PaddingLeft
    xy(1) = LineBaseline + PaddingTop - ScrollY    'base line
    'Log("GetXYCursor, xy(1): " & xy(1))
    xy(2) = LineTop + PaddingTop - ScrollY    'top of the line
    'Log("GetXYCursor, xy(2): " & xy(2))
    xy(3) = LineBottom + PaddingTop - ScrollY 'text bottom line
    'Log("GetXYCursor, xy(3): " & xy(3))
    
    Return xy
    
End Sub

Some other code that gives some more detailed information about the text layout in an EditText:

B4X:
    Type EditTextLine(iLineIndex As Int, _
                      iStartIndex As Int, _
                      iEndIndex As Int, _
                      iTop As Int, _
                      iLeft As Int, _
                      iRight As Int, _
                      strText As String)

Sub GetEditTextLines(edt As EditText, strText As String, oCS As CSBuilder) As List
    
    Dim i As Int
    Dim xy() As Int
    Dim iYOld As Int
    Dim iLeft As Int
    Dim iLineIndex As Int
    Dim lstLines As List
    Dim iFirstCharIndex As Int
    Dim iLastY As Int
    Dim iLastX As Int
    
    If oCS.IsInitialized Then
        strText = oCS.ToString
    End If
    
    lstLines.Initialize
    
    For i = 0 To strText.Length
        SetSelection(edt, i, i)
        xy = GetXYCursor(edt)
        If i = 0 Then
            iLeft = xy(0) 'will stay the same
            iYOld = xy(3)
        End If
        If xy(1) > iYOld Or i = strText.Length Then
            
            Dim tEDTLine As EditTextLine
            tEDTLine.iLineIndex = iLineIndex
            tEDTLine.iStartIndex = iFirstCharIndex
            tEDTLine.iEndIndex = i - 1
            tEDTLine.iLeft = iLeft
            tEDTLine.iRight = iLastX
            tEDTLine.iTop = iLastY
            tEDTLine.strText = strText.SubString2(iFirstCharIndex, i)
            lstLines.Add(tEDTLine)
            
            iLineIndex = iLineIndex + 1
            iFirstCharIndex = i
            iYOld = xy(3)
        Else
            iLastX = xy(0)
            iLastY = xy(3)
        End If
    Next
    
    Return(lstLines)
    
End Sub

RBS
 
Upvote 0
Top