I have a listview created as a resource and loaded on a dialog window. I want to detect and show a context menu only when items within the listview have been clicked.
MESSAGE_HANDLER(WM_CONTEXTMENU,OnContextMenu)
LRESULT OnContextMenu(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
int iSelected = -1;
int iFocusGroup = -1;
iSelected = SendMessage((HWND)wParam, LVM_GETNEXTITEM, -1,LVNI_SELECTED);
iFocusGroup = ListView_GetFocusedGroup((HWND)wParam);
if( iSelected != -1 && iFocusGroup == -1) {
hPopupMenu = CreatePopupMenu();
Insert
Menu(hPopupMenu, 0, MF_BYCOMMAND | MF_STRING | MF_ENABLED, ID_SHREDTASK_CTXMENU_DELETE, TEXT("Delete"));
TrackPopupMenu(hPopupMenu, TPM_TOPALIGN | TPM_LEFTALIGN, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0, m_hWnd, NULL);
}
return 0;
}
OK, I've edited this and it works the way it is presented here but the question still stands and can someone explain to me what's the thing with focus group here and why if I send the LVM_GETNEXTITEM message while in the dialog it returns != -1 ? isn't it solely for Listviews ?
EDIT :
Here is another alternative that I've worked out based on your responses:
LRESULT OnNotifyRClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
switch (uMsg)
{
case WM_NOTIFY:
switch (((LPNMHDR)lParam)->code)
{
case NM_RCLICK:
if (((LPNMHDR)lParam)->idFrom == IDC_LISTTASKFILES)
{
int iSelected = -1;
iSelected = SendMessage(GetDlgItem(IDC_LISTTASKFILES), LVM_GETNEXTITEM, -1,LVNI_SELECTED);
if( iSelected != -1 ) {
hPopupMenu = CreatePopupMenu();
InsertMenu(hPopupMenu, 0, MF_BYCOMMAND | MF_STRING | MF_ENABLED, ID_SHREDTASK_CTXMENU_DELETE, TEXT("Delete"));
TrackPopupMenu(hPopupMenu, TPM_TOPALIGN | TPM_LEFTALIGN, ((CPoint)GetMessagePos()).x, ((CPoint)GetMessagePos()).y, 0, m_hWnd, NULL);
}
bHandled = true;
return TRUE;
}
break;
break;
}
}
return false;
}
NM_RCLICK is your friend.
But it doesn't solve the whole problem, such as displaying a context menu when user hits the Windows menu key on his keyboard. This KB article shows how to combine NM_RCLICK and WM_CONTEXTMENU. (It's for the CTreeCtrl but adapting the code to CListView is trivial).
You will have OnContextMenu handler called regardless of click position within the listview. Now your task is to see where exactly click happened and decide on the action you want.
Your question make me think that you grabbed the code with ListView_GetFocusedGroup from internet as opposed to intentially writing it yourself. What you need to do however, is to send "hit test" message back to list view providing the point of interest (which is the click point): ListView_HitTest, ListView_HitTestEx.
Having this done you obtain the item and/or subitem in this location, and you can decide what to do next.
Related
I'm trying to add an EDIT control to a window used as a dropdown for a custom combobox-like control. Initially this dropdown window was implemented as a child (WS_CHILD) window of the desktop, which is similar to the "ComboLbox" window used by the real combobox. This worked just fine, however an EDIT window seems to just refuse to accept focus when it is put into such dropdown window. I.e. it is enabled and reacts to right mouse clicks, for example, but clicking on it or calling SetFocus() fails (the latter sets last error to ERROR_INVALID_PARAMETER).
Because of this, and also because of the way custom popup windows are implemented in many examples including Raymond Chen's fakemenu sample, I've changed the dropdown implementation to use WS_POPUP, with the main application window as owner. This has a known problem with stealing activation from the owner window when the popup is shown, however this can be addressed by returning MA_NOACTIVATE from WM_MOUSEACTIVATE handler for the popup window and it indeed works well initially, i.e. the owner window keeps activation when the popup shows up. But as soon as I click the EDIT control inside the popup, it calls, from its default window proc, SetFocus() to set the focus to itself, which deactivates the parent window.
My question is how can I prevent this from happening? I know that it can be done because WinForms ToolStripManager manages to allow editing text in a dropdown without deactivating the parent window and it also uses WS_POPUP style for the popup window. But how does it do it?
A solution was suggested in comments "prevent the host window from visibly appearing inactive by handling WM_NCACTIVATE" This should work as shown in the example below.
When menu window is opened, the host window (HostProc) will receive WM_NCACTIVATE message. Host will look for "menuclass", if menu class is found then host will return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam); to prevent the title bar for host window get painted inactive.
You also need to handle WM_NCACTIVATE in fake menu window. When menu window goes out of focus, WM_NCACTIVATE is received by MenuProc, at this point the menu can close itself.
#include <windows.h>
const wchar_t* menuclass = L"menuclass";
LRESULT CALLBACK MenuProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_CREATE:
CreateWindow(L"Edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 160, 30,
hwnd, NULL, NULL, NULL);
break;
case WM_NCACTIVATE:
{
if(!wparam)
{
//close the menu if its losing focus
PostMessage(hwnd, WM_CLOSE, 0, 0);
//tell parent to paint inactive, if user clicked on a different program
POINT pt;
GetCursorPos(&pt);
HWND hit = WindowFromPoint(pt);
HWND hparent = GetParent(hwnd);
if(hit != hparent && !IsChild(hparent, hit))
DefWindowProc(hparent, WM_NCACTIVATE, FALSE, 0);
}
break;
}
case WM_LBUTTONDOWN:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
//also handles other mouse/key messages associated with a menu...
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
LRESULT CALLBACK HostProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_NCACTIVATE:
//paint the window as active when custom menu starts
if(!wparam && FindWindow(menuclass, NULL))
return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam);
break;
case WM_RBUTTONUP:
{
//show the custom menu
POINT pt;
GetCursorPos(&pt);
CreateWindow(menuclass, NULL, WS_VISIBLE | WS_POPUP | WS_BORDER,
pt.x, pt.y, 200, 400, hwnd, 0, 0, 0);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpfnWndProc = HostProc;
wcex.lpszClassName = L"hostwnd";
RegisterClassEx(&wcex);
wcex.lpfnWndProc = MenuProc;
wcex.lpszClassName = menuclass;
RegisterClassEx(&wcex);
CreateWindow(L"hostwnd", L"Right click for menu ...",
WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, 0, 0, hInstance, 0);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
I find different examples in the Internet of how to program a PropertySheet in WinAPI, but they are not complete.
The code I am using is shown below. I have a PropertySheet with 3 Tabs, each one with a Dialog.
The different Dialogs are called, when I click the Tabs, so far it is working.
However, when I leave the PropertySheet pressing OK button, how do I get the contents in the Textboxes etc. of each Dialog?
Normally I used to do that in the DialogProc when WM_COMMAND/IDOK was received using:
GetDlgItemText( hDlg,IDC_TEXTBOX1, buf, 100);
But in the PropertySheet there is only one OK Button for all dialogs, no WM_COMMAND/IDOK is received in the DialogProc.
What shall I do?
Resource_file:
IDD_DIALOG_1 DIALOGEX 0, 0, 385, 186
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
BEGIN
LTEXT "param",IDC_STATIC,6,23,39,10
EDITTEXT IDC_TEXTBOX1,48,20,237,15
END
C Source:
LRESULT CALLBACK
Dialog1(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
char buf[500];
char* ptr;
int p; // =lParam, rin of edited person
int f;
switch (message)
{
case WM_INITDIALOG:
{
SetDlgItemText(hDlg, IDC_TEXTBOX1, "something");
return 0;
}
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDOK: // never reached (OK Button belongs to the PropertySheet!)
}
}
}
return FALSE;
} /* Dialog1 */
INT_PTR DoPropertySheet(HWND hwndOwner, LPARAM p)
{
PROPSHEETPAGE psp[3];
PROPSHEETHEADER psh;
memset(psp,0,sizeof(psp));
for(int i=0;i<3; i++)
{
psp[i].dwSize = sizeof(PROPSHEETPAGE);
psp[i].dwFlags = PSP_USETITLE;
psp[i].hInstance = hInstance;
psp[i].lParam = p;
}
psp[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG_1);
psp[0].pfnDlgProc = (DLGPROC)Dialog1;
psp[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG_2);
psp[1].pfnDlgProc = (DLGPROC)Dialog2;
psp[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG_3);
psp[2].pfnDlgProc = (DLGPROC)Dialog3;
psh.dwSize = sizeof(PROPSHEETHEADER);
psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
psh.hwndParent = hwndOwner;
psh.hInstance = hInstance;
psh.pszIcon = 0;
psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
psh.nStartPage = 0;
psh.ppsp = (LPCPROPSHEETPAGE) &psp;
psh.pfnCallback = NULL;
if (PropertySheet(&psh)) // 0:cancel, otherwise:1
{
//get contens of propertySheet here?? how??
}
return 0;
}
when user press OK or Apply all your pages got PSN_APPLY notification code. so you need look for WM_NOTIFY with PSN_APPLY code
when user press cancel you got PSN_RESET notification
INT_PTR CALLBACK PPDlgProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam)
{
union {
LPARAM lp;
NMHDR* hdr;
PSHNOTIFY* psn;
};
switch (umsg)
{
case WM_NOTIFY:
lp = lParam;
switch (hdr->code)
{
case PSN_APPLY:
DbgPrint("apply");
break;
case PSN_RESET:
DbgPrint("cancel\n");
break;
}
break;
}
return 0;
}
quick question...
I am working with treeview in win32 (VC++).
I want to remove selection facility provided for treeview. Can anyone tell what window message is posted onAfterSelect Event of tree view.
TV also has checkboxes. So disabling mouse click isn't an option...
Thanks in advance...
-
Varun
More Info
I am stuck at another point. My win32 application is essentially a modeless dialog - using CreateDialog & ShowWindow. After getting TVN_SELCHANGING, when I am returning 1, it isn't working. I think the default wndproc is getting called before I bypass the windows message. What should I do now?
I had this problem and just reversed the selection once it had already taken place. If you're not responding to it, anyway, then there shouldn't be any side effects.
case WM_NOTIFY:
{
if(wParam == IDC_TREE_MC)
{
LPNMHDR lpnmh = (LPNMHDR) lParam;
TVHITTESTINFO ht = {0};
if ((lpnmh->code == NM_CLICK) && (lpnmh->idFrom == IDC_TREE_MC)) // For Treeview Check Box Check Event
{
DWORD dwpos = GetMessagePos();
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)
PostMessage(hDlg, UM_CHECKSTATECHANGE, (WPARAM)lpnmh->hwndFrom, (LPARAM)ht.hItem);
else
TreeView_SelectItem(lpnmh->hwndFrom, NULL);
}
else if ((lpnmh->code == TVN_SELCHANGED ) && (lpnmh->idFrom == IDC_TREE_MC))
TreeView_SelectNode(lpnmh->hwndFrom, NULL);
}
break;
}
to remove selection facility provided for treeview
Could you please clarify this?
Do you want to prevent user from changing selection?
If you really want to do it, insert WM_NOTIFY case handler in the parent window, check for NMTREEVIEW code member (lParam is a pointer to NMTREEVIEW).
If code is TVN_SELCHANGING return 1 if you want to prevent selection change.
Returning 0 will alow selection change.
int CALLBACK WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
hWndDialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, WndProc);
if (hWndDialog != NULL)
{
ShowWindow(hWndDialog, SW_SHOW);
}
while(GetMessage(&Msg, NULL, 0, 0))
{
if(!IsDialogMessage(hWndDialog, &Msg))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
return 0;
}
INT_PTR CALLBACK WndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_BTN_REFRESH:
RefreshButtonHandler();
break;
case IDC_BTN_ADD_INSTALL:
AddInstallBtnHandler();
break;
case IDOK:
case IDCANCEL:
DestroyWindow(hDlg);
PostQuitMessage(0);
return (INT_PTR)TRUE;
break;
}
case WM_NOTIFY:
{
if(wParam == IDC_TREE_MC)
{
LPNMHDR lpnmh = (LPNMHDR) lParam;
TVHITTESTINFO ht = {0};
if ((lpnmh->code == NM_CLICK) && (lpnmh->idFrom == IDC_TREE_MC)) // For Treeview Check Box Check Event
{
DWORD dwpos = GetMessagePos();
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)
PostMessage(hDlg, UM_CHECKSTATECHANGE, (WPARAM)lpnmh->hwndFrom, (LPARAM)ht.hItem);
else
TreeView_SelectItem(lpnmh->hwndFrom, NULL);
}
else if ((lpnmh->code == TVN_SELCHANGING ) && (lpnmh->idFrom == IDC_TREE_MC))
return (INT_PTR)TRUE;
}
break;
}
case UM_CHECKSTATECHANGE:
{
//Handle TreeView Check State Event
}
break;
}
return (INT_PTR)FALSE;
}
Sorry for the bad formatting... I am sleep deprived :-)
I'm trying to create ListView with fixed width columns in winapi C++ project.
I try use a trick with handling a HDN_BEGINTRACK notification in Dialog Box Procedure just by returning TRUE in it. As I understood from different articles it might work.
Point is, that I catch it, but returning TRUE does not prevent from resizing.
Also tried returning TRUE just after getting WM_NOTIFY - the same effect.
Help me please. Here are some parts of code:
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance;
----
hDlg = CreateDialog(hInst,MAKEINTRESOURCE(IDD_DIALOG),
hWnd,(DLGPROC)DlgProc);
INITCOMMONCONTROLSEX icex;
icex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex);
HWND hWndListView = CreateWindow(
WC_LISTVIEW,
L"",
WS_CHILD | LVS_REPORT |WS_VISIBLE,
0, 0,
382,
200,
hDlg,
(HMENU)IDC_LIST,
hInst,
NULL);
----
//adding columns and items
----
DWORD exStyle = LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES;
ListView_SetExtendedListViewStyle(hWndListView,exStyle);
SetWindowTheme(hWndListView, L"Explorer", NULL);
----
return TRUE;
}
BOOL CALLBACK DlgProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
----
case WM_NOTIFY:
switch(((LPNMHDR)lParam)->code)
{
case HDN_BEGINTRACK:
OutputDebugString(L"HDN_BEGINTRACK\n");
return TRUE;
default:
break;
}
----
}
return FALSE;
}
Thanks in advance!
Returning TRUE from WM_NOTIFY in a dialog procedure only shows you've processed the message. To actually return a result value, you must also set the dialog's DWL_MSGRESULT:
case HDN_BEGINTRACK:
OutputDebugString(L"HDN_BEGINTRACK\n");
SetWindowLong(hDlg, DWL_MSGRESULT, TRUE); // prevent resizing
return TRUE; // message processed; check DWL_MSGRESULT for real result
The code below creates a window, implements a CListViewCtrl and tries to make a context menu with 3 options: new, edit & delete.
class CGuiView : public CWindowImpl<CGuiView, CListViewCtrl>
{
HMENU hPopupMenu;
MENUINFO m_ContextMenuInfo;
public:
DECLARE_WND_CLASS(NULL)
BOOL PreTranslateMessage(MSG* pMsg)
{
pMsg;
return FALSE;
}
BEGIN_MSG_MAP(CGuiView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
// MESSAGE_HANDLER(WM_LBUTTONUP,)
MSG_WM_CONTEXTMENU(OnContextMenu)
COMMAND_ID_HANDLER(ID_CTXMENU_NEW, OnNewTask)
END_MSG_MAP()
LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
CPaintDC dc(m_hWnd);
//TODO: Add your drawing code here
return 0;
}
void OnContextMenu ( HWND hwndCtrl, CPoint ptClick )
{
hPopupMenu = CreatePopupMenu();
InsertMenu(hPopupMenu, 0, MF_BYCOMMAND | MF_STRING, ID_CTXMENU_DELETE, TEXT("Delete"));
InsertMenu(hPopupMenu, ID_CTXMENU_DELETE, MF_BYCOMMAND | MF_STRING, ID_CTXMENU_EDIT, TEXT("Edit"));
InsertMenu(hPopupMenu, ID_CTXMENU_EDIT, MF_BYCOMMAND | MF_STRING | MF_ENABLED, ID_CTXMENU_NEW , TEXT("New"));
TrackPopupMenu(hPopupMenu, TPM_TOPALIGN | TPM_LEFTALIGN, ptClick.x, ptClick.y, 0,GetParent(), NULL);
}
LRESULT OnNewTask(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
CAboutDlg dlg;
dlg.DoModal();
return 0;
}
};
The problem is that the code above does not enter OnNewTask when I click on the New menu item that I create in the OnContextMenu function. What I am doing wrong?
You are providing GetParent() as an argument to the TrackPopupMenu API function. So WM_COMMAND is sent to the list view parent, not list view itself.
Have it sent to the list view, or forward commands from parent to list view. You can also use Spy++ tool to check the messages in the debugged process to see what exactly is sent and where.