[VB6] UserControl Ambient.UserMode workaround-VBForums
Results 1 to 14 of 14

Thread: [VB6] UserControl Ambient.UserMode workaround

  1. #1

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    15,315

    [VB6] UserControl Ambient.UserMode workaround

    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"

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {GDI+ Classes/Samples} {Unicode Open/Save Dialog} {Icon Organizer/Extractor}
    {VB and DPI Tutorial} {XP/Vista Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  2. #2
    PowerPoster
    Join Date
    Feb 2006
    Posts
    14,123

    Re: [VB6] UserControl Ambient.UserMode workaround

    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:

    Name:  sshot.png
Views: 223
Size:  8.6 KB

    This would appear to do everything required. What have I missed?
    Attached Files Attached Files
    Last edited by dilettante; Sep 29th, 2015 at 10:15 AM.

  3. #3

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    15,315

    Re: [VB6] UserControl Ambient.UserMode workaround

    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 12:13 PM.
    Insomnia is just a byproduct of, "It can't be done"

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {GDI+ Classes/Samples} {Unicode Open/Save Dialog} {Icon Organizer/Extractor}
    {VB and DPI Tutorial} {XP/Vista Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  4. #4
    PowerPoster
    Join Date
    Feb 2006
    Posts
    14,123

    Re: [VB6] UserControl Ambient.UserMode workaround

    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.

  5. #5

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    15,315

    Re: [VB6] UserControl Ambient.UserMode workaround

    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"

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {GDI+ Classes/Samples} {Unicode Open/Save Dialog} {Icon Organizer/Extractor}
    {VB and DPI Tutorial} {XP/Vista Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  6. #6
    PowerPoster
    Join Date
    Feb 2006
    Posts
    14,123

    Re: [VB6] UserControl Ambient.UserMode workaround

    Ok, I'm catching on.

    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?

  7. #7
    Addicted Member
    Join Date
    May 2011
    Posts
    155

    Re: [VB6] UserControl Ambient.UserMode workaround

    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
    Code:
    ' On opening Form1 (design-time)
    SimpleControl: IsCompileTime=True, UserMode=True
    ControlContainer: IsCompileTime=True, UserMode=False
    
    ' Compiling Project1.exe
    SimpleControl: IsCompileTime=True, UserMode=True
    ControlContainer: IsCompileTime=True, UserMode=False
    SimpleControl: IsCompileTime=False, UserMode=False
    
    ' On running project and showing Form1 (run-time)
    SimpleControl: IsCompileTime=False, UserMode=True
    ControlContainer: IsCompileTime=False, UserMode=True
    Basicly I'm using IsCompileTime with Ambient.UserMode in usercontrol's `ReadProperties` event like this:
    Code:
            If Ambient.UserMode And Not IsCompileTime(Extender) Then
                SubClass m_uSubclassCtl, m_hWndCtl, ObjPtr(Me), AddressOf RedirectDTControlWndProc
            End If
    cheers,
    </wqw>

  8. #8

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    15,315

    Re: [VB6] UserControl Ambient.UserMode workaround

    I also like your implementation. Two things...

    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"

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {GDI+ Classes/Samples} {Unicode Open/Save Dialog} {Icon Organizer/Extractor}
    {VB and DPI Tutorial} {XP/Vista Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  9. #9
    Addicted Member
    Join Date
    May 2011
    Posts
    155

    Re: [VB6] UserControl Ambient.UserMode workaround

    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.

    cheers,
    </wqw>

  10. #10

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    15,315

    Re: [VB6] UserControl Ambient.UserMode workaround

    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"

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {GDI+ Classes/Samples} {Unicode Open/Save Dialog} {Icon Organizer/Extractor}
    {VB and DPI Tutorial} {XP/Vista Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  11. #11
    Addicted Member
    Join Date
    May 2011
    Posts
    155

    Re: [VB6] UserControl Ambient.UserMode workaround

    Yes, Property Page is a missing scenario (along w/ MDI Child forms).

    2. UC1 contains a form within the uc, the form contains UC2
    I'm not sure I follow you on this one. How do you *contain* a form within a custom UserControl? `SetParent` API?

    cheers,
    </wqw>

  12. #12

    Thread Starter
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    15,315

    Re: [VB6] UserControl Ambient.UserMode workaround

    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"

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {GDI+ Classes/Samples} {Unicode Open/Save Dialog} {Icon Organizer/Extractor}
    {VB and DPI Tutorial} {XP/Vista Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  13. #13
    Addicted Member
    Join Date
    May 2011
    Posts
    155

    Re: [VB6] UserControl Ambient.UserMode workaround

    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.

    cheers,
    </wqw>

  14. #14
    Hyperactive Member
    Join Date
    Jun 2012
    Posts
    378

    Re: [VB6] UserControl Ambient.UserMode workaround

    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
    Name:  Image1.jpg
Views: 87
Size:  42.2 KB

    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width

Survey posted by VBForums.