I don't received NM_DBLCLK from my Tab control;
Each time I make a double-click I received NM_CLICK
With a ListView, no problem.
Who could help me?
Thank You
There is no more problem.
Thank You
Quote from: Grincheux on June 16, 2021, 07:25:16 AM
There is no more problem.
Please write a solution
someone will look for such a problem in the future.
Or was it some kind of personal mistake? Like a typo or something like that?
For the ghosts
Here is a link to the project.https://www.mediafire.com/file/yjuy8o9rcax7l2g/%2524ImgView%2524.7z/file (https://www.mediafire.com/file/yjuy8o9rcax7l2g/%2524ImgView%2524.7z/file)
The program is not finsihed it is for illusrating the post.I saw the solution here:https://social.msdn.microsoft.com/Forums/vstudio/en-US/8830d039-afe0-457a-9ab6-d3752c0f88be/cant-get-nmdblclk-notify-from-tab-control?forum=vcgeneral (https://social.msdn.microsoft.com/Forums/vstudio/en-US/8830d039-afe0-457a-9ab6-d3752c0f88be/cant-get-nmdblclk-notify-from-tab-control?forum=vcgeneral)
Many people have the same problem: No double-click (left or right) with a tab control.
The solution :Subclass the TAB window procWhen the TAB detects a WM_LBUTTONDBLCLK and Send a WM_NOTIFY with NM_DBLCLK
Quote; _______________________________________________________________________________________
; ... TabControlSubClassProc ... |
; _______________________________________________________________________________________
ALIGN 16
TabControlSubClassProc PROC __hWnd:HWND,__uMsg:QWORD,__wParam:WPARAM,__lParam:LPARAM PARMAREA=4*QWORD
LOCAL _NmHdr:NMHDR
CMP EDX,WM_LBUTTONDBLCLK
JE @NmDblClk
; _______________________________________________________________________________________
ALIGN 16
@Exit :
MOV [RSP + 20h],R9
MOV R9,R8
MOV R8,RDX
MOV RDX,RCX
MOV RCX,[lpOldTabControlWndProc + RIP]
CALL CallWindowProcA
RET
; _______________________________________________________________________________________
ALIGN 16
@NmDblClk :
TEST R8D,MK_LBUTTON
JZ @Exit
MOV RAX,[hTabs + RIP]
MOV _NmHdr.hwndFrom,RAX
MOV _NmHdr.idFrom,IDC_TAB_01
MOV _NmHdr.code,NM_DBLCLK
MOV RCX,[hWndMain + RIP] ; Send WM_NOTIFY / NM_DBLCLK to the parent window
MOV RDX,WM_NOTIFY
MOV R8D,IDC_TAB_01 ; Tab Control ID
LEA R9,_NmHdr ; Pointer on NMHDR
CALL SendMessageA
XOR EAX,EAX
RET
TabControlSubClassProc ENDP
Quote; _______________________________________________________________________________________
; ... W N D P R O C ... |
; _______________________________________________________________________________________
ALIGN 16
WndProc PROC __hWnd:HWND,__uMsg:QWORD,__wParam:WPARAM,__lParam:LPARAM PARMAREA=4*QWORD
LOCAL _Tci:TCITEM
LOCAL _iCurrentTab:DWORD
LOCAL _lpNmHDR:LPNMHDR
CMP EDX,WM_GETMINMAXINFO
JE @WmGetMinMaxInfos
CMP EDX,WM_NOTIFY
JE @WmNotify
; _______________________________________________________________________________________
ALIGN 16
@WmNotify :
MOV _lpNmHDR,R9
CMP DWORD PTR [R9].NMHDR.idFrom,IDC_TAB_01
JNE @NextContontrol_01
MOV EAX,DWORD PTR [R9].NMHDR.code
CMP EAX,TCN_SELCHANGING
JE @SelChanging
CMP EAX,TCN_SELCHANGE
JE @SelChange
CMP EAX,NM_DBLCLK ;NM_DBLCLK
JNE @Eoj
; Close the Window and remove the tab
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_GETCURSEL
XOR R8D,R8D
XOR R9D,R9D
CALL SendMessageA
CMP EAX,-1
JE @Eoj
TEST EAX,EAX ; Forbidden to delete the first tab ;
JZ @Eoj ; Which is the listview ;
MOV [_iCurrentTab],EAX
MOV _Tci._mask,TCIF_PARAM
MOV _Tci.lParam,0
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_GETITEM
MOV R8D,EAX
LEA R9,_Tci
CALL SendMessageA
TEST EAX,EAX
JE @Eoj
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_SETCURSEL
XOR R8D,R8D
XOR R9D,R9D
CALL SendMessageA
MOV RCX,_Tci.lParam
CALL DestroyWindow
MOV RCX,[hListView + RIP]
MOV RDX,SW_SHOW
CALL ShowWindow
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_DELETEITEM
MOV R8D,[_iCurrentTab]
XOR R9D,R9D
MOV RAX,OFFSET @Eoj
PUSH RAX
JMP SendMessageA
; _______________________________________________________________________________________
ALIGN 16
@NextContontrol_01 :
CMP DWORD PTR [R9].NMHDR.idFrom,IDC_LISTVIEW_01
JNE @NextContontrol_02
MOV EDX,DWORD PTR [R9].NMHDR.code
CMP EDX,NM_DBLCLK
JNE @Eoj
MOV RCX,R9
MOV RAX,OFFSET @Eoj
PUSH RAX
JMp LivstViewDoubleClick
; _______________________________________________________________________________________
ALIGN 16
@NextContontrol_02 :
JMP @Eoj
; _______________________________________________________________________________________
ALIGN 16
@WmNotify :
MOV _lpNmHDR,R9
CMP DWORD PTR [R9].NMHDR.idFrom,IDC_TAB_01
JNE @NextContontrol_01
MOV EAX,DWORD PTR [R9].NMHDR.code
CMP EAX,TCN_SELCHANGING
JE @SelChanging
CMP EAX,TCN_SELCHANGE
JE @SelChange
CMP EAX,NM_DBLCLK ;NM_DBLCLK
JNE @Eoj
; Close the Window and remove the tab if the user double-clicks on the tab
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_GETCURSEL
XOR R8D,R8D
XOR R9D,R9D
CALL SendMessageA
CMP EAX,-1
JE @Eoj
TEST EAX,EAX ; Forbidden to delete the fist tab ;
JZ @Eoj ; Which is the listview ;
MOV [_iCurrentTab],EAX
MOV _Tci._mask,TCIF_PARAM
MOV _Tci.lParam,0
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_GETITEM
MOV R8D,EAX
LEA R9,_Tci
CALL SendMessageA
TEST EAX,EAX
JE @Eoj
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_SETCURSEL
XOR R8D,R8D
XOR R9D,R9D
CALL SendMessageA
MOV RCX,_Tci.lParam
CALL DestroyWindow
MOV RCX,[hListView + RIP]
MOV RDX,SW_SHOW
CALL ShowWindow
MOV RCX,[hTabs + RIP]
MOV EDX,TCM_DELETEITEM
MOV R8D,[_iCurrentTab]
XOR R9D,R9D
MOV RAX,OFFSET @Eoj
PUSH RAX
JMP SendMessageA
; _______________________________________________________________________________________
ALIGN 16
@NextContontrol_01 :
CMP DWORD PTR [R9].NMHDR.idFrom,IDC_LISTVIEW_01
JNE @NextContontrol_02
MOV EDX,DWORD PTR [R9].NMHDR.code
CMP EDX,NM_DBLCLK
JNE @Eoj
MOV RCX,R9
MOV RAX,OFFSET @Eoj
PUSH RAX
JMp LivstViewDoubleClick
; _______________________________________________________________________________________
ALIGN 16
@NextContontrol_02 :
JMP @Eoj
; _______________________________________________________________________________________
ALIGN 16
@SelChanging :
MOV RCX,[hTabs + RIP]
MOV RDX,TCM_GETCURSEL
XOR R8D,R8D
XOR R9D,R9D
CALL SendMessageA
MOV RCX,[hTabs + RIP]
MOV RDX,TCM_GETITEM
MOV R8D,EAX
LEA R9,_Tci
MOV [R9].TCITEM.lParam,0
MOV [R9].TCITEM._mask,TCIF_PARAM
CALL SendMessageA
MOV RCX,_Tci.lParam
MOV RDX,SW_HIDE
MOV RAX,OFFSET @Eoj
PUSH RAX
JMP ShowWindow
; _______________________________________________________________________________________
ALIGN 16
@SelChange :
MOV RCX,[hTabs + RIP]
MOV RDX,TCM_GETCURSEL
XOR R8D,R8D
XOR R9D,R9D
CALL SendMessageA
MOV RCX,[hTabs + RIP]
MOV RDX,TCM_GETITEM
MOV R8D,EAX
LEA R9,_Tci
MOV [R9].TCITEM.lParam,0
MOV [R9].TCITEM._mask,TCIF_PARAM
CALL SendMessageA
MOV RCX,_Tci.lParam
MOV RDX,SW_SHOW
MOV RAX,OFFSET @Eoj
PUSH RAX
JMP ShowWindow
;_______________________________________________________________________________________
ALIGN 16
@End :
RET
WndProc ENDP
; _______________________________________________________________________________________
While processing WM_CREATE do :
Quote; _______________________________________________________________________________________
XOR EAX,EAX
MOV RCX,[hInstance + RIP]
MOV RDX,[hWndMain + RIP]
MOV [RSP + 58h],RAX
MOV [RSP + 50h],RCX
MOV DWORD PTR [RSP + 48h],IDC_TAB_01
MOV [RSP + 40h],RDX
MOV EAX,[RcWorkArea.bottom + RIP]
MOV DWORD PTR [RSP + 38h],EAX ; H
MOV EAX,[RcWorkArea.right + RIP]
MOV DWORD PTR [RSP + 30h],EAX ; W
MOV EAX,[RcWorkArea.top + RIP]
MOV DWORD PTR [RSP + 28h],EAX ; Y
MOV EAX,[RcWorkArea.left + RIP]
MOV DWORD PTR [RSP + 20h],EAX ; X
MOV R9D,WS_CHILD OR WS_VISIBLE OR WS_CLIPSIBLINGS
MOV R8,OFFSET szNullString
MOV RDX,OFFSET WC_TABCONTROLA
MOV ECX,WS_EX_OVERLAPPEDWINDOW
CALL CreateWindowExA
MOV [hTabs + RIP],RAX ; Window handle in RAX
MOV RCX,RAX
MOV RDX,GWL_WNDPROC
MOV R8,OFFSET TabControlSubClassProc ; Address of the new PROC
CALL SetWindowLongPtrA ; Make the subclass
MOV [lpOldTabControlWndProc + RIP],RAX ; Save the old proc
MOV RCX,[hTabs + RIP]
MOV RDX,WM_SETFONT
MOV R8,[hFontMain + RIP]
MOV R9D,TRUE
CALL SendMessageA
; _______________________________________________________________________________________
Sorry it is in assembler
It is interesting, I might try a test in C. I've never used the Tab control. I know some controls only notify and/or also notify through WM_NOTIFY to the main message loop. Treeview and Statusbar are two for example, but these do not need subclassing.
John Z
I wanted to add an icon on the tab.
I did not know how to do it, so I tried the double-click.
Hello,
I've also used subclassing and WM_LBUTTONDBLCLK processing in order to catch double clicks. I don't know if it's the right way, but it works and, like you, couldn't convince WM_NOTIFY to deliver :)
I have a few functions that I use when working with the tab control, maybe you'll find some use for them. There are a couple which deal with tab icons.
#define IDC_TAB 999
#define TAB_STYLE WS_OVERLAPPED|WS_CLIPCHILDREN| \
WS_CLIPSIBLINGS|WS_VISIBLE|WS_CHILD| \
TCS_SINGLELINE|TCS_TABS|TCS_RIGHTJUSTIFY| \
TCS_FOCUSNEVER
HWND Tab_NewTab ( HWND hWnd )
{
RECT wr;
HWND hTab;
ULONG_PTR style;
HFONT font;
GetClientRect ( hWnd, &wr );
hTab = CreateWindowEx ( 0, WC_TABCONTROL, NULL, TAB_STYLE, wr.left, wr.top,
wr.right-wr.left, wr.bottom-wr.top, hWnd,
(HMENU) IDC_TAB, NULL, NULL);
style = GetClassLongPtr ( hTab, GCL_STYLE );
if ( style )
{
style &= ~( CS_HREDRAW|CS_VREDRAW );
style |= ( CS_BYTEALIGNWINDOW|CS_BYTEALIGNCLIENT|CS_PARENTDC );
SetClassLongPtr ( hTab, GCL_STYLE, style );
}
SetClassLongPtr ( hTab, GCLP_HBRBACKGROUND,
(LONG_PTR)GetStockObject ( NULL_BRUSH ) );
style = GetWindowLongPtr ( hTab, GWL_STYLE );
if ( style )
{
style &= ~TCS_HOTTRACK;
SetWindowLongPtr ( hTab, GWL_STYLE, style );
}
font = (HFONT) GetStockObject ( DEFAULT_GUI_FONT );
SendMessage ( hTab, WM_SETFONT, ( WPARAM ) font, MAKELPARAM (TRUE,0) );
return hTab;
}
int Tab_NewTabPage ( HWND hTab, int index, TCHAR * text )
{
TCITEM tie;
tie.mask = TCIF_TEXT|TCIF_IMAGE;
tie.pszText = ( text == NULL ) ? TEXT("") : text;
return (int)SendMessage ( hTab, TCM_INSERTITEM,
index, (LPARAM) (LPTCITEM) &tie);
}
BOOL Tab_DeletePage ( HWND hTab, int index )
{
return (BOOL)SendMessage ( hTab, TCM_DELETEITEM, index, 0 );
}
int Tab_GetCurSel ( HWND hTab )
{
return (int)SendMessage ( hTab, TCM_GETCURSEL, 0, 0 );
}
int Tab_SetCurSel ( HWND hTab, int index )
{
return (int)SendMessage ( hTab, TCM_SETCURSEL, (WPARAM)index, 0 );
}
int Tab_GetCurFocus ( HWND hTab )
{
return (int)SendMessage ( hTab, TCM_GETCURFOCUS, 0, 0 );
}
int Tab_SetCurFocus ( HWND hTab, int index )
{
return (int)SendMessage ( hTab, TCM_SETCURFOCUS, (WPARAM)index, 0 );
}
int Tab_GetCount ( HWND hTab )
{
return (int)SendMessage ( hTab, TCM_GETITEMCOUNT, 0, 0 );
}
BOOL Tab_SetText ( HWND hTab, int index, TCHAR * text )
{
TCITEM tie;
tie.mask = TCIF_TEXT;
tie.pszText = ( text == NULL ) ? TEXT("") : text;
return (BOOL)SendMessage ( hTab, TCM_SETITEM,
index, (LPARAM) (LPTCITEM) &tie);
}
void Tab_SetPadding ( HWND hTab, int cx, int cy )
{
SendMessage ( hTab, TCM_SETPADDING, 0, MAKELPARAM ( cx, cy ) );
}
void Tab_SetImageList ( HWND hTab, HIMAGELIST hIml )
{
SendMessage ( hTab, TCM_SETIMAGELIST, 0, (LPARAM) hIml );
}
// Param. 2: int index : tab index for the tab to receive img
// Param. 3: int img_index : img index in the imagelist
BOOL Tab_SetImg ( HWND hTab, int index, int img_index )
{
TCITEM tie;
tie.mask = TCIF_IMAGE;
tie.iImage = img_index;
return (BOOL)SendMessage ( hTab, TCM_SETITEM,
index, (LPARAM) (LPTCITEM) &tie);
}
Thank You algermon_77, I like the first function that change the style.
I will study it and will use it. :D
The window class style mod was intended to make a tab with many pages behave less flickery on a system with very low video resources (I also uncommented the second call to SetClassLongPtr - if you disable CS_HREDRAW|CS_VREDRAW then shouldn't use gray brushes). Don't know if it's much use on a modern system, never bothered to benchmark :)
I modified the window style to remove hot tracking (just a matter of personal preference).
It is amazing to me that the Micro$oft documentation for the TAB control all indicates that NM_RDBLCLK, and NM_DBLCLK should be available but as everyone has pointed out it is not true. I never tried using the TAB control until now. If I every need it in a project, with dbl clicking, I'll be back to this thread. :)
John Z
Quoteif you disable CS_HREDRAW|CS_VREDRAW then shouldn't use gray brushes). Don't know if it's much use on a modern system, never bothered to benchmark
If you disable CS_HREDRAW and CS_VREDRAW, windows will not generate WM_ERASEBKGND
https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-erasebkgnd (https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-erasebkgnd)
I accidentally removed them and was surprise while processing the WM_PAINT message.
To avoid this I do like this:
I create 3 DCs
DC1 = CompatibleDC
DC2 = Compatible DC
DC3 = GetDC or BegingPaint
In the first I draw the image with resize (StretchBlt)
I draw the background in the DC2 => I make the WM_ERASEBKGND
In the second I transfert from the DC1 to DC2 using Bitblt
In the third, The window DC I use Bitblt to transfert the final image from DC2 to DC3
void ImageGetBitmap(HWND __hWnd,HDC __hDC,LPSTR __lpszFileName)
{
alignas(HANDLE) GFL_LOAD_PARAMS _Params ;
alignas(HANDLE) double _dThumbNailsWidth, _dThumbNailsHeight ;
alignas(HANDLE) HDC _hDCTemp, _hDCNew ;
alignas(HANDLE) HBITMAP _hBmpOldTemp, _hBmpOldNew ;
alignas(HANDLE) HBRUSH _hBrush ;
alignas(HANDLE) RECT _Rc ;
alignas(HANDLE) BITMAP _Bitmap ;
alignas(HANDLE) HBITMAP _hImgBitmap ;
alignas(DWORD) DWORD _dwThumbNailsWidth ; // Thumbnail Width
alignas(DWORD) DWORD _dwThumbNailsHeight; // Thumnail Height
GetClientRect(__hWnd,&_Rc) ;
_hDCTemp = CreateCompatibleDC(__hDC) ;
if(_hDCTemp)
{
_hDCNew = CreateCompatibleDC(__hDC) ;
if(_hDCNew)
{
_hImgBitmap = CreateCompatibleBitmap(__hDC,_Rc.right,_Rc.bottom) ;
if(_hImgBitmap)
{
_hBmpOldNew = SelectObject(_hDCNew,_hImgBitmap) ;
if(_hBmpOldNew)
{
_hBrush = GetStockObject(BLACK_BRUSH) ;
FillRect(_hDCNew,&_Rc,_hBrush) ;
gflGetDefaultLoadParams(&_Params) ;
_Params.Flags = GFL_LOAD_SKIP_ALPHA ;
_Params.FormatIndex = -1 ;
_Params.Origin = GFL_TOP_LEFT ;
_Params.ColorModel = GFL_ARGB ;
if(gflLoadBitmapIntoDDB(__lpszFileName,&_hImgBitmap,&_Params,NULL) == GFL_NO_ERROR)
{
GetObject(_hImgBitmap,sizeof(BITMAP),&_Bitmap) ;
_hBmpOldTemp = SelectObject(_hDCTemp,_hImgBitmap) ;
if(_hBmpOldTemp)
{
SetStretchBltMode(_hDCTemp,HALFTONE) ;
SetStretchBltMode(_hDCNew,HALFTONE) ;
if((_Bitmap.bmWidth < _Rc.right) && (_Bitmap.bmHeight < _Rc.bottom))
{
_dwThumbNailsWidth = (DWORD) _Bitmap.bmWidth ;
_dwThumbNailsHeight = (DWORD) _Bitmap.bmHeight ;
}
else
{
_dThumbNailsWidth = (double) _Rc.right ;
_dThumbNailsHeight = (double) _Rc.bottom ;
ImageResize((double) _Bitmap.bmWidth,(double) _Bitmap.bmHeight,(double) _Rc.right,(double) _Rc.bottom,&_dThumbNailsWidth,&_dThumbNailsHeight) ;
_dwThumbNailsWidth = (DWORD) _dThumbNailsWidth ;
_dwThumbNailsHeight = (DWORD) _dThumbNailsHeight ;
}
StretchBlt(_hDCNew,(_Rc.right - _dwThumbNailsWidth) / 2,(_Rc.bottom - _dwThumbNailsHeight) / 2,_dwThumbNailsWidth,_dwThumbNailsHeight,_hDCTemp,0,0,_Bitmap.bmWidth,_Bitmap.bmHeight,SRCCOPY) ;
BitBlt(__hDC,0,0,_Rc.right,_Rc.bottom,_hDCNew,0,0,SRCCOPY) ;
SelectObject(_hDCTemp,_hBmpOldTemp) ;
}
DeleteObject(_hImgBitmap) ;
}
SelectObject(_hDCNew,_hBmpOldNew) ;
}
DeleteObject(_hImgBitmap) ;
}
DeleteDC(_hDCTemp) ;
}
DeleteDC(_hDCNew) ;
}
return ;
}
QuoteIt is amazing to me that the Micro$oft documentation for the TAB control all indicates that NM_RDBLCLK, and NM_DBLCLK should be available but as everyone has pointed out it is not true.
And I think there are others controls...