Send a form to a sub declared in a DLL files - vb.net

I'm trying to create (finally) my own DLL files to some code that I will use constantly in a new project, I'm using Visual Basic 2010
I created the DLL correctly but I got a problem with a sub have the code Me.Handle
I don't know how to send the "me" to my sub
This is my sub (from an example in msdn)
Sub Start_Detection()
Dim di As New DEV_BROADCAST_DEVICEINTERFACE
di.dbcc_size = CUInt(Marshal.SizeOf(GetType(DEV_BROADCAST_DEVICEINTERFACE)))
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE
di.dbcc_reserved = 0
di.dbcc_classguid = Guid.Parse("{88BAE032-5A81-49f0-BC3D-A4FF138216D6}")
di.dbcc_name = Nothing
hDevNotify = RegisterDeviceNotification(Me.Handle, di, DEVICE_NOTIFY_WINDOW_HANDLE)
End Sub
When I put that inside a DLL, I don't know how to send the Me because in the DLL project says that "Me" isn't a member of my DLL project.
If I declare the sub as Sub Start_Detection(ByRef Form) or Sub Start_Detection(ByVal Form) the DLL project works ok, but when I call it from the windows form project a "Null Reference Exception" happen.
Is not possible to send forms as arguments in Visual Basic 2010 ?
Thanks!
Edit: I'm calling the sub in this way
Private Sub Frm_Config_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
MyDLL.Start_Detection(Me)
End Sub

You might also want to consider using the following naming convention for the parameter
Sub Start_Detection (ByVal sender As System.Object)
or
Sub Start_Detection (ByVal handle As IntPtr)
When your calling the method, pass the handle value to the Class method
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
If Me.IsHandleCreated Then
RecieveHandle.Start_Detection(Me.Handle)
End If
End Sub
Then once your in the class module, you can do a common trick to confirm the variable has been supplied and don't pass a reference to the whole form via the "Me" (VB) or "This" (C#) variable.
Public Class RecieveHandle
Public Shared Sub Start_Detection(ByVal sender As System.Object)
If sender Is Nothing Then
Throw New ArgumentException("Method requires sender parameter to be supplied")
End If
If Not TypeOf (sender) Is IntPtr Then
Throw New ArgumentException("Method requires a valid pointer (handle) to the form.")
End If
Dim myFormHandle As IntPtr = CType(sender, IntPtr)
Debug.Print(myFormHandle.ToInt64.ToString)
End Sub
End Class

Me belongs to a form not a dll. If you pass the IntPtr you will be fine.
Sub Start_Detection(ptr As IntPtr)
Dim di As New DEV_BROADCAST_DEVICEINTERFACE
di.dbcc_size = CUInt(Marshal.SizeOf(GetType(DEV_BROADCAST_DEVICEINTERFACE)))
di.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE
di.dbcc_reserved = 0
di.dbcc_classguid = Guid.Parse("{88BAE032-5A81-49f0-BC3D-A4FF138216D6}")
di.dbcc_name = Nothing
hDevNotify = RegisterDeviceNotification(ptr, di, DEVICE_NOTIFY_WINDOW_HANDLE)
End Sub
Usage:
Start_Detection(Me.Handle)

Related

Unable to run my own created exe inside parrent form (vb.net)

I have been able to run an external program using the following code.
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("user32.dll")> Public Shared Function SetParent(ByVal hwndChild As IntPtr, ByVal hwndNewParent As IntPtr) As Integer
End Function
Private Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("notepad.exe")
PRO.Start()
Do Until PRO.WaitForInputIdle = True
'Nothing
Loop
SetParent(PRO.MainWindowHandle, Me.Handle)
PRO.Dispose()
End Sub
This works fine..... (for notepad that is)
However If I swich notepad for my own vb.net application it fails to launch that aplication inside the form but rather runs it outside of the form. I thought that the application I am trying to launch might of had somthing in it so I created a new application with nothing in it (as bare as I could get it) and run that instead of notepad but it also fails to launch within its "parent" form but rather it also triggers outside of the "parent" form insted?
Could someone please help me fix this?
You just need to wait a tiny bit longer for the MainWindowHandle property to be populated.
Here's a kludge that'll do it:
Private Async Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("C:\Users\mikes\Desktop\temp.exe")
PRO.Start()
Await Task.Run(Sub()
PRO.WaitForInputIdle()
While PRO.MainWindowHandle.Equals(IntPtr.Zero)
Threading.Thread.Sleep(10)
End While
End Sub)
SetParent(PRO.MainWindowHandle, Me.Handle)
End Sub
If you want a ten second fail-safe, and exceptions caught, then you could change it up to:
Private Async Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
Try
Dim PRO As Process = New Process
PRO.StartInfo.FileName = ("C:\Users\mikes\Desktop\temp.exe")
PRO.Start()
Await Task.Run(Sub()
Dim timeout As DateTime = DateTime.Now.AddSeconds(10)
While timeout > DateTime.Now AndAlso PRO.MainWindowHandle.Equals(IntPtr.Zero)
Threading.Thread.Sleep(10)
End While
End Sub)
If (Not PRO.MainWindowHandle.Equals(IntPtr.Zero)) Then
SetParent(PRO.MainWindowHandle, Me.Handle)
Else
MessageBox.Show("Timed out waiting for main window handle.", "Failed to Launch External Application")
End If
Catch ex As Exception
MessageBox.Show(ex.ToString, "Failed to Launch External Application")
End Try
End Sub

Update label from mainform class with backgroundworker from another class

I have two classes.
Public Class MainForm
Private Project As clsProject
Private Sub btnDo_Click
...
Backgroundworker.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
and two methods inside MainForm
Public Shared Sub setLabelTxt(ByVal text As String, ByVal lbl As Label)
If lbl.InvokeRequired Then
lbl.Invoke(New setLabelTxtInvoker(AddressOf setLabelTxt), text, lbl)
Else
lbl.Text = text
End If
End Sub
Public Delegate Sub setLabelTxtInvoker(ByVal text As String, ByVal lbl As Label)
end class
I want to update the labels of MainForm from the clsProject constructor.
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..", MainForm.lblProgress)
but it does not update them.
What am I doing wrong?
The problem is that you are using the global MainForm instance to access the label in a background thread here:
Public Class clsProject
Public Sub New()
' When accessing MainForm.Label1 on the next line, it causes an exception
MainForm.setLabelTxt("HERE!", MainForm.Label1)
End Sub
End Class
It's OK to call MainForm.setLabelTxt, since that is a shared method, so it's not going through the global instance to call it. But, when you access the Label1 property, that's utilizing VB.NET's trickery to access the global instance of the form. Using the form through that auto-global-instance variable (which always shares the same name as the type) is apparently not allowed in non-UI threads. When you do so, it throws an InvalidOperationException, with the following error message:
An error occurred creating the form. See Exception.InnerException for details. The error is: ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.
I'm guessing that the reason you are not seeing the error is because you are catching the exception somewhere and you are simply ignoring it. If you stop using that global instance variable, the error goes away and it works. For instance, if you change the constructor to this:
Public Class clsProject
Public Sub New(f As MainForm)
' The next line works because it doesn't use the global MainForm instance variable
MainForm.setLabelTxt("HERE!", f.Label1)
End Sub
End Class
Then, in your MainForm, you would have to call it like this:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject(Me) ' Must pass Me
End Sub
Using the global instance from the background thread is not allowed, but when we use the same label from the background thread, without going through that global variable it works.
So it's clear that you cannot use the global MainForm variable from a background thread, but what may not be clear is that it's a bad idea to use it ever. First, it's confusing because it shares the same name as the MainForm type. More importantly, though, it is a global variable, and global state of any kind is almost always bad practice, if it can be avoided.
While the above example does solve the problem, it's still a pretty poor way of doing it. A better option would be to pass the setLabelTxt method to the clsProject object or even better have the clsProject simply raise an event when the label needs to be changed. Then, the MainForm can simply listen for those events and handle them when they happen. Ultimately, that clsProject class is probably some sort of business class which shouldn't be doing any kind of UI work anyway.
You cannot execute any action on GUI-elements from the BackgroundWorker directly. One way to "overcome" that is by forcing the given actions to be performed from the main thread via Me.Invoke; but this is not the ideal proceeding. Additionally, your code mixes up main form and external class (+ shared/non-shared objects) what makes the whole structure not too solid.
A for-sure working solution is relying on the specific BGW methods for dealing with GUI elements; for example: ProgressChanged Event. Sample code:
Public Class MainForm
Private Project As clsProject
Public Shared bgw As System.ComponentModel.BackgroundWorker
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
bgw = BackgroundWorker1 'Required as far as you want to called it from a Shared method
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
Public Shared Sub setLabelTxt(ByVal text As String)
bgw.ReportProgress(0, text) 'You can write any int as first argument as far as will not be used anyway
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.Label1.Text = e.UserState 'You can access the given GUI-element directly
Me.Label1.Update()
End Sub
End Class
Public Class clsProject
Public Sub New()
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..")
End Sub
End Class
Try:
Me.Invoke(...)
instead of lbl.Invoke(.... I had to do this. This is my implementation:
Delegate Sub SetTextDelegate(ByVal args As String)
Private Sub SetTextBoxInfo(ByVal txt As String)
If txtInfo.InvokeRequired Then
Dim md As New SetTextDelegate(AddressOf SetTextBoxInfo)
Me.Invoke(md, txt)
Else
txtInfo.Text = txt
End If
End Sub
And this worked for me.

Saving textbox name when clicked in order to input text

Hey all i am in need of some help getting my code working correctly like i am needing it to. Below is my code that when the user click on the textbox, it pops up a keyboard where they can click on any letter and it will type that letter into the textbox. Problem being is i can not seem to get the name of the text box to return so that it knows where to send the letters to.
Order in firing is:
TextBox1_MouseDown
keyboardOrPad.runKeyboardOrPad
kbOrPad.keyboardPadType
ClickLetters
Form1.putIntoTextBox
Form1
Private Sub TextBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Call keyboardOrPad.runKeyboardOrPad("SHOW") 'Just shows the keyboard
Call kbOrPad.keyboardPadType("PAD", TextBox1)
End Sub
Public Sub putIntoTextBox(ByRef what2Put As String, ByRef whatBox As TextBox)
whatBox.Text = what2Put '<-- has error Object reference not set to an instance of an object. for the whatBox.text
End Sub
kbOrPad class
Dim theBoxName As TextBox = Nothing
Public Sub keyboardPadType(ByRef whatType As String, ByRef boxName As TextBox)
theBoxName = boxName '<-- shows nothing here
Dim intX As Short = 1
If whatType = "PAD" Then
Do Until intX = 30
Dim theButton() As Control = Controls.Find("Button" & intX, True)
theButton(0).Enabled = False
intX += 1
Loop
ElseIf whatType = "KEYB" Then
End If
End Sub
Private Sub ClickLetters(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim btn As Button = CType(sender, Button)
If btn.Text = "Backspace" Then
Else
Call Form1.putIntoTextBox(btn.Text, theBoxName) 'theBoxName taken from keyboardPadType
End If
End Sub
Some visuals for you:
Pastebin code: http://pastebin.com/4ReEnJB0
make sure that theBoxName is a Module scoped variable, then I would populate it like this giving you the flexibility of implementing a shared TextBox MouseDown Handler:
Private Sub TextBox1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Dim tb As TextBox = CType(sender, TextBox)
Call keyboardPadType("PAD", tb)
End Sub
Try something like this
Public Class Form1
Dim myKborPad As New kbOrPad
Private Sub TextBox1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Dim tb As TextBox = CType(sender, TextBox)
Call myKborPad.keyboardPadType("PAD", tb)
End Sub
Edit Based on your PasteBin code.
I noticed you already have an instance of your keyboardPadType declared in your Module, use that instead of what I said earlier. That code should look like:
remove:
Dim myKborPad As New kbOrPad
and use the theKbOrPad that you created in your module like this:
Private Sub TextBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles TextBox1.MouseDown
Dim tb As TextBox = CType(sender, TextBox)
Call keyboardOrPad.runKeyboardOrPad("SHOW")
Call theKbOrPad.keyboardPadType("PAD", tb)
'Call kbOrPad.keyboardPadType("PAD", tb)
End Sub
Also about the current error your are getting, you are trying to use the default instance of your Form1 , it isn't the actual Form that you are running, you can code around this by making the method you are trying to use as shared. Like this:
Public Shared Sub putIntoTextBox(ByRef what2Put As String, ByRef whatBox As TextBox)
whatBox.Text = what2Put
End Sub
But however I would actually prefer to put it into your Module like this
Public Sub putIntoTextBox(ByRef what2Put As String, ByRef whatBox As TextBox)
whatBox.Text = what2Put
End Sub
and call it like this
Call putIntoTextBox(btn.Text, theBoxName)
after making above changes your code worked.
First, you should replace the ByRef with ByVal (anytime you don't know whether you should use one or the other, use ByVal).
Secondly, I believe you don't need the method, putIntoTextBox, I think you should be able to do that directly (might be threading problems that prevent it, but I don't think that's likely based on your description). You don't show where Form1 is set (or even if it is), and that's another potential problem.
Finally, the better way to call back into the other class is to use a delegate/lambada.
(I know, no code, but you don't provide enough context for a working response, so I'm just giving text).

How is it possible to parse the URL of the desired popup to the popup-form AND show hints/tooltips in the WebKit-Component?

I'm trying to use the WebKit-component (http://www.webkit.org/) in VB with the help of Visual Studio 2008.
This is running without problems, except for two following two issues:
1. Hints/Tooltips are not shown (e.g. as there usually will appear one if you stay with the mouse over the Google-logo)
2. If there's a popup-window, I don't know how to get the new desired URL.
I'm already working a few days on this matter and couldn't find any solution yet :(
Maybe you know a solution to this problem.
Cheers
Markus G.
P.S.: If you need more than the following Source Code to analyze the problem, then let me know ...
Source Code Form1
Imports System.IO
Imports WebKit
Public Class frmMain
Private _url As String
Private _mode As String
Private _popupUrl As String
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
On Error Resume Next
Dim bLogging As Boolean
setWindowAndBrowserSettings()
_url = "http://www.google.com"
browserComp.Navigate(_url)
End Sub
Private Sub setWindowAndBrowserSettings()
Me.Text = "Test - Browser"
Me.WindowState = FormWindowState.Maximized
browserComp.Dock = DockStyle.Fill
browserComp.Visible = True
End Sub
Private Sub browserComp_NewWindowCreated(ByVal sender As Object, ByVal e As WebKit.NewWindowCreatedEventArgs) Handles browserComp.NewWindowCreated
'frmPopup.WindowState = FormWindowState.Maximized
frmPopup.Text = "ixserv - POPUP"
frmPopup.popup.Navigate(_popupUrl)
frmPopup.Show()
End Sub
Private Sub browserComp_NewWindowRequest(ByVal sender As Object, ByVal e As WebKit.NewWindowRequestEventArgs) Handles browserComp.NewWindowRequest
e.Cancel = False
_popupUrl = browserComp.Url.ToString ' WHERE can I get the clicked URL? This is the old one of the remaining window
End Sub
End Class
Code Form2
Public Class frmPopup
End Class
Following popup/new-Window-Create-function works for me:
Private Sub browserComp_NewWindowCreated(ByVal sender As Object, ByVal e As WebKit.NewWindowCreatedEventArgs) Handles browserComp.NewWindowCreated
frmPopup.Text = "POPUP"
Dim popupBrowser As WebKit.WebKitBrowser
popupBrowser = e.WebKitBrowser
frmPopup.Controls.Add(popupBrowser)
frmPopup.Show()
End Sub
whereas frmPopup is a new form.
Before I tried this I already added the Webkit-component to the new form, which might had been the problem. I assume, the trick is, to create a new WebKitBrower-element that is directly connected to the argument e.WebkitBrowser instead of overloading an existing webkitbrowser-component in the form. Don't ask me for reasons for this now (I really don't know) :P
Oh, I should add that I used the Webkit.NET component. The same trick works also for the OpenWebkitSharp-wrapper
The hint-problem still remains ...

VB.NET example code for calling other exe to act like as MDI child form? is it possible?

ok here is the scenario.
1. i've create a project that has 1 form named form1.exe
2. i've also create a project that has 1 MDI form.
in this MDI Form. i'would like to call "form1.exe" act like / behave like MDI Form child.
i've tried using this code :
Public Shared Function SetParent(ByVal hWndChild As IntPtr, ByVal hWndParent As IntPtr) As IntPtr
End Function
Private Sub ShowNewForm(ByVal sender As Object, ByVal e As EventArgs) Handles NewToolStripMenuItem.Click, NewToolStripButton.Click, NewWindowToolStripMenuItem.Click
Dim myProcess As Process = New Process()
myProcess.StartInfo.FileName = "D:\tesVB.exe"
myProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal
myProcess.Start()
myProcess.WaitForInputIdle()
SetParent(myProcess.MainWindowHandle, Me.Handle)
myProcess.WaitForExit()
End Sub
Above code is worked but that new child form(form1.exe) doesn't act like it should be! When i maximized or minimized it. it don't act like MDI Child Form.
Can anyone give me another better example code ? thx before.
hahaha.... found it my self. hope this solution would be good for others.
Private Sub ShowNewForm(ByVal sender As Object, ByVal e As EventArgs) Handles NewToolStripMenuItem.Click, NewToolStripButton.Click, NewWindowToolStripMenuItem.Click
Dim eAssembly As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom("D:\form1.exe")
Dim eForm As Form = eAssembly.CreateInstance("form1.Form1", True)
Me.AddOwnedForm(eForm)
eForm.MdiParent = Me
eForm.Show()
End Sub
Private Sub ShowNewForm(ByVal sender As Object, ByVal e As EventArgs) Handles NewToolStripMenuItem.Click, NewToolStripButton.Click, NewWindowToolStripMenuItem.Click
Dim eAssembly As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom("D:\form1.exe")
Dim eForm As Form = eAssembly.CreateInstance("form1.Form1", True)
Me.AddOwnedForm(eForm)
eForm.MdiParent = Me
eForm.Show()
End Sub
Runtime error
eform is a form and eAssembly.CreateInstance("form1.Form1", True) returns an object