Creating systray icon with vbscript? - vbscript

I can only use vbscript on this computer, I would like to display an icon in the systray.
I found a similar question and an answer using VBA.
Display new mail icon in Windows taskbar using VBScript
There is also this code from Microsoft which explains how to do it from VBA
How to use the System Tray directly from Visual Basic
These solution work with the following API call
Public Declare Function Shell_NotifyIcon Lib "shell32" _
Alias "Shell_NotifyIconA" _
(ByVal dwMessage As Long, pnid As NOTIFYICONDATA) As Boolean
The problem is how to create the user-defined type in vbscript.
Public Type NOTIFYICONDATA
cbSize As Long
hwnd As Long
uId As Long
uFlags As Long
uCallBackMessage As Long
hIcon As Long
szTip As String * 64
End Type
VBScript only has a "variant" for all variable, there isn't a TYPE statement to create the kind of thing Shell_NotifyIcon needs.
I have read somewhere the possibility of using a class statement.
Perhaps something like this ?
Class NOTIFYICONDATA
cbSize
hwnd
uId
uFlags
uCallBackMessage
hIcon
szTip(64)
End Class
However I think my syntax is wrong or that the class object doesn't work that way. Or maybe, since each component of that class is a variant instead of the type that Shell_NotifyIcon expect, it won't work ?
I don't mind using a ugly has to force vbscript engine into making the proper memory object to use the API call, if there is any way ?
(In a later episode, if I manage to create a systray icon, I would like to get click events from the icon and display a context menu (vbscript lacks a form object to create user interfaces, except for the extremely limited forms from the HTA objects which I rather not use))

As pointed out by #Lankymart, VBScript can't use Windows APIs directly. You could write a COM wrapper that exposes particular interfaces to VBScript, but then you could just as well implement your whole application in VB6 or VB.net.

Related

Calling FindWindowEx with program WIndowClass

I'm trying to use FindWindowEx to determine whether a certain program is running or not.
FindWindow(NULL, "Mozilla Firefox");
This works fine as long as I'm on firefox's start page. A workaround I found was:
FindWindow(NULL, "MozillaWindowClass");
But that left me wondering if that was specifically crafted for firefox, but it turns that it appeareantly works for other applications:
FindWindow(NULL, "OllyDbgWindowClass");
So my question is can I just use FindWindow with an argument like "programXWindowClass" for any program? Are there any exceptions to this?
Is "programXWindowClass" guaranteed to exist?
There is no requirement for a caller to RegisterClassEx to follow any particular pattern, that maps a window class name to any other information (like the application name). Any caller can pick any valid window class name they like.
Keep in mind two notable consequences of this:
A window class name need not be unique to any given application. All UWP applications use the window class "Windows.UI.Core.CoreWindow" by default, for example.
A window class name can change across different versions of an application, or even different invocations of an application.
Is "programXWindowClass" guaranteed to exist?
No. What you observed is merely a coincidence in naming.

How to get parent form of nested VB6 UserControl

There is a similar question here, which unfortunately does not help me. When I make a call to UserControl.Parent, either a Form or another UserControl can be returned. If a Form is returned, I have what I want. But if a UserControl is returned, I have no way of iterating up the chain, since UserControl is the base class name, and I do not have access to the base class name outside of the control's implementation.
Technically, I could probably get around this by exposing the Parent property on every single UserControl in the application, but I would really like to avoid doing this (we have thousands of them).
My ultimate goal is to get a reference to the parent form which is hosting the control, so that the control can subscribe to the Form_Unload event. Here the control will remove and clean up a hosted .NET interopped control which is preventing the VB6 UserControl from raising its UserControl_Terminated event, thus leaking GDI objects and memory.
So far I have tried to make calls to GetParent(), GetWindow() and GetAncestor() functions in USER32.dll in the UserControl_Initialize and UserControl_Resize events, and then cross referencing with the hWnds on the forms in the Forms collection, but both of these events seem to be raised before the UserControl has been sited on its host form.
I did some fiddling around, and this should give you something to go on. I just created two UserControls, called Internal and External. Internal has a command button, External has a frame. I put an instance of Internal on External, inside the frame. Then I put an instance of External on a form, whose name I just left at Form1.
So, I have a control within a control on a form. The problem is to find a reference to Form from the context of the internal control.
First, I have a method called Test on the Form, so:
Public Sub Test
MsgBox "Test Succeeded"
End Sub
Now, in the internal control's command button's Click event handler:
Private Sub cmdTest_Click()
UserControl.ParentControls(0).Parent.Test
End Sub
Running the Form project and clicking the command button successfully calls the Form's Test method. (Yee ha.)
So, to distinguish between a nested control and a control directly on the form, you can do something along these lines:
Private Sub cmdTest_Click()
If TypeOf UserControl.Parent is Form Then
UserControl.Parent.Test
Else
UserControl.ParentControls(0).Parent.Test
End If
End Sub
I then tried nesting the external control in another control (External2) and putting an instance of External2 on the form. In the debug window, I did this:
? typename(usercontrol.ParentControls(0))
External
? typename(usercontrol.ParentControls(0).Parent)
External2
And that's as far as I got. Trying things like this:
? typename(usercontrol.ParentControls(0).Parent.ParentControls(0))
or anything similar, got an "Object doesn't support this property or method" error.
If you need to evaluate controls nested more than one deep (you haven't said for sure, but "iterate up the chain" suggests that you might), that might be beyond the capabilities of this technique. You can mess with the Controls and Name properties as well, and maybe figure something out. But it looks like the ParentControls property doesn't extend to parents of controls that are themselves controls, unless of course you go to the trouble of exposing them with a different property name. At least you can iterate one more link up the chain with this.
In general, it doesn't look like the Parent property or its derivatives contain actual references to the parent objects, but some sort of subset of their properties and methods. For example, I can get the hWnd property of the UserControl, but not of UserControl.Parent. Even specifically referencing the parent by name (the name of the actual control instance is External1, so ? External1.hWnd) fails to get the hWnd property, throwing an "Object Required" error. That would appear to complicate the possibility of using the API for a solution.
Anyway, I leave it to you to play around with. If you get further than I have, I would be interested to see your results.
I was able to find the parent form by traversing parent/child relationships using HWNDs and the win32 API. My code is roughly as follows:
Private Declare Function GetParent Lib "USER32" (ByVal Hwnd As Long) As Long
Private Declare Function GetClassName Lib "USER32" Alias "GetClassNameA" _
(ByVal Hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Public Function FindParentForm(Hwnd As Long) As Form
Dim ParentHwnd As Long
Dim CandidateForm As Form
Dim strTmp As String * 255
Dim lngLngth As Long
Dim strTitle As String
ParentHwnd = Hwnd
Do While (Not ParentHwnd = 0)
ParentHwnd = GetParent(ParentHwnd)
lngLngth = GetClassName(ParentHwnd, strTmp, 255)
strTitle = Left(strTmp, lngLngth)
'ThunderFormDC is the Debug version of a VB6 form, and ThunderRT6FormDC is the Release/Runtime version of a VB6 form
If strTitle = "ThunderFormDC" Or strTitle = "ThunderRT6FormDC" Then
Exit Do
End If
Loop
For Each CandidateForm In Forms
If CandidateForm.Hwnd = ParentHwnd Then
Set FindParentForm2 = CandidateForm
Exit Function
End If
Next
'Didn't find the parent form somehow
Set FindParentForm2 = Nothing
End Function
As I mentioned in the question, the problem with this solution is that during the UserControl_Initialize and UserControl_Resize events, the parent/child relationships have not been setup between HWNDs yet. If you try to traverse the parent/child relationships, you will find that the parent of the usercontrol is a class named "Static".
I was able to work around this issue by searching for the parent form in a manual Init() procedure for my user control. However, many forms do not call the Init() procedure unless the tab it is on is clicked on (in an attempt to get some sort of lazy loading implemented in VB6). I was able to work around this by refactoring the control to not dynamically create/add the .NET interopped control until the Init() procedure was called. By this time, the parent/child relationships seem to be set up.
An alternative solution to the lazy loading problem is to hook a WndProc procedure, and listen for the WM_CHILDACTIVATED message. However, this message only gets sent when the child control changes parents. It does not propogate to grandchildren. It should, however, be possible to hook another WndProc to the new parent control and listen for it's own WM_CHILDACTIVATED message and so on until a child control gets parented to a ThunderForm. However, since WndProc can only be implemented in a static module, I didn't feel like keeping track of parent/child relationships.
I'm rusty, but isn't the Container property what you're after?
Be aware, when interopping with dot net, that dot net has very different garbage collection. Just because the VB6 objects have been released doesn't mean that the dot net objects have been. They could still be lurking in memory and - in rare cases - could still be firing events on timers.
So make sure that your dot net control is cleaning itself up properly, and has a method which VB6 can fire in order to force this termination to occur.
See: https://msdn.microsoft.com/en-us/library/aa445702(v=vs.60).aspx
I solved my referencing problem using UserControl.Parent in ReadProperty.
I tried GetParent() with no success. Also tried UserControl.Parent in Initialize.
The problem is that during Initialize the object doesn't exist. It is being builted before link to form (Parent).
In ReadProperty (or Resize) it already exists and you will be able to use UserControl.Parent.
For example, I use UserControl.Parent.name to save some information in window register.
This is asociated with this topic so I post it here for use as necessary in special limited cases. It can't get too deep into the nesting, but was is a quick way to get information in my case and others may find it of use.
I have a a Control (cmdPageDown1) placed on a User Control, that is placed on a User Control (xMCGridNew1), which is placed on a Form (frmMC).
As others have pointed out, I can't get to the Form Name with any quick single line of code, BUT with these simple lines, I can get the names of the other 2 nested user controls and the lower level Control.
For Example:
'Debug.Print '[Roadblock Here] 'Top Level frm MC (Parent)
Debug.Print Screen.ActiveControl.Name 'SubLevel 1 xMCGridNew1 (Child)
Debug.Print UserControl.Name 'SubLevel 2 xpage (GrandChild)
Debug.Print ActiveControl.Name 'SubLevel 3 cmdPageDown1 (Great Grandchild)
This will yield in the immediate window corresponding to the three nest levels.
xMCGridNew1
xPage
cmdPageDown1
As mentioned by another poster (Orignal Poster) in this thread, regarding attempted use of the Container property:
The Container property has two issues. First, UserControl's do not have a >Container property. You must get it off of the UserControl.Extender >property. Second, the Container may be another UserControl. Since the >UserControl.Extender property is a base class property, it is not accessible >outside of that UserControl's specific implementation. I.e. UserControl1 >cannot access UserControl2.UserControl.Extender.Container. Thus the >Container property has the same problems as the Parent property - I would >need to modify every user control in our application (too many). – Taedrin >Jan 15 '16 at 15:08

create vbscript vbdataobject

I am using a program which makes it possible to customize dialogues via vbscript. Now, the form has an attribute which seems to be a vbDataObject (VarType returns 13).
How can I read the content and create a new vbDataObject to assign it to the attribute?
Don't know if this is exactly the answer, but it seems that vbDataObject's are indeed COM objects, but they just do not expose an IDispatch interface (so they're not true automation objects). As such, VBScript just isn't capable of accessing their content, since it needs an IDispatch interface for it. It just has an IUnknown interface which it doesn't know what to do with.
Passing them around should be possible, though, so if another true automation COM object can manipulate them for you (maybe there is such an object in the app designed to do this?), you could use that to make an object with the correct settings and pass that to the form.
Another option is to contact the authors of the app and ask them if they're willing to give this one object an IDispatch interface as well. Since they presumably went the length to put IDispatch interfaces on most of their objects, this omission might just be an oversight on their part.

How to create a wrapper DLL/Type Library in VB6?

In my previous question, I asked why I kept getting the error message bad DLL calling convention when trying to call functions from a DLL. The general consensus was that I needed to change the calling convention to cdecl. Makes sense.
Unfortunately, I cannot place it right there on the function declaration importation. I had to either "create a wrapper DLL" or "create a type library for the DLL."
I am very unfamiliar with VB as my main focus at work is C# and this is the first time working in the language for a very long time. I'm not sure exactly how to accomplish this task.
I'm also confused as to how a wrapper DLL helps things. Supposedly I can't decorate a function import with cdecl in my code but if I move that exact function import to a new VB6 DLL and then reference that DLL it suddenly works?
I actually think this question was better on the topic.
To sum up, you can "place it right there on the function declaration importation", but the VB6 IDE doesn't know how to debug such a thing. But the compiler deals with it just fine. Once you compile it into a dll then your main project can access the functionality that was compiled.
Perhaps you are asking how to move these into a dll? If that is the case, you need to create a new Project of type "ActiveX Dll". Name it something like PwrUSB. Next, add a class (or rename the default/empty one if it is provided) to something like PwrUSBApi. Next, in the properties window, set the class to GlobalMultiUse. In a module called MDeclares, drop in all of your declarations:
'from your other post...
Public Declare Function InitPowerDevice CDecl Lib "PwrDeviceDll.dll" (ByRef firmware() As Byte) As Long
Back in your PwrUSBApi class:
'forward your calls to the dll
Public Function InitPowerDevice (ByRef firmware() As Byte) As Long
InitPowerDevice = MDeclares.InitPowerDevice(firmware)
End Function
You could create a more fully fledged object model from the API, but I'd start with this simple wrapper until you sort out all of the APIs.
Oh yeah, back in your main project you'd add a reference your new wrapper PwrUSB.dll in the Project menu. Then in the code you would use it something like this:
Dim numOfDevices as Long
Dim firmware() As Byte
Redim firmware(0 to 31)
numOfDevices = PwrUSB.InitPowerDevice(firmware)
Good luck.
A wrapper DLL in VB6 would still need to use the CDecl decorator or else a typelib created to deal with the DLL's function signatures.
The only advantage in creating a VB6 wrapper for this is to make it easier to debug the calling program from within the VB6 IDE, where CDecl has no effect. The wrapper would be small, and created once as a native code DLL, making CDecl effective there.
See your other thread(s) for additional answers. I suspect your real problem is that you were not passing the right kind of argument.

Registering Window Class

I guess my question is relatively easy for those of you who spent time dealing with Win32 API.
So my question is:
After initializing a WNDCLASSEX instance we need to "register" it using the "RegisterClassEx" function, why? Why do we do that? What's the meaning of this registration and in what cases I need to register things?
The ATOM returned by RegisterClassEx uniquely identifies your "window class" which can then be referred to in other windows APIs. [MSDN]
Effectively it is a hash so as to reduce the amount of data processed each time a window is created or looked for. It does also mean that multiple windows with same features can be easily created and identified.
I was addressing the practical reasons above. Hans Passant's answer correctly explains this is the OO class concept provided for C. Further MSDN example.
The word Class in the function name is significant. When you write code in an object oriented language, like C++, Delphi, Java or C# etcetera, then you use the class keyword to create objects that have behavior. But the winapi was designed to be used from C, a language that doesn't have such functionality. The RegisterClassEx() function is an emulation of that, it lets you create a window that "derives" its behavior from a named class, behavior that you can override. With every window that you create using that class name behaving identically.
The WNDCLASSEX structure you pass gives a window its default behavior. The most significant members of this structure are:
lpszClassName. That's the equivalent of the C++ class name. You can later call CreateWindowEx() and pass that name to get a window that behaves a certain way. Windows itself calls RegisterClassEx() to register several of its built-in window classes that you then can readily re-use in your own code. "EDIT", "BUTTON" and "LISTBOX" are good examples of that.
lpfnWndProc. This is what gives a window class its specific default behavior. The address of its window procedure that implement message handlers for specific messages. You can further customize the default behavior, in other words "derive" your own class from the base class, by specifying another window procedure in the CreateWindowEx() call. Such a window procedure must always call DefWindowProc(), the equivalent of calling the base class method. Or in other words, a window has one virtual method.
hIcon, etcetera. These are the equivalent of properties of the base class, they set default values that affect the default message handlers. Helping you to keep your window procedure simple. It is for example rarely necessary to write a message handler for WM_ERASEBKGND, the hbrBackground member sets the default background for a window.
Windows requires you to call RegisterClassEx() even if you don't plan on re-using a window. Which is by far the most common usage of the function in your own code. You don't start to really take advantage of it until you write a library that implements controls, windows that other code can use. Like "EDIT".

Resources