WPF Generics:How to define a list of objects as parameter of a function - wpf

I want create a generic (general) method which accepts the return types of LINQ 2 SQL query (a list of objects- IEnumerable) against different table as the parameter of the function. How can I define my parameter for the function ? I will use this function to bind the data to a grid.

I got the answer here
Public Sub FormatGrid(Of T)(ByVal varList As IEnumerable(Of T))
End Sub

Related

Is it possible to create an array or collection of functions in vb.net?

I'm writing a really basic function that sorts inbound emails to a group mailbox into folders based on who they're intended to be read by. It's a daft thing to need to do, as we all have our own email addresses, but companies often do daft things!
The criteria that determine "who the email is for" are quite complex, so I'm writing a series of simple functions to return if it's "for" a particular person
Function IsRichard(msg As Outlook.MailItem) As Boolean
...
End Function
Function IsTim(msg As Outlook.MailItem) As Boolean
...
End Function
At present I'm then running all of these in sequence in a control function Function WhoIsItFor(). However, this sub is now getting a bit long and ungainly, so I was wondering if it was possible to define an Array or Collection of Functions so that I can do something along the lines of:
Const AllFunctions as Function () = {IsRichard, IsTim ...}
Function WhoIsItFor(msg as Outlook.MailItem) as String
For Each thisFunction as Function in AllFunctions
if thisFunction(msg) then
return (thisFunction.name)
end if
next
end function
You should use the AddressOf operator and the define the array type with the delegate Func:
Dim AllFunctions() As Func(Of Outlook.MailItem, Boolean) = {AddressOf IsRichard, AddressOf IsTim}
Function WhoIsItFor(msg As Outlook.MailItem) As String
For Each thisFunction As Func(Of Outlook.MailItem, Boolean) In AllFunctions
If thisFunction(msg) Then
Return thisFunction.Method.Name
End If
Next
Return Nothing
End Function
Alternative approach is to create a class (vb.net is an object-oriented language ;)) to represent a person(or anything which can "react" to the email).
With such approach when you adding or removing persons you don't need to touch "main" code which handling emails.
' Create an abstraction to represent mail handler
Public Interface IMailHandler
Function CanHandle(MailItem mail) As Boolean
Sub Handle(MailItem mail)
End Interface
' Implement for Tim
Public Class Tim Inherits IMailPerson
Public Function CanHandle(MailItem mail) As Boolean
Return mail.Subject = "For Tim"
End Function
Public Sub Handle(MailItem mail)
' Move to Tim folder
End Sub
End Class
' Implement for Bob
Public Class Bob Inherits IMailPerson
Public Function CanHandle(MailItem mail) As Boolean
' Can be anything else
End Function
Public Sub Handle(MailItem mail)
' Delete Bob messages
End Sub
End Class
Usage
Dim handlers As New List(Of IMailHandler) From { New Tim(), New Bob() }
var handler = handlers.
Where(Function (h) h.CanHandle(mail)).
DefaultIfEmpty(new DefaultHandler()).
First()
handler.Handle(mail)
With approach above you can keep related logic in one place:
decision about "can this mail be handled"
actual action for this mail

VBA Array Public Method cant be modified by components

I'm working with a rather complex problem in VBA and I decided to structure it in different classes.
One of the classes containd an array and I need to modify the component individualy outside the class.
The problem can de reduced to the definition of the class
Public a As Variant
Private Sub Class_Initialize()
ReDim a(5) As Double
' Set some value
a(1) = 20
End Sub
And a sub to access the data:
Sub Test()
Dim b As New DummyTest
b.a(4) = 7
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
The result on the debug window is 20 0. I've checked this result with an inspection and confirm that the property a cannot be modified from the exterior of the class.
Funny part is that the property can be modified if replaced by other array, for example:
Sub Test2()
Dim b As New DummyTest
b.a = Array(1, 2, 3, 4, 5)
Debug.Print (b.a(1))
Debug.Print (b.a(4))
End Sub
Behaves as expected. I'm confused as element access is allowed inside the class context and in any other situation, but in this specific case it doesn't wotk. There is no error msg, it simply refuses to change the content of the array.
I'll find work around solutions but missing this way to access data is being a really PITA.
Hope you can help me with this issue.
Thank you in advance!
Your "property" is breaking encapsulation, which pretty much defeats its entire purpose.
Public a As Variant
From a public interface standpoint, this is a read-write property (if you added a new class module with Implements DummyTest you'd have to add Property Get and Property Let members for it). From the class' standpoint, it's a public instance field.
When a class encapsulates an array, a collection, a dictionary, or any other data structure, the last thing you want is for that data structure to be publicly exposed, with anyone anywhere being able to just overwrite the class' entire internal state as it pleases - you're not getting any of the advantages of classes that way.
First step to proper encapsulation is to make the instance field Private. Next step is to expose actual Property accessors. You want the array data to be writable? Expose an indexed Property Let member for it.
Private a As Variant
Private Sub Class_Initialize()
ReDim a(0 To 10)
End Sub
Public Property Get Value(ByVal index As Long) As Variant
Value = a(index)
End Property
Public Property Let Value(ByVal index As Long, ByVal newvalue As Variant)
a(index) = newvalue
End Property
This code behaves exactly as advertised:
Public Sub Test()
Dim b As DummyTest
Set b = New DummyTest
b.Value(4) = 7
Debug.Print b.Value(1)
Debug.Print b.Value(4)
End Sub
Note that because the array is encapsulated, that client code is absolutely unable to re-assign the array itself: the array is abstracted away and only accessible through the means exposed by the class.
Reference variable in VBA must be declared, so that we know what type of object we are referring to
i.e. Dim b As DummyTest ' No new required in this step
Before using a declared reference to store information create an instance of the Class.
i.e. Set b = New DummyTest
This might seem confusing but is actually very logical. Because b is a Class the Dim statement sets up b to hold the address of a DummyTest object. At the time of the Dim statement no object has been created (no memory allocated) so the address of b is nothing (no value or null). The set statement tells VBA to create an instance of your DummyTest class (allocate the memory it needs) and puts the address of the instance (memory) into variable b. Then because VBA knows that b points to an instance of a class you never see the address of b when you use it, just the data to which b is pointing. You can get the the address b is holding using the function Objptr but this is only something you would do in very special/specific circumstances

Passing an array typed with a class implementing an interface when a function/procedure expects an array typed with the interface?

Let's say I have an interface ITest1 defined:
Option Explicit
Sub it1Test(cTest As ITest1)
End Sub
And then I built a class CTest1 that implements it:
Option Explicit
Implements ITest1
Private Sub ITest1_it1Test(cTest As ITest1)
End Sub
Private Sub Test1()
Dim cT1 As CTest1
cT1 = New CTest1
ITest1_it1Test cT1
End Sub
If I try compiling this code everything works perfectly fine, which in general is what I expect.
But then in my not so simple world, rather than passing a single element I want to pass a whole array.
So my new interface ITest2 looks like this
Option Explicit
Sub it2Test(cTest() As ITest2)
End Sub
And a class CTest2 implementing it:
Option Explicit
Implements ITest1
Private Sub ITest1_it1Test(cTest() As ITest1)
End Sub
Private Sub Test2()
Dim cT2(1 To 1) As CTest2
cT2(1) = New CTest2
ITest2_it2Test cT2
End Sub
This time I am not able to compile with an error:
Compile error:
ByRef argument type mismatch
with highlighted the cT2 parameter of function ITest2_it2Test as the source of error.
Is there any way to pass an array of class implementing an interface if the function expects array of type of that interface. In other words does it make sense to use interface as a type of array parameter?
I'm aware that it is possible to use Variant as an array type but it undermines plenty of benefits that I have thanks to the use of interface.
I can't find anything related to such specific case. I have checked number of pages about ways to implement interfaces in general (from MS docs to CPearson page). I can't also find anything here on Stack Overflow. I have checked all 78 questions tagged vba and excel with keywords interface and array and some of the suggested questions.

How to compare two objects by value

I need to compare two objects of the same class by value to see whether all their values match or not.
For background this is so I can compare before and after values of a wpf datagrid row
Say the objects are both House class with properties of name, street, town (all strings).
So the class would be
public class House
public property name as string
public property street as string
public property town as string
end class
Should I
1) override equals in the House class and in it check name=name, street=street, town=town
2) make the House class implement IComparable and create a compare function that implements it, checking each property as 1
3) there's a better way you know that I dont!
I'd appreciate an example based on this scenario if possible.
Many thanks
You should be using Option 1 : Overriding the Equals method.
Why ?
Because the Equals() method is supposed to be used when you want to compare if two objects are the same.
So what's the use of IComparabe ?
IComparable interface has a different purpose. Its goal is to check if an object is supposed to go before or after another object. Therefore this is used by sorting methods.
You can implement the IComparable interface and check if two object's CompareTo() method return 0. However it only means that they are supposed to get the same ranking, not that they are equals...
Is there another approach ?
There are plenty differents ways of doing what you want to do. But since there is a simple and elegant method that is here, let's use that one. The main difficulty in programming an application is to find the tools that are already here to do what you want...
So how to Override the Equals() method ?
This link to MSDN explains how to override the Equals method
In short (I'm just copy/pasting from MSDN and removing error checking for clarity here) :
Public Class Point
Protected x As Integer
Protected y As Integer
Public Sub New (xValue As Integer, yValue As Integer)
Me.x = xValue
Me.y = yValue
End Sub
Public Overrides Overloads Function Equals(obj As Object) As Boolean
Dim p As Point = CType(obj, Point)
Return Me.x = p.x And Me.y = p.y
End Function
End Class
Do not use this straight ahead and read article first, as you must do some error checking in the Equals, because it could throw some exception when converting Object to Point...

How to implement multiple levels of sorting on a CollectionViewSource

I originally posted this as a LINQ query - got it working and then realised I had a problem. You can't use LINQ queries to select/filter and order the items in a CollectionViewSource (why oh why didn't I check this first, why oh why isn't this possible?).
So, I am now trying to work out how to sort a filtered CollectionViewSource.
My CollectionViewSource is bound to an ObservableCollection(Of MediaItems). MediaItems contains a child/nested List(Of AdvertOptions).
The parent ObservableCollection(Of MediaItems) - class is structured as follows:
MediaItems
.ID (int)
.Src (string)
.Advert (bool)
.AdOptions As List(Of AdvertOptions)
.Counter (int)
AdvertOptions class consists of:
.Age (int)
.Gender (int)
.Priority (int)
I am filtering out any MediaItems that don't meet the following criteria:
MediaItems.Advert = true
AdOptions.Age = x (parameter within triggered function called to perform the filter/sort)
AdOptions.Gender = y (parameter within triggered function called to perform the filter/sort)
After the CollectionViewSource has been filtered, I need to sort the items based on two sort orders so the resulting CollectionViewSource items can be navigated through in my applicaiton using the CollectionViewSource navigation methods (MoveCurrentToX etc).
The sort order I need to apply is:
AdOptions.Priority (in descending order)
By Counter (in ascending order)
The way I am filtering is by using these functions:
Public Shared Sub FilterByAdvertisement(ByVal Item As Object, ByVal e As FilterEventArgs)
Dim MediaObjectItem As MediaObject = TryCast(e.Item, MediaObject)
If Not MediaObjectItem.IsAdvertisingMedia = True Then
e.Accepted = False
End If
End Sub
Public Shared Sub FilterByAvertisementOption(ByVal Item As Object, ByVal e As FilterEventArgs)
Dim MediaObjectItem As MediaObject = TryCast(e.Item, MediaObject)
Dim Items = From m In MediaObjectItem.AdOptions Select m Where m.Age = Current.Age And m.Gender = Current.Gender
If Items.Count = 0 Then
e.Accepted = False
End If
End Sub
Just for reference, I am adding the filter as follows:
Public AdvertisingDataView As CollectionViewSource
AddHandler AppLocal.AdvertisingDataView.Filter, AddressOf FilterByAdvertisement
AddHandler AppLocal.AdvertisingDataView.Filter, AddressOf FilterByAdvertisementOption
I now need to work out how to sort the filtered items. The problem is, CollectionViewSource seems to have limited support for sorting. I can easily sort the Counter using:
AdvertisingDataView.SortDescriptions.Add(New SortDescription("Counter", ListSortDirection.Ascending))
But that's my secondary sort - I want to Sort first by AdOptions.Priority (requires sub-selecting the right item), then by Counter.
I was wondering if creating Groups would help but can't work out if this will provide the sorting capability I'm looking for.
I have looked at the possibility of converting to a ListCollectionView instead of CollectionViewSource, then using a CustomSort but I can't work out how I could implement this and if it would also offer the capability I'm looking for given my primary sort is a value within a nested list.
Can anyone contribute to help me achieve my outcome?
Ben
You can achieve multiple levels of sorting on the default view of your CollectionViewSource.
There are essentially 3 types of views automatically generated by WPF, all deriving from the CollectionView base class:
ListCollectionView -> Created when the collection implements IList.
BindingListCollectionView -> Created when the collection implements IBindingList.
EnumerableCollectionView -> Created when the collection implements nothing but IEnumerable.
You can always add to multiple SortDescriptor to the SortCollection of your default view like this -
ListCollectionView lcv =
(ListCollectionView)CollectionViewSource.GetDefaultView(myCollection);
lcv.SortDescriptions.Add(new SortDescription(…));
Refer to these links for further refernce -
http://bea.stollnitz.com/blog/?p=387
http://bea.stollnitz.com/blog/index.php?s=collectionviewsource
I don't know how to write it in VB, but i can show you in c# how it's made:
YourListView = CollectionViewSource.GetDefaultView(tempListView
.OrderBy(x => x.FirstSorting)
.ThenBy(y => y.SecondSorting));
Look how to do the same in VB, and you will fix this.
You have a link right here - http://linqsamples.com/linq-to-objects/ordering/ThenBy-lambda-csharp
Good luck!

Resources