Issues with my View Manager Class

Roger Garstang

Well-Known Member
Licensed User
Longtime User
Class is below. I use it like:

B4X:
VM.Initialize(pgFrame.Panel, True, True, False)
VM.AddLabel(-1, -1, 1, 1, "Username:", Colors.Black, "User")
VM.AddTextBox(-1, -2, 1, 1, "User", Utility.CurUser, "Username", False, VM.DataType_Uppercase_All, VM.CharFilter_Upper_Alpha & VM.CharFilter_Numeric, VM.ActionBtn_Next, "", Me, 1, 20)

Before I had the class without the panel passed to Initialize and the Add subs had no Left/Top/Width/Height stuff or any of the code/variables that adjust position or size. This worked fine. Then I added everything else to try to adjust for font sizes and scaling and make sort of a document flow.

pgFrame is a scrollview, so the inner panel being set to a width of -1 messes up my calculations that I need to fix for width and height being -1 to fill the container (Calculations cause the view to be added with a width/height < 1 so it throws a fit when running). The Flow code seems to be working ok other than that, but the big issue is font calculations. I'm not overriding the Font Scale since I set that to False in Init of the Class, so that code we can ignore. I calculate the size using a Canvas since I need height and width. It comes back with sizes that are way too small though. Am I doing something wrong with the Canvas or is it just coming back wrong?

B4X:
'Class module
Private Sub Class_Globals
   Type MyTextbox(Name As String, DataType As Int, ActionBtn As Int, ActionSub As String, ActionSubModule As Object, MinChar As Int, MaxChar As Int, EditView As EditText)
   Private IME As IME ' Put here instead of below so class is considered an Activity
   Private MyViews As List
   Private NameMap As Map
   Private Padding As Int
   Private MinMaxWarn As Boolean
   Private ScaleViews As Boolean
   Private FontScale As Float
   Private OverrideFontScale As Boolean
   Private Parent As Panel
   Private CurX As Int
   Private NextX As Int
   Private CurY As Int
   Private NextY As Int
End Sub

' Initialize the Class
' Container: Panel to place all controls in
' ShowMinMaxWarn: Set if Toast Messages are to show when Min and Max Char requirements aren't met
' ViewScaling: Sets if Views are to be scaled to fit the text they contain
' OverrideFontScaling: Set if You wish to override the user's font size scale
Public Sub Initialize(Container As Panel, ShowMinMaxWarn As Boolean, ViewScaling As Boolean, OverrideFontScaling As Boolean)
Dim Ref As Reflector

   MyViews.Initialize
   NameMap.Initialize
   IME.Initialize("IME")
   Padding = 5dip
   MinMaxWarn = ShowMinMaxWarn
   ScaleViews = ViewScaling
   OverrideFontScale = OverrideFontScaling
   Parent = Container
   CurX = 5dip
   NextX = 5dip
   CurY = 5dip
   NextY = 5dip
   
   Ref.Target = Ref.GetContext
   Ref.Target = Ref.RunMethod("getResources")
   Ref.Target=Ref.RunMethod("getConfiguration")
   FontScale= Ref.GetField("fontScale")
End Sub

#Region Constants
' General Text Data [Type_Class_Text]
Sub DataType_Text As Int
   Return 1
End Sub

' Numbers Only [Type_Class_Number]
Sub DataType_Num As Int
   Return 2
End Sub

' Floating Point Numbers with sign and decimal [Type_Class_Number | Type_Number_Flag_Decimal | Type_Number_Flag_Signed]
Sub DataType_Float As Int
   Return 12290
End Sub

' DateTime Text [Type_Class_DateTime]
Sub DataType_DateTime As Int
   Return 4
End Sub

' Date Text [Type_Class_DateTime | Type_DateTime_Variation_Date]
Sub DataType_Date As Int
   Return 20
End Sub

' Time Text [Type_Class_DateTime | Type_DateTime_Variation_Time]
Sub DataType_Time As Int
   Return 36
End Sub

' Password [Type_Class_Text | Type_Text_Variation_Password]
Sub DataType_Password As Int
   Return 129
End Sub

' Email Text [Type_Class_Text | Type_Text_Variation_Email_Address]
Sub DataType_Email As Int
   Return 33
End Sub

' URL Text [Type_Class_Text | Type_Text_Variation_URI]
Sub DataType_URL As Int
   Return 17
End Sub

' Phone Number Text [Type_Class_Phone]
Sub DataType_Phone As Int
   Return 3
End Sub

' Name Text [Type_Class_Text | Type_Text_Variation_Person_Name | Type_Text_Flag_Cap_Words]
Sub DataType_Name As Int
   Return 8289
End Sub

' Uppercase Every Char [Type_Class_Text | Type_Text_Cap_Characters]
' (Overrides all other Uppercase Flags)
Sub DataType_Uppercase_All As Int
   Return 4097
End Sub

' Uppercase First Letter of Each Sentence [Type_Class_Text | Type_Text_Cap_Sentences]
Sub DataType_Uppercase_Sentences As Int
   Return 16385
End Sub

' Uppercase First Letter of Each Word [Type_Class_Text | Type_Text_Cap_Words]
' (Overrides Sentences Uppercase Flag)
Sub DataType_Uppercase_Words As Int
   Return 8193
End Sub

' Text should have Auto Correction Applied [Type_Class_Text | Type_Text_Flag_Auto_Correct]
Sub DataType_Auto_Correct As Int
   Return 32769
End Sub

' No Suggestions/Auto Correct [Type_Class_Text | Type_Text_Flag_No_Suggestions]
Sub DataType_No_Suggestions As Int
   Return 524289
End Sub

' Shows Next Button and will navigate to Next View
Sub ActionBtn_Next As Int
   Return 5
End Sub

' Shows Previous Button and will navigate to Previous View
Sub ActionBtn_Previous As Int
   Return 7
End Sub

' Shows Done Button and has option to execute ActionSub
Sub ActionBtn_Done As Int
   Return 6
End Sub

' Shows Go Button and has option to execute ActionSub
Sub ActionBtn_Go As Int
   Return 2
End Sub

' Shows Search Button and has option to execute ActionSub
Sub ActionBtn_Search As Int
   Return 3
End Sub

' Shows Send Button and has option to execute ActionSub
Sub ActionBtn_Send As Int
   Return 4
End Sub

' Upper and Lower Case Alpha Chars
' Returns: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
Sub CharFilter_Alpha As String
   Return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
End Sub

' Uppercase Alpha Chars
' Returns: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Sub CharFilter_Upper_Alpha As String
   Return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
End Sub

' Number Chars
' Returns: "0123456789"
Sub CharFilter_Numeric As String
   Return "0123456789"
End Sub
#End Region

' Left/Top: Position to place view, -1 to Place in Current Flow Position, -2 to Place in Next Flow Position.
' Width/Height: Width/Height of View. If ScaleViews is True it will adjust to fit font. -1 to Fit to Full Width/Height.
' Text: Text of Label.
' Color: Color of Label
' LinkedView: Name String for View that will get focus when Label is Clicked
Sub AddLabel(Left As Int, Top As Int, Width As Int, Height As Int, Text As String, Color As Int, LinkedView As String) As Rect
Dim myControl As Label
Dim viewRect As Rect
Dim ref As Reflector

   myControl.Initialize("Label")
   myControl.Text = Text
   myControl.TextColor = Color
   myControl.TextSize = 16
   myControl.Typeface = Typeface.DEFAULT_BOLD
   ref.Target = myControl
   ref.RunMethod2("setLines", 1, "java.lang.int")
   ref.RunMethod2("setHorizontallyScrolling", True, "java.lang.boolean")
   ref.RunMethod2("setEllipsize", "MARQUEE", "android.text.TextUtils$TruncateAt")
   ref.RunMethod2("setMarqueeRepeatLimit", -1, "java.lang.int")
   ref.RunMethod2("setSelected", True, "java.lang.boolean")
   
   myControl.Tag = LinkedView
   
   If Top = -2 Then
      Top = NextY
   Else If Top = -1 Then
      Top = CurY
   End If
   If Left = -2 Then
      Left = NextX
   Else If Left = -1 Then
      Left = CurX
   End If
   
   If Width = -1 Then Width = Parent.Width - Padding - Left
   If Height = -1 Then Height = Parent.Height - Padding - Top
   
   If OverrideFontScale = True Then myControl.TextSize = myControl.TextSize / FontScale
   
   Parent.AddView(myControl, Left, Top, Width, Height)
   
   If ScaleViews = True Then ' Scale View to Size required for Font
      Dim testCanvas As Canvas
      
      testCanvas.Initialize(myControl)
      Height = testCanvas.MeasureStringHeight(Text, myControl.Typeface, myControl.TextSize)
      If myControl.Height < Height Then myControl.Height = Height
      If Text.Length > 0 Then ' Calculate Min Width
         Width = testCanvas.MeasureStringWidth(Text, myControl.Typeface, myControl.TextSize)
         If myControl.Width < Width Then myControl.Width = Width
      End If
      myControl.SetLayout(myControl.Left, myControl.Top, myControl.Width, myControl.Height)
   End If
   
   CurX = Left
   NextX = Left + myControl.Width + Padding
   CurY = Top
   NextY = Top + myControl.Height + Padding
   
   viewRect.Initialize(Left, Top, Left + myControl.Width, Top + myControl.Height)
   Return viewRect
End Sub

Sub Label_Click
Dim Name As String
Dim MyControl As Label
Dim LV As View
   
   MyControl = Sender
   Name = MyControl.Tag
   If Name.Length > 0 Then
      LV = NameMap.Get(Name)
      If LV.IsInitialized = True Then LV.RequestFocus
   End If
End Sub

' Left/Top: Position to place view, -1 to Place in Current Flow Position, -2 to Place in Next Flow Position.
' Width/Height: Width/Height of View. If ScaleViews is True it will adjust to fit font. -1 to Fit to Full Width/Height.
' Name: Name given to the control for reference or use in a Select Case Block.
' DataType: Use one of the Constants within this Class to Specify the Data Type for the View (Can combine with OR).
' CharFilter: Use one of the Constants within this Class and/or your own string to Specify the Text Filter for the View's Allowed Chars (Join Strings with &amp;).
' ActionBtn: Use one of the Constants within this Class to Specify the Action Button for the View's Keyboard.
' ActionSub: Name of Sub to call for Action Buttons other than Next and Previous.
' ActionSubModule: Activity/Module that Sub is in.  (Usually [Me] can be used if Sub is in Activity Module the View resides in).
' Min/Max Char: Set to desired value or 0 for no restriction.
Public Sub AddTextBox(Left As Int, Top As Int, Width As Int, Height As Int, Name As String, Text As String, Hint As String, MultiLine As Boolean, DataType As Int, CharFilter As String, ActionBtn As Int, ActionSub As String, ActionSubModule As Object, MinChar As Int, MaxChar As Int) As Rect
Dim ref As Reflector
Dim viewRect As Rect
Dim newTextBox As MyTextbox

   newTextBox.Initialize
   newTextBox.EditView.Initialize("Edit")
   newTextBox.DataType = DataType
   newTextBox.ActionBtn = ActionBtn
   newTextBox.ActionSub = ActionSub
   newTextBox.ActionSubModule = ActionSubModule
   newTextBox.MinChar = MinChar
   newTextBox.MaxChar = MaxChar
   newTextBox.Name = Name
   newTextBox.EditView.Text = Text
   newTextBox.EditView.Hint = Hint
   newTextBox.EditView.TextSize = 18
   
   If Top = -2 Then
      Top = NextY
   Else If Top = -1 Then
      Top = CurY
   End If
   If Left = -2 Then
      Left = NextX
   Else If Left = -1 Then
      Left = CurX
   End If
   
   If Width = -1 Then Width = Parent.Width - Padding - Left
   If Height = -1 Then Height = Parent.Height - Padding - Top
   
   If OverrideFontScale = True Then newTextBox.EditView.TextSize = newTextBox.EditView.TextSize / FontScale
   
   Parent.AddView(newTextBox.EditView, Left, Top, Width, Height)
   
   If ScaleViews = True Then ' Scale View to Size required for Font
      Dim testCanvas As Canvas
      
      testCanvas.Initialize(newTextBox.EditView)
      Height = testCanvas.MeasureStringHeight(CharFilter_Upper_Alpha, newTextBox.EditView.Typeface, newTextBox.EditView.TextSize)
      If newTextBox.EditView.Height < Height Then newTextBox.EditView.Height = Height
      If MinChar > 0 Then ' Calculate Min Width
         Width = (testCanvas.MeasureStringWidth(CharFilter_Upper_Alpha, newTextBox.EditView.Typeface, newTextBox.EditView.TextSize) / 26) * MinChar
         If newTextBox.EditView.Width < Width Then newTextBox.EditView.Width = Width
      End If
   End If
   
   CurX = Left
   NextX = Left + newTextBox.EditView.Width + Padding
   CurY = Top
   NextY = Top + newTextBox.EditView.Height + Padding
   
   ref.Target = newTextBox.EditView
   If MultiLine Then
      newTextBox.EditView.SingleLine = False
      newTextBox.EditView.Wrap= True
      newTextBox.DataType = Bit.Or(newTextBox.DataType, 131072) ' Multiline Text Flag
      ref.RunMethod2("setImeOptions", Bit.Or(1073741824, newTextBox.ActionBtn), "java.lang.int") 'flagNoEnterAction= 1073741824
   Else
      newTextBox.EditView.SingleLine = True
      newTextBox.EditView.Wrap= False
      ref.RunMethod2("setImeOptions", Bit.Or(268435456, newTextBox.ActionBtn), "java.lang.int") 'flagNoExtractUi= 268435456
   End If
   newTextBox.EditView.InputType = newTextBox.DataType
   
   If CharFilter.Length = 0 Then
      If Bit.And(DataType, DataType_Phone) = DataType_Phone Then
         CharFilter = CharFilter_Numeric & "*#+-P()N,/ "
      Else If Bit.And(DataType, DataType_URL) = DataType_URL Then
         CharFilter = CharFilter_Alpha & CharFilter_Numeric & "$.,'-+!*_~:%()"
      Else If Bit.And(DataType, DataType_Email) = DataType_Email Then
         CharFilter = CharFilter_Alpha & CharFilter_Numeric & "!#$%&'*+-/=?^_`{|}~."
      Else If Bit.And(DataType, DataType_Float) = DataType_Float Then
         CharFilter = CharFilter_Numeric & ".-"
      Else If Bit.And(DataType, DataType_Num) = DataType_Num Then
         CharFilter = CharFilter_Numeric
      End If
   End If
   If CharFilter.Length > 0 Then IME.SetCustomFilter(newTextBox.EditView, newTextBox.EditView.InputType, CharFilter)
   
   IME.AddHandleActionEvent(newTextBox.EditView)
   
   newTextBox.EditView.Tag = MyViews.Size
   MyViews.Add(newTextBox)
   If Name.Length > 0 Then NameMap.Put(Name, newTextBox.EditView)
   
   viewRect.Initialize(Left, Top, Left + newTextBox.EditView.Width, Top + newTextBox.EditView.Height)
   Return viewRect
End Sub

' Get Text of Named Views
Public Sub GetText(Name As String) As String
Dim FoundName As Label

   FoundName = NameMap.Get(Name)
   If FoundName.IsInitialized = False Then
      Return ""
   Else
      Return FoundName.Text
   End If
End Sub

' Set Padding between Views (Default is 5dip)
Sub SetPadding(NewPadding As Int)
   Padding = NewPadding
End Sub

' Get Padding between Views (Useful to set Left for New Line or in calculations)
Sub GetPadding As Int
   Return Padding
End Sub

' Test if Value in View meets Min Character Requirements
Public Sub isValid(Name As String) As Boolean
Dim FoundName As Label

   FoundName = NameMap.Get(Name)
   If FoundName.IsInitialized = True Then
      Dim MTB As MyTextbox
      Dim index As Int
      index = FoundName.Tag
      If index > -1 Then
         MTB = MyViews.Get(index)
         Return FoundName.Text.Length >= MTB.MinChar
      End If
   End If
   Return False
End Sub

Private Sub IME_HandleAction As Boolean
Dim ET As EditText
Dim MTB As MyTextbox
Dim index As Int

   ET = Sender
   index = ET.Tag
   If index > -1 Then
      MTB = MyViews.Get(index)
      Select Case MTB.ActionBtn
         Case ActionBtn_Next
            If MyViews.Size > index + 1 Then
               MTB = MyViews.Get(index + 1)
               MTB.EditView.RequestFocus
               Return True
            End If
         Case ActionBtn_Previous
            If index > 0 Then
               MTB = MyViews.Get(index - 1)
               MTB.EditView.RequestFocus
               Return True
            End If
         Case Else ' Done, Go, Search, Send
            If MTB.ActionSub.Length > 0 Then CallSubDelayed(MTB.ActionSubModule, MTB.ActionSub)
      End Select
   End If
End Sub

Private Sub Edit_FocusChanged (HasFocus As Boolean)
   If HasFocus = False AND MinMaxWarn = True Then
      Dim ET As EditText
      Dim MTB As MyTextbox
      Dim index As Int
      
      ET = Sender
      index = ET.Tag
      If index > -1 Then
         MTB = MyViews.Get(index)
         If ET.Text.Length < MTB.MinChar Then ToastMessageShow(MTB.Name & " Needs to be at least " & MTB.MinChar & " Characters Long", False)
      End If
   End If
End Sub

Private Sub Edit_TextChanged (Old As String, New As String)
Dim cursorPOS As Int
Dim ET As EditText
Dim MTB As MyTextbox
Dim index As Int

   If New.CompareTo(Old) <> 0 Then
      ET = Sender
      index = ET.Tag
      If index > -1 Then
         MTB = MyViews.Get(index)
         cursorPOS = MTB.EditView.SelectionStart
         If MTB.MaxChar > 0 Then
            If New.Length > MTB.MaxChar AND MinMaxWarn = True Then ToastMessageShow("Too many Characters input", False)
            MTB.EditView.Text = New.SubString2(0, Min(MTB.MaxChar, New.Length))
         End If
         MTB.EditView.SelectionStart = Min(cursorPOS, MTB.EditView.Text.Length)
      End If
   End If
End Sub
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
Class is below. I use it like:

B4X:
VM.Initialize(pgFrame.Panel, True, True, False)
VM.AddLabel(-1, -1, 1, 1, "Username:", Colors.Black, "User")
VM.AddTextBox(-1, -2, 1, 1, "User", Utility.CurUser, "Username", False, VM.DataType_Uppercase_All, VM.CharFilter_Upper_Alpha & VM.CharFilter_Numeric, VM.ActionBtn_Next, "", Me, 1, 20)

Before I had the class without the panel passed to Initialize and the Add subs had no Left/Top/Width/Height stuff or any of the code/variables that adjust position or size. This worked fine. Then I added everything else to try to adjust for font sizes and scaling and make sort of a document flow.

pgFrame is a scrollview, so the inner panel being set to a width of -1 messes up my calculations that I need to fix for width and height being -1 to fill the container (Calculations cause the view to be added with a width/height < 1 so it throws a fit when running). The Flow code seems to be working ok other than that, but the big issue is font calculations. I'm not overriding the Font Scale since I set that to False in Init of the Class, so that code we can ignore. I calculate the size using a Canvas since I need height and width. It comes back with sizes that are way too small though. Am I doing something wrong with the Canvas or is it just coming back wrong?

For the width problem, look at the sub getWidth in my class CheckList. That would solve it.
 
Upvote 0

Roger Garstang

Well-Known Member
Licensed User
Longtime User
Cool, thanks for the GetWidth. I was just about to ask if there was such a thing. I knew there had to be something.

Ok. I played with the rest a little bit and got it half working. I'm a little confused as to why Canvas and StringUtils return two different heights when it is a single line. Does it do some extra calculations? I'm also getting an odd error when trying to use it my Edit View that looks internal and possibly related to classes.

This works fine for my Label:

B4X:
Height = strUtil.MeasureMultilineTextHeight(myControl, Text)
'Height = testCanvas.MeasureStringHeight(Text, myControl.Typeface, myControl.TextSize)

This fails for my EditView saying-
Return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
java.lang.IllegalArguementException: Layout: -16 < 0

I don't use -16 anywhere, so String Utils must be calculating it somehow.

B4X:
Height = strUtil.MeasureMultilineTextHeight(newTextBox.EditView, CharFilter_Alpha)
'Height = testCanvas.MeasureStringHeight(CharFilter_Alpha, newTextBox.EditView.Typeface, newTextBox.EditView.TextSize)
 
Upvote 0

Roger Garstang

Well-Known Member
Licensed User
Longtime User
Ok, I got around the goofy error by using reflection to make the canvas method work sort of how it must work for the label, but for the Edit Text and add in Padding:

B4X:
testCanvas.MeasureStringHeight(CharFilter_Alpha, newTextBox.EditView.Typeface, newTextBox.EditView.TextSize) + ref.RunMethod("getPaddingTop") + ref.RunMethod("getPaddingBottom")

Sizes good now and no error, but it appears the background of the Edit Text doesn't draw right unless it is above a certain minimum size/height or something when added to the panel. Android looks like it uses the 9Patch stuff and whatever part of the background fits in the rect of the Edit View is all that draws/stretches when the size is then adjusted, so it looks really weird. Seems I'm stuck in this circle of needing the size required, but not being able to use canvases and such until the control is added at which point it is drawn in the specified size and not the scaled size since I calculate after.

I tried to Invalidate the control, but it didn't redraw the background, and sometimes it is actually off by 1-2 pixels when getting padding, so I may end up hard coding values and using reflection to setPadding instead since it is just way too much padding.

So, I guess there are a couple questions:

1. What needs done to redraw the Background?
2. I've been wanting to dive into the 9 Patch stuff, so if used in an Edit View will it actually use the Content area I specify in the image and set the padding to what the image says so I don't need reflection to do it?
 
Upvote 0
Top