Why must I use UserControl.MousePointer instead of Me.MousePointer? - vb6

In VB6 on a UserControl, I must use UserControl.MousePointer = vbDefault instead of Me.MousePointer = vbDefault. I can use Me.MousePointer on a Form (and Form.MousePointer doesn't work).
Why must I use UserControl.MousePointer instead of Me.MousePointer?
I mean literally the text "UserControl", not UserControl as the placeholder for another control name.

Me isn't what you seem to think it is. It is a reference to the current instance of the module you use it in, not "magic."
To get what you want you must add this property to the default interface of your UserControl, e.g.:
Option Explicit
Public Property Get MousePointer() As MousePointerConstants
MousePointer = UserControl.MousePointer
End Property
Public Sub Test()
MsgBox Me.MousePointer
End Sub
In VB6 Forms are a little different, probably as a holdover from 16-bit VB to make porting old code easier. These always seem to inherit from a hidden interface. This is defined in a type library you don't have access to since Microsoft did not release it as part of VB6. Attempting to query it typically comes up with an error like:
Cannot jump to 'MousePointer' because it is in the library 'Unknown10' which is not currently referenced
From this alone it seems likely that using Me always carries a small performance penalty. Instead of going directly to the module's procedures it appears to me that you are going through its default COM interface.
You'd have to inspect the compiled code to determine whether there is a performance penalty, and if so how much. I don't see this documented so otherwise we are just guessing about it.
In any case there is little reason to ever use Me unless you must in order to qualify something.
Crummy example but:
Option Explicit
Private mCharm As Long
Public Property Get Charm() As Long
Charm = mCharm
End Property
Public Property Let Charm(ByVal RHS As Long)
mCharm = RHS
'Maybe we do more here such as update the user interface or some
'other things.
End Property
Public Sub GetLucky(ByVal Charm As Long)
'Do some stuff.
Charm = Charm + Int(Rnd() * 50000)
'etc.
Me.Charm = Charm 'Here we use Me so we can assign to the property Charm.
End Sub
That's really about the only legitimate use for Me anyway: scoping to the desired namespace. Relying on it because in typing it brings up IntelliSense is just lazy.
If anything Forms are "broken" not UserControls.

Figured it out. It turns out that since a UserControl is an ActiveX control, VB6 does some magic for you. With a Form control, it's not an ActiveX control which is why the MousePointer property is accessible via Me, like you'd expect.
For UserControl's, the UserControl control you create in VB6 sits on another control - the ActiveX control. That ActiveX control is accessible via UserControl. Something like this:
class ActiveXControl
{
int MousePointer;
VBUserControl control;
}
class VBUserControl
{
}
class YourUserControl : VBUserControl
{
ActiveXControl UserControl;
// we must use UserControl.MousePointer
}
but for a Form, it's more like this:
class Form
{
int MousePointer;
}
class YourForm : Form
{
// we actually inherit from Form so we use Me.MousePointer
}

Related

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

How to detect in VB6 if an event handler has been assigned?

If I have a class in VB6, with some events
Public Event SomethingHappened
and I later want to fire that event
RaiseEvent SomethingHappened
This works fine, in my form which is hosting the class
Public WithEvents TheObject as MyClass
...
Public Sub TheObject_SomethingHappened
...
BUT, is there any way to tell in the code which Raises the event, whether the event has been assigned a handler?
Because I would like to do some default behaviour if not.
I see that in VB.NET there is a automatic "SomethingHappenedEvent" variable declared, but that doesn't seem to work in VB6.
I can't find any mention of this on Google, so I suspect it's not possible, but...
As I mentioned in a comment, Microsoft has often dealt with this in its controls and classes by passing a ByRef Boolean "cancel default action" argument to the event handler.
If the handler exist without setting Cancel = True before returning then a default action is taken by the component.
That could be taken as a viable pattern based on established use. There may be alternatives but this seems pretty simple and clean to implement when you have events where you want to offer default actions.

vb6 : accessing registry values

I'm writing a simple vb6 button which tests the access of the registry values.
I have the following:
Private Function registry_read(key_path, key_name) as variant
Dim registry as object
set registry = CreateObject("WScript.shell")
registry_read = registry.regread(key_path & key_name)
End function
Private Sub Command1_Click()
MsgBox registry_read("HKEY_LOCAL_MACHINE\SOFTWARE\PROCESS\frmMain_Values\", "Version")
end Sub
I have Project Menu -> References
and select Microsoft WMI Scripting V1.1 Library selected
and Windows Script Host Object Model referenced
however my msgbox is still coming up blank. I did check the registry path and it is correct. any ideas?
thanks in advance.
You need to comment out the line 'on error resume next' while you are developing. If an error is occurring, you will not be able to see the details. It could be not found or access denied etc.
Also there are two ways to reference an object. Early binding ie Dim rs as new adobdb.recordset and late binding set rs = CreateObject("Adodb.recordset"). The first method (early binding) forces you to declare a reference and the second (late) does not. There are advantages and disadvantages to both (ie early binding is faster, gives intellisense, easier debugging, etc) http://word.mvps.org/faqs/interdev/earlyvslatebinding.htm
#bugtussle While your statements are correct, wqw's statements are also. Whether you use the New keyword or CreateObject actually hasn't anything to do with whether an object is early or late bound. What does matter is whether you declare the object variable with a registered type or not. I believe that you actually explain this correctly in your article.
I'd like to mention also that your article is well-written and has good information, but IMHO contains also a couple of minor inaccuracies. What you call "Dual Interface" binding in your article (and explain well) is generally referred to as "vTable" or "very early" binding. VB6 supports vTable binding where possible.
Now, as you have said, the sole requirement to be a COM class is that the class must implement iUnknown. A "dual interface" simply means a COM class that implements both iUnknown and iDispatch: a COM class that supports late binding must implement the latter. VB doesn't directly support COM objects that don't implement iDispatch (having some COM classes that don't support late binding and some that do would pretty clearly be problematical in VB); in other words VB only supports COM classes that implement a dual interface. (However, there are tricks using SendMessage's GETOLEINTERFACE message that bypass the requirement.)
Also, it isn't quite that iUnknown is bypassed altogether, it is that iUnknown.QueryInterface() is bypassed, instead going directly to the virtual table. iUnknown.AddRef() is still called, of course.
Regarding New vs. CreateObject: VB has an optimization strategy for classes defined within a project that are instantiated within that project using the New keyword. However, there are also important differences between the two if you are using a class outside of a project context; this page http://msdn.microsoft.com/en-us/library/Aa241758 does a good job of summarizing them.
I'm curious as well to know what error the OP got. :)

Can't implement a class in VB6

I'm trying to implement an interface in VB6. I have defined the class Cast_Speed like this...
Public Function Run_Time() As Long
End Function
and the implementation like this...
Option Explicit
Implements Cast_Speed
Public Function Cast_Speed_Run_Time() As Long
Cast_Speed_Run_Time = 0
End Function
but attempting to compile it gives 'object module needs to implement 'Run_Time' for interface 'Cast_Speed'. Can anyone see what I am doing wrong? My subroutines seem to be quite all right, but all the functions I try have this problem.
It doesn't like the underscore character in the method name. Try using RunTime() instead.
I just tested it without the underscore and it works fine for me:
'// class Cast_Speed
Option Explicit
Public Function RunTime() As Long
End Function
'// class Class1
Option Explicit
Implements Cast_Speed
Public Function Cast_Speed_RunTime() As Long
Cast_Speed_RunTime = 0
End Function
While you can make interface implementations public, it isn't considered good practice, any more than it is considered good practice to allow an interface to be directly instantiated as you also can do. It is simply an example of the maxim that it is possible to write extremely bad code in VB6. :)
Best practice is as follows:
Interface instancing property is PublicNotCreatable.
Implemented Interface Methods are scoped Private.
Thus:
Dim x as iMyInterface
Set x = new MyiMyInterfaceImplementation
x.CalliMyInterfaceMethodA
x.CalliMyInterfaceMethodY
And so on. If someone attempts to directly instantiate the interface, that should cause an error, and if someone attempts to call an implemented method directly instead of polymorphically through the interface that should return an error too.
Unless I'm mistaken, Interface Implementations in VB6 needed to be private (even though the interface declares them as public).
Try changing:
Public Function Cast_Speed_Run_Time() As Long
To:
Private Function Cast_Speed_Run_Time() As Long
You can also read up on implementing interfaces in VB6 here (which seems to back me up).
For a good overview of this subject, see http://msdn.microsoft.com/en-us/library/aa260635(v=vs.60).aspx#ifacebased_vbifaces .

Why does VS2005 create both a member and a field for web service fields?

When adding a web reference in Visual Studio 2005, I've noticed that every element within the wdsl is duplicated. E.g. for element ItemOne, the interface it generates contains both ItemOne and itemOneField. Both are the same thing, but one is a member and the other is a field. I suspect the field is just a getter for the member.
I can imagine using a field instead of a member for this...but in that case my tendency would have been to make the member private, to avoid clutter. This, despite the fact that the normal motivation for making such a member private is to hide implementation details, which is obviously not applicable in this case.
I realize that changing this now would likely introduce compatibility issues, but I don't see why they did it this way the first time.
Do not point out that such a change would introduce compatibility issues with previous versions of VS. I am interested in the original reasoning behind this.
It's a property with a backing field. What's the problem? Were you expecting it to generate an automatic property? They didn't exist until recently. Why change what works, especially since ASMX (and WSDL.EXE) is pretty much dead technology.
"I am interested in the original reasoning behind this"
as everything past 3.0 framework, the only way to create properties were having a private variable and the property name
private string myItemField;
public string myItem() {
get {
return myItemField;
}
set {
myItemField = value;
}
}
but now, there is no need for it...
public string myItem { get; set; }
the thing is, that this last code is compiled as the original one at the top, even if it's easier to write, it is compiled in the same old way, you will end up with a private variable and a property.
Same thing happens when you add a Web Reference, it needs a variable to hold the "stuff" and then the method...

Resources