Results 1 to 8 of 8

Thread: Subclassing Needed??

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 1999
    Posts
    363

    Post

    Hi, I want to take a menu that's presented on a main window, and bring it down into its child windows. I know how to capture and insert menus. Would I then have to subclass the child windows and then pass that to the menus that reside on the parent window? If I subclass, would I be responsible for passing every message or could I just subclass the menu? Or is there a better way not involving subclassing?

    This is all new to me, so I could be way off on the strategy. Any help's greatly appreciated.

    Thanks,
    Wade

  2. #2

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 1999
    Posts
    363

    Post

    Ok, after I append menus to a child window, I then test for those IDs in my callback procedure to call the corresponding menu item in the parent window.

    Can I assign different IDs to each menu within a child, but use the same constants across the set of child windows? For example, Change Business Date may be given an ID of 1008 and Close may be given an ID of 1034. In every child window, could I always use the 1008 for Change Business Date and 1034 for Close and then test for this constant (and the active window) in my callback procedure??

    Thanks in advance,
    Wade

  3. #3
    Guru Aaron Young's Avatar
    Join Date
    Jun 1999
    Location
    Red Wing, MN, USA
    Posts
    2,177

    Post

    I'm not quite sure why you'd want to replicate the MDIForms Menu on Child Forms, but here's how you can do it:

    You can't add a Menu to an MDI Child, so you'll have to use Standard Forms, if you want to, then there's an extra line I've remarked out in the Code, that will use the SetParent API to make the Normal Form a Child of the MDIForm, (not a True Child, but as good as it gets):

    Add a Standard Module..
    Code:
    '---[Duplicate Menu]---
    'Written by Aaron Young, March 9th 2000
    '
    Public Type MENUITEMINFO
        cbSize As Long
        fMask As Long
        fType As Long
        fState As Long
        wID As Long
        hSubMenu As Long
        hbmpChecked As Long
        hbmpUnchecked As Long
        dwItemData As Long
        dwTypeData As String
        cch As Long
    End Type
    
    '---[Menu API's]---
    Public Declare Function GetMenu Lib "user32" (ByVal Hwnd As Long) As Long
    Public Declare Function SetMenu Lib "user32" (ByVal Hwnd As Long, ByVal hMenu As Long) As Long
    Public Declare Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As Long, ByVal un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long
    Public Declare Function CreateMenu Lib "user32" () As Long
    Public Declare Function InsertMenuItem Lib "user32" Alias "InsertMenuItemA" (ByVal hMenu As Long, ByVal un As Long, ByVal bool As Boolean, ByRef lpcMenuItemInfo As MENUITEMINFO) As Long
    Public Declare Function GetMenuItemCount Lib "user32" (ByVal hMenu As Long) As Long
    
    '---[Window & Messaging API's]---
    Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal Hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
    Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal Hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal Hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
    Public Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
    Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
    
    '---[API Constants]---
    Public Const GWL_WNDPROC = (-4)
    Public Const WM_QUIT = &H12
    Public Const WM_CLOSE = &H10
    Public Const WM_DESTROY = &H2
    Public Const WM_COMMAND = &H111
    
    Public Const MIIM_STATE = &H1
    Public Const MIIM_ID = &H2
    Public Const MIIM_SUBMENU = &H4
    Public Const MIIM_CHECKMARKS = &H8
    Public Const MIIM_TYPE = &H10
    Public Const MIIM_DATA = &H20
    Public Const MIIM_ALL = MIIM_STATE Or MIIM_ID Or MIIM_SUBMENU Or MIIM_CHECKMARKS Or MIIM_TYPE Or MIIM_DATA
    
    Public Const MFT_STRING = &H0&
    
    '---[UDT - Used to Store Subclassing Info for Multiple Forms]---
    Private Type PrevFunc
        Hwnd As Long
        PrevWndFunc As Long
    End Type
    
    Private lHwndFunc() As PrevFunc
    Private iFuncs As Long
    
    '--[Source Window Handle, (MDIForm)]---
    Public lSrcHwnd As Long
    
    Public Sub SubClassForm(ByVal Hwnd As Long)
        'Subclass the Form
        ReDim Preserve lHwndFunc(iFuncs)
        lHwndFunc(iFuncs).Hwnd = Hwnd
        lHwndFunc(iFuncs).PrevWndFunc = SetWindowLong(Hwnd, GWL_WNDPROC, AddressOf WindowProc)
        iFuncs = iFuncs + 1
    End Sub
    
    Private Function WindowProc(ByVal Hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
        '---[WindowProc Callback Function used by ALL Subclassed Windows]---
        Dim lPrevFunc As Long
        'Get the Original WindowProc Address for this Window
        lPrevFunc = GetPrevFunc(Hwnd)
        Select Case Msg
        Case WM_COMMAND
            'Send the Menu Selection to the Source Window, (MDIForm)
            Call SendMessage(lSrcHwnd, Msg, wParam, ByVal lParam)
        Case WM_CLOSE, WM_QUIT, WM_DESTROY
            'Automatically Remove Window Subclassing when the Window is unloaded
            Call SetWindowLong(Hwnd, GWL_WNDPROC, lPrevFunc)
            Call RemoveFunc(Hwnd)
        End Select
        'Pass Messages to Original Window Proc
        WindowProc = CallWindowProc(lPrevFunc, Hwnd, Msg, wParam, lParam)
    End Function
    
    Private Function GetPrevFunc(ByVal Hwnd As Long) As Long
        'Function to find the Previous Window Proc of a Specified Window Handle
        Dim iIndex As Long
        For iIndex = 0 To UBound(lHwndFunc)
            If lHwndFunc(iIndex).Hwnd = Hwnd Then Exit For
        Next
        GetPrevFunc = lHwndFunc(iIndex).PrevWndFunc
    End Function
    
    Private Sub RemoveFunc(ByVal Hwnd As Long)
        'Sub to Tidy up the Array of Subclassing Info after Removing an Element
        Dim iIndex As Long
        Dim iNewIndex As Long
        For iIndex = 0 To UBound(lHwndFunc)
            If lHwndFunc(iIndex).Hwnd <> Hwnd Then
                With lHwndFunc(iNewIndex)
                    .Hwnd = lHwndFunc(iIndex).Hwnd
                    .PrevWndFunc = lHwndFunc(iIndex).PrevWndFunc
                    iNewIndex = iNewIndex + 1
                End With
            End If
        Next
        If iNewIndex Then ReDim Preserve lHwndFunc(iNewIndex - 1)
        iFuncs = iNewIndex
    End Sub
    Add a Class Module and Name it Subclass..
    Code:
    '---[Class Module: Subclass]---
    Private lDestMenu As Long
    
    Public Function DuplicateMenu(ByVal SrcHwnd As Long, ByVal DestHwnd As Long) As Boolean
        '---[Enumerate all Items in the Source windows Menu and Duplicate it]---
        Dim lSrcMenu As Long
        lSrcHwnd = SrcHwnd
        lSrcMenu = GetMenu(SrcHwnd)
        If lSrcMenu Then
            lDestMenu = CreateMenu
            EnumerateMenuItems lSrcMenu, lDestMenu
            'Assign the Duplicate Menu to the Destination Window
            Call SetMenu(DestHwnd, lDestMenu)
            'Subclass the Window to Capture the Menu Messages
            Call SubClassForm(DestHwnd)
            'Include this next line to make the Form a Child of the MDI Client
            'Call SetParent(DestHwnd, FindWindowEx(SrcHwnd, 0, "MDIClient", vbNullString))
            DuplicateMenu = True
        End If
    End Function
    
    Private Sub EnumerateMenuItems(ByVal lMainMenu As Long, ByVal DstMenu As Long, Optional ByVal Level As Long)
        '---[Recursive Sub Enumerates a Menu's Items at ALL Levels]---
        Dim iItems As Long
        Dim iItem As Long
        Dim tMENUITEMINFO As MENUITEMINFO
        Dim lSubMenu As Long
        
        'Get the Number of Items in this Menu/Submenu
        iItems = GetMenuItemCount(lMainMenu)
        For iItem = 0 To iItems - 1
            'Prepare the UTD to Retrieve all Menu Item Info
            With tMENUITEMINFO
                .fMask = MIIM_ALL
                .fType = MFT_STRING
                .dwTypeData = Space(256)
                .cch = 256
                .cbSize = Len(tMENUITEMINFO)
            End With
            Call GetMenuItemInfo(lMainMenu, iItem, True, tMENUITEMINFO)
            With tMENUITEMINFO
                If .hSubMenu Then
                    'Menu Item has a SubMenu
                    'Replicate and Enumerate it
                    lSubMenu = CreateMenu
                    EnumerateMenuItems .hSubMenu, lSubMenu, Level + 1
                    .hSubMenu = lSubMenu
                End If
                'Add this Menu Item to the Duplicate Copy
                Call InsertMenuItem(DstMenu, iItem, True, tMENUITEMINFO)
            End With
        Next
    End Sub
    
    Public Sub ReDrawMenu(ByVal Hwnd As Long)
        'The Menu needs to be reassign and redrawn when
        'the Window Gets/Loses Focus
        Call SetMenu(Hwnd, lDestMenu)
    End Sub
    In Each Form that is to Duplicate the MDIForms Menu..
    Code:
    Private DupMenu As New Subclass
    
    Private Sub Form_Activate()
        Static bSet As Boolean
        If Not bSet Then bSet = DupMenu.DuplicateMenu(MDIForm1.Hwnd, Hwnd)
        DupMenu.ReDrawMenu Hwnd
    End Sub

  4. #4

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 1999
    Posts
    363

    Post

    Aaron,

    The reason I'm bringing the parent menu down into child windows is because I'm working
    with an external accounting app that has dozens of windows in each module and the windows
    do not stay within a central container, so users could become confused.

    Can this work for an external app?

    Thanks,
    Wade

  5. #5
    Guru Aaron Young's Avatar
    Join Date
    Jun 1999
    Location
    Red Wing, MN, USA
    Posts
    2,177

    Post

    Yes, this can work for an External Application, as you're not Subclassing the External App, you're Subclassing your Own Form(s) and Forwarding Messages to the External App.

    Just Replace MDIForm.hwnd with the Window Handle of the External Application.

    I've tried it and it does work.

  6. #6

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 1999
    Posts
    363

    Post

    Thanks for the awesome code, Aaron. But both the parent and child windows are external
    apps. If I make the destination window an external app, the menu copies over, but doesn't send
    its messages to the parent. I want the child to have the parent's menu and my app to be the go-
    between that detects the user's menu selection and sends it to the parent.

    Can I use LoadLibraryEx to map the child into the same process as my app or
    AttachThreadInput to attach the threads? Then the child could have the menu, but I could
    subclass it so that my app could read its messages.

    Thanks,
    Wade

    Edited by WadeD on 03-13-2000 at 02:45 PM

  7. #7
    Guru Aaron Young's Avatar
    Join Date
    Jun 1999
    Location
    Red Wing, MN, USA
    Posts
    2,177

    Post

    I'm afraid not, now you're talking about subclassing
    something that doesn't belong to your thread,
    which you can only do going through a Windows DLL which you can't create with VB.

    Here's an idea that just forced its way to the surface of
    my dried up little grey cells..

    What if you made the Child App's Window a Child of your own
    Applications Main Form, then Add the Parent Apps Menu to your Form, this would at least group the Parent Apps Menu
    with the Child App on the Same Form and the subclassing
    principle would be the same as that demonstrated in my example.

    You should just need to locate the Window Handle on the 3rd
    Party Child App and use the SetParent API to put it
    inside your own Form or a Picturebox on your own Form.

    Otherwise dust off that C++ Manual and enjoy

  8. #8

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 1999
    Posts
    363

    Post

    Thanks for all the help. 2 questions. If I don't pass the message in WindowProc, it locks up. Do I
    have to pass every message?

    The menu seems to be deleted every time the window loses focus. So if the user clicks on the
    picture box (containing the external child app) and then the window, they see a menu disappear
    and reappear each time. Is there a way to keep the menu from being deleted?

    Thanks again,
    Wade

    Edited by WadeD on 03-15-2000 at 11:03 AM

Posting Permissions

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



Click Here to Expand Forum to Full Width