Editing the treeview item label using win32. - winapi

How do we edit the item's labels and setting the edit control with the modified text?
I have been doing so but the item is not being override with the new text entered.Can anyone please help me out to achieve this by using win32.
This is what I had done.
if(((LPNMHDR)lParam)->code == TVN_BEGINLABELEDIT)
{
hEdit=TreeView_GetEditControl(hTree);
SetFocus(hEdit);
}
if(((LPNMHDR)lParam)->code == TVN_ENDLABELEDIT)
{
char Text[256] = "";
tvItem.hItem = Selected;
SendDlgItemMessage(hWnd,IDC_TREE,TVM_GETITEM,0,(LPARAM)&tvItem);
GetWindowText(hEdit, Text, sizeof(Text));
tvItem.pszText = Text;
SendDlgItemMessage(hWnd,IDC_TREE,TVM_SETITEM,0,(LPARAM)&tvItem);
}
What's the miracle you know it is showing the entered text in "tvItem.psText" ,but when I am setting the item it is not showing the item label modified and the label is same as old text.Can anyone please help to make that edited text appears on the item label.
Thanks In Advance,
Siva V

Read the documentation, it tells you what you need to know:
lParam
Pointer to an NMTVDISPINFO structure. The item member of this structure is a TVITEM structure whose hItem, lParam, and pszText members contain valid information about the item that was edited. If label editing was canceled, the pszText member of the TVITEM structure is NULL; otherwise, pszText is the address of the edited text.
Return value
If the pszText member is non-NULL, return TRUE to set the item's label to the edited text. Return FALSE to reject the edited text and revert to the original label.
Remarks
If the pszText member is NULL, the return value is ignored.
If you specified the LPSTR_TEXTCALLBACK value for this item and the pszText member is non-NULL, your TVN_ENDLABELEDIT handler should copy the text from pszText to your local storage.
So, try something like this:
...
if (((LPNMHDR)lParam)->code == TVN_BEGINLABELEDIT)
{
hEdit = TreeView_GetEditControl(hTree);
SetFocus(hEdit);
break;
}
if (((LPNMHDR)lParam)->code == TVN_ENDLABELEDIT)
{
if ((LPNMTVDISPINFO)lParam)->item.pszText != NULL)
return TRUE; // or FALSE to reject the next text...
break;
}
...

Below the snippet to stop editing on Escape/Enter keys.
// Global var
WNDPROC oldWndProc;
...
// In main WindowProc, WM_NOTIFY
NMHDR* pHdr = (LPNMHDR)lParam;
if (pHdr->code == TVN_BEGINLABELEDIT) {
HWND hEdit = TreeView_GetEditControl(hTree);
oldWndProc = (WNDPROC) SetWindowLongPtr(hEdit, GWL_WNDPROC, (LONG_PTR)&newWndProc);
}
...
LRESULT CALLBACK newWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_GETDLGCODE)
return (DLGC_WANTALLKEYS | CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam));
return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam);
}

This code just shows you some clues.
LPNMHDR hdr;
NMLVDISPINFO *di;
hdr = (LPNMHDR)lParam;
di = (NMLVDISPINFO*)lParam;
...
...
switch(hdr->code)
{
...
case TVN_ENDLABELEDIT:
if(di->tvItem.pszText != NULL)
{
//if you click 'Enter Key' after changing string, di->tvItem.pszText has already changed string.
//So, the edited string will change edit control label, by using 'return TRUE'
return TRUE;
}
else
{
//if you cancel to edit by using 'Esc Key', di->tvItem.pszText value is NULL
//So, the edited string will cancel, by using 'return FALSE'
return FALSE;
}
}

Related

How to cancel item label editing in Tree-View control upon ESC keydown in WinAPI

I have a dialog box with a Tree-View control where the user can edit the item labels. I want the user to be able to cancel the label edit by pressing ESC key.
The problem is that pressing ESC closes the dialog window immediately.
I have tried getting the handle to the EditBox control by a TreeView_GetEditControl() call upon TVN_BEGINLABELEDIT message and subclassing it to trap the ESC key, but when I do that, typing in edit box becomes impossible.
What is the problem?
Here is the relevant code:
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam) {
switch(message) {
//...
case WM_NOTIFY:
{
LPNMHDR pNmHdr = (LPNMHDR)lParam;
switch(pNmHdr->code) {
case TVN_BEGINLABELEDIT:
{
HWND hwndTV = (HWND)GetWindowLongPtr(hWnd, GWLP_USERDATA); // stored handle to Tree-View ctl
HWND hWndEditBox = TreeView_GetEditControl(hwndTV);
// subclass edit box
TreeViewGlobals::g_wpOrigEditBoxProc =
(WNDPROC)SetWindowLongPtr(hWndEditBox,
GWLP_WNDPROC, (LONG_PTR)EditBoxCtl_SubclassProc);
break;
}
case TVN_ENDLABELEDIT:
{
SetWindowLongPtr(hWnd, DWLP_MSGRESULT, (LONG)TRUE); // accept edit
return TRUE;
}
default:
break;
}
}
default:
break;
}
return FALSE;
}
INT_PTR CALLBACK EditBoxCtl_SubclassProc(HWND hWndEditBox, UINT message,
WPARAM wParam, LPARAM lParam) {
switch(message) {
HANDLE_MSG(hWndEditBox, WM_GETDLGCODE, EditBoxCtl_OnGetDlgCode);
HANDLE_MSG(hWndEditBox, WM_KEYDOWN, EditBoxCtl_OnKey); // does not receive WM_KEYDOWN for ESC unless I handle WM_GETDLGCODE above
default:
break;
}
return CallWindowProc(TreeViewGlobals::g_wpOrigEditBoxProc,
hWndEditBox, message, wParam, lParam);
}
UINT EditBoxCtl_OnGetDlgCode(HWND hWndEditBox, LPMSG lpmsg) {
if(lpmsg) {
if(lpmsg->message == WM_KEYDOWN && lpmsg->wParam == VK_ESCAPE) {
return DLGC_WANTMESSAGE;
}
}
return 0;
}
void EditBoxCtl_OnKey(HWND hWndEditBox, UINT vk, BOOL fDown,
int cRepeat, UINT flags) {
switch(vk) {
case VK_ESCAPE:
Beep(4000, 150); // never beeps
break;
default:
break;
}
}
P.S. I noticed that when I remove WM_GETDLGCODE handler in EditBoxCtl_SubclassProc(), it becomes possible to type in the edit box again, but then I can't trap WM_KEYDOWN for ESC key from that procedure.
Below is the solution that I found. The trick seems to be calling the original control proc with WM_GETDLGCODE intercepted in subclass proc, storing the return value and then returning it with DLGC_WANTALLKEYS or DLGC_WANTMESSAGE flag set to prevent system from further processing the keystroke.
The upside to this approach is that pressing ESC cancels editing and reverts the item label to its original text, and pressing ENTER while editing no longer just closes the dialog(which was another problem) without any additional code to handle those cases.
Here is the code that works:
INT_PTR CALLBACK EditBoxCtl_SubclassProc(HWND hWndEditBox, UINT message,
WPARAM wParam, LPARAM lParam) {
switch(message) {
//HANDLE_MSG(hWndEditBox, WM_GETDLGCODE, EditBoxCtl_OnGetDlgCode); // can't use this: need wParam and lParam for CallWindowProc()
case WM_GETDLGCODE: {
INT_PTR ret = CallWindowProc(TreeViewGlobals::g_wpOrigEditBoxProc,
hWndEditBox, message, wParam, lParam);
MSG* lpmsg = (MSG*)lParam;
if(lpmsg) {
if(lpmsg->message == WM_KEYDOWN &&
(lpmsg->wParam == VK_ESCAPE || lpmsg->wParam == VK_RETURN) )
{
return ret | DLGC_WANTALLKEYS;
}
}
return ret;
}
default:
break;
}
return CallWindowProc(TreeViewGlobals::g_wpOrigEditBoxProc,
hWndEditBox, message, wParam, lParam);
}
The problem is that the modal dialog has its own message loop and its own translation with IsDialogMessage. Using the MFC I would say, just use PreTranslateMessage but this isn't available in plain WinApi. You don't have access to the internal message loop and the keyboard interface.
So the Escape key is handled inside the message loop. And causes a WM_COMMAND message with IDCANCEL to be sent. (See the MSDN specs about dialogs)
Maybe the easiest way is to interrcept the WM_COMMAND message sent to the dialog, check if who has the focus and if the inplace edit control has the focus you just set the focus back to the tree control and eat forget the IDCANCEL and don't close the dialog.
you need remember the tree-view hwnd when you receive TVN_BEGINLABELEDIT (in class member, associated with dialog) and zero it when you receive TVN_ENDLABELEDIT. when user press esc or enter in modal dialog box - you receive WM_COMMAND with IDCANCEL (on esc) or IDOK( on enter). you need check saved tree-view hwnd and if it not 0 - call TreeView_EndEditLabelNow
switch (uMsg)
{
case WM_INITDIALOG:
m_hwndTV = 0;
break;
case WM_NOTIFY:
switch (reinterpret_cast<NMHDR*>(lParam)->code)
{
case TVN_BEGINLABELEDIT:
m_hwndTV = reinterpret_cast<NMHDR*>(lParam)->hwndFrom;
return TRUE;
case TVN_ENDLABELEDIT:
m_hwndTV = 0;
//set the item's label to the edited text
SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE);
return TRUE;
}
break;
case WM_CLOSE:
EndDialog(hwndDlg, 0);
break;
case WM_COMMAND:
switch (wParam)
{
case IDCANCEL:
if (m_hwndTV)
{
TreeView_EndEditLabelNow(m_hwndTV, TRUE);
}
else
{
EndDialog(hwndDlg, IDCANCEL);
}
break;
case IDOK:
if (m_hwndTV)
{
TreeView_EndEditLabelNow(m_hwndTV, FALSE);
}
else
{
EndDialog(hwndDlg, IDOK);
}
break;
}
break;
}

Hot tracking list item selection in a combo box

I have a combo box and I need to intercept the changement of the selection while the user changes the selection by just hovering with the mouse without clicking. This is for displaying complementary information about the item the user is hovering over.
CBN_SELCHANGE won't do the job, because this message gets fired only when the user has actually changed the selection by clicking on one of the combo box items or when the up/down keys are pressed.
Apparently no message is fired while the user is just hovering over the the combobox.
Illustration
E.g: I need to know when the user moves the mouse from the entry 2 to the entry 33.
This is c++ subclass based on c# article which you mentioned:
LRESULT CALLBACK ComboProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubClass, DWORD_PTR)
{
if (msg == WM_CTLCOLORLISTBOX)
{
COMBOBOXINFO ci = { sizeof(COMBOBOXINFO) };
GetComboBoxInfo(hwnd, &ci);
if (HWND(lParam) == ci.hwndList)
{
int pos = SendMessage(ci.hwndList, LB_GETCURSEL, 0, 0);
OutputDebugStringA(std::to_string(pos).c_str());
OutputDebugStringA("\n");
}
}
if (msg == WM_NCDESTROY)
{
RemoveWindowSubclass(hwnd, ComboProc, uIdSubClass);
}
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
...
SetWindowSubclass(hComboBox, ComboProc, 0, 0);
This was tested on Windows 10.
This can only report the hover selection in drop down list, it can't change the selection.

Winapi ListView can't prevent columns from resizing

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

Detect clicking inside listview and show context menu

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.

How to set the text on the "save" button in Windows' file dialog?

I'm trying to set the text on the "save" button of the Windows "Save file as..." dialog.
I've set up the hook, received the message, found the button (nb. If I call "GetWindowText()" I see "&Save" so I know it's the right button).
Next I changed the text using "SetWindowText()" (and called "GetWindowText()" to check it - the text is correct).
But ... the button still says "Save".
I can change the "Cancel" button using the exact same code - no problem. What's so special about the "Save" button? How can I change it.
Code (for what it's worth):
static UINT_PTR CALLBACK myHook(HWND hwnd, UINT msg, WPARAM, LPARAM)
{
if (msg == WM_INITDIALOG) {
wchar_t temp[100];
HWND h = GetDlgItem(GetParent(hwnd),IDOK);
GetWindowTextW(h,temp,100); // temp=="&Save"
SetWindowTextW(h,L"Testing");
GetWindowTextW(h,temp,100); // temp=="Testing"
}
}
I finally made it work....
I'm pretty sure there's something funny going on with the "Save" button but this code will wrestle it into submission:
// I replace the dialog's WindowProc with this
static WNDPROC oldProc = NULL;
static BOOL CALLBACK buttonSetter(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Set the button text on every window redraw....
if (msg == WM_ERASEBKGND) {
SetDlgItemTextW(hwnd,IDOK,L"OK");
}
return oldProc(hwnd, msg, wParam, lParam);
};
// This is the callback for the GetWriteName hook
static UINT_PTR CALLBACK GWNcallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND dlg = GetParent(hwnd);
if (msg == WM_INITDIALOG) {
oldProc = (WNDPROC)GetWindowLongPtr(dlg, GWL_WNDPROC);
if (oldProc !=0) {
SetWindowLongPtr(dlg, GWL_WNDPROC, (LONG)buttonSetter);
}
}
// We need extra redraws to make our text appear...
InvalidateRect(dlg,0,1);
}
You probably need to redraw the window after setting the text.
Try calling UpdateWindow() after setting the text.
Use CDM_SETCONTROLTEXT message to set the text rather than mess with SetWindowText directly, i.e.
SendMessage(hwnd, CDM_SETCONTROLTEXT, IDOK, L"Testing");
http://msdn.microsoft.com/en-us/library/ms646960(VS.85).aspx has more on customizing open/save dialogs

Resources