Why do I not receive the WM_MENUCHAR message? - c++

I implemented an IContextMenu3 interface and I am trying to capture keystrokes for my own custom accelerator. The problem is that if i hover over my submenu in the root menu I dont get any WM_MENUCHAR messages whereas if I hover over a submenu that is inside one of my submenu's then I do.
I know that the WM_INITMENUPOPUP message only gets sent if there is a child. The WM_MENUCHARhas the caveat that no accelerators are bound to the key. I know this caveat to be held since when I press a key, I get the distinctive 'no-accelerator' beep.
Is there another caveat that I am not aware of?
This is the smallest code I can get that reproduces the problem:
HRESULT CFolderViewImplContextMenu::QueryContextMenu(HMENU hmenu, UINT uMenuIndex, UINT idCmdFirst, UINT idCmdLast, UINT /* uFlags */)
{
UINT uID = idCmdFirst;
HMENU hSubmenu = CreatePopupMenu();
MENUITEMINFO mii = { 0 };
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_SUBMENU | MIIM_ID | MIIM_STRING;
mii.dwTypeData = str_toWchar("test");
mii.wID = uID++;
mii.hSubMenu = hSubmenu;
InsertMenuItem ( hmenu, 0, TRUE, &mii );
InsertMenu ( hSubmenu, 0, MF_BYPOSITION, uID++, L"&Notepad" );
InsertMenu ( hSubmenu, 1, MF_BYPOSITION , uID++, L"&Internet Explorer" );
HMENU hSubmenu2 = CreatePopupMenu();
MENUITEMINFO mii2 = {0};
mii2.cbSize = sizeof(MENUITEMINFO);
mii2.fMask = MIIM_ID | MIIM_TYPE | MIIM_SUBMENU;
mii2.fType = MFT_OWNERDRAW;
mii2.wID = uID++;
mii2.hSubMenu = hSubmenu2;
InsertMenuItem ( hSubmenu, 0, TRUE, &mii2 );
InsertMenuA ( hSubmenu2, 0, MF_BYPOSITION, uID++, "");
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, uID - idCmdFirst );
}

WM_MENUCHAR is forwarded only for submenus. (It can't be forwarded for top-level menu items because that would be a Catch-22. You want to forward it to the context menu handler for the menu item that the the key corresponds to, but you can't do that until you have the answer to WM_MENUCHAR!)

How about this:
If you are handling IContextMenu3 messages, consequently WM_DRAWITEM, you can use WindowFromDC() to get menu window's HWND from WM_DRAWITEM, then subclass it and catch WM_KEYDOWN or do whatever you like with it.
I tried it (was doing some other stuff than this) and it works.

The problem is the first item in the submenu. If the first item in the submenu is also a submenu, then the message doesnt get passed. So I instead put a normal item there.

Related

Tiling menuItems in win32 as per particular design

I am creating a pop up menu item in win32 . I was able to create a popup menu item and then I added few icons to it. This is how its looking
sameple code
HMENU Controls = CreatePopupMenu();
InsertMenu(Controls, 0 , MF_STRING, 1, L"FirstMenu00");;
static HBITMAP bmpSource = NULL;
bmpSource = (HBITMAP)LoadImage(NULL, L"C:\\Users\\mac\\bit.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
MENUITEMINFO mii{};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_ID | MIIM_BITMAP | MIIM_DATA;
mii.wID = 1;
mii.hbmpItem = bmpSource;// &paHbm[i];
bool st = InsertMenuItem(Controls, 10, TRUE, &mii);
std::string errSTr = GetLastErrorAsString();
TrackPopupMenuEx(Controls, TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_LEFTBUTTON | TPM_HORPOSANIMATION, xPos, yPos, hwnd, NULL);
I am trying to arrange the menu icons in the following fashion
Ie I am trying to create a pattern where first row has 2 icons and the next row has 3 icons .
How can I achieve the same ?
You can use MF_MENUBREAK or MF_MENUBARBREAK flag in AppendMenu function to create a multi-column menu item. It seems that each raw has the same columns.
Here is a sample you can refer to:\
HMENU hmenuPopup = CreatePopupMenu();
// Add the bitmap menu items to the menu.
bool st = AppendMenu(hmenuPopup, MF_BITMAP, uID, (LPCWSTR)paHbm);
if (!st)
{
ErrorExit(L"AddBitmapMenu");
}
st = AppendMenu(hmenuPopup, MF_BITMAP | MF_MENUBARBREAK, uID + 1, (LPCWSTR)LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP2)));
if (!st)
{
ErrorExit(L"AddBitmapMenu");
}
More reference: Menu Item Separators and Line Breaks, Displaying menu items in multiple columns

Creating TreeView with nodes and checkboxes

I have created TreeView like this:
TreeView=CreateWindowEx(0, WC_TREEVIEW, TEXT("Tree View"), WS_VISIBLE | WS_CHILD, 0, 0, 200, 500, hwnd, (HMENU)ID_TREE_VIEW, GetModuleHandle(NULL), NULL);
Now I added one item to it like shown on this website.
It all okay, but after hours and hours of googling I still didn't found answer to these questions:
How to add subitems (nodes)?
How to add checkbox on each item (how to determine if specified checkbox is checked)?
EDIT #4:
In response to OPs request, I have added an example that removes checkbox from a parent node.
THE PROBLEM IS THAT CHECKBOX STILL APPEARS WHEN USER SELECTS A NODE AND PRESSES SPACEBAR.
This question solves that problem.
EDIT #3:
I have added the code that creates a node that is already checked.
It is the second child bode in the WM_CREATE handler.
END OF EDIT
case WM_CREATE:
{
// this is your treeview
TreeView = CreateWindowEx(0, WC_TREEVIEW,
TEXT("Tree View"), WS_VISIBLE | WS_CHILD,
0, 0, 200, 500,
hwnd, (HMENU)ID_TREE_VIEW, GetModuleHandle(NULL), NULL);
/************ enable checkboxes **************/
DWORD dwStyle = GetWindowLong( TreeView , GWL_STYLE);
dwStyle |= TVS_CHECKBOXES;
SetWindowLongPtr( TreeView , GWL_STYLE, dwStyle );
/************ add items and subitems **********/
// add root item
TVINSERTSTRUCT tvis = {0};
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is root item";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = TVI_ROOT;
HTREEITEM hRootItem = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// and here is an example of removing a checkbox
// from a specific item/subitem in case you ever need it
TVITEM tvi;
tvi.hItem = hRootItem ;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
TreeView_SetItem( TreeView, &tvi );
// add firts subitem for the hTreeItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is first subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
HTREEITEM hTreeSubItem1 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// now we insert second subitem for hRootItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT | TVIF_STATE; // added extra flag
tvis.item.pszText = L"This is second subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
// for demonstration purposes let us check this node;
// to do that add the following code, and add the extra flag for
// mask member like above
tvis.item.stateMask = TVIS_STATEIMAGEMASK;
tvis.item.state = 2 << 12;
HTREEITEM hTreeSubItem2 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// let us expand the root node so we can see if checked state is really set
TreeView_Expand( TreeView, hRootItem, TVE_EXPAND );
}
return 0L;
EDIT #2:
Here Is the part that explains how to check if item is checked ( it now properly checks when you click on a checkbox and when you press spacebar! ) :
case WM_NOTIFY:
{
LPNMHDR lpnmh = (LPNMHDR) lParam;
if( lpnmh->idFrom == ID_TREE_VIEW ) // if this is our treeview control
{
switch( lpnmh->code ) // let us filter notifications
{
case TVN_KEYDOWN: // tree has keyboard focus and user pressed a key
{
LPNMTVKEYDOWN ptvkd = (LPNMTVKEYDOWN)lParam;
if( ptvkd->wVKey == VK_SPACE ) // if user pressed spacebar
{
// get the currently selected item
HTREEITEM ht = TreeView_GetSelection( ptvkd->hdr.hwndFrom );
// Prepare to test items state
TVITEM tvItem;
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = (HTREEITEM)ht;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
// Request the information.
TreeView_GetItem( ptvkd->hdr.hwndFrom, &tvItem );
// Return zero if it's not checked, or nonzero otherwise.
if( (BOOL)(tvItem.state >> 12) - 1 )
MessageBox( hwnd, L"Not checked!", L"", MB_OK );
else
MessageBox( hwnd, L"Checked!", L"", MB_OK );
}
}
return 0L; // see the documentation for TVN_KEYDOWN
case NM_CLICK: // user clicked on a tree
{
TVHITTESTINFO ht = {0};
DWORD dwpos = GetMessagePos();
// include <windowsx.h> and <windows.h> header files
ht.pt.x = GET_X_LPARAM(dwpos);
ht.pt.y = GET_Y_LPARAM(dwpos);
MapWindowPoints( HWND_DESKTOP, lpnmh->hwndFrom, &ht.pt, 1 );
TreeView_HitTest(lpnmh->hwndFrom, &ht);
if(TVHT_ONITEMSTATEICON & ht.flags)
{
// Prepare to receive the desired information.
TVITEM tvItem;
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = (HTREEITEM)ht.hItem;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
// Request the information.
TreeView_GetItem( lpnmh->hwndFrom, &tvItem );
// Return zero if it's not checked, or nonzero otherwise.
if( (BOOL)(tvItem.state >> 12) - 1 )
MessageBox( hwnd, L"Not checked!", L"", MB_OK );
else
MessageBox( hwnd, L"Checked!", L"", MB_OK );
}
}
default:
break;
}
}
}
break;
The relevant idea for proper testing when spacebar is pressed is handling of TVN_KEYDOWN message.
We use this message to get NMTVKEYDOWN structure filled, which will give us virtual key code of the pressed button and the HWND of the treeview that sent the notification.
Now we use TreeView_GetItem() macro to get the currently selected node and we check its state the same way we did when we did hit testing.
My only problem is concerning this part from the documentation for TVN_KEYDOWN:
Return value
If the wVKey member of lParam is a character key code, the character
will be used as part of an incremental search. Return nonzero to
exclude the character from the incremental search, or zero to include
the character in the search. For all other keys, the return value is
ignored.
I just do not know what to do with the return result so I have put 0L.
Important note: If you need to return value from dialog box procedure use something like this:
SetWindowLongPtr( hwnd, DWLP_MSGRESULT, (LONG_PTR)1 );
return TRUE;
see the remarks for Return value in this documentation and use SetWindowLongPtr instead of SetWindowLong so you can support both x32 and x64 versions of Windows.
That would be all. Hopefully you have your problem solved. If you need further help leave a comment.
END OF EDIT
I have never done checking if tree item is checked but I believe that accepted answer to this question is the way to go.
NOTE:
I would highly appreciate if there someone who can provide code snippet for showing how to determine if treeview node is checked or not.

Win32 Toolbar Dropdown Button Message Handling

I have a toolbar to a window in my application. I have read MSDN docs and can handle command and notification messages. My problem is with a dropdown menu attached to a button on the toolbar. The menu items open modal dialog boxes and wait for user to finalize settings changes. After the user clicks 'ok' or 'cancel' focus returns to the main window, however, the toolbar is still under the impression that the left mouse button is down so everytime i drag the mouse over the toolbar button that was clicked the button takes the 'button is checked' state and 'appears' to be pressed.
Is the problem related to the tracking of mouse events?
Here is notification message handling within window proc:
case WM_NOTIFY:
{
LPNMHDR lpnm = ( ( LPNMHDR )lParam );
LPNMTOOLBAR lpnmTB = ( ( LPNMTOOLBAR )lParam );
switch( lpnm->code )
{
case TBN_DROPDOWN:
{
// Get the coordinates of the button.
RECT rc;
SendMessage( lpnmTB->hdr.hwndFrom, TB_GETRECT, ( WPARAM )lpnmTB->iItem, ( LPARAM )&rc );
// Convert to screen coordinates.
MapWindowPoints( lpnmTB->hdr.hwndFrom, HWND_DESKTOP, ( LPPOINT )&rc, 2 );
// handle dropdown menus
return HandleTexEditDropdown( hWnd, lpnmTB, rc );
}
default:
break;
}
break;
}
Here is handle TexEditDropdown():
LRESULT CALLBACK CWindowManager::HandleTexEditDropdown( HWND hWnd, LPNMTOOLBAR lpnm, RECT &rc )
{
HRESULT hr = S_OK;
switch( lpnm->iItem )
{
case IDM_EDITTEXTURE_FILL:
{
// Get the menu.
HMENU hMenuLoaded = LoadMenu( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDR_BUCKETFILL ) );
// Get the submenu for the first menu item.
HMENU hPopupMenu = GetSubMenu( hMenuLoaded, 0 );
// Set up the pop-up menu.
// In case the toolbar is too close to the bottom of the screen,
// set rcExclude equal to the button rectangle and the menu will appear above
// the button, and not below it.
TPMPARAMS tpm;
tpm.cbSize = sizeof( TPMPARAMS );
tpm.rcExclude = rc;
// Show the menu and wait for input.
// If the user selects an item, its WM_COMMAND is sent.
INT nCmd = TrackPopupMenuEx( hPopupMenu,
TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL | TPM_RETURNCMD,
rc.left, rc.bottom, hWnd, &tpm );
DestroyMenu( hMenuLoaded );
switch( nCmd )
{
case IDM_BUCKETFILLSETTINGS:
DialogBox( GetModuleHandle( NULL ),
MAKEINTRESOURCE( IDD_TEXEDITBUCKETFILL ),
hWnd,
( DLGPROC )TexEditSettingsProc );
break;
default:
return 0;
}
SendMessage( ( HWND )lpnm->hdr.hwndFrom, TB_MARKBUTTON, lpnm->iItem, MAKELPARAM( FALSE, 0 ) );
UpdateWindow( hWnd );
SetStateChange();
return 1;
}
case IDM_EDITTEXTURE_RECOVER:
{
// Get the menu.
HMENU hMenuLoaded = LoadMenu( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDR_RECOVER ) );
// Get the submenu for the first menu item.
HMENU hPopupMenu = GetSubMenu( hMenuLoaded, 0 );
// Set up the pop-up menu.
// In case the toolbar is too close to the bottom of the screen,
// set rcExclude equal to the button rectangle and the menu will appear above
// the button, and not below it.
TPMPARAMS tpm;
tpm.cbSize = sizeof( TPMPARAMS );
tpm.rcExclude = rc;
// Show the menu and wait for input.
// If the user selects an item, its WM_COMMAND is sent.
INT nCmd = TrackPopupMenuEx( hPopupMenu,
TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL | TPM_RETURNCMD,
rc.left, rc.bottom, hWnd, &tpm );
DestroyMenu( hMenuLoaded );
switch( nCmd )
{
case IDM_RECOVERFILE:
{
WCHAR wcs[ MAX_PATH ] = L"",
wcsFiletype[ MAX_PATH ] = L"",
wcsFilename[ MAX_PATH ] = L"";
D3DXIMAGE_INFO info;
// get currently loaded image info
if( FAILED( hr = CTextureEditor::GetImageInfo( &info ) ) )
{
DebugStringDX( ClassName, "Failed to CTextureEditor::GetImageInfo() at AuxiliaryViewportProcess()", __LINE__, hr );
break;
}
if( !CTextureEditor::CatImageFileType( info.ImageFileFormat, wcsFiletype ) )
{
DebugStringDX( ClassName, "Invalid image filetype at AuxiliaryViewportProcess()", __LINE__, hr );
break;
}
wsprintf( wcs, L"GDEImage Filetype (*%s)", wcsFiletype );
memcpy( &wcs[ 26 ], L"*", sizeof( WCHAR ) );
memcpy( &wcs[ 27 ], wcsFiletype, 4 * sizeof( WCHAR ) );
// Declare and initialize an OPENFILENAME struct to use for OpenFile Dialog
OPENFILENAME ofn;
ZeroMemory( ( void* )&ofn, sizeof( OPENFILENAME ) );
ofn.lStructSize = sizeof( ofn ); // SEE NOTE BELOW
ofn.hwndOwner = hWnd;
ofn.lpstrTitle = L"Recover Image From File";
ofn.lpstrFilter = wcs;
ofn.lpstrFile = wcsFilename;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = L"image file";
ofn.lpstrInitialDir = app.GetBinDirectory();
// OpenFile Dialog
if( GetOpenFileName( &ofn ) )
{
if( FAILED( hr = CTextureEditor::Recover( 0, wcsFilename ) ) )
{
DebugStringDX( ClassName, "Failed to CTextureEditor::Recover() at AuxiliaryViewportProc()", __LINE__, hr );
break;
}
}
break;
}
default:
return 0;
}
SendMessage( ( HWND )lpnm->hdr.hwndFrom, TB_MARKBUTTON, lpnm->iItem, MAKELPARAM( FALSE, 0 ) );
UpdateWindow( hWnd );
SetStateChange();
return 1;
}
default:
return 0;
}
return 0;
}
I think the returned value for TBN_DROPDOWN is not correct -- you are returning 1 which maps to TBDDRET_NODEFAULT meaning "The drop-down was not handled", you'll need to return TBDDRET_DEFAULT or TBDDRET_TREATPRESSED.

Selecting and Highlighting an Item from List View

I would like to select and highlight an item from a list view control and am using the following code
#include <Windows.h>
#include <commctrl.h>
int main() {
//Hardcoded Handle to the ListView Windows of Add Printer Dialog
HWND hwndListView = (HWND)0x000206D6;
DWORD dwProcessID;
::GetWindowThreadProcessId( hwndListView, &dwProcessID );
HANDLE process=OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_QUERY_INFORMATION, FALSE, dwProcessID);
LVITEM lvi;
LVITEM* _lvi=(LVITEM*)VirtualAllocEx(process, NULL, sizeof(LVITEM), MEM_COMMIT, PAGE_READWRITE);
lvi.state = LVIS_FOCUSED | LVIS_SELECTED ;
lvi.stateMask = LVIS_FOCUSED | LVIS_SELECTED ;
lvi.mask = LVIF_STATE;
WriteProcessMemory(process, _lvi, &lvi, sizeof(LVITEM), NULL);
::SendMessage(hwndListView, LVM_SETITEMSTATE, (WPARAM)0, (LPARAM)_lvi);
VirtualFreeEx(process, _lvi, 0, MEM_RELEASE);
}
The result I am getting is
instead of the item getting selected and highlighted
Please let me know what might be going wrong
There are restrictions on which processes can set focus on a window, and chances are that the app selecting the ListView items does not satisfy those restrictions while the dialog is active. For example, the HWND being focused must be attached to the calling thread's message queue. So the highlighting app will have to use AttachThreadInput() before calling SetFocus() on another app's windows.

How do I include bitmaps on items I've added to the end of a context menu?

I'm currently writing a Windows Explorer Shell Extension. Everything is ok so far but I'm having trouble to insert menu items WITH MenuItemBitmaps at the end of the context menu.
Here is the code I used without the bitmaps:
HRESULT CSimpleShlExt::QueryContextMenu(HMENU hmenu, UINT /*uMenuIndex*/, UINT uidFirstCmd, UINT /*uidLastCmd*/, UINT uFlags)
{
InsertMenu(hmenu, -1, MF_SEPARATOR, uidFirstCmd++, _T(""));
InsertMenu(hmenu, -1, MF_STRING | MF_BYPOSITION, uidFirstCmd++, _T("SimpleShlExt Test Item"));
InsertMenu(hmenu, -1, MF_STRING | MF_BYPOSITION, uidFirstCmd++, _T("SimpleShlExt Test Item 2"));
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 3); // 3 because we do have three menu items!!!
}
This code does what I want. It adds a separator and two menu items at the end of the context menu when I right click in the Windows explorer.
I can also add bitmaps to these menu items with this code:
HRESULT CSimpleShlExt::QueryContextMenu(HMENU hmenu, UINT uMenuIndex, UINT uidFirstCmd, UINT /*uidLastCmd*/, UINT uFlags)
{
// load the bitmap from the resource
HBITMAP hBitmap = (HBITMAP)LoadImage((HMODULE)_AtlBaseModule.m_hInst,
MAKEINTRESOURCE(IDB_BITMAP), IMAGE_BITMAP, 16, 16, 0);
InsertMenu(hmenu, uMenuIndex++, MF_SEPARATOR, uidFirstCmd++, _T(""));
InsertMenu(hmenu, uMenuIndex++, MF_STRING | MF_BYPOSITION, uidFirstCmd++, _T("SimpleShlExt Test Item"));
SetMenuItemBitmaps(hmenu, uMenuIndex - 1, MF_BITMAP | MF_BYPOSITION, hBitmap, hBitmap);
InsertMenu(hmenu, uMenuIndex++, MF_STRING | MF_BYPOSITION, uidFirstCmd++, _T("SimpleShlExt Test Item 2"));
SetMenuItemBitmaps(hmenu, uMenuIndex - 1, MF_BITMAP | MF_BYPOSITION, hBitmap, hBitmap);
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 3); // 3 because we do have three menu items!!!
}
But now the menu items are placed somewhere in the middle of the context menu and not at the end. Simply setting -1 instead of uMenuIndex doesn't work. The menu items are indeed placed at the end but the bitmaps are not shown.
Any ideas?
The documentation for SetMenuItemBitmaps says nothing about accepting -1 as a valid position like InsertMenu does. You know the command IDs of the items you've added, and you know they're unique, so add the bitmaps by command instead of by position.
InsertMenu(hmenu, -1, MF_STRING | MF_BYPOSITION, uidFirstCmd, _T("SimpleShlExt Test Item"));
SetMenuItemBitmaps(hmenu, uidFirstCmd, MF_BITMAP | MF_BYCOMMAND, hBitmap, hBitmap);
++uidFirstCmd;
You're ignoring the instructions that the menu host has given you regarding where to put your menu items. The only reason you've seen success so far is because the menu host hasn't added any other items after you added yours, and all the other menu extensions have played by the rules and added their items where they were told. If they decide to ignore the rules like you, then they might end up at the end instead of yours.