For you usercontrol (UC) creators out there. Everyone else -- won't apply to you.
Ambient.UserMode tells us whether the UC's container is in design mode or user mode/run-time. Unfortunately, this isn't supported in all containers. Word, IE may not report what you expect. Some containers may not implement that property.
VB always implements the Ambient.UserMode property. However, that can be misleading. If you have a UC on a form in design view, UC says Ambient.UserMode = False; great. But if you are creating a new UC and inside that new UC, you add an existing/different UC, that inner UC will report False also; great because this new UC is in design view. Here's the kicker. Now you add that new UC to the form. The inner UC now reports Ambient.UserMode as True, even though the form is in design view
Is this a problem for you? Maybe, only if you are actually testing that property. Let's say you use that property to determine whether or not to start subclassing, whether to start some image animation, maybe start API timers, whatever. You designed your control to not do that if the UC's container is in design view. Works well until your control is placed in another control that is placed on some other container. When your control (compiled or not) is a grandchild, container-wise, it will report Ambient.UserMode as True within VB. Other containers may report different things. The suggestion below allows your customer/user to override and properly set that property.
Let me use a real world example. I designed an image control. That control has a property to begin animation when the UC is in run-time. Well, someone wanted to add my control to a custom UC they were designing. They wanted the animation to occur when their new UC was in run-time. Animation started when their UC was placed on a form in design-time. Not what they wanted. Since my control had a property to start/stop animation, the simple solution was to default not to start animation and also for their UC to check its own Ambient.UserMode and depending on its value, start animation.
This worked well. But what if my UC began doing stuff when its Ambient.UserMode was True, but had no way for the containing control to tell it to stop or don't start at all? That containing control is out of luck.
The following is a workaround that if became a template for all your UCs, you can avoid this problem in any UC you create. Any paying customers for your UC can be educated to the new property and how to use it for their purposes.
Here is a sample of the 'template'. It exposes a Public UserMode property that allows the UC's container to dictate/tell the UC what UserMode it should use. This could be ideal for other non-VB6 containers that either report incorrectly or don't report at all the Ambient.UserMode.
Code:
Public Enum AmbientUserModeENUM
aumDefault = 0
aumDesignTime = 1
aumRuntime = 2
End Enum
Private m_UserMode As AmbientUserModeENUM
Public Property Let UserMode(newVal As AmbientUserModeENUM)
If Not (newVal < aumDefault Or newVal > aumRuntime) Then
m_UserMode = newVal
Call pvCheckUserMode
PropertyChanged "UserMode"
End If
End Property
Public Property Get UserMode() As AmbientUserModeENUM
UserMode = m_UserMode And &HFF
End Property
Private Sub pvCheckUserMode()
Select Case (m_UserMode And &HFF)
Case aumDefault
On Error Resume Next
m_UserMode = m_UserMode And &HFF
m_UserMode = m_UserMode Or Abs(UserControl.Ambient.UserMode) * &H100&
On Error GoTo 0
Case aumRuntime
m_UserMode = (m_UserMode And &HFF) Or &H100
Case Else
m_UserMode = m_UserMode And &HFF
End Select
If (m_UserMode And &H100) Then ' user mode is considered True
' do whatever is needed. Maybe set the UserMode property of any child usercontrols
Else ' user mode is considered False
' do whatever is needed. Maybe set the UserMode property of any child usercontrols
End If
End Sub
Private Sub UserControl_InitProperties()
' set any new control, initial properties
' apply any actions needed for UserMode
Call pvCheckUserMode
End Sub
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
' read all written properties
' apply any actions needed for UserMode
m_UserMode = PropBag.ReadProperty("AUM", aumDefault)
Call pvCheckUserMode
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
PropBag.WriteProperty "AUM", (m_UserMode And &HFF), aumDefault
End Sub
Though the call to pvCheckUserMode is placed in Init/ReadProperties, it could be moved to UserControl_Show if desired, depending on your needs.
Last edited by LaVolpe; Dec 6th, 2015 at 05:16 PM.
Reason: clarified couple statements
Insomnia is just a byproduct of, "It can't be done"
Perhaps I'm missing a subtle point. I have code I used for this but there isn't much to it: just an extra Property I call RunMode and I add it to each UserControl when I (a.) need to nest them and (b.) have sensitivity to whether the actual run state is "active."
Consider three UserControls that don't do much of anything, but all have the following code in them:
Code:
Option Explicit
Public Property Get RunMode() As Boolean
RunMode = CLng(Ambient.UserMode)
On Error Resume Next
RunMode = Extender.Parent.RunMode
End Property
Private Sub UserControl_InitProperties()
Print "IP: "; TypeName(Me); ", UserMode = "; Ambient.UserMode; ", RunMode = "; RunMode
End Sub
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
Print "RP: "; TypeName(Me); ", UserMode = "; Ambient.UserMode; ", RunMode = "; RunMode
End Sub
Then these UserControls (Inner, Middle, and Outer) are nested and I use all three on one Form:
This would appear to do everything required. What have I missed?
Last edited by dilettante; Sep 29th, 2015 at 09:15 AM.
You're not missing much. If the inner control has code that should not be run while any of the outer container(s) are in design view, then the workaround may be useful. I've seen this problem addressed in threads in the past, especially concerning subclassing and not wanting to subclass while not in run-time.
Edited: Since Ambient.UserMode isn't guaranteed to be supported outside of VB, having this option allows the non-VB6 container ability to set/override the property
Last edited by LaVolpe; Sep 29th, 2015 at 11:13 AM.
Insomnia is just a byproduct of, "It can't be done"
Note: The CLng() call above hurts nothing but was a vestige left over from hacking around to develop something that seems to work. So I dropped it here and it should be dropped from the code in my previous post as well.
So what about a minor rewrite?
Code:
Public Property Get RunMode() As Boolean
RunMode = True
On Error Resume Next
RunMode = Ambient.UserMode
RunMode = Extender.Parent.RunMode
End Property
Since other non-VB "containers" (Office UserForms, IE pages, and ?) are never in design-mode anyway because they can't compile a UserControl... that seems like it would do everything required. Plus the container doesn't require any awareness at all.
Ah, think I'm finally catching up. Your modification queries the paret object's new RunMode property vs the parent passing its UserMode down to the children? Not bad & simpler.
My only concern at this point is non VB6 containers. A compiled UC added to a Word document for example does not return Ambient.UserMode as True. Is that a problem? Not a matter of semantics: design time vs run time. If the UC is suppose do stuff when sited in Word and it's Ambient.UserMode is always false, then the control might need some sort of external property to allow it to activate as planned. Obviously the control doesn't know its in Word, IE, Excel, VB whatever.
I like the simplicity of your solution, however, I feel a complete workaround would have the UC being able to override its VB-given Ambient.UserMode value, top down?
Insomnia is just a byproduct of, "It can't be done"
For IE as a host it doesn't matter because there is no "design time" in IE. However this isn't true even for HTML pages when edited by an application using the TRiEdit (DHTMLEdit) control and certainly not for an Office application.
You could add a Property Let RunMode to allow container override but it means adding a module-level variable to retain its state... hmm gets ugly faster than I thought.
And how does the container know what value to set the property to and when? Are we back to another "chicken and egg" problem? Or is it reliable to just use some event within the container to know when "run state" is True and set the property True then?
I'm trying to solve the subclassing issue of constituent, that is *not* to subclass grand-children controls of a form in design-time.
This is strictly VB6 hack, here is what I've come up with
Code:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
Public Function IsCompileTime(Extender As Object) As Boolean
Dim oTopParent As Object
Dim oUserControl As UserControl
On Error GoTo QH
Set oTopParent = Extender.Parent
Set oUserControl = AsUserControl(oTopParent)
Do While Not oUserControl Is Nothing
If oUserControl.Parent Is Nothing Then
Exit Do
End If
Set oTopParent = oUserControl.Parent
Set oUserControl = AsUserControl(oTopParent)
Loop
Select Case TypeName(oTopParent)
Case "Form", "UserControl"
IsCompileTime = True
End Select
QH:
End Function
Public Function AsUserControl(oObj As Object) As UserControl
Dim pControl As UserControl
If TypeOf oObj Is Form Then
'--- do nothing
Else
Call CopyMemory(pControl, ObjPtr(oObj), 4)
Set AsUserControl = pControl
Call CopyMemory(pControl, 0&, 4)
End If
End Function
It turns at design-time top level container is reporting to be a generic `VB.Form` or `VB.UserControl` while at run-time it's `TypeName` is a concrete `frmMyForm` or `ctxMyControl` or whatever the ProgID of the container is.
Testing a form with a container control with constituent simple control, both with `Debug.Print` in `ReadProperties` show this
1) Obviously any implementation that attempts to get the usermode value from the top level host, the assumption is that the host implements the UserMode property and that it returns a 'correct' value. This is a single point of failure. If the child usercontrol needs to know that value, and that value cannot be trusted, then I think the only real answer is to pass that value to the usercontrol manually from the top level host, likely as a public property.
2) Just to be more robust, should your AsUserControl() method stop when a 'form' is identified as the control's container? Reason I ask is that a usercontrol can contain a form and a MDI form can contain a picturebox which may contain a usercontrol. Would your method still work for you in one of those cases?
Bottom line. I don't believe there is an solid solution for trusting the Ambient.UserMode value returned by VB unless you know the host (VB, Word, Access, etc) and can code around for any differences. In any other scenario, unless there is some trickle down property that can be overridden by the user, one can only take that value at face-value. The idea for this thread was to have UC-creators think about offering its container a method/property to override and either pass down the override value or allow child controls to query that override value.
In my usercontrols, I will offer such a public property and continue to use logic that kinda looks like the following, but also supply a public override property. If I wrote a uc capable of hosting, I'd offer a property for the child controls to retrieve the usermode used by, not necessarily retrieved by, my control.
Code:
On Error Resume Next
bUserMode = UserControl.Ambient.UserMode
If Err Then
bUserMode = True ' assume true if the container does not implement that property
Err.Clear
End IF
Last edited by LaVolpe; Dec 6th, 2015 at 01:28 PM.
Reason: typos
Insomnia is just a byproduct of, "It can't be done"
Yes, custom property seems bulletproof solution but constituent controls rely on parent control following convention. I don't think a `PictureBox` can be a parent, only a container of a UserControl (`Parent` vs `Container` property). This part: "a usercontrol can contain a form" I cannot understand what you mean. Otherwise you are right that MDI forms are definately not considered in current `IsCompileTime` implementation.
Well, I just implemented this quick fix on all of the usercontrols of a project of mine that's been bothering me with sporadic compile-time crashes. Very annoying as the build as automated and remotely triggered but the AV dialog is untrappable and cannot be switched off reliably. Will have to wait and see if this fixes anything.
Yes, the picturebox point was an example for considering MDI forms.
Regarding the usercontrol form example.
1. Project1 form hosts UC1
2. UC1 contains a form within the uc, the form contains UC2
3. UC2 is just another control, nothing special
From UC2, would your logic stop at the form in UC1 or continue on to the host form in project1? I didn't attempt to build a project to test the question & just floating it out there, seeing if it sticks
Edited: And another scenario: a uc can exist in a property page and possibly other VB 'parent' objects.
Last edited by LaVolpe; Dec 6th, 2015 at 02:37 PM.
Insomnia is just a byproduct of, "It can't be done"
No. If you create a new UC project and choose to add a form, you can, just like adding a module, class, etc. To test that, don't use a group project, start with a new UC project, do what's needed, then add another new UC project as a group for testing. You can add that 2nd UC to the first UC's form.
Insomnia is just a byproduct of, "It can't be done"
Oh, you mean the ActiveX Control (OCX) project contains a form. This is not related to the way `IsCompileTime` function works.
`IsCompileTime` just traverses control's `Parent`s and checks if the top level parent is a "real" one in client executable vs a "fake" one provided by the IDE in design/compile-time. There is no way to have a control1->form->control2 chain as the form has to be top level if there is a form in the chain at all.
Notice that control1->control2 is a possible chain when control2 is being compiled and control1 settings are being transferred by the compiler from source .ctl/.ctx files to final .exe/.dll/.ocx through `Read/WriteProperties` etc.
That was my simple 2-step plan: find top level parent, check if fake.
The major hack is `AsUserControl` function as `VB.UserControl` interface is not "castable" i.e. a reference to `ctxMyControl` cannot be `QI` for `VB.UserControl` interface directly although every VB6-created control implements it. Only internal functions of `ctxMyControl` can call `VB.UserControl` functions on self with or w/o `UserControl` prefix. Probably every VB6 custom control's (default) interface extends `VB.UserControl` interface.
Just "another" way to determine if the UserControl is in design-mode or run-mode is to check if the root window of the UserControl (which is placed on a VB.Form, VB.PropertyPage or another VB.UserControl) is "wndclass_desked_gsk".
Code:
Dim hWnd As Long
Const GA_ROOT As Long = 2
hWnd = GetAncestor(UserControl.hWnd, GA_ROOT)
If hWnd <> 0 Then
Dim Buffer As String, RetVal As Long
Buffer = String(256, vbNullChar)
RetVal = GetClassName(hWnd, StrPtr(Buffer), Len(Buffer))
If RetVal <> 0 Then Value = CBool(Left$(Buffer, RetVal) = "wndclass_desked_gsk")
End If
Downside in this solution is that the result is not meaningful while on UserControl_ReadProperties. The window must be visible, e.g. after UserControl_Show was fired.
Hi.
This post is very interesting. I put the code of La Volpe into my UC to study the solution.
I do not have problems with UC within other UC. I have a UC in a form.
If I launch the project execution from the IDE correctly the Usermode property returns zero.
But is it not possible to distinguish between exe runtime and IDE runtime?
Thanks
As this topic raised up again I just want to provide the way I finally choose for this. (for completeness about the topic)
It's actually based on wqweto's hack, but optimized so it also works when VB6 OCX is used within VBA environment.
Helper function:
Code:
Public Function GetTopUserControl(ByVal UserControl As Object) As VB.UserControl
If UserControl Is Nothing Then Exit Function
Dim TopUserControl As VB.UserControl, TempUserControl As VB.UserControl
CopyMemory TempUserControl, ObjPtr(UserControl), 4
Set TopUserControl = TempUserControl
CopyMemory TempUserControl, 0&, 4
With TopUserControl
If .ParentControls.Count > 0 Then
Dim OldParentControlsType As VBRUN.ParentControlsType
OldParentControlsType = .ParentControls.ParentControlsType
.ParentControls.ParentControlsType = vbExtender
If TypeOf .ParentControls(0) Is VB.VBControlExtender Then
.ParentControls.ParentControlsType = vbNoExtender
CopyMemory TempUserControl, ObjPtr(.ParentControls(0)), 4
Set TopUserControl = TempUserControl
CopyMemory TempUserControl, 0&, 4
Dim TempParentControlsType As VBRUN.ParentControlsType
Do
With TopUserControl
If .ParentControls.Count = 0 Then Exit Do
TempParentControlsType = .ParentControls.ParentControlsType
.ParentControls.ParentControlsType = vbExtender
If TypeOf .ParentControls(0) Is VB.VBControlExtender Then
.ParentControls.ParentControlsType = vbNoExtender
CopyMemory TempUserControl, ObjPtr(.ParentControls(0)), 4
Set TopUserControl = TempUserControl
CopyMemory TempUserControl, 0&, 4
.ParentControls.ParentControlsType = TempParentControlsType
Else
.ParentControls.ParentControlsType = TempParentControlsType
Exit Do
End If
End With
Loop
End If
.ParentControls.ParentControlsType = OldParentControlsType
End If
End With
Set GetTopUserControl = TopUserControl
End Function
As this topic raised up again I just want to provide the way I finally choose for this. (for completeness about the topic)
It's actually based on wqweto's hack, but optimized so it also works when VB6 OCX is used within VBA environment.
Helper function:
Code:
Public Function GetTopUserControl(ByVal UserControl As Object) As VB.UserControl
If UserControl Is Nothing Then Exit Function
Dim TopUserControl As VB.UserControl, TempUserControl As VB.UserControl
CopyMemory TempUserControl, ObjPtr(UserControl), 4
Set TopUserControl = TempUserControl
CopyMemory TempUserControl, 0&, 4
With TopUserControl
If .ParentControls.Count > 0 Then
Dim OldParentControlsType As VBRUN.ParentControlsType
OldParentControlsType = .ParentControls.ParentControlsType
.ParentControls.ParentControlsType = vbExtender
If TypeOf .ParentControls(0) Is VB.VBControlExtender Then
.ParentControls.ParentControlsType = vbNoExtender
CopyMemory TempUserControl, ObjPtr(.ParentControls(0)), 4
Set TopUserControl = TempUserControl
CopyMemory TempUserControl, 0&, 4
Dim TempParentControlsType As VBRUN.ParentControlsType
Do
With TopUserControl
If .ParentControls.Count = 0 Then Exit Do
TempParentControlsType = .ParentControls.ParentControlsType
.ParentControls.ParentControlsType = vbExtender
If TypeOf .ParentControls(0) Is VB.VBControlExtender Then
.ParentControls.ParentControlsType = vbNoExtender
CopyMemory TempUserControl, ObjPtr(.ParentControls(0)), 4
Set TopUserControl = TempUserControl
CopyMemory TempUserControl, 0&, 4
.ParentControls.ParentControlsType = TempParentControlsType
Else
.ParentControls.ParentControlsType = TempParentControlsType
Exit Do
End If
End With
Loop
End If
.ParentControls.ParentControlsType = OldParentControlsType
End If
End With
Set GetTopUserControl = TopUserControl
End Function
Usage inside a UserControl:
Code:
GetTopUserControl(Me).Ambient.UserMode
I was testing a component, and I don't know why, but the control was in ChildForm (maximized), and when I closed it, VBIde closed. With this function I solved my problem ...