NO

Author Topic: NM_DBLCLK with Tab Control  (Read 4521 times)

Grincheux

  • Guest
NM_DBLCLK with Tab Control
« on: June 15, 2021, 08:10:23 PM »
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
« Last Edit: June 15, 2021, 08:14:44 PM by Grincheux »

Grincheux

  • Guest
Re: NM_DBLCLK with Tab Control
« Reply #1 on: June 16, 2021, 07:25:16 AM »
There is no more problem.
Thank You

Offline bitcoin

  • Member
  • *
  • Posts: 179
Re: NM_DBLCLK with Tab Control
« Reply #2 on: June 16, 2021, 12:33:31 PM »
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?

Grincheux

  • Guest
Re: NM_DBLCLK with Tab Control
« Reply #3 on: June 16, 2021, 04:17:17 PM »
For the ghosts

Here is a link to the project.

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
Many people have the same problem: No double-click (left or right) with a tab control.
The solution :

Subclass the TAB window proc
When 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
« Last Edit: June 16, 2021, 04:40:44 PM by Grincheux »

Offline John Z

  • Member
  • *
  • Posts: 860
Re: NM_DBLCLK with Tab Control
« Reply #4 on: June 16, 2021, 05:23:01 PM »
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

Grincheux

  • Guest
Re: NM_DBLCLK with Tab Control
« Reply #5 on: June 16, 2021, 05:57:21 PM »
I wanted to add an icon on the tab.
I did not know how to do it, so I tried the double-click.

Offline algernon_77

  • Member
  • *
  • Posts: 33
Re: NM_DBLCLK with Tab Control
« Reply #6 on: June 17, 2021, 12:05:17 PM »
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.

Code: [Select]

#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);
}

« Last Edit: June 17, 2021, 05:10:25 PM by algernon_77 »

Grincheux

  • Guest
Re: NM_DBLCLK with Tab Control
« Reply #7 on: June 17, 2021, 03:59:44 PM »
Thank You algermon_77, I like the first function that change the style.
I will study it and will use it. :D

Offline algernon_77

  • Member
  • *
  • Posts: 33
Re: NM_DBLCLK with Tab Control
« Reply #8 on: June 17, 2021, 05:21:41 PM »
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).
« Last Edit: June 17, 2021, 05:27:31 PM by algernon_77 »

Offline John Z

  • Member
  • *
  • Posts: 860
Re: NM_DBLCLK with Tab Control
« Reply #9 on: June 18, 2021, 02:15:44 PM »
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

Grincheux

  • Guest
Re: NM_DBLCLK with Tab Control
« Reply #10 on: June 18, 2021, 03:56:10 PM »
Quote
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


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
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

Code: [Select]
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 ;
}
« Last Edit: June 18, 2021, 03:59:08 PM by Grincheux »

Grincheux

  • Guest
Re: NM_DBLCLK with Tab Control
« Reply #11 on: June 18, 2021, 04:00:44 PM »
Quote
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.

And I think there are others controls...