ListBox.SelectedObjectCollection is not queryable - vb.net

I'm new to programming, and I'm making a new project in visual studio 2017rc. There I want to make the use of a list box. I want to select the items in list box and make some actions.
So I wrote the following code:
Dim SelectedItems = (From i In ListBox1.SelectedItems).ToList
For Each selecteditem In SelectedItems
Peca = selecteditem
Call CATIA_Windows_app.Save()
Next
But this is returning an error:
I had used this code in other application and it works.
Have I forgot something?
Thanks

You need an object that implements IEnumerable(Of T) for a LINQ query and that type only implements IEnumerable. The way to get the former from the latter is to call the Cast method. For instance, if your ListBox contains String objects you can do this:
From item In myListBox.SelectedItems.Cast(Of String)()

Related

How to pass a list (returned in a VB.net function) in VBScript?

I have a system function in VB.net that return a list of Strings (I've debugged this function).
And right now I need to call that system function in VBScript. How can I get that list of strings?
VB.net system function
Public Function someList As List(Of String)
Return List
End Function
VBScript:
Dim results
Set results = System.someList
For Each result In results
wscript.echo result
Next
The problem is that VBScript doesn't understand Generic classes (or is it COM that is the problem?), so you'll have to convert your list to something non-generic such as ArrayList.
I had the same problem when trying to use my C# library from VBScript and I wrote a simple conversion function (in C#) to overcome this problem:
public ArrayList toArrayList(IEnumerable collection)
{
var arrayList = new ArrayList();
foreach (object element in collection)
{
arrayList.Add(element);
}
return arrayList;
}
Some more helper functions I needed to get things working can be found here: ScriptingInteropHelper

Is it possible to create a multi dimensional array with varying types?

I have 3 controls that consists of a label, a listbox and a textbox on the same line. On a different line I have 3 different controls of the same type which is label, a listbox and a textbox. I want to put them into a 3 dimensional array like this:
Dim multiArray() As Object = { {Lane1Label, ListBox1, TextBox1},
{Lane2Label, ListBox2, TextBox2} }
But it's not letting me do this. Is there a way?
Make a class (maybe even a custom or user control) to contain each line of controls:
Public Class ControlLine
Public Property Lane As Label
Public Property List As ListBox
Public Property Text As TextBox
End Class
Then create a single dimensional array of these objects (or usually even better: a List(Of ControlLine) ) and put your items in here:
Dim lines() As ControlLine = {
New ControlLine With { Lane = LaneLabel1, List = ListBox1, Text = TextBox1},
New ControlLine With { Lane = LaneLabel2, List = ListBox2, Text = TextBox2}
}
This is much better because the items in your array remain strongly-typed, for good compile-time checking and IDE support for things like intellisense. Recent versions of Visual Studio can also accomplish this via Tuples.
And again, also consider abstracting this further into custom or user controls, where you can create a whole set with one simple constructor call, place one control on the form and have the whole set line up properly, and even think about data binding these ControlLines to a container like a FlowLayoutPanel instead of managing all the controls and arrays and placement yourself.
Just do this:
Dim multiArray(,) As Object = _
{ _
{Lane1Label, ListBox1, TextBox1}, _
{Lane2Label, ListBox2, TextBox2} _
}
Note the , in the declaration of the two-dimensional array.

Sort a list by a function results

I want to sort a list by the results of functions that are contained in list items. How would go about doing that?
Here is an example. Let's say I have a following object:
Public Class MyListObject
Public MyText1 As String
Public MyText2 As String
Public Function AddSuffix(ByVal MySuffix As String) As String
Return Mytext1 & MySuffix
End Function
End Class
(massively oversimplified for the sake of example)
After that I have a list of these objects:
Dim ResultList As New List(Of MyListObject)
Now, for example, to sort the list by the field MyText1 value, I used this:
ResultList = ResultList.OrderBy(Function(x) GetType(MyListObject).GetField("MyText1").GetValue(x)).ToList
(used reflection, so I can pass the field name as a string)
How can I sort this list by the function AddSuffix result and simultaneously pass the parameter MySuffix?
I imagine it would look something like this, but obviously this doesn't work
ResultList = ResultList.OrderBy(Function(x) GetType(MyListObject).GetField("AddSuffix(""_myCustomSuffix"")").GetValue(x)).ToList
PS. I realize, that in this example the function is pretty much meaningless. The actual code is much more complex and that function does return different sortable data for each list item.
I am guessing you have a pretty good reason to use reflection, but I would recommend avoiding it even if you have too many fields and methods.
ResultList = ResultList.OrderBy(Function(o) o.AddSuffix("_myCustomSuffix")).ToList
The VB way of calling a method by name is something like this (also not tested)
ResultList = ResultList.OrderBy(Function(o) CallByName(o,
"AddSuffix", CallType.Method, "_myCustomSuffix")).ToList

Using CallByName to set Item(x) property

As a bit of background, I have a .net <-> COM object bridge that uses VB.net as a middleman, with a lot of reflection to get the job done.
I've run into a hurdle where I'm needing to use CallByName() to set a pretty standard property which is defined as
Public Default Property Item (
index As Integer
) As String
Get
Set
which would normally be called as .Object(1) = "new value", however the bridge code at the moment tries to get .Object(1) as an object then call Set on it using CallByName() (which obviously doesn't work).
With other collections I am happily able to use CallByName() to make method calls .Clear() and .Add("new value") but this property doesn't have these methods and besides, I'd like to solve it for a more generic approach so that code from the other side of the bridge can call the .Object directly.
Is someone able to suggest a way to Set an array-type property directly using CallByName(), or perhaps suggest an alternative reflection function that can be called to achieve this?
The default property can be used as a normal property, using its name. So, given a class:
Class Foo
Default Public Property Item(index As Integer) As String
Get
'...
End Get
Set(value As String)
'...
End Set
End Property
End Class
These three property assignments all have the same effect:
Dim Bar As New Foo
Bar(1) = "x"
Bar.Item(1) = "x"
CallByName(Bar, "Item", CallType.Set, 1, "x")
For array-type properties, the parameter(s) are passed to CallByName before the value when setting.
You did not show how you were using CallByName on that property, which leaves us to guess what is wrong. The syntax of .Object(1) = "new value" is also a little confusing: does the leading dot means that Object itself is some sort of collection on some other Type?
The basic answer lies in looking at the declaration, not how it is used normally. The fact that you can omit "Item" normally because it is the Default, does not apply here:
'foo(1) ==> foo.Item(1) = "Ziggy" ==>
CallByName(foo, "Item", CallType.Set, 1, "Ziggy")
The procName argument would be the property name, Item in this case. CallType.Set means you want the prop setter (Let or Set seem to both work). The first argument would be the index of the item to set/get, the last would be the data to pass.
If .Object is supposed to mean you are trying to reference a collection property, then the answer is about the same:
'foo.bars(1) ==> foo.Bars.Item(1) = "Zoey" ==>
CallByName(foo.Bars, "Item", CallType.Set, 1, "Zoey")

Reference to object properties

I have a list of objects of type say Person, and I want to export Person records to an excel-sheet (I am using a proprietary excel component for VB.NET). Using a form with checkboxes the user can specify which Person properties should be exported.
Instead of having an enormous if-then-else tree where I check to see if each checkbox (corresponding to a property) has been checked, I have a data structure where for each property in Person I keep a boolean (checked/unchecked) and the name of the property as a string. I then use two for-loops like this:
For Each p As Person In Persons
...
For Each item As ExportColumnData In ExportColumnTable
...
If item.Checked Then
...
Dim o As Object = CallByName(p, item.PropertyName, CallType.Get, Nothing)
SaveValueToExcelSheet(o)
...
End If
...
Next
...
Next
However, this is not type-safe since I am using CallByName supplying PropertyName as a string. Is there a more elegant and type-safe way I can achieve the same thing? I need some way (other than a string) to reference the properties of these Person objects.
The CallByName function uses reflection to find and execute the property getter by string name, so you are right that it is unsafe in the sense that there will be no compile-time checking done to ensure that the properties by those names actually do exist in the Person type.
Unfortunately, short of a big If/Else block, or something similar, there is no "safe" way to do this in a way which will allow for compile-time type checking. If you want it to check that at compile-time, you need to call the property by name directly in code, and if you are doing that, it will have to be in a big conditional block of some sort.
There are things you could do to minimize or shift the location of the ugliness. For instance, you could create an enumeration of all the Person properties and add a method to the Person class which returns the property value given the enumeration item using a big Select Case block. That would make the logic reusable but not really any less ugly. Not only that, but doing it that way kind of puts the type-checking responsibility on your code, not the compiler.
Alternatively, you could, for instance, set the tag of each CheckBox control to a delegate which takes a Person object and returns the correct property value for that option from the given Person object. Then, in the loop, you could just call the delegate in the tag to retrieve the value. For instance, if you had a delegate like this:
Private Delegate Function GetPersonProperty(x As Person) As Object
Then you could set the Tag of the CheckBox controls like this:
chkFullName.Tag = New GetPersonProperty(Function(x As Person) x.FullName)
chkAge.Tag = New GetPersonProperty(Function(x As Person) x.Age)
Then, in your loop, you could invoke the delegate in the Tag to get the value, like this:
Dim myDelegate As GetPersonProperty = CType(item.Tag, GetPersonProperty)
Dim value As Object = myDelegate.Invoke(p)
But that's rather overly-complicated for such a simple task.
In the end, if the compile-time type checking is really important, I'd just bite the bullet and make the big conditional block. If it's not really that important, I'd just stick with the reflection and put some decent exception handling in the code.
You say you already have a class where you store the information about the properties of your Person class. You can use this to store the PropertyInfos as well.
Here's an example:
Class Person
Public Property Name As String
Public Property Age As Integer
End Class
Class ExportProperty
Public Property [Property] As PropertyInfo
Public Property Export As Boolean
End Class
Sub Main()
'' Create a List(Of ExportProperty) from all public properties of Person
Dim properties = GetType(Person).GetProperties() _
.Select(Function(p) New ExportProperty With { .[Property] = p}) _
.ToList()
'' Say we want to export only the Age field
properties.Single(Function(p) p. [Property].Name = "Age").Export = True
'' Create a person instance to export
Dim pers = New Person With { .Name = "FooBar", .Age = 67 }
'' Only export the properties with Export = True
For Each prop in properties.Where(Function(p) p.Export)
'' Use the PropertyInfo.GetValue-method to get the value of the property
''
Console.WriteLine(prop.[Property].GetValue(pers, Nothing))
Next
End Sub
Your solution is perfectly fine, as long as the contents in ExportColumnData are correct. If these are computed dynamically at runtime, you're fine.
Otherwise, or alternatively, you can do the following: use Type.GetProperties to get a list of PropertyInfo objects. You can then use these instead of a mere String to extract property values in your loop:
Dim o As Object = item.PropertyInfo.GetValue(p, Nothing)