If you just need unicode strings to be displayed, I'd imagine you can just use the W menu apis (InsertMenuItemW, with the MENUITEMINFOW struct and StrPtr(unicodestring), maybe with an extra vbNullChar tacked on).
Just got a simple solution to work with :
GetMenu
GetSubMenu
SetMenuItemInfo
But since the GetMenu API needs a hWnd of the Menu window it works fine on forms, but not USER CONTROLS?
Anyone know of any workarounds? It doesn't like GetMenu(usercontrol.hwnd) and returns 0 always ??
The user control is almost certainly making its own menu; just add a property get, .MenuHWND
Make a module level variable; m_hWndMenu, then find where the control is doing its menu stuff, and copy the hwnd to that variable (nothing fancy, just =), then add the property get.
The user control is almost certainly making its own menu; just add a property get, .MenuHWND
Make a module level variable; m_hWndMenu, then find where the control is doing its menu stuff, and copy the hwnd to that variable (nothing fancy, just =), then add the property get.
I don't need to create any properties since I'm not passing this outside of the User Control, but rather doing it inside the UC.
If you just need to create a popup menu on the fly. Pretty easy stuff. Here are list of the APIs needed (use the unicode variety as needed). The constants are easy enough to get
Dim hMenu As Long, puMenu As Long, lResult as Long
Dim mPt As POINTAPI, mRect As RECT
GetCursorPos mPt
hMenu = CreatePopupMenu
If hMenu <> 0 Then ' if so, add our menu(s)
AppendMenu hMenu, MF_SEPARATOR Or MF_DISABLED, 0&, ByVal 0&
AppendMenu hMenu, MF_STRING, 110, "&LaVolpe Added Menu Item"
puMenu = CreatePopupMenu ' create a menu item that will contain submenus
AppendMenu hMenu, MF_STRING Or MF_POPUP, puMenu, "LaVolpe Added Menu Item w/Submenus"
AppendMenu puMenu, MF_STRING, 111, "Submenu A"
AppendMenu puMenu, MF_STRING, 112, "Submenu B"
End If
lResult = TrackPopupMenu(hMenu, TPM_LEFTALIGN Or TPM_NOANIMATION Or TPM_RETURNCMD Or TPM_TOPALIGN, mPt.X, mPt.Y, vbDefault, UserControl.hWnd, mRect)
DestroyMenu puMenu ' edited: this line unnecessary. submenus are destroyed with the parent menu
DestroyMenu hMenu
Edited: For clarity. When passing strings to unicode APIs, you'll change the String parameters in the API to Long instead. Then pass the strings with StrPtr(menuCaption)
Last edited by LaVolpe; Oct 19th, 2014 at 09:58 AM.
Insomnia is just a byproduct of, "It can't be done"
Its funny all these years I never came accross these:
CreatePopupMenu
AppendMenu
DestroyMenu
TrackPopupMenu
It's a very nice idea, but now that I already have an implemented Menu System, I'd rather not go & reWrite/convert my whole project as there are quite a few forms/menus etc..
All I need is to be able to add Unicode support to my Caption, and it seems the GetMenu, GetSubMenu, SetMenuItemInfo do just that.
The only problem is It won't wont directly on UserControl / Menus :/
Can you show us how you assigned your menu to the usercontrol? What do the modified menu captions look like? What exactly isn't working? Far more details please
Insomnia is just a byproduct of, "It can't be done"
Well, I have a normal menu created with the Menu Editor on my UserControl, however, they are all hidden and only accesed via the right click me.PopUpMenu
No in order to rename mnuCopy.caption = [some unicode string] I need to use the GetMenu, GetSubMenu to modify & pass a Unicode string to the Menu.
So to access, the menu first we need a handle to it via theHandle= GetMenu(usercontrol.hwnd)
Once we have theHandle we pass it and try get the getSubMenu handle and once we have that we pass it to the SetmenuItemInfo API to update the menu string etc...
Hidden menu items, in vb are created on the fly. Only if you have a visible menu bar (i.e., can see the menu items) will GetMenu return anything. Now, I'm not sure (without testing) whether you can make all items visible in design & just before the UC shows itself, you can change them all to hidden will have any effect. My guess is: nope.
BTW: for testing, if you change the Visible property to True, GetMenu(Usercontrol.hWnd) will probably return a menu handle
Insomnia is just a byproduct of, "It can't be done"
BTW: for testing, if you change the Visible property to True, GetMenu(Usercontrol.hWnd) will probably return a menu handle
Five conditions apply for successful retrieval of a hMenu from a UserControl-defined Menu:
- the GetMenu-call would need to be performed against the ToplevelForm.hwnd: GetMenu(Usercontrol.Parent.hWnd)
- the TopLevel-Menu-Entry in the Menu-Designer would need to be switched to Visible=True
- its 'NegotiatePosition' Enum-Entry in the Menu-Designer would need to be set to something <> 0 as well
- the hosting Form needs its NegotiateMenus Property at True (though that's usually the default)
- the Usercontrol in question would need to have the Focus
All of the above centers around the "Form-Menu-contribution-Mode" of the Usercontrol-Menus -
not around the PopupMenu-Usage of UserControl-defined Menus (which are far more common).
So, other than a hooking-solution which is able to catch the PopUp at creation-time -
there's only the approach of writing the whole thing "from scratch" as you already suggested
(and which I would prefer too).
... the GetMenu-call would need to be performed against the ToplevelForm.hwnd
Ah, thanx for the reminder. Per msdn, "If the window is a child window, the return value is undefined". I guess it is possible for a child window to have a menu if the window class (via CreateWindowEx) has been defined with a hMenu? Not that curious though
Insomnia is just a byproduct of, "It can't be done"
So this problem resolved, but it lead to another
Now the MENU bar becomes visible on a Windowless Form :/
Well, and here I thought, I managed to point out a few reasons, why you should *not* try it this way...
Originally Posted by some1uk03
I've tried removing the border with SetWindowPos but no result.. any ideas how to hide the Menu Bar again?
Why trying to pile a workaround on top of another workaround?
It really *is* better to approach the problem from the ground up - as LaVolpe already suggested.
If you encapsulate this new (Unicode-)PopUp-functionality in a small class
(including some nice Event-Definitions) - then going through all your UserControls
and doing a replace of the VB-PopupMenu-functionality against that of your new Class,
it shouldn't take all that much time, I'd think.
Besides, could you post a ScreenShot of your "Windowless-Forms", with one or two of your
UserControls on top of it - just to get a better Picture of what you're trying to accomplish in the end?
If you encapsulate this new (Unicode-)PopUp-functionality in a small class
(including some nice Event-Definitions) - then going through all your UserControls
and doing a replace of the VB-PopupMenu-functionality against that of your new Class,
it shouldn't take all that much time, I'd think.
I'd think there wouldn't be dozens upon dozens of VB PopupMenu calls in the project.
Couple suggestions:
1) Class as suggested
2) A function in the usercontrol or a module called where you pass a parameter identifying which "menu" to call. In that routine, create the menus on the fly & display them, passing the result back. Then you can respond to that result
3) If you don't have tons of these, create the menus just once on UC Initialize, display them as needed with the TrackPopupMenu API & destroy them on the UC Terminate event
Insomnia is just a byproduct of, "It can't be done"
Accelerator keys (i.e., underscored character) can be set on menu items via the ampersand (&)
Shortcut keys can be trapped if monitoring keystrokes via the form/uc KeyPreview property. If you want to make shortcut keys similar to a VB menu bar, then that is a bit more work. This MSDN link has information to get you started
Insomnia is just a byproduct of, "It can't be done"
& Finally any ideas on being able to add icons ?
I've been researching around it seems you can do it with the GetMenu, GetSubMenu, SetMenuItemInfo API's, but not the CreatePopupMenu, AppendMenu, TrackPopupMenu, DestroyMenu, method.
SetMenuItemInfo simply requires a menu handle. Since you are creating them, you have them.
Note: Probably not an issue, but setting bitmaps to menus via SetMenuItemInfo prior to Win98 can't be done with the last member of the MENUITEMINFO structure. That member was added in Win98.
From the sample code in post #8 above...
1) GetMenu for a popup menu would return the hMenu value
2) GetSubMenu for a popup menu would return the puMenu value.
The GetSubMenu's hMenu parameter would be the hMenu value
So, basically, you just need to cache/use the hMenu value of the popup menu you are creating/displaying & the APIs should work just fine
If your bitmaps are bigger than 16x16, you may need to resize them
Edited: Another option, other than SetMenuItemInfo is SetMenuItemBitmaps API. Simply set both the hBitmapUnchecked & hBitmapChecked parameters to the same bitmap handle or use different handles as desired. However, it's strongly recommended to use only 1bit b&w bitmaps for that API
Last edited by LaVolpe; Oct 9th, 2014 at 12:52 PM.
Insomnia is just a byproduct of, "It can't be done"
The primary problem you're messing with here is getting Unicode strings into and out of API calls. It's a bit tricky because VB6 likes to convert its internal Unicode strings back into ASCII (one byte per char) strings before passing them anywhere (other than its own procedures). But VB6 truly is Unicode compatible. It's just a bit hidden. Interestingly, all the controls are Unicode capable as well. However, here's some code that shows how to get true Unicode strings from VB6 into Windows API calls. Try it with a control and see the results. It could be adapted to work with menus as well. (Although VB6 menus have their own set of idiosyncrasies, such as you have to twist the typical Windows API calls a bit to make them work.)
Code:
Option Explicit
'
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
'
Private Declare Function DefWindowProcW Lib "user32.dll" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function GetClientRect Lib "user32.dll" (ByVal hWnd As Long, lpRect As RECT) As Long
Private Declare Function InvalidateRect Lib "user32.dll" (ByVal hWnd As Long, lpRect As RECT, ByVal bErase As Long) As Long
Private Declare Function SysAllocStringLen Lib "oleaut32.dll" (ByVal OleStr As Long, ByVal bLen As Long) As Long
Private Declare Sub PutMem4 Lib "msvbvm60.dll" (Destination As Any, Value As Any)
'
Public Property Let UniCaption(ctrl As Object, sUniCaption As String)
Dim uRect As RECT
Const WM_SETTEXT As Long = &HC
' USAGE: UniCaption(SomeControl) = s
'
' This is known to work on Form, MDIForm, Checkbox, CommandButton, Frame, & OptionButton.
' Other controls are not known.
'
' As a tip, build your Unicode caption using ChrW or possibly in Wordpad.
' Also note the careful way we pass the string to the unicode API call to circumvent VB6's auto-ASCII-conversion.
DefWindowProcW ctrl.hWnd, WM_SETTEXT, 0&, ByVal StrPtr(sUniCaption)
GetClientRect ctrl.hWnd, uRect
InvalidateRect ctrl.hWnd, uRect, 1&
End Property
Public Property Get UniCaption(ctrl As Object) As String
Const WM_GETTEXT As Long = &HD
Const WM_GETTEXTLENGTH As Long = &HE
' USAGE: s = UniCaption(SomeControl)
'
' This is known to work on Form, MDIForm, Checkbox, CommandButton, Frame, & OptionButton.
' Other controls are not known.
Dim lLen As Long
Dim lPtr As Long
'
lLen = DefWindowProcW(ctrl.hWnd, WM_GETTEXTLENGTH, 0&, ByVal 0&) ' Get length of caption.
If lLen Then ' Must have length.
lPtr = SysAllocStringLen(0&, lLen) ' Create a BSTR of that length.
PutMem4 ByVal VarPtr(UniCaption), ByVal lPtr ' Make the property return the BSTR.
DefWindowProcW ctrl.hWnd, WM_GETTEXT, lLen + 1&, ByVal lPtr ' Call the default Unicode window procedure to fill the BSTR.
End If
End Property
Well, with all the bloat listed above, and the fact that I've already done full unicode support in VB6 for the clipboard, all captions, label, notepad files, and textboxes, I just couldn't resist the challenge. Here's a bit of code to put a unicode string into a VB6 menu caption. Just create a form, create a single menu item on that form, and then paste in and run this code.
Code:
Option Explicit
'
Private Declare Function ModifyMenuW Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long, ByVal wIDNewItem As Long, ByVal lpString As Any) As Long
Private Declare Function GetMenu Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetMenuItemCount Lib "user32" (ByVal hMenu As Long) As Long
Private Declare Function GetSubMenu Lib "user32" (ByVal hMenu As Long, ByVal nPos As Long) As Long
'
Private Const MF_BYPOSITION = &H400&
'
Private Sub Form_Load()
Dim s As String
'
s = ChrW(&HCD38) & ChrW(&HC988) & ChrW(&HBD38) & ChrW(&H7EBA)
ModifyMenuW GetMenu(Me.hwnd), 0, MF_BYPOSITION, 0, ByVal StrPtr(s)
End Sub
As a note, I've also included the necessary API calls to traverse through your main menu and sub-menus, but I'll let you sort out the code to do that (unless you ask, and then I might throw together a loop or maybe even a recursive procedure for you).
I wish the actual source code text was unicode but it's not, so you have to do strange things to actually build a unicode string. Another option is to throw RTF text boxes on your form, paste unicode into them, and then transfer it to your menu at runtime. Yet another option is to use the strings in a .RES file, which are set up for precisely these kinds of things.
And as yet another thought, I do know how to save a unicode string as a property. You have to coerce it into the frx with a byte array, or build a custom control just for that purpose.
Oh what the heck. I almost had it done anyway. Here's the core of how to traverse through up to a three level menu system and put unicode in all the captions. Someone should really make this a recursive routine though, or maybe a routine with a dynamic array of longs with the precise menu position, down into the sub-menus. First array value = main menu position, second array value = sub-menu position, third array value = sub-sub-menu position, etc. And then pass the new unicode caption. Just some thoughts.
And for the uninitiated, passing a string into one of the "W" API calls with the StrPtr(s) function is the big secret to passing VB6 unicode strings into the API.
Code:
Option Explicit
Private Const MF_BYPOSITION = &H400&
'
Private Declare Function GetMenu Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetMenuItemCount Lib "user32" (ByVal hMenu As Long) As Long
Private Declare Function GetSubMenu Lib "user32" (ByVal hMenu As Long, ByVal nPos As Long) As Long
Private Declare Function ModifyMenuW Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long, ByVal wIDNewItem As Long, ByVal lpString As Any) As Long
'
Private Sub Form_Load()
Dim hMenu(1 To 3) As Long
Dim MenuItemsCount(1 To 3) As Long
Dim i As Long
Dim j As Long
Dim k As Long
Dim s As String
'
s = ChrW(&HCD38) & ChrW(&HC988) & ChrW(&HBD38) & ChrW(&H7EBA)
'
hMenu(1) = GetMenu(Me.hwnd)
If hMenu(1) <> 0 Then
MenuItemsCount(1) = GetMenuItemCount(hMenu(1))
For i = 0 To MenuItemsCount(1) - 1
ModifyMenuW hMenu(1), i, MF_BYPOSITION, 0, StrPtr("1 " & Format$(i) & " " & Format$(j) & " " & Format$(k) & " " & s)
hMenu(2) = GetSubMenu(hMenu(1), i)
If hMenu(2) <> 0 Then
MenuItemsCount(2) = GetMenuItemCount(hMenu(2))
For j = 0 To MenuItemsCount(2) - 1
ModifyMenuW hMenu(2), j, MF_BYPOSITION, 0, StrPtr("2 " & Format$(i) & " " & Format$(j) & " " & Format$(k) & " " & s)
hMenu(3) = GetSubMenu(hMenu(2), j)
If hMenu(3) <> 0 Then
MenuItemsCount(3) = GetMenuItemCount(hMenu(3))
For k = 0 To MenuItemsCount(3) - 1
ModifyMenuW hMenu(3), k, MF_BYPOSITION, 0, StrPtr("3 " & Format$(i) & " " & Format$(j) & " " & Format$(k) & " " & s)
Next
End If
Next
End If
Next
End If
End Sub
Elroy, note that VB menus with the visible property set to False are not created until the visible property is set to true. It is not uncommon for someone to toggle visibility based on control contents or form events.
Also, unfortunately, I don't think this applies here. The topic pertains to a usercontrol, not a form (post #4)
Insomnia is just a byproduct of, "It can't be done"
To LaVolpe <---- What??? I thought the whole point of this thread was to show how to paste a unicode caption into an existing VB6 menu item without overly bloated code. Hmmm, a menu on a user control? I'm not sure what that'd be. The caption? That's EASY to do in unicode (my post #22). Are we talking about popup menus? They'd be easily adapted to what I've already posted. Again, I think "ModifyMenuW hMenu, iPos, MF_BYPOSITION, 0, StrPtr(s)" is the entire answer to what's being discussed here.
*shrugs*
It almost seems like we're now talking about some kind of custom user control with popup menus. IDK, but that line of ModifyMenuW will get unicode into any true menu item.
In this case, the popup menus are created dynamically via APIs. Since they have to be created, might as well just set the correct caption during creation.
For forms, many times, popup menus have the Visible property set to False. If so, they are created on the fly by VB when displayed. Therefore, to change the menu captions in that scenario, you'd have to subclass to catch the menu before it is displayed or make it visible just before displaying it. I have no issues with what you posted, but as I replied ... doesn't really fit this scenario
Insomnia is just a byproduct of, "It can't be done"
Interesting, you're right, LaVolpe. The PopupMenu captions aren't set, even if you leave them visible in the IDE, change them in code, and then make them invisible after the change.
I've done my fair share of hooking and subclassing, but I do try to avoid it if possible. It just always makes clicking the "Stop" button in the IDE a scary proposition.
I did a bit of testing and here's the way I'd do it. I hope you're still listening some1uk03. If you create a form that's JUST for your popup menu information. Put whatever popup menus you want in this special form, and DON'T hide any of the menus. Load this form somewhere in your project, set all the captions to whatever Unicode you want them to be, and then hide this entire form (not the menu items).
Now, using object.PopupMenu with your frmSpecialMenuForm.MenuToPopup, you can pop up Unicode menus on any other form or control you like.
That seems like a far easier way than getting into subclassing.
Rather than use another Form as a menu placeholder and modifying the captions for Unicode it seems much easier to just create the popmenu in Unicode to begin with. The code is quite simple, and the attached demo even supports icons. No subclassing is necessary.
Nice piece of code, DrUnicode. Thanks for sharing.
Hey, if you don't mind sharing a bit more, what are you using to make your .RC scripts for the Resource Compiler? Also, what Resource Compiler are you using, the standard VB6 one? (I apologize for hijacking this thread and will start another if the answer gets too involved.)
The .RES file for my primary project is about 70 meg. I've written my own routines to create resource compiler scripts, but I'd gladly use another if I could find a good one.
Nothing fancy here...
I create Resource ".RC" files with Notepad and save as Unicode.
To compile I use RC.exe. If you don't have it, you can download it from SDKs.
Mine is from Win 8.1 SDK.
Ahhh, thanks so much, DrUnicode. Yeah, I'm good to go on the RC.exe command line commands. In fact, my little .RC creator program even creates my .BAT file for me. And good ole Notepad as a programming/scripting tool. I just thought you may have had something fancier for recording your Unicode data and then creating a .RC file to feed into the RC.exe compiler.
And yeah, it's somewhat off-topic, but just to put it out there, the 64k limit for files in the .RES file is totally bogus. I stuff files into it that are well over a meg, and they've never failed me. That note in the MSDN help must be some hangover from VB-DOS or something, who knows. The only problem I've run into is that, when files are pulled out of the .RES file, they always seem to be sized on 4 byte boundaries. The new .DOCX and .XLSX files MUST be exactly the right size or Office 2010 and beyond will throw up on you.
Again, DrUnicode, thanks for the great code and the info.
Elroy
DrUnicode - having explored your project, I'm trying to add images to the menu. You seem to be using the ImageList to handle these.
I'd like to NOT use any dependencies, MSCOMCTL / ImageList. So, I tried saving the Flags in a Resource and loading them via the LoadResPicture(101, vbResBitmap). It only seems to show a white box rather than the image ?
Do these need to be any particular format? 16x16? colour depth etc..? Any specifics?
some1uk03, I'm guessing here a bit, but did you set "AutoRedraw=True", and also do a "pic.Picture=pic.Image" before you passed your pic.Picture into the call to AppendMenuW? If you used drawing to create your icon, you MUST do the "pic.Picture=pic.Image" before it gets coded into the hDC.
Regards,
Elroy
p.s. Post your code and I bet someone will fix it for you.
Just looking through the MSDN help, and here's a line for you: "To pass the persistent graphic to a Windows API when AutoRedraw is set to True, use the object's hDC property."
And just an FYI, the "Handle" or hDC is the default property of the "Picture" object, and that's actually what's being passed into the API. However, if you don't have persistent graphics (i.e., AutoRedraw = True), you don't have a persistent graphic to pass.
Do these need to be any particular format? 16x16? colour depth etc..? Any specifics?
Despite claims that you can only use 16x16 images, the Flag Bitmaps in Imagelist are 23x15 pixel (24bpp).
You have to get a picture with handle from the imagelist to send to menu item. With Imagelist you have the ListImages collection to work with.
You can load the bitmaps from resource file but you will need an array or collection of pictures since all the bitmaps need to be persistent during the popup.
Attached is a modified project that does the same as Imagelist except that it gets its bitmaps from resource file.
Final Question: How do we get images with Transparency? Hence we can't use Icons [well we can but would require some deal of coding/conversione etc..] but bitmaps instead. I tried placing a PINK BG on the Image, but it seems its not getting rid of it automatically.
Any ideas?
Last edited by some1uk03; Oct 21st, 2014 at 05:44 AM.
Yes, via the SetMenuItemBitmaps but just tested with a white Background and same thing. It doesn't render white as transparent? :/
Perhaps it requires BitBlt'ing a PINK BG and then use the outputted picture?
Yep, forgot that for the transparency to work correctly, the icons for that API need to be monochrome:
Code:
The selected and clear bitmaps should be monochrome. The system uses the Boolean AND operator to combine bitmaps with the menu so that the white part becomes transparent and the black part becomes the menu-item color. If you use color bitmaps, the results may be undesirable.
I'm not sure there is an easy way to draw transparency without making the menus owner-drawn & then rendering the icon/bitmap as desired. An alternate method, not much easier, would be to obtain the menu's bkg color (theme & O/S specific) and replace "white" with that color so the bitmap appears to be transparent. Sorry, off top of my head, no easy solutions come to mind
Insomnia is just a byproduct of, "It can't be done"