VB.net avoiding cross thread exception with extension method - vb.net

Hello
I am trying to implement a solution for updating form controls without using a delegate.
I am attempting to use the 1st solution on this page:
http://www.dreamincode.net/forums/blog/143/entry-2337-handling-the-dreaded-cross-thread-exception/
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Module MyInvoke
<Extension()> _
Public Sub CustomInvoke(Of T As ISynchronizeInvoke)(ByVal control As T, ByVal toPerform As Action(Of T))
If control.InvokeRequired Then
control.Invoke(toPerform, New Object() {control})
toPerform(control)
End If
End Sub
End Module
The site gives this as example of how to use:
Label1.CustomInvoke(l => l.Text = "Hello World!")
But i get 'l' is not declared error.
As you can see im very new to VB or any OOP.
I can get the second solution on that page to work (using delegates) but i have quite a few things to do in this thread and it seems like i would need to write a new delegate sub for each thing, which seems wasteful.
What i need to do is select the 1st item from a combobox, update a textbox.text with the selected item, and pass the selected item to a function.
Then wait for x seconds and start again, selecting the second item.
I can get it to work in a single threaded application, but i need the interface to remain responsive.
Any help greatly appreciated.
EDIT:
OK so changing the syntax worked for the example.
However if i change it from
Label1.CustomInvoke(Sub(l) l.text = "hello world!")
(which worked just fine) to:
Dim indexnumber As Integer = 0
ComboBox1.CustomInvoke(Sub(l) l.SelectedIndex = indexnumber)
I get a cross threading error as though i didnt even use this method:
Cross-thread operation not valid: Control 'ComboBox1' accessed from a thread other than the thread it was created on.
So now im back to where i started?
Any further help very much appreciated.

Per your second issue; I think you need to add an Else:
Public Sub CustomInvoke(Of T As ISynchronizeInvoke)(ByVal control As T, ByVal toPerform As Action(Of T))
If control.InvokeRequired Then
control.Invoke(toPerform, New Object() {control})
Else
' ^^^^
toPerform(control)
End If
End Sub

You’re confusing VB and C# syntax. Your lambda is (almost, missing braces) valid C# but in VB you must write this differently:
Label1.CustomInvoke(Sub (l) l.Text = "Hello World!")
And yes, this syntax s*cks. Sorry. :-(

Label1.CustomInvoke(l => l.Text = "Hello World!")
This is C# syntax.
The VB.NET equivalent is:
Label1.CustomInvoke( Sub(l) l.Text = "Hello World!" )
... updating form controls without using a delegate...
Just FYI - A lambda expression, which is what this is using, is a form of delegate. It's just a more convenient syntax for declaring and defining delegates - but you're still using delegates here.

Related

Creating a new IUI Automation Handler object in order to subscribe to an automation event

So, here it goes. To start, A disclaimer, I understand that MS Access is not built for this kind of work. It is my only option at this time.
I have done just a bit of Automation using UIAutomationClient and I have successfully used its other features, however I cannot for the life of me get it to subscribe to events.
Normally, it is supposed to be a bit like this:
Dim CUI as new CUIAutomation
Dim FocusHandler as IUIAutomationFocusChangedEventHandler
Set FocusHandler = new IUIAutomationFocusChangedEventHandler(onFocusChanged)
C.AddFocusChangedEventHandler(Element,TreeScope_Children, null, FocusHandler)
end function
'
'
Function onFocusChanged(src as Object, args as AutomationEventArgs)
''my code here
end function
Yet when I attempt this, I get the error "expected end of statement" on the line:
FocusHandler = new IUIAutomationFocusChangedEventHandler(onFocusChanged)
additionally, if I leave off the (onFocusChanged) I get the error "Invalid use of new Keyword".
It seems like I am missing a reference somewhere. The usual drop down when using "new" does not contain the IUI handler classes though they are in the object library.
I am not sure if there is just some piece I am not accounting for in the code since I am using vba, but all examples seem to be for .net or C#/C++. Any help would be appreciated.
Additionally, I have no problem finding the element in question and all other pieces work fine. If you need any other pieces of the code let me know.
Edit: added set to line 3. No change in the problem though.
After two years this probably isn't relevant any more, but perhaps somebody else encounters this problem... The answer is to create a new class that implements the HandleAutomationEvent method.
Here I created a class named clsInvokeEventHandler and (importantly) set the Instancing property to PublicNotCreatable:
Option Explicit
Implements IUIAutomationEventHandler
Private Sub IUIAutomationEventHandler_HandleAutomationEvent(ByVal sender As UIAutomationClient.IUIAutomationElement, ByVal eventId As Long)
Debug.Print sender.CurrentName
End Sub
And to use it:
Sub StartInvokeHandler()
Dim oUIA As New CUIAutomation8
Dim oRoot As IUIAutomationElement
Dim InvokeHandler As clsInvokeEventHandler
Set InvokeHandler = New clsInvokeEventHandler
Set oRoot = oUIA.GetRootElement
oUIA.AddAutomationEventHandler UIA_Invoke_InvokedEventId, oRoot, TreeScope_Descendants, Nothing, InvokeHandler
End Sub

Difference between lambda syntax and addressof VB.NET

I've recently entered the joyful world of VB.NET, for the life of me however, I can't seem to figure out why the following is not working.
When I write this code here, all is well:
MyNavigationCommand = New RelayCommand(AddressOf Navigate)
Private Sub Navigate()
Navigator.NavigateTo(NavigationRoutes.DetailScreen)
End Sub
However, when I try to do exactly the same using the lambda syntax, my code inside the lambda doesn't get hit when I click the button that triggers the command.
The following line, doesn't work:
MyNavigationCommand = New RelayCommand(Sub() Navigator.NavigateTo(NavigationRoutes.DetailScreen))
This should work exactly the same as my previous approach, shouldn't it? Or am I missing something?
I'm not sure what's going wrong for you. This is my code that I wrote to test this:
Sub Main
Dim MyNavigationCommand = New RelayCommand(AddressOf Navigate)
Dim MyNavigationCommand2 = New RelayCommand(Sub() Console.WriteLine("!"))
Navigate
MyNavigationCommand
MyNavigationCommand2
End Sub
Public Delegate Sub RelayCommand
Public Sub Navigate()
Console.WriteLine("!")
End Sub
When run this code produces three lines of !.

Is it possible to dynamically specify the type of event when using AddHandler?

I am trying to write a clever little function here that will add a specified delegate as an event handler to all the controls in a collection for any dynamic event. What I'm trying to do is write this as a completely generic function so that I could possibly use it in various different projects (perhaps including it in some sort of tools library).
Basically I want to specify a group of controls, the delegate to handle the event, and the type of event to handle. The problem that I'm running up against is that I can't figure out how to dynamically specify the event at run time.
Here's my 'work-in-progress' sub:
Private Sub AddHandlerToControls(controlList As ControlCollection, eventToHandle As EventHandler, eventHandlerDelegate As Func(Of Object, EventArgs), Optional filterList As List(Of Type) = {})
For Each controlInList As Control In controlList
If controlInList.HasChildren Then
AddHandlerToControls(controlInList.Controls, controlInList.MouseEnter, eventHandlerDelegate, filterList)
End If
If filterList.Count > 0 Then
If filterList.Contains(controlInList.GetType) = False Then
Continue For
End If
End If
AddHandler controlInList.MouseEnter, eventHandlerDelegate
Next
End Sub
Ideally I would like to use the eventToHandle parameter there at the end in the AddHandler statement instead of specifically using controlInList.MouseEnter. Like this:
AddHandler eventToHandle, eventHandlerDelegate
That way I could call this function dynamically in a form.load method, and call it sort of like how I did earlier in the sub where it's recursively calling itself for child controls. Somehow say "for this list of controls I would like to use this delegate as the 'MouseEnter' event handler". Like So:
AddHandlerToControls(Me.Controls, control.MouseEnter, MouseEnterHandlerDelegate, new List(Of Type) {TextBox, ComboBox})
This could just be wishfull thinking, I'm starting to think that this isn't quite possible at this level of 'genericness', but it's an interesting enough problem that I thought I should at least ask.
Edit for solution:
Jon Skeet's suggestion of using Reflection ended up working for me. Here's the final function:
Private Shared Sub AddHandlerToControls(controlList As Control.ControlCollection, eventToHandle As String, eventHandlerDelegate As MethodInfo, Optional filterList As List(Of Type) = Nothing)
For Each controlInList As Control In controlList
If controlInList.HasChildren Then
AddHandlerToControls(controlInList.Controls, eventToHandle, eventHandlerDelegate, filterList)
End If
If Not filterList Is Nothing Then
If filterList.Contains(controlInList.GetType) = False Then
Continue For
End If
End If
Dim dynamicEventInfo As EventInfo = controlInList.GetType.GetEvent(eventToHandle)
Dim handlerType As Type = dynamicEventInfo.EventHandlerType
Dim eventDelegate As [Delegate] = [Delegate].CreateDelegate(handlerType, eventHandlerDelegate)
dynamicEventInfo.AddEventHandler(controlInList, eventDelegate)
Next
End Sub
And how I call it and the delegate used:
AddHandlerToControls(Controls, "MouseClick", GetType(MainFrm).GetMethod("MouseClickEventDelegate"), New List(Of Type) From {GetType(TextBox), GetType(ComboBox)})
Shared Sub MouseClickEventDelegate(sender As Object, eventArgs As EventArgs)
sender.SelectAll()
End Sub
This allowed me to set all text boxes and combo boxes on my form (there's quite a few) to select all text when clicked into, in about 20 lines of code. The best part is that if I add any in the future, I won't have to worry about going back to add this handler, it'll be taken care of at run time. It may not be the cleanest solution, but it ended up working pretty well for me.
Two options:
Specify a "subscription delegate" via a lambda expression. I wouldn't like to guess at what this would look like in VB, but in C# it would be something like:
(control, handler) => control.MouseEnter += handler;
Then you just need to pass each control to the delegate.
Specify the event name as a string, and use reflection to fetch the event and subscribe (Type.GetEvent then EventInfo.AddEventHandler).

Accessing Form1 Properties From Thread

I have an exceptionhandler function that basically just writes a line to a textbox on Form1. This works fine when being run normally but the second I use a thread to start a process it cannot access the property. No exception is thrown but no text is written to the textbox:
Public Sub ExceptionHandler(ByVal Description As String, Optional ByVal Message As String = Nothing)
' Add Error To Textbox
If Message = Nothing Then
Form1.txtErrLog.Text += Description & vbCrLf
Log_Error(Description)
Else
Form1.txtErrLog.Text += Description & " - " & Message & vbCrLf
Log_Error(Description, Message)
End If
MessageBox.Show("caught")
End Sub
Is it possible to access a form's properties from a thread this way or would it be easier to write to a text file or similar and refresh the textbox properties every 10 seconds or so (Don't see this as a good option but if it's the only way it will have to do!).
Also, still new to VB so if I have done anything that isn't good practice please let me know!
No, you shouldn't access any GUI component properties from the "wrong" thread (i.e. any thread other than the one running that component's event pump). You can use Control.Invoke/BeginInvoke to execute a delegate on the right thread though.
There are lots of tutorials around this on the web - many will be written with examples in C#, but the underlying information is language-agnostic. See Joe Albahari's threading tutorial for example.
You have to use delegates. Search for delegates in VB.
Here a peace of code that does the job.
Delegate Sub SetTextCallback(ByVal text As String)
Public Sub display_message(ByVal tx As String)
'prüfen ob invoke nötig ist
If Me.RichTextBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf display_message)
Me.Invoke(d, tx)
Else
tx.Trim()
Me.RichTextBox1.Text = tx
End If
End Sub

How to mock a method (custom behavior) with Rhino Mocks in VB.NET

How can I mock one method with RhinoMocks in VB.Net? The reference I found is in C#:
Expect.Call(delegate{list.Add(0);}).IgnoreArguments()
.Do((Action<int>)delegate(int item) {
if (item < 0) throw new ArgumentOutOfRangeException();
});
SharpDevelop converts this to:
Expect.Call(Function() Do
list.Add(0)
End Function).IgnoreArguments().Do(DirectCast(Function(item As Integer) Do
If item < 0 Then
Throw New ArgumentOutOfRangeException()
End If
End Function, Action(Of Integer)))
But that doesn't work either (it doesn't compile).
This is what I want to do: create a new object and call a method which sets some properties of that method. In real-life this method, will populate the properties with values found in the database. In test, I would like to mock this method with a custom method/delegate so that I can set the properties myself (without going to the database).
In pseudo-code, this is what I'm trying to do:
Dim _lookup As LookUp = MockRepository.GenerateMock(Of LookUp)()
_luvalue.Expect(Function(l As LookUp) l.GetLookUpByName("test")).Do(Function(l As LookUp) l.Property = "value")
Unfortunately you're attempting to do both a Sub lambda and a Statement Lambda. Neither are supported in VS2008 (but will be in the upcoming version of VS). Here is the expanded version that will work for VB
I'm guessing at the type of m_list
Class MockHelper
Dim m_list as new List(Of Object)
Public Sub New()
Expect(AddressOf CallHelper).IgnoreArguments().Do(AddressOf Do Hepler)
End Sub
Private Sub CallHelper()
m_list.Add(0)
End Sub
Private Sub DoHelper(ByVal item as Integer)
if item < 0 Then
Throw New ArgumentOutOfRangeException
End If
End Sub
End Class
I have never mocked something w/ both a delegate and a lambda so I can't give a full solution to this problem, but I did want to share some example code for the usual "AssertWasCalled" function in Rhino Mocks 3.5 for vb developers because I spent some time trying to grok this... (keep in mind the below is kept simple for brevity)
This is the method under test - might be found inside a service class for the user object
Public Sub DeleteUserByID(ByVal id As Integer) Implements Interfaces.IUserService.DeleteUserByID
mRepository.DeleteUserByID(id)
End Sub
This is the interactive test to assert the repository method gets called
<TestMethod()> _
Public Sub Should_Call_Into_Repository_For_DeleteProjectById()
Dim Repository As IUserRepository = MockRepository.GenerateStub(Of IUserRepository)()
Dim Service As IUserService = New UserService(Repository)
Service.DeleteUserByID(Nothing)
Repository.AssertWasCalled(Function(x) Wrap_DeleteUserByID(x))
End Sub
This is the wrap function used to ensure this works w/ vb
Function Wrap_DeleteUserByID(ByVal Repository As IUserRepository) As Object
Repository.DeleteUserByID(Nothing)
Return Nothing
End Function
I found this to be a very nasty solution, but if it helps someone w/ the same issues I had it was worth the time it took to post this ;)