Enumerate all controls in a form (redundant) - vb.net

I'm trying to enumerate all the controls in a form that satisfy a certain condition like the code beelow
Public Enum MethodSeachEnum
StartsWith = 1
EndsWith = 2
Contains = 3
End Enum
Public Function GetAllControls(Control As Control, Key As String, MethodSeach As MethodSeachEnum, ControlType As Type, Optional UseTag As Boolean = True) As IEnumerable(Of Control)
Dim controls = Control.Controls.Cast(Of Control)()
Return (controls.SelectMany(Function(ctrl) GetAllControls(ctrl, Metodo)).Concat(controls).Where(Function(c)
Select Case MethodSeach
Case MetodoSeachEnum.EndsWith
If (UseTag) Then
Return c.Tag.ToString.ToUpper.EndsWith(Key.ToUpper) And c.GetType() Is ControlType
Else
Return c.Name.ToUpper.EndsWith(Key.ToUpper) And c.GetType() Is ControlType
End If
Case MetodoSeachEnum.StartsWith
If (UseTag) Then
Return c.Tag.ToString.ToUpper.StartsWith(Key.ToUpper) And c.GetType() Is ControlType
Else
Return c.Name.ToUpper.StartsWith(Key.ToUpper) And c.GetType() Is ControlType
End If
Case MetodoSeachEnum.Contains
If (UseTag) Then
Return c.Tag.ToString.ToUpper.Contains(Key.ToUpper) And c.GetType() Is ControlType
Else
Return c.Name.ToUpper.Contains(Key.ToUpper) And c.GetType() Is ControlType
End If
Case Else
Return False
End Select
End Function))
End Function
Inside my form there is a GroupBox and inside that some TextBox. These TextBox are not returned and I'm not understanding why...
Here how I call this function
Dim ctrls = GetAllControls(FormTagliente, "txtQuote", MetodoSeachEnum.StartsWith, GetType(TextBox), False)
For Each txt As TextBox In ctrls
...
Next

There is IMHO too few information to answer your question "why that doesn't work for your specific case"
Also the GetAllControls with two argument is missing in your code maybe the problem lies there
Anyway I toyed a little with your code (but haven't tested it so it's more a POC) and here's what I got :
Enum SearchMethod
StartsWith = 1
EndsWith = 2
Contains = 3
End Enum
Function GetAllControls(Of T As Control)(ctrl As Control, key As String, method As SearchMethod, Optional useTag As Boolean = True) As IEnumerable(Of T)
' TODO validate args
Dim upperKey = key.ToUpper
Dim searchPredicates() As Func(Of String, Boolean) = {
Function(src, tgt) src.StartsWith(upperKey),
Function(src, tgt) src.EndsWith(upperKey),
Function(src, tgt) src.Contains(upperKey)
}
Dim ctrlSelector As Func(Of Control, String) = If(useTag, Function(c) c.Tag.ToString.ToUpper, Function(c) c.Name.ToUpper)
Return GetAllControlsIterator(Of T)(ctrl, ctrlSelector, searchPredicates(CInt(method) - 1))
End Function
Private Iterator Function GetAllControlsIterator(Of T As Control)(ctrl As Control, ctrlSelector As Func(Of Control, String), searchPredicate As Func(Of String, Boolean)) As IEnumerable(Of T)
For Each child In ctrl.Controls
If searchPredicate(ctrlSelector(child)) AndAlso TypeOf child Is T Then Yield DirectCast(child, T)
For Each grandChild In GetAllControlsIterator(Of T)(child, ctrlSelector, searchPredicate)
Yield DirectCast(grandChild, T)
Next
Next
End Function
The idea was to separate the "construct the criteria logic" to the actual "loop, search, yield" one, using a generic constraint to force the targetType to be a Control (and having directly the "good" return type). I also find simpler to use an Iterator block but that's more personal
Maybe that could help you solve your problem ?

Related

VB.NET Sub inside of a Function? What is this?

So I'm reading through my source code looking for places to improve the code when I come across this unholy chunk of code.
Public Function ReadPDFFile(filePath As String,
Optional maxLength As Integer = 0) As List(Of String)
Dim sbContents As New Text.StringBuilder
Dim cArrayType As Type = GetType(PdfSharp.Pdf.Content.Objects.CArray)
Dim cCommentType As Type = GetType(PdfSharp.Pdf.Content.Objects.CComment)
Dim cIntegerType As Type = GetType(PdfSharp.Pdf.Content.Objects.CInteger)
Dim cNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.CName)
Dim cNumberType As Type = GetType(PdfSharp.Pdf.Content.Objects.CNumber)
Dim cOperatorType As Type = GetType(PdfSharp.Pdf.Content.Objects.COperator)
Dim cRealType As Type = GetType(PdfSharp.Pdf.Content.Objects.CReal)
Dim cSequenceType As Type = GetType(PdfSharp.Pdf.Content.Objects.CSequence)
Dim cStringType As Type = GetType(PdfSharp.Pdf.Content.Objects.CString)
Dim opCodeNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.OpCodeName)
Dim ReadObject As Action(Of PdfSharp.Pdf.Content.Objects.CObject) = Sub(obj As PdfSharp.Pdf.Content.Objects.CObject)
Dim objType As Type = obj.GetType
Select Case objType
Case cArrayType
Dim arrObj As PdfSharp.Pdf.Content.Objects.CArray = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CArray)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In arrObj
ReadObject(member)
Next
Case cOperatorType
Dim opObj As PdfSharp.Pdf.Content.Objects.COperator = DirectCast(obj, PdfSharp.Pdf.Content.Objects.COperator)
Select Case System.Enum.GetName(opCodeNameType, opObj.OpCode.OpCodeName)
Case "ET", "Tx"
sbContents.Append(vbNewLine)
Case "Tj", "TJ"
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case "QuoteSingle", "QuoteDbl"
sbContents.Append(vbNewLine)
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case Else
'Do Nothing
End Select
Case cSequenceType
Dim seqObj As PdfSharp.Pdf.Content.Objects.CSequence = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CSequence)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In seqObj
ReadObject(member)
Next
Case cStringType
sbContents.Append(DirectCast(obj, PdfSharp.Pdf.Content.Objects.CString).Value)
Case cCommentType, cIntegerType, cNameType, cNumberType, cRealType
'Do Nothing
Case Else
Throw New NotImplementedException(obj.GetType().AssemblyQualifiedName)
End Select
End Sub
Using pd As PdfSharp.Pdf.PdfDocument = PdfSharp.Pdf.IO.PdfReader.Open(filePath, PdfSharp.Pdf.IO.PdfDocumentOpenMode.ReadOnly)
For Each page As PdfSharp.Pdf.PdfPage In pd.Pages
ReadObject(PdfSharp.Pdf.Content.ContentReader.ReadContent(page))
If maxLength > 0 And sbContents.Length >= maxLength Then
If sbContents.Length > maxLength Then
sbContents.Remove(maxLength - 1, sbContents.Length - maxLength)
End If
Exit For
End If
sbContents.Append(vbNewLine)
Next
End Using
'Return sbContents.ToString
Dim ReturnList As New List(Of String)
For Each Line In sbContents.ToString.Split(vbNewLine)
If String.IsNullOrWhiteSpace(Line.Trim) Then
Else
ReturnList.Add(Line.Trim)
End If
Next
Return ReturnList
End Function
All this does is read the text parts of a PDF using PDFSharp. What caught my eye however was line 17. Is that a Sub inside of the function?
So, what exactly is this Sub inside of a function? I didn't write this code so I've never seen anything like this before.
How does this work exactly and why wouldn't I use a function to do the processing and then return the results?
In short, my question is, what is this, how does it work, and why would I want to use something like this?
That's a so-called Lambda expression. They're used to create inline (or more correctly: in-method) methods, which makes them more dynamic than normal methods.
In your example a lambda expression is not necessary and only makes the code harder to understand. I suppose the author of that code wrote a lambda expression instead of a separate method in order to not expose ReadObject to any outside code.
One of the best uses for a lambda expression IMO is when you want to make thread-safe calls to the UI thread, for instance:
If Me.InvokeRequired = True Then
Me.Invoke(Sub() TextBox1.Text = "Process complete!")
Else
TextBox1.Text = "Process complete!"
End If
...where the same code without a lambda would look like this:
Delegate Sub UpdateStatusTextDelegate(ByVal Text As String)
...somewhere else...
If Me.InvokeRequired = True Then
Me.Invoke(New UpdateStatusTextDelegate(AddressOf UpdateStatusText), "Process complete!")
Else
UpdateStatusText("Process complete!")
End If
...end of somewhere else...
Private Sub UpdateStatusText(ByVal Text As String)
TextBox1.Text = Text
End Sub
There are also other examples where lambda expressions are useful, for instance if you want to initialize a variable but do some processing at first:
Public Class Globals
Public Shared ReadOnly Value As Integer = _
Function()
DoSomething()
Dim i As Double = CalculateSomething(3)
Return Math.Floor(3.45 * i)
End Function.Invoke()
...
End Class
Yet another usage example is for creating partially dynamic event handlers, like this answer of mine.

Why is this returning Index out of bound exception

When I get to the Read loop I get an index out of bounds error. I think its on the reader ordinal value, but I am not sure why I am getting it.
Private Function Create(Reader As SqlDataReader) As IEnumerable(Of MyObject)
SetOrdinals(MyObjectReader)
Dim MyObjects = New List(Of MyObject)
While MyObjectReader.Read()
Dim Temp = New MyObject() With {
.FirstValue = MyObjectReader.GetValue(Of Integer)(MyObjectReader(FirstValue_Ord)),
.SecondValue = If(MyObjectReader.GetValue(Of String)(MyObjectReader(SecondValue_Ord)), String.Empty).Trim(),
.ThirdValue = If(MyObjectReader.GetValue(Of String)(MyObjectReader(ThirdValue_Ord)), String.Empty).Trim(),
MyObjects.Add(Temp)
End While
Return MyObjects
End Function
Private Sub SetOrdinals(MyObjectReader As SqlDataReader)
FirstValueOrd = MyObjectReader.GetOrdinal("FirstValue")
SecondValue_Ord = MyObjectReader.GetOrdinal("SecondValue")
ThirdValue_Ord = MyObjectReader.GetOrdinal("ThirdValue")
End Sub
End Class
Public Module Extensions
<Extension>
Function GetValue(Of T)(rdr As SqlDataReader, i As Integer) As T
If rdr.IsDBNull(i) Then
Return Nothing
End If
Return DirectCast(rdr.GetValue(i), T)
End Function
End Module
You should just be passing in the ordinal to the GetValue calls:
While MyObjectReader.Read()
Dim Temp = New MyObject() With {
.FirstValue = MyObjectReader.GetValue(Of Integer)(FirstValue_Ord),
.SecondValue = If(MyObjectReader.GetValue(Of String)(SecondValue_Ord), String.Empty).Trim(),
.ThirdValue = If(MyObjectReader.GetValue(Of String)(ThirdValue_Ord), String.Empty).Trim()
}
MyObjects.Add(Temp)
End While
Here my version :)
Private Function Create(reader As SqlDataReader) As IEnumerable(Of MyObject)
Dim objects As New List(Of MyObject)()
Dim ordinals As New Ordinals(reader)
While reader.Read()
Dim Temp As New MyObject With
{
.FirstValue = reader.GetValueOrDefault(Of Integer)(ordinals.FirstValue),
.SecondValue = reader.GetValueOrDefault(ordinals.SecondValue, "").Trim(),
.ThirdValue = reader.GetValueOrDefault(ordinals.ThirdValue, "").Trim()
}
objects.Add(Temp)
End While
Return MyObjects
End Function
Private Class Ordinals
Public Property FirstValue As Integer
Public Property SecondValue As Integer
Public Property ThirdValue As Integer
Public Sub New(reader As SqlDataReader)
FirstValue = reader.GetOrdinal(nameOf(FirstValue))
SecondValue = reader.GetOrdinal(nameOf(SecondValue))
ThirdValue = reader.GetOrdinal(nameOf(ThirdValue))
End Sub
End Class
Public Module Extensions
<Extension>
Function GetValueOrDefault(Of T)(reader As SqlDataReader, ordinal As Integer) As T
Return reader.GetValueOrDefault(Of T)(ordinal, Nothing)
End Function
<Extension>
Function GetValueOrDefault(Of T)(reader As SqlDataReader,
ordinal As Integer,
defaultValue As T) As T
Dim value = reader(ordinal)
If value = DbNull.Value Then
Return defaultValue
End If
Return DirectCast(value, T)
End Function
End Module
Because extension method execute checking for DbNull.Value against already extracted object, we get rid from reading same value twice from SqlDataReader.
SqlDataReader.IsDbNull(index) reads value before checking for DbNull.
Extension method have two overloads:
- One which return default value of given type, if value is DbNull.Value. Nothing in vb.net is default value of type.
- And one which takes parameter for default value you want return if value is DbNull.Value. Possibility pass default value makes lines where you create new object shorter and more readable. We get rid of inline if statement.
Your extension method with name GetValue have "side effects". By name consumer of this method expect to get value from SqlDataReader. So he can expect to get DbNull.Value if database query return NULL, but instead he get null for string or 0 for integer. Name GetValueOrDefault is little bid more informative, so you don't need to go inside method to check what is doing.

Call a different string method based on a flag in VB.NET

I want to be able to call a different String method based on what is passed in a parameter. For example, my parameter is "%XYZ%". If there are percent signs on both sides of XYZ, further down in code I want to say SomeString.Contains("XYZ").
If there is a percent sign only on the left of XYZ, further down in code I want to say
SomeString.EndsWith("XYZ").
Ideally, I want something like this:
With objSearchTerms
If Not String.IsNullOrEmpty(.ProjectName) Then
If .ProjectName.StartsWith("%") AndAlso .ProjectName.EndsWith("%") Then
'MyStringMethod = Contains
ElseIf .ProjectName.EndsWith("%") Then
'MyStringMethod = StartsWith
ElseIf .ProjectName.StartsWith("%") Then
'MyStringMethod = EndsWith
Else
'MyStringMethod = Equals
End If
End If
End With
Then further down I want to be able to say:
filingList = filingRepository.GetList (Function(e) e.SERFFTrackingToFilings.Any(Function(x) x.SERFFTracking.Number.*MyStringMethod*(objTerms.TrackingNumber))
Thank you.
If you had a reference to the string that you want to run the method on, then you could assign the method to a Func<T, TResult> delegate, and invoke the delegate, something like.
Dim MyStringMethod As Func(Of String, Boolean)
'...
MyStringMethod = AddressOf(SomeString.Contains)
'...
Dim result As Boolean = MyStringMethod(SomeOtherString) 'SomeString.Contains(SomeOtherString)
But it looks like your SomeString is in an anonymous function where you may not already have a reference to it. Possible workaround:
Dim MyStringMethod As Func(Of String, String, Boolean)
'...if...
MyStringMethod = Function(a As String, b As String) a.Contains(b)
'...else if...
MyStringMethod = Function(a As String, b As String) a.StartsWith(b)
'...etc.
filingList = filingRepository.GetList (Function(e) e.SERFFTrackingToFilings.Any(Function(x) MyStringMethod(x.SERFFTracking.Number, objTerms.TrackingNumber)))
I ended up doing the following:
I created a Module:
Module StringExtensions
<Extension()>
Public Function ProcessString(ByVal strToCheck As String, ByVal strOperand As String, ByVal strWhatTocheck As String) As Boolean
Select Case strOperand
Case "Contains"
Return strToCheck.Trim.ToUpper.Contains(strWhatTocheck.Trim.ToUpper)
Case "StartsWith"
Return strToCheck.Trim.ToUpper.StartsWith(strWhatTocheck.Trim.ToUpper)
Case "EndsWith"
Return strToCheck.Trim.ToUpper.Contains(strWhatTocheck.Trim.ToUpper)
Case "Equals"
Return strToCheck.Trim.ToUpper.Equals(strWhatTocheck.Trim.ToUpper)
Case Else
Throw New ApplicationException("Can't match ProcessString(). ")
End Select
End Function
End Module
Then in the calling method I used the module like this:
Dim filingListDTO As New List(Of DTO.FilingDetailsViewModel)
Dim strNumberOperand As String = ""
Dim strProjectNumber As String = ""
With model
If Not String.IsNullOrEmpty(.ProjectNumber) Then
If .ProjectNumber.StartsWith("%") AndAlso .ProjectNumber.EndsWith("%") Then
strNumberOperand = "Contains"
ElseIf .ProjectNumber.EndsWith("%") Then
strNumberOperand = "StartsWith"
ElseIf .ProjectNumber.StartsWith("%") Then
strNumberOperand = "EndsWith"
Else
strNumberOperand = "Equals"
End If
strProjectNumber = .ProjectNumber.Replace("%", "").Trim.ToUpper
.ProjectNumber = strProjectNumber
End If
End With
filingListDTO = (From f In <<someList>>
Where f.ProjectNumber.ProcessString(strNumberOperand, strProjectNumber) _
Select f).ToList

Replacement for "If object = Nothing Then" in Strict Mode VB.NET

I have a function which has a selectedID parameter of type "object".
If my parameter is the default for the underlying type: i.e. Integer default is zero, I want some action to take place.
Without "Strict On", I can use:
If selectedID = Nothing Then
'Do Something
End If
Do I have to do something like:
If (TypeOf selectedID Is Integer AndAlso selectedID.Equals(0)) _
OrElse (TypeOf selectedID Is String AndAlso selectedID.Equals(Nothing)) _
OrElse .. other types go here .. Then
'Do something
End If
Or is there a simpler method that I'm missing?
I eventually implemented Neolisk's suggestion, which had the advantage of being short, all-encompassing and very re-usable:
Public Function IsDefaultObject(obj As Object) As Boolean
Return obj.Equals(GetDefaultValue(obj.GetType()))
End Function
Public Function GetDefaultValue(t As Type) As Object
If (t.IsValueType) Then Return Activator.CreateInstance(t)
Return Nothing
End Function
I originally went with the solution of creating a function IsDefaultObject(obj) which tells me if an object has had a default value assigned. I planned to add to it as more types got noticed.
Private Function IsDefaultObject(obj As Object) As Boolean
If obj Is Nothing Then Return True
If String.IsNullOrEmpty(obj.ToString()) Then Return True
If obj.Equals(0) Then Return True
If obj.Equals(New Date()) Then Return True
Return False
End Function
Of course, I could have used the solution in Hans Passant's comment:
Private Function IsDefaultObject(obj As Object) As Boolean
Return Microsoft.VisualBasic.CompilerServices.Operators.
ConditionalCompareObjectEqual(obj, Nothing, False)
End Function
You can also use a nullable type for this.
Dim selectedID As Integer? = nothing
...
if selectedID isnot nothing then
dim value as integer = selectedID.value
...
end if
Another way you can check the nullable type has been assigned a value.
if selectedID.hasValue = true then
dim value as integer = selectedID.value
...
end if

VB.NET Generic Function

What I want to do is, based on the type of T do different opperations. Below is a simple example of my problem.
Public Shared Function Example(Of T)() As T
Dim retval As T
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
ElseIf TypeOf retval Is Integer Then
Dim myInt As Integer = 101
retval = myInt
End If
Return retval
End Function
I get the error "Value of Type 'String' Cannot be converted to 'T'" Same with the integer part. If I cast either to an object before asigning them to retval it works but I think that would defeat my purpose and be less efficient. Any Ideas? Thanks!
It's probably a bit late, but try this:
Public Shared Function CAnyType(Of T)(ByRef UTO As Object) As T
Return CType(UTO, T)
End Function
Public Shared Function ExecuteSQLstmtScalar(Of T)(ByVal strSQL As String) As T
Dim T_ReturnValue As T
' Here we have the result of a DB query '
Dim obj As Object = "Value from DB query cmd.ExecuteScalar"
Dim strReturnValue As Object = obj.ToString();
Try
Dim tReturnType As Type = GetType(T)
If tReturnType Is GetType(String) Then
Return CAnyType(Of T)(strReturnValue)
ElseIf tReturnType Is GetType(Boolean) Then
Dim bReturnValue As Boolean = Boolean.Parse(strReturnValue)
Return CAnyType(Of T)(bReturnValue)
ElseIf tReturnType Is GetType(Integer) Then
Dim iReturnValue As Integer = Integer.Parse(strReturnValue)
Return CAnyType(Of T)(iReturnValue)
ElseIf tReturnType Is GetType(Long) Then
Dim lngReturnValue As Long = Long.Parse(strReturnValue)
Return CAnyType(Of T)(lngReturnValue)
Else
MsgBox("ExecuteSQLstmtScalar(Of T): This type is not yet defined.")
End If
Catch ex As Exception
End Try
Return Nothing
End Function
(the secrect is casting your generic result to object, then casting from type Object to template type T).
PS:
You are responsible to ensure that your code works correctly with nullable types and NOT nullable types, as well as System.DbNull.Value. For example when string is NULL and return value type is Boolean (not nullable). On a sidenote, please also note that VB Nothing is NOT equal NULL, it's equal to C#'s default(T) (e.g. System.Guid.Empty for Guid)
With a generic method, T will be of exactly one type each time. Let's say that you have code calling Example(Of Integer). Now, in your mind, replace T with Integer. The resulting method will contain these lines (amongst others).
Dim retval As Integer
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
' more code follows '
Assigning a String to an integer like that will never work. Sure, that code will also never execute, since the If-block prevents that, but the code will still not compile. (As a side not, the above code will fail to compile because the TypeOf keyword is restricted to use with reference types, but that is another story)
Typically when creating generic methods, you will want to do the same thing with whatever input you get, but in a type safe manner. If you want to have different behavior for different types of input, you are usually better off by overloading the methods instead.
retVal = (T) "Hello World!"
Do retval = Ctype(Mystring, T) or retVal = Ctype(MyInt, T)
An alternative solution is encapsulate this kind of logic in a class and use VB CallByName function:
Class Aux(Of T)
Public Value As T
Private dicc As Dictionary(Of String, Object)
Sub New()
dicc = New Dictionary(Of String, Object)
dicc.Add("system.string", "hola")
dicc.Add("system.int32", 15)
dicc.Add("system.double", 15.0)
End Sub
Public Function Test() As T
Dim typeName As String = GetType(T).ToString.ToLower
If dicc.ContainsKey(typeName) Then
CallByName(Me, "Value", CallType.Set, dicc(typeName))
End If
Return Value
End Function
Protected Overrides Sub Finalize()
MyBase.Finalize()
If Not (dicc Is Nothing) Then dicc.Clear()
dicc = Nothing
End Sub
End Class