Subclassing-VBForums
Page 1 of 2 12 LastLast
Results 1 to 40 of 43

Thread: Subclassing

  1. #1

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Subclassing

    I suspect this has been answered elsewhere, but, if folks don't mind, I'd like to put it in my own words, and then have people critique what I've said to make sure I've got this stuff clear. However, this first section is basically groundwork. I'd primarily like a critique of the numbered list.

    I do a fair amount of subclassing in my primary project, and I'm considering reworking it. I don't do this lightly because things are basically working the way they are, and I don't often start ripping into code that works. However, I am often adding new things, and those may sometimes "break" old things.

    I have a fair understanding of what Windows is doing when we subclass. Basically, it's just adding code from some static (BAS) procedure I wrote to the code-chain that's called when some window (with a hWnd) is sent a message. And, when I use SetWindowLong to do this, I just specify the hWnd I want to hook, making sure to keep track of the previous procedure's address in the code-chain so I can unhook when I'm done. And yes, if we're smart, we can monitor for WM_DESTROY in our hooked code, and unhook when we see that.

    And, problems arise when doing this in the IDE because we have a "Stop" button which can circumvent our ability to unhook when our hWnd is no longer being used (i.e., destroyed), which causes an IDE crash. And this happens regardless of whether or not we're monitoring for WM_DESTROY.

    (Also, yes, there are assembly insertion routines that possibly further protect the IDE, but let's set those aside for this thread.)

    Okay, my actual question is about the differences between using SetWindowLong versus adopting the comctl32 #410, #412, #413 approach. Here are my understanding of the differences (and just general understandings).

    1. When we use the comctl32 approach, we no longer have to keep track of the prior code's address in the code-chain. Somehow, comctl32 does that for us.
    2. We still have to unhook when we're done.
    3. We still have to keep track of the hWnd values we've hooked, and the address of our own code we've hooked them to.
    4. We no longer need to worry about an unhook order when we simultaneously hook a hWnd multiple times. This is actually the major advantage of the comctl32 approach over the SetWindowLong approach.
    5. We can still crash the IDE if we don't unhook.
    6. Also, when using comctl32, we must supply an address to an instantiated object, but it doesn't necessarily seem to be required that it's the same object being subclasses. This is one I'm particularly unclear about. Basically, it's the third argument of #410 and #412.


    Because I find myself writing encapsulated routines that hook my forms for various reasons, and I might use multiple of these on a single form, I'm considering these rewrites. And, I'd like to make sure my thinking is clear before undertaking this.

    For reference, I've also posted the comctl32 API declarations:

    Code:
    
    Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    
    Thanks,
    Elroy

    EDIT1: Also, I've got no clue what "Optional ByVal dwRefData As Long" is in the #410 call.
    Last edited by Elroy; Apr 18th, 2017 at 11:14 AM.

  2. #2
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    The uIDSubclass is a key of sorts. It links both SetWindowSubclass & RemoveWindowSubclass

    The dwRefData is like a user lParam in other APIs. It's anything you want to provide or nothing at all, hence optional and will be zero if not provided.

    when using comctl32, we must supply an address to an instantiated object
    Not sure what you mean by that statement. Most on this forum posted examples where the dwRefData parameter was assigned a class object and then modified that API parameter from ByVal Long as ByRef Any. That final parameter is for "your use" and any legitimate value (or object) can be assigned to it.

    Some advantages: 1) easy to use, 2) more or less IDE friendly, 3) and can debug while subclassing. It isn't crash-proof but a lot better than the old-fashioned method or rigging up your own external DLL/thunks.
    Last edited by LaVolpe; Apr 18th, 2017 at 11:32 AM.
    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}

  3. #3
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,296

    Re: Subclassing

    yeah just re-iterating what LaVolpe said...

    uIdSubclass can be anything unique. It's just an Id. The combination of this Id plus callback must be unique per subclass.

    Using an object just makes forwarding from a stub easier. A technique first published by Karl E. Peterson - http://vb.mvps.org/samples/HookXP/

    dwRefData can also be used for anything, it's passed directly to the SubclassProc.

    Krool demonstrates it's usefulness in his CCRP when he subclasses several windows to the same SubclassProc.

    edit: also the ordinals are only needed for win2000. @Lavolpe: Who still uses Win2K?

    actually i'm just jealous it's long disappeared from my VMs.
    Last edited by DEXWERX; Apr 18th, 2017 at 11:53 AM.

  4. #4
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    692

    Re: Subclassing

    Elroy: there's a link at the bottom of the Karl E Petersen page that DEXWERX shared. You don't want to miss it:

    http://vb.mvps.org/articles/vsm20090716.pdf

    In that PDF, Karl describes in more detail how his sample works. IMO his approach is still the most elegant way to subclass in VB, although it isn't (and could never be) 100% IDE-safe.
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  5. #5

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    @Tanner & Dex: Yes, thanks for the resources.

    @LaVolpe (and others): Ok, the dwRefData is cool. I've tested and it's passed to the hooked procedure every time it's called. So, apparently, comctl32 is stuffing it somewhere upon the initial hooking, and then showing it back to us on every message to the hWnd hooked. Yes, I can see the usefulness of that.

    Now, regarding that uIdSubclass argument. It's good to know that it's a key. And LaVolpe, I'm taking you to heart that the combination of uIdSubclass and hWnd make a unique subclass hook. I was thinking through how to best assure that that combination was unique. I'm definitely imagining (and have) situations where a single form may be subclassed more than once (for different purposes). Each of these "purposes" would surely have different hook procedures, so I was thinking that the hook procedure address would serve as a good key.

    For instance, something like this (in BAS module):
    (Be sure to notice the three red/bold AddressOfSubclassProc where it's used for uIdSubclass.)

    Code:
    
    Option Explicit
    '
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function GetWindowSubclass Lib "comctl32" Alias "#411" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, pdwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    '
    Private m_bSubclassed As Boolean
    Private Const ERROR_SUCCESS = 0&
    '
    
    Public Sub SubclassForm(frm As Form, Optional dwRefData As Long)
        m_bSubclassed = SetWindowSubclass(frm.hWnd, AddressOfSubclassProc, AddressOfSubclassProc, dwRefData)
    End Sub
    
    Public Sub UnSubclassForm(frm As Form)
        m_bSubclassed = RemoveWindowSubclass(frm.hWnd, AddressOfSubclassProc, AddressOfSubclassProc) = ERROR_SUCCESS
    End Sub
    
    Public Function SubclassProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
        Const WM_DESTROY = &H2&
        '
        If uMsg = WM_DESTROY Then
            m_bSubclassed = RemoveWindowSubclass(hWnd, AddressOfSubclassProc, AddressOfSubclassProc) = ERROR_SUCCESS
        End If
        '
        Debug.Print dwRefData
        '
        SubclassProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    
    Private Function AddressOfSubclassProc()
        AddressOfSubclassProc = ProcAddress(AddressOf SubclassProc)
    End Function
    
    Private Function ProcAddress(TheProcWithAddressOf As Long)
        ProcAddress = TheProcWithAddressOf
    End Function
    
    Sure, I'd have to do more work to make it handle multiple forms, but I want to make sure I've got the concepts down first.

    And then something like this in the form:

    Code:
    
    Option Explicit
    '
    
    Private Sub Form_Load()
        SubclassForm Me, 1234
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
        UnSubclassForm Me ' But not really needed because WM_DESTROY is monitored.
    End Sub
    
    
    And, once I get comfortable with it, I'll probably abandon placing the UnSubclassForm call in the Form_Unload event.

    Is all of my thinking clear?

  6. #6
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,296

    Re: Subclassing

    no.... uIdSubclass + Hook procedure are the key...

    you can hook many windows or the same window many times with the same hook procedure, as long as uIdSubclass is different.
    Last edited by DEXWERX; Apr 18th, 2017 at 02:34 PM.

  7. #7

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Ok, one more question. Do I really need to keep track of m_bSubclassed in the above BAS module?

    Because, if I don't need to save that, then making this work for multiple forms becomes very easy. So, I guess, the question is, is there any problem if RemoveWindowSubclass is called, and it's not subclassed? For instance, is there anything wrong with altering the BAS module to look like this:

    Code:
    
    Option Explicit
    '
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function GetWindowSubclass Lib "comctl32" Alias "#411" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, pdwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    '
    
    Public Function GetRefData(frm As Form) As Long
        Call GetWindowSubclass(frm.hWnd, AddressOfSubclassProc, AddressOfSubclassProc, GetRefData)
    End Function
    
    Public Sub SubclassForm(frm As Form, Optional dwRefData As Long)
        Call SetWindowSubclass(frm.hWnd, AddressOfSubclassProc, AddressOfSubclassProc, dwRefData)
    End Sub
    
    Public Sub UnSubclassForm(frm As Form)
        ' Only needed if we specifically want to un-subclass before we're closing the form.
        Call RemoveWindowSubclass(frm.hWnd, AddressOfSubclassProc, AddressOfSubclassProc)
    End Sub
    
    Public Function SubclassProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
        Const WM_DESTROY = &H2&
        '
        If uMsg = WM_DESTROY Then
            Call RemoveWindowSubclass(hWnd, AddressOfSubclassProc, AddressOfSubclassProc)
        End If
        '
        Debug.Print dwRefData
        '
        SubclassProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    
    Private Function AddressOfSubclassProc()
        AddressOfSubclassProc = ProcAddress(AddressOf SubclassProc)
    End Function
    
    Private Function ProcAddress(TheProcWithAddressOf As Long)
        ProcAddress = TheProcWithAddressOf
    End Function
    
    Also noticed that I wrote a GetRefData function which seems to work just fine. That may also come in useful somewhere down the line.

  8. #8

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    @DEXWERX: Ok, if what you say is correct, then I'm just using the hook procedure's address TWICE as the key, which isn't good. Therefore, I need to change it to use the hWnd as uIdSubclass. That would look like this:

    Code:
    
    Option Explicit
    '
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function GetWindowSubclass Lib "comctl32" Alias "#411" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, pdwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    '
    
    Public Function GetRefData(frm As Form) As Long
        Call GetWindowSubclass(frm.hWnd, AddressOfSubclassProc, frm.hWnd, GetRefData)
    End Function
    
    Public Sub SubclassForm(frm As Form, Optional dwRefData As Long)
        Call SetWindowSubclass(frm.hWnd, AddressOfSubclassProc, frm.hWnd, dwRefData)
    End Sub
    
    Public Sub UnSubclassForm(frm As Form)
        ' Only needed if we specifically want to un-subclass before we're closing the form.
        Call RemoveWindowSubclass(frm.hWnd, AddressOfSubclassProc, frm.hWnd)
    End Sub
    
    Public Function SubclassProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
        Const WM_DESTROY = &H2&
        '
        If uMsg = WM_DESTROY Then
            Call RemoveWindowSubclass(hWnd, AddressOfSubclassProc, hWnd)
        End If
        '
        Debug.Print dwRefData
        '
        SubclassProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    
    Private Function AddressOfSubclassProc()
        AddressOfSubclassProc = ProcAddress(AddressOf SubclassProc)
    End Function
    
    Private Function ProcAddress(TheProcWithAddressOf As Long)
        ProcAddress = TheProcWithAddressOf
    End Function
    
    
    Correct?

    EDIT1: And, because we don't have to keep track of anything, it's already all set up for using on as many forms as I like.

  9. #9
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,296

    Re: Subclassing

    You're artificially limiting yourself compared to Karl's single static forwarding SubclassProc. Using his method, you can have many many WinProc's located in seperate classes or forms. OR use the same class to subclass many different windows.

    It's not just elegant, it's genius. This is the flexibility that was intended by the API, and is a typical pattern used by Microsoft frameworks like MFC or thunks in ATL.

  10. #10

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Quote Originally Posted by DEXWERX View Post
    Using his method, you can have many many WinProc's located in seperate classes or forms. OR use the same class to subclass many different windows.
    I'm not clear on why the above doesn't do that. Just as an example, let's say I modify the above to prohibit the user from changing the width of a sizable form.

    And then I have another module that prohibits the user from changing the height of a sizable form. But this second module has a different hook procedure (and therefore a different hook procedure address).

    Ok, I have a Form1, Form2, and Form3 (all sizable). I decide to restrict the width of Form1, restrict the height of Form2, and both for Form3.

    Let's further say that the hWnd of Form1 = 1, hWnd of Form2 = 2, hWnd of Form3 = 3. And let's say the hook procedure's address for width = 8, and the hook procedure's address for restricting height = 9.

    Our Form1 hook's key will be 1 & 8.
    Our Form2 hook's key will be 2 & 9.
    Our Form3 will have two hooks and two keys: 3 & 8 and also 3 & 9.

    No duplicates. Doesn't this work? Or is my thinking flawed?

  11. #11
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,296

    Re: Subclassing

    Quote Originally Posted by Elroy View Post
    I'm not clear on why the above doesn't do that. Just as an example, let's say I modify the above to prohibit the user from changing the width of a sizable form.
    ...
    No duplicates. Doesn't this work? Or is my thinking flawed?
    no you've got it right. your method works fine with different static procedures.

    edit: it's just ugly... kind of like using 2 extra functions instead of just passing AddressOf ModuleName.YourProc directly to the API.

    Also like you said - I can't think of any point of using the same SubclassProc to subclass the same window, but using Karl's method you can.
    Last edited by DEXWERX; Apr 18th, 2017 at 03:01 PM.

  12. #12

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Quote Originally Posted by DEXWERX View Post
    you can hook ... the same window many times with the same hook procedure
    It seems that that's the only thing I'm losing. However, I can't imagine why I'd need to do that, but who knows. I'll remember that.

    Is it possible that hWnd + Hook address + uIdSubclass (all three combined) is the actual key? If that's the case, I might just always stuff a ZERO into uIdSubclass.

    EDIT1: Ok, I changed all the above (post #8), the red marked uIdSubclass arguments to ZERO, and then subclassed two forms with the procedure, and everything seemed to work fine. I even checked the SetWindowSubclass and RemoveWindowSubclass return values, and everything looked good. I'd like some other opinions, but I think all three values make the key.
    Last edited by Elroy; Apr 18th, 2017 at 03:14 PM.

  13. #13
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    Elroy, here's something I generally employ... Not suggesting it be used, but just an example. By using an interface and passing that in the various parameters of the bas module methods, all we need to do is IMPLEMENT the stub to receive the subclassing within whatever we want: form, uc, class, etc.

    1. An interface for receiving subclassed messages. This is a class called: IProxyStub
    Code:
    Option Explicit
    
    Public Function WndProc(ByVal hWnd As Long, ByVal uMsg As Long, _
                            ByVal wParam As Long, ByVal lParam As Long, _
                            ByVal uIdSubclass As Long) As Long
    End Function
    2. The bas module
    Code:
    Public Declare Function DefSubclassProc Lib "comctl32.dll" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    Private Const WM_DESTROY As Long = &H2
    
    Public Sub SubclassHwnd(hWnd As Long, IProxy As IProxyStub)
        SetWindowSubclass hWnd, AddressOf pvWndProc, ObjPtr(IProxy), ObjPtr(IProxy)
    End Sub
    
    Public Sub UnsubclassHwnd(hWnd As Long, IProxy As IProxyStub)
        RemoveWindowSubclass hWnd, AddressOf pvWndProc, ObjPtr(IProxy)
    End Sub
    
    Private Function pvWndProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, _
                                ByVal uIdSubclass As Long, ByVal IProxy As IProxyStub) As Long
    
        If uMsg = WM_DESTROY Then
            UnsubclassHwnd hWnd, IProxy
            pvWndProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
        Else
            pvWndProc = IProxy.WndProc(hWnd, uMsg, wParam, lParam, uIdSubclass)
        End If
        
    End Function
    3. Sample receiving in a form
    Code:
    Option Explicit
    
    Implements IProxyStub
    
    Private Sub Command1_Click()
        SubclassHwnd Me.hWnd, Me
    End Sub
    
    Private Function IProxyStub_WndProc(ByVal hWnd As Long, ByVal uMsg As Long, _
                            ByVal wParam As Long, ByVal lParam As Long, _
                            ByVal uIdSubclass As Long) As Long
                            
        IProxyStub_WndProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
        Debug.Print uMsg, wParam, lParam
        
    End Function
    Tweak to taste
    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}

  14. #14
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,296

    Re: Subclassing

    Quote Originally Posted by Elroy View Post
    It seems that that's the only thing I'm losing. However, I can't imagine why I'd need to do that, but who knows. I'll remember that.

    Is it possible that hWnd + Hook address + uIdSubclass (all three combined) is the actual key? If that's the case, I might just always stuff a ZERO into uIdSubclass.
    falfalone ran into some wird bugs when using Zero for uIdSubclass...

    http://www.vbforums.com/showthread.p...ubclass-method

    but from the looks of it he doesn't unsubclasses the window? which is really the only requirement... to using the API.
    so who knows if it's even related.
    Last edited by DEXWERX; Apr 18th, 2017 at 03:26 PM.

  15. #15

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Quote Originally Posted by DEXWERX View Post
    it's just ugly... kind of like using 2 extra functions instead of just passing AddressOf ModuleName.YourProc directly to the API.
    But you've got to do that anyway if you're going to monitor for WM_DESTROY, and automatically unhook. Right?

    EDIT1:

    Quote Originally Posted by DEXWERX View Post
    falfalone ran into an issue always using Zero for uIdSubclass...
    That's certainly worth knowing. I'll stick with throwing the hWnd in there.
    I actually found the post where he talked about the problem. I'm not sure I understand the problem he was having, but I've certainly got hWnd, so no foul in stuffing it into uIdSubclass.
    Last edited by Elroy; Apr 18th, 2017 at 03:25 PM.

  16. #16
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,296

    Re: Subclassing

    Quote Originally Posted by Elroy View Post
    But you've got to do that anyway if you're going to monitor for WM_DESTROY, and automatically unhook. Right?

    Code:
    Call RemoveWindowSubclass(hWnd, AddressOf ModuleName.SubclassProc, hWnd)
    also I would make SubclassProc private.

  17. #17

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    @LaVolpe (post #13): Yeah, that's something similar to something I already do in one spot. And that's certainly a nice way to handle the monitoring of WM_DESTROY without having to think about it each time we write a hook procedure.

    I was actually just finishing up another idea when I took the time to think through your post. Also, I've learned something else. Apparently we can make multiple calls to SetWindowSubclass, using exact same hWnd, pfnSubclass, and uIdSubclass, and there's no harm done (even with no intervening calls to RemoveWindowSubclass). We could use this to change the value of dwRefData if we had that need.

    Here's my idea that I threw together. It doesn't use "Implements" and it does make twice as many hooks as we absolutely need, but it vastly simplifies everything from the coder's perspective. We just make our hook, and forget about it. Also, we can hook as many windows as we like to as many static procedures as we like. The only thing I've prohibited is hooking the same window to the same procedure multiple times. You can do it, but only the last hook will hold, as I'm not allowing you to specify uIdSubclass.

    Here it is:

    Code:
    
    Option Explicit
    '
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function GetWindowSubclass Lib "comctl32" Alias "#411" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, pdwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    '
    ' This must also be used in any other hook (as passed in with TheHookProcAddress).
    Public Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    '
    
    Public Function GetSubclassRefData(hWnd As Long, TheHookProcAddress As Long) As Long
        Call GetWindowSubclass(hWnd, TheHookProcAddress, hWnd, GetSubclassRefData)
    End Function
    
    Public Sub SubclassSomeWindow(hWnd As Long, TheHookProcAddress As Long, Optional dwRefData As Long)
        Call SetWindowSubclass(hWnd, AddressOfSubclassProcForUnhook, hWnd, TheHookProcAddress)
        Call SetWindowSubclass(hWnd, TheHookProcAddress, hWnd, dwRefData)
    End Sub
    
    Public Sub UnSubclassSomeWindow(hWnd As Long, TheHookProcAddress As Long)
        ' Only needed if we specifically want to un-subclass before we're closing the form (or control).
        Call RemoveWindowSubclass(hWnd, AddressOfSubclassProcForUnhook, hWnd)
        Call RemoveWindowSubclass(hWnd, TheHookProcAddress, hWnd)
    End Sub
    
    Private Function SubclassProcForUnhook(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
        ' Use the above argument list for any other hooks developed for other purposes.
        Const WM_DESTROY = &H2&
        '
        If uMsg = WM_DESTROY Then
            Call RemoveWindowSubclass(hWnd, AddressOfSubclassProcForUnhook, hWnd)
            Call RemoveWindowSubclass(hWnd, dwRefData, hWnd)
        End If
        '
        SubclassProcForUnhook = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    
    Private Function AddressOfSubclassProcForUnhook()
        AddressOfSubclassProcForUnhook = ProcAddressForUnhook(AddressOf SubclassProcForUnhook)
    End Function
    
    Private Function ProcAddressForUnhook(TheProcWithAddressOf As Long)
        ProcAddressForUnhook = TheProcWithAddressOf
    End Function
    
    And to test, I put together a Sub Main. The same module also had a couple of hook procedures I used for testing:
    Code:
    
    Option Explicit
    
    Public Sub main()
        Form2.Show
        Form3.Show
    End Sub
    
    Public Function Hook1(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
    
        Debug.Print hWnd, dwRefData
    
    
        Hook1 = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    
    Public Function Hook2(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
    
        Debug.Print hWnd, dwRefData
    
    
        Hook2 = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    
    
    And then, in Form1:

    Code:
    
    Option Explicit
    '
    
    Private Sub Form_Click()
        MsgBox GetSubclassRefData(Me.hWnd, AddressOf Hook1)
        MsgBox GetSubclassRefData(Me.hWnd, AddressOf Hook2)
    End Sub
    
    Private Sub Form_Load()
        SubclassSomeWindow Me.hWnd, AddressOf Hook1, 123123
        SubclassSomeWindow Me.hWnd, AddressOf Hook2, 456456
        SubclassSomeWindow Me.hWnd, AddressOf Hook2, 9999999
    End Sub
    
    
    
    And Form2:

    Code:
    
    Option Explicit
    
    Private Sub Form_Click()
        MsgBox GetSubclassRefData(Me.hWnd, AddressOf Hook1)
        MsgBox GetSubclassRefData(Me.hWnd, AddressOf Hook2)
    End Sub
    
    Private Sub Form_Load()
        SubclassSomeWindow Me.hWnd, AddressOf Hook1, 123
        SubclassSomeWindow Me.hWnd, AddressOf Hook2, 456
    End Sub
    
    
    To my eyes, it just seems to make subclassing completely trivial.

    EDIT1: I've stared at your (LaVolpe's) approach some more. It seems the primary difference is that I'm jumping straight to some static procedure, whereas you're jumping into whatever form (or other maybe custom user control) that's doing the subclassing.
    Last edited by Elroy; Apr 18th, 2017 at 04:34 PM.

  18. #18
    Frenzied Member
    Join Date
    Jun 2015
    Posts
    1,296

    Re: Subclassing

    Here's what I do, it's functionally the same as Karl / Lavolpe

    ISubclass.cls (I actually use a typelib for this, instead of using a Dual/ IDispatch Interface)
    Code:
    Public Function SubclassProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal dwRefData As Long) As Long
    End Function
    MSubclass.bas
    Code:
    Option Explicit
    
    Private Declare Sub AddRef Lib "msvbvm60" Alias "__vbaObjAddref" (ByVal Obj As IUnknown)
    Private Declare Function Release Lib "msvbvm60" Alias "VarPtr" (ByVal Obj As Any) As IUnknown
    Private Declare Function SetWindowSubclass Lib "comctl32" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As ISubclass, ByVal dwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As ISubclass) As Long
    Public Declare Function DefSubclassProc Lib "comctl32" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    
    Private Function SubclassProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As ISubclass, ByVal dwRefData As Long) As Long
        Const WM_NCDESTROY As Long = &H82&
        If uMsg = WM_NCDESTROY Then
            RemoveSubclass hWnd, uIdSubclass
            SubclassProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
        Else
            SubclassProc = uIdSubclass.SubclassProc(hWnd, uMsg, wParam, lParam, dwRefData)
        End If
    End Function
    
    Public Function SetSubclass(ByVal hWnd As Long, ByRef This As ISubclass, Optional ByVal dwRefData As Long) As Long
        AddRef This
        SetSubclass = SetWindowSubclass(hWnd, AddressOf MSubclass.SubclassProc, This, dwRefData)
    End Function
    
    Public Function RemoveSubclass(ByVal hWnd As Long, ByRef This As ISubclass) As Long
        RemoveSubclass = RemoveWindowSubclass(hWnd, AddressOf MSubclass.SubclassProc, This)
        Release This
    End Function
    CHook1.cls
    Code:
    Implements ISubclass
    Private Function ISubclass_SubclassProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal dwRefData As Long) As Long
        Debug.Print "CHook1", hWnd, dwRefData
        ISubclass_SubclassProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    Form1
    Code:
    Option Explicit
    Implements ISubclass
    
    Private Sub Form_Load()
        SetSubclass hWnd, Me
        SetSubclass hWnd, New CHook1
    End Sub
    
    Private Function ISubclass_SubclassProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal dwRefData As Long) As Long
        Debug.Print "Form1", hWnd, dwRefData
        ISubclass_SubclassProc = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    Last edited by DEXWERX; Apr 18th, 2017 at 05:07 PM. Reason: added Reference Counting tricks

  19. #19
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    I've stared at your (LaVolpe's) approach some more. It seems the primary difference is that I'm jumping straight to some static procedure, whereas you're jumping into whatever form (or other maybe custom user control) that's doing the subclassing.
    Circumstances should dictate which approach you use. If I'm only subclassing to say, restrict window size, then I will use a subclass procedure in a module. However, if I am running multiple forms and want to subclass things, like replacing/preventing a textbox context menu for example along with other controls specific to the form, then I may opt to use an implementation or hybrid combo. Subclassing multiple windows using the same subclass procedure can get a bit cumbersome (not just testing for messages but also for hWnds) and splitting them off into separate classes could be a solution. Subclassing doesn't need to be a hassle, just needs a bit of forethought. Common controls made it easier for IDE
    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}

  20. #20
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    692

    Re: Subclassing

    @Elroy: Ouch, so many uses of the "Call" keyword. It burns my eyes...

    You've already gotten tons of great advice form the experts. Here's a few (unwarranted) pieces of advice from a non-expert. This stuff is fresh in my mind as I recently migrated a bunch of projects to comctl subclassing after years of using thunks...

    1) My approach is basically identical to Dex and LaVolpe's. We all stole this from Karl. His "Implements"-based method means you don't have to use AddressOf everywhere - instead, you get the "more readable" approach of starting/ending subclassing via "StartSubclassing hWnd, Me". This makes it clear which objects in your project are performing subclassing (because they'll all be implementing "ISubclass"). I like that clarity.

    2) Don't assume that the subclassed function will always want to call DefSubclassProc. Allow the caller to call this if they want, but sometimes, a subclasser may want to fully replace the default function. Your current code doesn't allow for this. (And in fact, always subclassing with two functions means that the precise order of when DefSubclassProc gets called is gonna be funky.)

    3) It's probably trivial, but I still think WM_NCDESTROY is the preferable place for teardown. Raymond agrees. WM_DESTROY won't hurt anything, but it feels "right" that parent windows get torn down *after* child windows.

    4) Perhaps not relevant, but I'll mention it anyway: if you move to a comctl subclassing technique, don't intermix it with old thunk-based methods. Move your entire project over at once. If you intermix methods, you risk memory leaks, as discovered by @wqweto (and commented on by Karl).

    5) There's a lot of ByVal/ByRef intermixing going on in your current approach. You may want to switch everything to ByVal to clarify (since we don't have const parameters in VB6, argh)

    Sorry for all the pedantic stuff, but maybe there's something helpful in there. Welcome to the comctl subclassing club.
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  21. #21
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    3) It's probably trivial, but I still think WM_NCDESTROY is the preferable place for teardown. Raymond agrees. WM_DESTROY won't hurt anything, but it feels "right" that parent windows get torn down *after* child windows.
    I'll stick with WM_DESTROY for one reason only. Unless there is a need to process WM_NCDESTROY why process an extra message (or more)? Now, if a child window can cancel/prevent the parent's WM_DESTROY; that would be a reason.

    The following quote by Raymond Chen is a good example of why you want WM_NCDESTROY if it applies to you. However, unless you are handling the WM_NCCREATE message; it may be just personal choice because you are subclassing an existing/created window 99.9999% of the time.
    Note also that if you cause the WM_NCCREATE message to return failure, then all you will get is WM_NCDESTROY; there will be no WM_DESTROY since you never got the corresponding WM_CREATE.
    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}

  22. #22
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    692

    Re: Subclassing

    Quote Originally Posted by LaVolpe View Post
    I'll stick with WM_DESTROY for one reason only. Unless there is a need to process WM_NCDESTROY why process an extra message (or more)? Now, if a child window can cancel/prevent the parent's WM_DESTROY; that would be a reason.
    Yep, great point. Obviously Set/RemoveWindowSubclass handles unhook order automatically, but using old subclassing techniques, WM_NCDESTROY was important for cases where a parent window subclassed itself, *and* it was subclassed by a child. Unhooking in WM_DESTROY meant the parent unhooked out-of-order, causing breaks in the chain.

    So I've always unhooked on WM_NCDESTROY, and old habits die hard, I guess. <shrug> As I said, a trivial difference, and if skipping 1+ extra messages is seen as a perk, no reason to avoid WM_DESTROY.
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  23. #23
    Hyperactive Member
    Join Date
    Feb 2017
    Posts
    380

    Re: Subclassing

    Hello, excuse me...

    The other day reading about WM_NCDESTROY, I modified my subclass/unsubclass procedures like this:

    When subclassing, I store the address of my Window Procedure in a variable.
    When receiving WM_DESTROY, I check whether the current procedure of the window (the one at the top of the chain) is mine, if it is, then unsubclass, if not, I wait until the WM_NCDESTROY message to unsubclass.

  24. #24

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Wow, this is great. I've learned a LOT in this thread.

    To LaVolpe, Dex, & Tanner. I've heard you "loud and clear" regarding the "Implements" approach to doing this. And I may someday adopt that approach. However, it just doesn't quite seem to fit in with my way of thinking, so I put it all together slightly differently.

    Here's my "fairly final" static module for doing all of this:

    Code:
    
    Option Explicit
    '
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function GetWindowSubclass Lib "comctl32.dll" Alias "#411" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, pdwRefData As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    '
    ' This must also be used in any other hook (as passed in with TheHookProcAddress).
    Public Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    '
    
    Public Sub SubclassSomeWindow(hWnd As Long, TheHookProcAddress As Long, Optional dwRefData As Long)
        ' "Each subclass is uniquely identified by the address of the pfnSubclass and its uIdSubclass" (Microsoft.com).
        '
        ' Apparently, we can call this as often as we like with the same hWnd and TheHookProcAddress with no harm done,
        '   and it doesn't hurt anything.  Apparently, it auto-unhooks and then re-hooks.  This could be used to change
        '   the value of dwRefData if we ever had that need.
        ' "To change reference data you can make subsequent calls to SetWindowSubclass" (Microsoft.com).
        '
        ' The following is a stub for creating a static (BAS) procedure that would be used for TheHookProcAddress.
        '
        '       Private Function MySpecialHook(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
        '           '
        '           ' Do whatever you need to do...
        '           '
        '           MySpecialHook = DefSubclassProc(hWnd, uMsg, wParam, lParam)
        '       End Sub
        '
        ' And then, to hook it, do the following:
        '
        '       Call SubclassSomeWindow(hWndToHook, AddressOf MySpecialHook, OptionalLongData)
        '
        ' And that's it.  It'll automatically unhook.
        '
        Call SetWindowSubclass(hWnd, AddressOfSubclassProcForUnhook, hWnd, TheHookProcAddress)  ' This one allows us to monitor WM_DESTROY.
        Call SetWindowSubclass(hWnd, TheHookProcAddress, hWnd, dwRefData)                       ' This is the one that'll actually hook to what we're trying to accomplish.
    End Sub
    
    Public Function GetSubclassRefData(hWnd As Long, TheHookProcAddress As Long) As Long
        ' This one is used only to fetch the optional dwRefData you may have specified when calling SubclassSomeWindow.
        Call GetWindowSubclass(hWnd, TheHookProcAddress, hWnd, GetSubclassRefData)
    End Function
    
    Public Sub UnSubclassSomeWindow(hWnd As Long, TheHookProcAddress As Long)
        ' Only needed if we specifically want to un-subclass before we're closing the form (or control),
        '   otherwise, it's automatically taken care of when the window closes.
        Call RemoveWindowSubclass(hWnd, TheHookProcAddress, hWnd)
        Call RemoveWindowSubclass(hWnd, AddressOfSubclassProcForUnhook, hWnd)
    End Sub
    
    '********************************************************
    '********************************************************
    '********************************************************
    '
    ' Private modules used only herein from here down.
    '
    '********************************************************
    '********************************************************
    '********************************************************
    
    Private Function SubclassProcForUnhook(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
        Const WM_DESTROY = &H2&
        '
        If uMsg = WM_DESTROY Then
            ' This gets executed even AFTER the IDE "Stop" button is pressed.
            ' As a note, ALL COM objects are uninstantiated before we get to here
            ' when the IDE "Stop" button is pressed.
            Call RemoveWindowSubclass(hWnd, dwRefData, hWnd)                        ' Remove this one FIRST in case there are any COM object methods being called in it.
            Call RemoveWindowSubclass(hWnd, AddressOfSubclassProcForUnhook, hWnd)   ' Then remove the hook that got us here.
        End If
        '
        SubclassProcForUnhook = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    End Function
    
    Private Function AddressOfSubclassProcForUnhook()
        AddressOfSubclassProcForUnhook = ProcAddressForUnhook(AddressOf SubclassProcForUnhook)
    End Function
    
    Private Function ProcAddressForUnhook(TheProcWithAddressOf As Long)
        ProcAddressForUnhook = TheProcWithAddressOf
    End Function
    
    What I find super cool is that there are no module-level variables, but it can still be called as many times as I like to subclass as many windows as I like, possibly subclassing the same window twice for different purposes.

    Also, I've been testing it quite a bit, and it actually seems "relatively" IDE Stop-Button safe. Surprisingly, the SubclassProcForUnhook (with the monitoring of WM_DESTROY) is still called even AFTER the Stop-Button is clicked, thereby cleaning up things. This was quite a surprise.

    Another thing I discovered is that, when the Stop-Button is clicked, apparently, all COM objects are immediately un-instantiated. And only THEN are all the windows destroyed. Therefore, if you try to use any COM object's method after the Stop-Button has been clicked, you'll get an error, and quite likely IDE crash.

    I'm still open for critiques, but I'm starting to get VERY excited about this stuff.

    Happy hump day,
    Elroy

  25. #25
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    692

    Re: Subclassing

    @Eduardo: if you use Set/RemoveWindowSubclass APIs, you can remove your subclass whenever you want, even if it isn't at the top of the chain. The APIs will correctly modify their internal subclassing table either way. (So you shouldn't need to store or check whether you're at the top of the chain.)

    @Elroy: I'm glad this hasn't been too painful so far!

    FWIW, I still have concerns with your latest code. Can you see why double-subclassing may have unintended side-effects? Notice when DefSubclassProc get called - it may not be when you think it is.

    There's also still the problem of "what if a subclass wants to *replace* the default proc". This is impossible with your current iteration. DefSubclassProc always gets called. Many subclassing solutions involve *replacing* the default window procedure with one of your own making (vs just "peeking" at window data, then letting the default window proc do its thing). I don't know that you've mentioned what specifically you're using subclassing for, but IMO you'll want to allow for both behaviors.

    Just want to make sure you are absolutely certain you are happy with this arrangement before you start using it everywhere throughout your project, because it can be hard to undo some things after-the-fact.
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  26. #26

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Quote Originally Posted by Tanner_H View Post
    There's also still the problem of "what if a subclass wants to *replace* the default proc".
    Dang, Tanner, you're right. In fact, one of the first places I wanted to use it, I wanted to do precisely that.

    Ok, back to the drawing board. Maybe "Implements" is the way to go after all. I've got some other work I've just GOT to give some attention, so I'll be back later.

  27. #27
    Hyperactive Member
    Join Date
    Feb 2017
    Posts
    380

    Re: Subclassing

    Quote Originally Posted by Tanner_H View Post
    @Eduardo: if you use Set/RemoveWindowSubclass APIs, you can remove your subclass whenever you want, even if it isn't at the top of the chain. The APIs will correctly modify their internal subclassing table either way. (So you shouldn't need to store or check whether you're at the top of the chain.)
    And if the other subclass was made with another subclassing system that don't use Set/RemoveWindowSubclass?

    But I'm not using Set/RemoveWindowSubclass.

  28. #28
    Fanatic Member
    Join Date
    Aug 2013
    Posts
    692

    Re: Subclassing

    Quote Originally Posted by Eduardo- View Post
    And if the other subclass was made with another subclassing system that don't use Set/RemoveWindowSubclass?
    Then you either need to manually maintain a table yourself (which tracks subclassing order, so you can restore wndprocs manually), or you need to be *very* precise about always subclassing and unsubclassing in proper order.

    The trouble I ran into is that there's really no choice but to unsubclass in WM_NCDESTROY. If you aren't at the top of the chain in WM_NCDESTROY, and you aren't using Set/RemoveWindowSubclass, then you have to manually restore the proper subclassing chain order. If you're doing a lot of subclassing from different procedures, this becomes very messy. I struggled to track subclassing order correctly across large projects, and it was the source of some frustrating, difficult-to-replicate bugs.

    This is why many of us (I assume) have switched to just using the subclass APIs. While not 100% IDE-safe, they're close, and they potentially prevent a ton of headaches. (I also found them much more reliable on Linux/OSX under Wine, FWIW - especially compared to thunks. Not sure if anyone else is interested in that.)
    Check out PhotoDemon, a pro-grade photo editor written completely in VB6. (Full source available at GitHub.)

  29. #29
    Hyperactive Member
    Join Date
    Feb 2017
    Posts
    380

    Re: Subclassing

    Quote Originally Posted by Tanner_H View Post
    Then you either need to manually maintain a table yourself (which tracks subclassing order, so you can restore wndprocs manually), or you need to be *very* precise about always subclassing and unsubclassing in proper order.
    The point is: how would Set/RemoveWindowSubclass know the proper chain if in the middle some other subclass method was used (perhaps from a third party component)?

    Quote Originally Posted by Tanner_H View Post
    The trouble I ran into is that there's really no choice but to unsubclass in WM_NCDESTROY. If you aren't at the top of the chain in WM_NCDESTROY, and you aren't using Set/RemoveWindowSubclass, then you have to manually restore the proper subclassing chain order. If you're doing a lot of subclassing from different procedures, this becomes very messy.
    I think my way of checking in the WM_DESTROY if the current windows procedure is my window proc, that means that it's a the top chain order, and unsubclass there must work, it's must be safe to unsubclass if my subclass is a the top order.

    Quote Originally Posted by Tanner_H View Post
    I struggled to track subclassing order correctly across large projects, and it was the source of some frustrating, difficult-to-replicate bugs.
    And what subclassing method were you using?

    Quote Originally Posted by Tanner_H View Post
    This is why many of us (I assume) have switched to just using the subclass APIs. While not 100% IDE-safe, they're close, and they potentially prevent a ton of headaches. (I also found them much more reliable on Linux/OSX under Wine, FWIW - especially compared to thunks. Not sure if anyone else is interested in that.)
    This issue of subclassing is very "dangerous", and I use quite much (not a lot, but only when needed) of subclassing.
    I have several projects using the same "technology" and it's working quite fine.
    I can preprocess, postprocess or replace completely the window's code for any particular message.
    I can add subclassing very easily, just a few lines need to be added on any form, Usercontrol or class module.

    So I'm not very much encouraged to change the method, at least not now.

    One advantage of moving to Set/RemoveWindowSubclass I guess it would be to get rid of the external dll. Now when I don't want that dependency, I need to include the subclasser modules into the project, and then it gets very difficult to debug anything.

  30. #30
    Fanatic Member
    Join Date
    Jun 2012
    Posts
    699

    Re: Subclassing

    In my ComCtls SubclassProc I also do a un-subclass when receiving WM_UAHDESTROYWINDOW, beside WM_NCDESTROY. (#define WM_UAHDESTROYWINDOW 0x90)
    I know that WM_UAHDESTROYWINDOW is kind of internal windows message, but it gets especially fired in the IDE when doing "design-time" subclassing. Thus it greatly stabilized my subclassing in this regard.

    Also I avoided of specifiying "ByVal uIdSubclass As ISubclass" in the SubclassProc, instead used "ByVal uIdSubclass As Long" and do a "PtrToObj(uIdSubclass)" later on, which also stabilized the subclassing.

    Also, avoid using ordinals, e.g. #410 for SetWindowSubclass. It may change in future windows version, rather use the named version. (ordinal #410 is only needed for W2K)

  31. #31
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    Quote Originally Posted by Eduardo
    The point is: how would Set/RemoveWindowSubclass know the proper chain if in the middle some other subclass method was used (perhaps from a third party component)?
    This can be a huge pain when multiple usercontrol instances subclass the same parent form, using traditional subclassing. Here's a post touching on such scenarios. Bottom line and assuming you don't have to worry about outside sources subclassing your window, you can create procedures that safely unsubclass in the proper order.

    Though I was completely comfortable with JIT thunks for IDE-safe subclassing; I don't think I'll ever return to that method, unless a really good reason comes up. Common Controls is reliable, mostly safe in IDE, and probably has continued life-time support. Wonder when hooking/unhooking will be included
    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}

  32. #32
    Hyperactive Member
    Join Date
    Feb 2017
    Posts
    380

    Re: Subclassing

    I sometimes copy an object (usercontrol, class or whatever) from some project into other, and I cannot have several subclassing methods mixed.
    So if I decide to migrate to another subclassing method, I would have to rewrite the subclassing related parts of several existing projects that now are working fine.
    To gain what?

    And about the future, what do you think that is less probably that MS could break, the SetWindowSubclass API or the SetWindowLong API?
    I don't believe that they will break anyone, but SetWindowLong, CallWindowProc are very established.

    If there is an strong argument I'll migrate, but I still have my doubts.
    Remember the saying "if it's working, don't fix it".

    Edit: At some point I'll have to do it. Because if the SetWindowSubclass API is becoming an standard, it will be safer to coexist with third party controls with that method.
    But right now I have other priorities.
    Last edited by Eduardo-; Apr 19th, 2017 at 04:23 PM.

  33. #33
    Fanatic Member
    Join Date
    Jun 2012
    Posts
    699

    Re: Subclassing

    Quote Originally Posted by Eduardo- View Post
    I sometimes copy an object (usercontrol, class or whatever) from some project into other, and I cannot have several subclassing methods mixed.
    So if I decide to migrate to another subclassing method, I would have to rewrite the subclassing related parts of several existing projects that now are working fine.
    To gain what?

    And about the future, what do you think that is less probably that MS could break, the SetWindowSubclass API or the SetWindowLong API?
    I don't believe that they will break anyone, but SetWindowLong, CallWindowProc are very established.

    If there is an strong argument I'll migrate, but I still have my doubts.
    Remember the saying "if it's working, don't fix it".
    You do not need to change existing projects. But in case you start a new one you can benefit of the better SetWindowSubclass method, making life easier.

  34. #34

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Quote Originally Posted by Elroy View Post
    Quote Originally Posted by Tanner_H View Post
    There's also still the problem of "what if a subclass wants to *replace* the default proc".
    Dang, Tanner, you're right. In fact, one of the first places I wanted to use it, I wanted to do precisely that.

    Ok, back to the drawing board. Maybe "Implements" is the way to go after all. I've got some other work I've just GOT to give some attention, so I'll be back later.
    And also...

    Quote Originally Posted by Tanner_H View Post
    Notice when DefSubclassProc get called - it may not be when you think it is.
    Actually, I've been doing some more testing, and I think I might be okay. Here's a bullet-list of things I've figured out when using the comctl32 method:

    1. For a particular hWnd, the last procedure hooked will be the first to execute. In other words, it's a LIFO (last-in-first-out) system.
    2. If we call SetWindowSubclass repeatedly with the same hWnd, same pfnSubclass, same uIdSubclass, and same dwRefData, it does nothing at all. Not even the order of the hooked functions will change, even if other functions were hooked later, and then it was called again with the same hWnd, pfnSubclass, uIdSubclass, and dwRefData.
    3. Similar to the above, if we call SetWindowSubclass repeatedly, and nothing changes but the dwRefData, the dwRefData is changed like we want, but the order of execution of the functions still stays the same as it was.
    4. When unhooking, we can call RemoveWindowSubclass in any order we like, with no harm.
    5. We don't have to call DefSubclassProc in a particular hooked function, but if we don't, all other "downstream" hooked functions won't execute.


    And, the reason my above (post #24) is still going to work for me is because I hook the actual procedure that I'll use to "do stuff" as the last hook (first to execute). Therefore, if I do something and don't want any more code to execute, I just won't call DefSubclassProc.

    The only way this could be a problem is the case where I'm hooking a window for two separate reasons, and it's always required that both of my hooked functions execute. If the first one to execute doesn't call DefSubclassProc, then the other one won't ever execute.

    Also, I should probably make a note that, if "uMsg = WM_DESTROY", then I want to make sure and call DefSubclassProc so that my unhooking code will get executed.

    I'm not sure that all makes sense to anyone but me, but hopefully it DOES make sense to me.

    I'm still in a test mode, but I'm close to slipping it in to start converting my subclassing in my main project. If I have problems, I'll report back.

    All The Best,
    Elroy

  35. #35
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    Elroy, your comments about subclassing being called after IDE stop button pressed... I wouldn't rely on it when compiled. You could probably run some tests printing out to a file, but when compiled, that module may be destroyed nearly immediately and there will be no static procedure to call back to which may result in a crash on exit. To simulate a the stop button when compiled: End.
    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}

  36. #36

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Quote Originally Posted by LaVolpe View Post
    Elroy, your comments about subclassing being called after IDE stop button pressed... I wouldn't rely on it when compiled. You could probably run some tests printing out to a file, but when compiled, that module may be destroyed nearly immediately and there will be no static procedure to call back to which may result in a crash on exit. To simulate a the stop button when compiled: End.

    Oh gosh, LaVolpe, I'm not sure we're talking about exactly the same thing. When compiled, all I've got to depend on is that, as my windows unload, my hooked procedures will be called so that I can undo the hooks. In other words, there just has to be one last time through so I can see if "uMsg = WM_DESTROY" and then unhook.

    What surprised me is that, in the IDE, when I click the "Stop" button, I still get that one last time through where "uMsg = WM_DESTROY" even after the "Stop" button is clicked.

    Are you thinking I don't get that one last time through in a compiled program? That would be disastrous. But I don't think that's what you're saying.

    EDIT1: In other words, I've got to believe that all my "windows" are destroyed (as well as all my COM objects) before the actual static code is thrown out of memory.

    EDIT2: And I'd never dare use the END statement.
    Last edited by Elroy; Apr 19th, 2017 at 07:03 PM.

  37. #37
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    Are you thinking I don't get that one last time through in a compiled program? That would be disastrous. But I don't think that's what you're saying.
    Yes, if END was executed in a compiled app. However, anyone using END in a compiled app deserves whatever they get IMO.

    I just wouldn't suggest that subclassing is even safer in IDE because it appears you get the WM_DESTROY message; definitely wouldn't rely on it unless fully tested in several environments: UC, PropertyPage, etc. But, if it is reliable 100% of the time, that's probably an unexpected 'feature'. However, tests I've done with traditional subclassing prove that what happens in IDE doesn't always happen when compiled & vice versa. Lots of writing subclass procedure events to file when compiled, just so I could see/debug where it broke, even though it worked in IDE.

    COM objects created by VB are destroyed when END/Stop button are executed. This is documented. Stuff created outside of VB, via APIs for example, may not be destroyed until the IDE (process) is completely terminated.

    Edited: One more thing about End/Stop button... Any form/class/etc Terminate events are NOT triggered. So whatever code is in there might as well not be there in that case. Simple example, place MsgBox "C'ya" in a Form's Terminate event, run form and hit the End button; run it again but closing the form normally.
    Last edited by LaVolpe; Apr 19th, 2017 at 07:34 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}

  38. #38

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    2,623

    Re: Subclassing

    Alrighty, I can't thank everyone enough for letting me "think through" this stuff with you. Thank you ... thank you ... thank you!!! Several of you were just absolutely wonderful in keeping me on the correct track.

    In the end (and in consideration of the KISS principle), I decided to abandon my double-hooking approach (posts #17 & #24). It was just getting more complex than I thought it needed to be.

    I truly appreciate the ideas that used "Implements", but that just doesn't feel right for me either. To allow good testing, and to be able to easily slip it into my existing "huge" project (which is all I really maintain these days), I've decided to put together a single BAS module that handles all the subclassing. It'll have a "generic" part, and then also a more specific part for all the specific subclassing.

    The "generic" part is as follows (to be placed in a BAS module):

    Code:
    
    '
    ' Notes on subclassing with Comctl32.DLL:
    '
    '   1.  "Each subclass is uniquely identified by the address of the pfnSubclass and its uIdSubclass"
    '       (quote from Microsoft.com).
    '
    '   2.  For a particular hWnd, the last procedure hooked will be the first to execute.
    '       In other words, it's a LIFO (last-in-first-out, or last-hooked-first-to-execute) system.
    '
    '   3.  If we call SetWindowSubclass repeatedly with the same hWnd, same pfnSubclass,
    '       same uIdSubclass, and same dwRefData, it does nothing at all.
    '       Not even the order of the hooked functions will change,
    '       even if other functions were hooked later, and then it was called again
    '       with the same hWnd, pfnSubclass, uIdSubclass, and dwRefData.
    '
    '   4.  Similar to the above, if we call SetWindowSubclass repeatedly,
    '       and nothing changes but the dwRefData, the dwRefData is changed like we want,
    '       but the order of execution of the functions still stays the same as it was.
    '        "To change reference data you can make subsequent calls to SetWindowSubclass"
    '       (quote from Microsoft.com).
    '
    '   5.  When unhooking, we can call RemoveWindowSubclass in any order we like, with no harm.
    '
    '   6.  We don't have to call DefSubclassProc in a particular hooked function, but if we don't,
    '       all other "downstream" hooked functions won't execute.
    '
    '   7.  If "uMsg = WM_DESTROY" we should absolutely call DefSubclassProc so that other
    '       possible hooks can also unhook.
    '
    '   8.  A hooked function will get executed even AFTER the IDE "Stop" button is pressed.
    '       This gives us an opportunity to unhook everything if things are done correctly.
    '       Note that all COM objects are un-instantiated BEFORE the hooked functions are
    '       called when "uMsg = WM_DESTROY" when the "Stop" button is pressed.
    '
    '   9.  dwRefData can be used for whatever we want.
    '
    '
    ' The following is a stub that can be used for creating new functions to hook:
    '
    '        Private Function MySpecialHook(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
    '            If uMsg = WM_DESTROY Then
    '                Call RemoveWindowSubclass(hWnd, AddressOf_MySpecialHook, hWnd)
    '                MySpecialHook = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    '                Exit Function
    '            End If
    '            '
    '            ' Do what we need to do...
    '
    '
    '            ' If the following line is called, other hooks will also execute.
    '            ' It's not absolutely necessary that we do this (except for above when uMsg = WM_DESTROY).
    '            MySpecialHook = DefSubclassProc(hWnd, uMsg, wParam, lParam)
    '        End Function
    '
    ' ... and also ...
    '
    '       Private Function AddressOf_MySpecialHook() as long
    '           AddressOf_MySpecialHook = ProcedureAddress(AddressOf MySpecialHook)
    '       End Function
    '
    '
    ' To use all of this, do the following steps:
    '
    '   1.  Write our MySpecialHook function, naming it what we like.
    '
    '   2.  Write a AddressOf_MySpecialHook function that gives us the address of our MySpecialHook function.
    '       The ProcedureAddress function will help with this.
    '
    '   3.  Make a call to SubclassSomeWindow, and that's it.  It will automatically unhook if done correctly.
    '       And the IDE "Stop" button should be okay too.
    '
    Option Explicit
    '
    Private Const WM_DESTROY = &H2&
    '
    Private Declare Function SetWindowSubclass Lib "comctl32.dll" Alias "#410" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, Optional ByVal dwRefData As Long) As Long
    Private Declare Function GetWindowSubclass Lib "comctl32.dll" Alias "#411" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, pdwRefData As Long) As Long
    Private Declare Function DefSubclassProc Lib "comctl32.dll" Alias "#413" (ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Declare Function RemoveWindowSubclass Lib "comctl32.dll" Alias "#412" (ByVal hWnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long
    '
    '
    '
    
    Public Sub SubclassSomeWindow(hWnd As Long, AddressOf_ProcToHook As Long, Optional dwRefData As Long)
        Call SetWindowSubclass(hWnd, AddressOf_ProcToHook, hWnd, dwRefData)
    End Sub
    
    Public Function GetSubclassRefData(hWnd As Long, AddressOf_ProcToHook As Long) As Long
        ' This one is used only to fetch the optional dwRefData you may have specified when calling SubclassSomeWindow.
        ' Typically this would only be used by the hooked procedure, but it is available to anyone.
        Call GetWindowSubclass(hWnd, AddressOf_ProcToHook, hWnd, GetSubclassRefData)
    End Function
    
    Public Sub UnSubclassSomeWindow(hWnd As Long, AddressOf_ProcToHook As Long)
        ' Only needed if we specifically want to un-subclass before we're closing the form (or control),
        '   otherwise, it's automatically taken care of when the window closes.
        ' Be careful, some subclassing may require additional cleanup that's not done here.
        Call RemoveWindowSubclass(hWnd, AddressOf_ProcToHook, hWnd)
    End Sub
    
    Private Function ProcedureAddress(AddressOf_TheProc As Long)
        ' A private "helper" function for writing the AddressOf_... functions (see above notes).
        ProcedureAddress = AddressOf_TheProc
    End Function
    
    
    And then, to start my testing, I've attached a project that has all the above plus an actual subclassing setup in it. It's a subclassing procedure to assure a form will stay the size I want, even if the monitor can't accommodate it. For testing, I just made a really wide form.

    Still open for critiques, but I really do think I'm getting close to having my "core" in place.

    Again, MANY THANKS!,
    Elroy

    EDIT1: I've tested it compiled, but if you also wish to do that, you'll have to do a command-line compile, as the IDE will resize the form during compile when it's too big for your monitor(s). The following is the command-line I used to do it (in a BAT file).

    Code:
    "C:\Program Files (x86)\Microsoft Visual Studio\VB98\VB6" /MAKE "C:\Users\Elroy\Desktop\SetWindowSubclass\ERS_Comctl32_Subclassing.vbp" /outdir "C:\Users\Elroy\Desktop\SetWindowSubclass"
    You'll have to modify it for whatever folder you unzipped things.
    Attached Files Attached Files
    Last edited by Elroy; Apr 19th, 2017 at 09:03 PM.

  39. #39
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    16,005

    Re: Subclassing

    8. A hooked function will get executed even AFTER the IDE "Stop" button is pressed.
    This gives us an opportunity to unhook everything if things are done correctly.
    Note that all COM objects are un-instantiated BEFORE the hooked functions are
    called when "uMsg = WM_DESTROY" when the "Stop" button is pressed..
    I'm willing to buy that statement with two caveats ... 1) not yet tested in all classes (uc, property page, etc) and 2) if the app doesn't crash. You want to crash your sample in that zip? Do the following. Don't worry about the changes you are making; they won't be saved

    1. Add a command button to your form2
    2. In the command button, add this code
    Code:
    Dim f As Form2
    Set f = New Form2
    f.Show vbModal
    3. Run the project, click the command button, and press the stop/end button
    Edited: It wouldn't have mattered if the 2nd instance was not subclassing - crashes just the same

    Wanna try another one, more likely scenario? Generate an unhandled error and hit the End button from the debug msgbox. For example, add a button to Form2 and add this in the code. Run the project, click the button, and click the End button on the debug msgbox:
    Code:
    Debug.Print 1/0
    This type of subclassing isn't crash-proof in IDE, but it is more stable than traditional subclassing contained within the IDE itself and the big plus: debugging while subclassing. At least one thing is still in common: don't hit the End/Stop button while subclassing is active. The alternate thunk method of subclassing is a bit more safe with regard to these issues, but not as user-friendly.
    Last edited by LaVolpe; Apr 21st, 2017 at 02:04 AM.
    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}

  40. #40

Page 1 of 2 12 LastLast

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.