Please help.
I have a form and a class.
Form - frmTestTool
Class - MainClass
What I am trying to do is to print text everytime the mouse cursor is moved. So the scenario is, I have a software where I embedded the custom command. So I open the custom command and the form will pop up, I need to select somewhere in the software before clicking the "PlaceText" button in the form. After clicking the "PlaceText" button it will implement btnPlaceText_Click_1 but will no longer trigger "OnMouseMove".
Scenario 1(WORKING WELL steps)
Select location in the software
Open Custom Command
Select Place Text
Move MouseCursor (prints "Hello Word" every mouse move)
Scenario 2(NOT WORKING steps)
Open Custom Command
Select location in the software
Select Place Text
Move Mouse Cursor (this time, OnMouseMove does not triggered)
Here's the Code
Partial Public Class frmTestTool
Inherits Form
Public Sub btnPlaceText_Click_1(sender As Object, e As EventArgs) Handles btnPlaceText.Click
WriteMessage("Hello World")
End Sub
End Class
Public Class TextWizard
Inherits BaseStepCommand
Private Shared ofrmTestTool As frmTestTool = New frmTestTool()
Public Overrides Sub OnSuspend()
MyBase.OnSuspend()
End Sub
Public Overrides Sub OnResume()
MyBase.OnResume()
End Sub
Public Overrides Sub OnStart(ByVal commandID As Integer, ByVal argument As Object)
MyBase.OnStart(commandID, argument)
Try
m_running = True
m_oTxnMgr = ClientServiceProvider.TransactionMgr
ofrmTestTool = New frmTestTool()
ofrmTestTool.Show()
Catch commonException As CmnException
ClientServiceProvider.ErrHandler.ReportError(ErrorHandler.ErrorLevel.Critical, MethodBase.GetCurrentMethod().Name, commonException, commandFailed)
End Try
End Sub
Protected Overrides Sub OnMouseDown(ByVal view As GraphicView, ByVal e As GraphicViewManager.GraphicViewEventArgs, ByVal position As Position)
MyBase.OnMouseDown(view, e, position)
ofrmTestTool.btnPlaceText_Click_1(Nothing, Nothing)
End Sub
End Class
I think you need to use RemoveHandler
RemoveHandler Me.MouseMove, AddressOf OnMouseMove
If you don't know the addresses of the handlers then you will need to use System.Reflection to find and remove them.
Sub RemoveEvents(Of T As Control)(Target As T, ByVal EventName As String)
Dim oFieldInfo As FieldInfo = GetType(Control).GetField(EventName, BindingFlags.[Static] Or BindingFlags.NonPublic)
Dim oEvent As Object = oFieldInfo.GetValue(Target)
Dim oPropertyInfo As PropertyInfo = GetType(T).GetProperty("Events", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim oEvenHandlerList As EventHandlerList = CType(oPropertyInfo.GetValue(Target, Nothing), EventHandlerList)
oEvenHandlerList.RemoveHandler(oEvent, oEvenHandlerList(oEvent))
End Sub
Call the Sub like this:
RemoveEvents(Me, "EventMouseMove")
' This can be used for event, just enter the string as "Event<EventName>" where the event name is the event you want to remove all handlers for on the target.
Related
I have an application whose main window upon click of a button gives users an option to load a list of files in the cloud.
Private Sub ImportCloudContent()
Dim cloudForm As Form_CloudImport
cloudForm = New Form_CloudImport()
cloudForm.Show()
cloudForm.populateDataGrid()
AddHandler cloudForm._DownloadComplete, New EventHandler(AddressOf OpenProject)
cloudForm.DownloadNotifier(FullPathOfContent)
End Sub
Ideally I should be able to get the value of the FullPathOfContent variable and pass it onto Open Project, but I am not sure how to go about it.
In the new Window users can click and download the file they want. Below is the section of code that handles the download in the Form_CloudImport class :
Private Async Sub Btn_download_Click(sender As Object, e As EventArgs) Handles Btn_download.Click
Dim fileNameRows As DataGridViewSelectedRowCollection = datagridview_cloudContent.SelectedRows
Dim fileName As String
Dim fileType As String = Cloud.CONTENT
Dim FullPathOfContent As String
For Each fileNameRow As DataGridViewRow In fileNameRows
fileName = fileNameRow.Cells(0).Value.ToString() & ".zip"
Try
FullPathOfContent = CloudToCCT(fileName, fileType)
Catch ex As Exception
CSMessageBox.ShowError("Content Import failed : ", ex)
End Try
Next
Me.Close()
DownloadNotifier(FullPathOfContent)
End Sub
Once the download is complete, the main window needs to call some of its methods. I am new to VB and have created a custom event to facilitate this(again in the Form_CloudImport class)
Public Event _DownloadComplete(e As String)
Public Sub DownloadNotifier(FullPathOfContent As String)
RaiseEvent _DownloadComplete(FullPathOfContent)
End Sub
According to what have read, once the download method is complete, it will fire the DownloadNotifier method, which will raise the _DownloadComplete event and the MainWindow should trigger the following events.
However, I receive the below errors in the MainWindow part of the code :
Value of type 'MainWindow.EventHandler' cannot be converted to 'Form_CloudImport._DownloadCompleteEventHandler'
and
'FullPathOfContent' is not declared. It may be inaccessible due to its protection level.
This question seems to be very long but any help would be appreciated. Thank you in advance!
First things first, you should create a type and event with proper names and signature and raise it properly.
Public Class CloudImportForm
Public Event DownloadComplete As EventHandler(Of DownloadCompleteEventArgs)
Protected Overridable Sub OnDownloadComplete(e As DownloadCompleteEventArgs)
RaiseEvent DownloadComplete(Me, e)
End Sub
'...
End Class
Public Class DownloadCompleteEventArgs
Inherits EventArgs
Public Sub New(contentPath As String)
Me.ContentPath = contentPath
End Sub
Public ReadOnly Property ContentPath As String
End Class
In that form, you would have code that performed a download and then raised that event.
'...
Dim contentPath = GetContentPath()
'Perform download here.
'Raise event.
OnDownloadComplete(New DownloadCompleteEventArgs(contentPath))
In your main form you would create and configure the download form, which includes handling the event, and then display it.
Dim cloudForm As New CloudImportForm
AddHandler cloudForm.DownloadComplete, AddressOf CloudImportForm_DownloadComplete
cloudForm.PopulateDataGrid()
cloudForm.Show()
The method you specify as the event handler should have the appropriate signature and it should retrieve the content path from the e parameter.
Private Sub CloudImportForm_DownloadComplete(sender As Object, e As DownloadCompleteEventArgs)
Dim contentPath = e.ContentPath
'Use contentPath here.
End Sub
I create a number of sub-forms, each of the same class, from my parent form.
Each of the subforms defines an event that should cause the parent form to create a new subform of a different class. - However only the latest created sub form is having it's events handled.
Thinking that it is because I have reused the reference to the sub-form, I store each of the subforms in a linked list of type subform.
SubForm Code.
Public Class UserEditor
Property ID As Integer
Public Event EditGroup(ByRef group As GroupPrincipal, ByVal newWindow As Boolean, ByVal Source As Integer)
Public Sub New(ByVal U As UserPrincipal, ByVal theId As Integer)
InitializeComponent()
ID = theId
UserEditor1.DisplayUserPrincipal(U)
End Sub
Private Sub UserEditor1_GroupEdit(ByRef groupItem As GroupListItem) Handles UserEditor1.GroupEdit
RaiseEvent EditGroup(groupItem.Grp, False, ID)
End Sub
Code on parentform
Public Class ASADManager
Dim WithEvents UserEditorInstance As UserEditor
Dim WithEvents UserEditorList As New List(Of UserEditor)
This code triggered on selection of leafobject in a displayed list.
If TypeOf leafObject Is UserPrincipal Then
UserEditorInstance = New UserEditor(currentLeaf.Principal, UserEditorList.Count) With {
.Text = currentLeaf.SamAccountName
}
UserEditorInstance.Show()
UserEditorInstance.Activate()
UserEditorList.Add(UserEditorInstance)
End If
Private Sub EditGroup(ByRef grp As GroupPrincipal, newWindow As Boolean, Source As Integer) Handles UserEditorInstance.EditGroup
If Not newWindow Then
Dim GEdit As New GroupEditorForm(grp) With {
.Text = grp.Name
}
GEdit.Show()
End If
End Sub
Everything works fine in the latest opened UserEditorInstance, it captures and re-throws the event, and that is cause by the parent, which opens the GroupEditor form.
However, if I select a 2nd LeafObject (to open another UserEditor window) it's events are trapped, and not the previous.
How do I trap both?
Hope this explanation makes it clear what I am doing (and doing wrong)
I'm just getting started with custom classes so I wrote a button strip. It inherits a panel and populates it with buttons from strings passed to my .Add().
ButtonStrip1.Add("Button","Texts","Go Here")
I'd like for it to be able to naturally grab ButtonStrip1.Click's handler and pass it on to child buttons and delete it from ButtonStrip1.
I haven't been able to figure that out, so I've been doing
Public Class ButtonStrip
Inherits Panel
Public Property Innermargin As Integer = 5
Dim Offset As Integer = Innermargin
Dim Buttons = New List(Of ButtonStrip_Button)
Dim StoredFN As EventHandler
Public Sub New()
End Sub
Function Add(fn As EventHandler, ParamArray ByVal Values As String())
StoredFN = fn
For Each V In Values
Dim B As New ButtonStrip_Button
Buttons.Add(B)
Me.Controls.Add(B)
B.Text = V
B.Left = Offset + Innermargin
B.Top = Innermargin
Offset = B.Left + B.Width
AddHandler B.Click, fn
Next
RemoveHandler Me.Click, fn
Me.Width = Offset + Innermargin
Me.Height = Buttons(0).height + Innermargin * 2
End Function
Function Add(ParamArray ByVal Values As String())
If StoredFN Is Nothing Then
Throw New Exception("First Add() must supply a function")
End If
Me.Add(StoredFN, Values)
End Function
End Class
Public Class ButtonStrip_Button
Inherits System.Windows.Forms.Button
Public Sub New()
AutoSize = True
AutoSizeMode = AutoSizeMode.GrowAndShrink
End Sub
End Class
which is called by
ButtonStrip1.Add(AddressOf ButtonStrip1_Click,"Button","Texts","Go Here")
What I'd basically like to do is (psuedo-code)
Function Add(fn As EventHandler, ParamArray ByVal Values As String())
If StoredFN is Nothing Then StoredFN = Me.Click
...
AddHandler B.Click, Me.Click
Next
RemoveHandler Me.Click, Me.Click
...
End Function
I've tried changing a few things and googled a lot. I've also tried using CallByName(Me,"Click",CallType.Method) and with CallType.Get, but the error I get is Expression 'Click' is not a procedure, but occurs as the target of a procedure call. It also returns this exact same message for unhandled events, such as ButtonStrip1 has no MouseDown Event.
I've also tried using MyClass.
Not seen here is an alternative .Add() that add StoredFN to B.click
For instance, this click event works with my code
Private Sub ButtonStrip1_Click(sender As Object, e As EventArgs) Handles ButtonStrip1.Click
msgbox("You clicked " & sender.text & ".")
End Sub
What I was suggesting was something like this:
Public Class ButtonStrip
Inherits Panel
Public Event ButtonClick As EventHandler(Of ButtonClickEventArgs)
Protected Overridable Sub OnButtonClick(e As ButtonClickEventArgs)
RaiseEvent ButtonClick(Me, e)
End Sub
Private Sub Buttons_Click(sender As Object, e As EventArgs)
OnButtonClick(New ButtonClickEventArgs(DirectCast(sender, Button)))
End Sub
Public Sub Add(ParamArray values As String())
Dim btn As New Button
AddHandler btn.Click, AddressOf Buttons_Click
'...
End Sub
End Class
Public Class ButtonClickEventArgs
Inherits EventArgs
Public ReadOnly Property Button As Button
Public Sub New(button As Button)
Me.Button = button
End Sub
End Class
Now there's no need to pass event handlers around. When a Button is clicked, the ButtonStrip handles that event and then raises its own ButtonClick event. Neither the ButtonStrip nor the Buttons have to care about any methods that handle that event as it will be handled the same way any other event is. In a form, you'd then handle the ButtonClick event of the ButtonStrip and get the Button that was clicked from e.Button. You could also add an Index property if you wanted to know the position of the Button rather than the Button itself.
When the form loads, it stars a thread to find all the computers in the network with the use of a library, then for each computer it creates a class which is stored in a list, that class handles the TCP communication between the computer and the remote end, when data is received i want to show it on my form
The code looks something like this
Public Class FormHub
Public Sub ChangeUI (ByVal Text as String)
.....
End Sub
Private Sub FormHub_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim thr As New Thread(AddressOf FindComputers)
thr.Start()
End Sub
Sub FindComputers()
For Each Computer As String In APINetworkItems.GetAllComputersInDomain
For Each Address As IPAddress In Dns.GetHostEntry(Computer).AddressList
If Address.AddressFamily = AddressFamily.InterNetwork Then
Dim handler As New RemoteEnd
handler.Connect(New IPEndPoint(Address, Port), Address, Computer)
ConnectionList.Add(handler)
End If
Next
Next
End Sub
Public Class RemoteEnd
Public Sub Connect(ByVal EndPoint As IPEndPoint, ByVal IP As IPAddress, ByVal Name As String)
.........
End Sub
Public Sub Receive()
....
<Here i want to call a sub on the Form>
End Sub
End Class
Googled it, nothing seems to work... what do i do?
This is the most common problem for people who are just starting to understand multi threading. Think about how WinForm controls interact with calling elements. They use events to signal to the outside world that something happened within them. You can do the same:
Public Class SomeForm
Private connectionsList As New List(Of RemoteEnd)
Public Property Port As Integer
Sub FindComputers()
For Each comp As String In APINetworkItems.GetAllComputersInDomain
For Each addr As IPAddress In Dns.GetHostEntry(comp).AddressList.Where(Function(a) a.AddressFamily = AddressFamily.InterNetwork)
Dim remote As New RemoteEnd
' Add a handler to handle the Connected event that the RemoteEnd class exposes, and then call its Connect sub.
' Note that we do not add the instance to the list yet, as it's not really connected yet (not as long as the RemoteEnd class
' hasn't raised the Connected event...)
AddHandler remote.Connected, AddressOf RemoteEnd_Connected
remote.Connect(New IPEndPoint(addr, Port), addr, comp)
Next
Next
End Sub
Private Sub RemoteEnd_Connected(ByVal sender As Object, ByVal e As EventArgs)
' When the form catches the event, it restores the reference to the instance that raised it, and
' add the instance to the list. Keep in mind that the event will be handled on the same thread it was raised!
' That means that if you want to display data in a form control, you need to invoke the form to make the change!
' Here we just add a reference to a list, so it doesn't matter.
Dim remote = DirectCast(sender, RemoteEnd)
connectionsList.Add(remote)
DoSomething(remote)
End Sub
Private Sub DoSomething(ByVal remote As RemoteEnd)
' ...
End Sub
End Class
Public Class RemoteEnd
Public Event Connected(ByVal sender As Object, ByVal e As EventArgs)
Public Sub Connect(ByVal EndPoint As IPEndPoint, ByVal IP As IPAddress, ByVal Name As String)
' To work efficiently, when this sub is called we need to start the asynchronous process and return immediately.
' When the connection is fully handled, we will raise the event and carry a reference to this instance to the form.
' Because QueueUserWorkItem only takes in one state object to pass parameters, we create a single object that
' contains all the information needed to connect and pass that.
Dim params = New ConnectionInfo(EndPoint, IP, Name)
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf HandleConnectionAsync), params)
End Sub
Private Sub HandleConnectionAsync(ByVal connectionInfos As ConnectionInfo)
' ...
' Here we raise the Connected event for the outside world, carrying a reference to this instance,
' and possibly an instance derived from EventArgs. Here we return nothing.
RaiseEvent Connected(Me, Nothing)
End Sub
End Class
Public Class ConnectionInfo
Public Property EndPoint As IPEndPoint
Public Property IP As IPAddress
Public Property Name As String
Public Sub New(ByVal _ep As IPEndPoint, ByVal _ip As IPAddress, ByVal _name As String)
EndPoint = _ep
IP = _ip
Name = _name
End Sub
End Class
Your RemoteEnd class has no reason whatsoever to even be aware of the form, this is very important, because you want each class of yours to be loosely coupled to others. If a class depends on another, they both should be in the same assembly, but if not they should be separate, so that they can be reused elsewhere. If your form depends on your class, and your class depends on your form, it's called codependency, and it is very bad from an architectural point of view. It might work, but it will be hell to maintain.
As for your original question, once you are setup with the above code, you will notice that the code in the RemoteEnd_Connected handler is actually executed on the same thread that we created on the threadpool in the RemoteEnd class. That means that within that handler, you cannot play with UI controls, because they are on another thread. You need to ask the form to call the delegate with the parameters you need:
Private Delegate Sub SetTextDelegate(ByRef ctrl As Control, ByVal text As String)
Private delSetText As New SetTextDelegate(AddressOf SetText)
Private Sub SetText(ByRef ctrl As Control, ByVal text As String)
ctrl.Text = text
End Sub
Private Sub DoSomething()
If Me.InvokeRequired Then
Me.Invoke(delSetText, {SomeTextBox, "This is the text to set..."})
Else
SomeTextBox.Text = "This is the text to set..."
End If
End Sub
Public Event DocumentCompleted As WebBrowserDocumentCompletedEventHandler
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted, New
WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
Private Sub DoStuff(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
End Sub
How can I pass the homeTeam and guestTeam when firing the DocumentCompleted event.
I want to ge the above to values to inside the Dostuff method.
Please help.
First of all, you cannot have this hanging in the middle of nowhere:
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted,
New WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
AddHandler probably needs to be in some Initialize method, which could be inside Sub New, after InitializeComponent, or inside Form_Load, or as soon as you expect it to be triggered (after a specific event). Notice here that you are using a default event of a native .NET component, with a default event type. In this case you cannot directly consume anything other than what it already provides, when triggered. See WebBrowser.DocumentCompleted Event on MSDN.
You can, however, override all relevant classes and have your own MyWebBrowser control and your own event, with would contain additional properties. See below example:
Public Class Form1
Sub New()
' This call is required by the designer.
InitializeComponent()
Dim browser As New MyWebBrowser
AddHandler browser.MyDocumentCompleted, AddressOf DoStuff
End Sub
Private Sub DoStuff(ByVal sender As Object, ByVal e As MyWebBrowserDocumentCompletedArgs)
Dim guestTeam As String = e.GuestTeam 'guest team
Dim homeTeam As String = e.HomeTeam 'and home team are both accessible
'so you can do some processing on them
End Sub
Public Class MyWebBrowserDocumentCompletedArgs : Inherits WebBrowserDocumentCompletedEventArgs
Dim _homeTeam As String
Dim _guestTeam As String
Public ReadOnly Property HomeTeam
Get
Return _homeTeam
End Get
End Property
Public ReadOnly Property GuestTeam
Get
Return _guestTeam
End Get
End Property
Sub New(url As Uri, homeTeam As String, guestTeam As String)
MyBase.New(url)
_homeTeam = homeTeam
_guestTeam = guestTeam
End Sub
End Class
Public Class MyWebBrowser : Inherits WebBrowser
Public Delegate Sub MyWebBrowserDocumentCompletedEventHandler(e As MyWebBrowserDocumentCompletedArgs)
Public Event MyDocumentCompleted As MyWebBrowserDocumentCompletedEventHandler
Protected Overrides Sub OnDocumentCompleted(e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
MyBase.OnDocumentCompleted(e)
'homeTeam and guestTeam need to be extracted from the current instance of MyWebBrowser, and passed further
RaiseEvent MyDocumentCompleted(New MyWebBrowserDocumentCompletedArgs(e.Url, "homeTeam", "guestTeam"))
End Sub
End Class
End Class
If your project is relatively small, you can indeed have those as global variables, as #Vlad suggested in the comments.