Class is below. I use it like:
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:
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 &).
' 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: