Listicon: Anchor the first column (Updated April 24, 2010)

Share your advanced PureBasic knowledge/code with the community.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Listicon: Anchor the first column (Updated April 24, 2010)

Post by netmaestro »

This library include allows you to create a ListIcon gadget that is identical in all respects to a normal listicon, except that the first column is anchored and won't move or scroll. It stays fixed. Also, it is drawn in a way that looks to the user as though it is fixed.

How it works:

The concept is fairly simple. The area containing Column 0 is removed from the clipping region of the control, so that while Column 0 is alive and well, it is never drawn. Then we take full responsibility for drawing Column 0 in the same way the gadget would do it, more or less. The main exception is that when the gadget's DC is scrolled to the left, we leave Column 0 the way it is. Simple as the idea is, it seems to take a fair whack of code to make it happen.


Update April 24/2010:

Added code to the gadget's WM_NCDESTROY proc to free all its parts when the gadget is freed. The gadget responds to FreeGadget() and HideGadget() properly now. Also, fixed a couple of minor display issues w/selected text color and focusrect display for themed/unthemed. Blends in better now. Afaik all that's left to do is gridlines.

Update March 29:

1) Made button for column 0 pressable.

2) Reworked image creation for the buttons.

In the course of making the button for column 0 pressable, I came across a problem in the way the button image was being drawn. In order to grab the image from the dc of the header or the listicon, the listicon must be scrolled all the way to the right. Otherwise button 0 is out of the clipping region. So I was scrolling to the right on a column resize to get the correct image, but what about when someone is scrolled over halfway to the left when he resizes column 0? His scroll is reset and he has to rescroll. No good.

Also, in order to make the 3 button states for pressability, I have to press the button and hover over it. When I press the button I have to draw it and then let it go, which then generates a button-press event which I can't stop. It's automatic and there is no return value for #HDN_ITEMCLICK to refuse it. So that's another problem.

As a solution, I'm getting the metrics and style from the listicon header and creating a new header of the same characteristics. I'm grabbing the 3 images, pressing and hovering etc. and once I have them I'm destroying the temporary header. This solves both of the above problems. All this takes place in the UpdateImages() procedure.

Update March 28:

1) Replaced the thread monitoring the cursor with a different approach using SetCapture_() / ReleaseCapture_(). There are no threads in this library now. Thanks to srod for helping me debug this.

2) Fixed a bug in the #WM_SETFONT / #WM_MOVE handler

3) Updated the demo prog to demonstrate multiple instances, SetGadgetFont and ResizeGadget situations.

Update March 26:

Fixed several bugs, removed completely the array storing the items. I should have realized from the start that it wasn't needed. The gadget will now behave correctly in the following situations:

1) Multiple instances in an application

2) Compiling with Tailbite.

Update March 23:

Code is mostly reworked to allow eventual mulitple gadgets in an application, the only thing left on that issue is the static array. That has to be replaced with something else. The capture problem is solved.

Update March 22, 2009:

Quite a few issues have been covered in this update. The gadget will now handle the following gracefully:

1) SetGadgetFont
2) ResizeGadget
3) #PB_Listicon_FullRowSelect
4) Resizing Column 0
5) Too-long text in column 0

Yet todo: #PB_ListIcon_Gridlines, make column 0 button pushable, multiple instances

Code: Select all

;========================================================================= 
; Library:               FixedIconGadget 
; Author:                Lloyd Gallant (netmaestro) 
; Contributors:          Stephen Rodriguez (srod) 
; Date:                  March 20, 2009 
; Target Compiler:       PureBasic 4.3 
; Target OS:             Microsoft Windows All 
; License:               Free, unrestricted, no warranty 
;========================================================================= 
; 
; Command: FixedIconGadget(), identical in all respects to ListIconGadget() 
; 
#LVM_SUBITEMHITTEST = #LVM_FIRST + 57 

Structure FIXEDICONDATA 
  listproc.i 
  buttonimgproc.i 
  colimgproc.i 
  headerproc.i 
  listhwnd.i 
  header.i 
  colimg_g.i 
  colimg.i  
  buttonimg_g.i 
  buttonimg.i 
  button_pressedimg.i 
  button_hoverimg.i 
  lineH.i 
  col_0_width.i 
  scroll_height.i 
  col_0_line_g.i 
  button_0_line_g.i 
  list_line_g.i 
  sizelin.i 
  resizing_col_0.i 
  themed.i 
  mouseinbutton.i 
  hdnotify.HD_NOTIFY 
EndStructure 

Declare headerproc(hwnd,msg,wparam,lparam) 

Procedure ThemesEnabled() 
  dlv.DLLVERSIONINFO 
  dlv\cbsize=SizeOf(DLLVERSIONINFO) 
  lib=OpenLibrary(#PB_Any,"comctl32.dll") 
  If lib 
    CallFunction(lib,"DllGetVersion",@dlv) 
    DLLVersion = dlv\dwMajorVersion 
    CloseLibrary(lib) 
  EndIf 
  If DLLVersion = 6 
    ProcedureReturn 1 
  Else 
    ProcedureReturn 0 
  EndIf 
EndProcedure 

Procedure PaintColumn_0(*fdata.FIXEDICONDATA) 
  If Not *fdata\themed 
    tcorr = 2 
  EndIf 
  With hti.LVHITTESTINFO 
    \pt\x = GetGadgetItemAttribute(GetDlgCtrlID_(*fdata\listhwnd),0,#PB_ListIcon_ColumnWidth)+5 
    \pt\y = 30 
  EndWith 
  SendMessage_(*fdata\listhwnd,#LVM_SUBITEMHITTEST,0,hti) 
  item = hti\iitem : If item<0 : item=0 : EndIf 
  StartDrawing(ImageOutput(*fdata\buttonimg)) 
  l1 = Point(5,ImageHeight(*fdata\buttonimg)-1) 
  l2 = Point(5,ImageHeight(*fdata\buttonimg)-2) 
  l3 = Point(5,ImageHeight(*fdata\buttonimg)-3) 
  l4 = Point(3,3) 
  StopDrawing() 
  hdc = StartDrawing(ImageOutput(*fdata\colimg)) 
  Box(0,0,ImageWidth(*fdata\colimg),ImageHeight(*fdata\colimg),l4) 
  Box(ImageWidth(*fdata\colimg)-3,0,1,ImageHeight(*fdata\colimg),l3) 
  Box(ImageWidth(*fdata\colimg)-2,0,1,ImageHeight(*fdata\colimg),l2) 
  Box(ImageWidth(*fdata\colimg)-1,0,1,ImageHeight(*fdata\colimg),l1) 
  
  DrawingFont(GetGadgetFont(GetDlgCtrlID_(*fdata\listhwnd))) 
  numvisible = ImageHeight(*fdata\colimg)/*fdata\lineH 
  cc=0 
  For i=item To item+numvisible 
    If i=GetGadgetState(GetDlgCtrlID_(*fdata\listhwnd)) 
      exstyle = SendMessage_(*fdata\listhwnd, #LVM_GETEXTENDEDLISTVIEWSTYLE,0,0) 
      If exstyle & #LVS_EX_FULLROWSELECT 
        SetRect_(sel.RECT,0,cc**fdata\lineH+tcorr,ImageWidth(*fdata\colimg)+5,cc**fdata\lineH+*fdata\lineH+tcorr) 
      Else 
        SetRect_(sel.RECT,0,cc**fdata\lineH+tcorr,ImageWidth(*fdata\colimg)-3,cc**fdata\lineH+*fdata\lineH+tcorr) 
      EndIf 
      hBrush = CreateSolidBrush_(GetSysColor_(#COLOR_HIGHLIGHT)) 
      FillRect_(hdc, sel, hBrush) 
      DeleteObject_(hBrush) 
      text$ = GetGadgetItemText(GetDlgCtrlID_(*fdata\listhwnd),i,0) 
      While TextWidth(text$)>=ImageWidth(*fdata\colimg)-TextWidth("EE") 
        text$=Left(text$,Len(text$)-1) 
      Wend 
      If text$<>GetGadgetItemText(GetDlgCtrlID_(*fdata\listhwnd),i,0) : text$+".." : EndIf 
      DrawText(4,cc**fdata\lineH+tcorr,text$,#White,GetSysColor_(#COLOR_HIGHLIGHT)) 
      If Not ThemesEnabled()
        DrawFocusRect_(hdc, sel) 
      EndIf
    Else 
      text$ = GetGadgetItemText(GetDlgCtrlID_(*fdata\listhwnd),i,0) 
      While TextWidth(text$)>=ImageWidth(*fdata\colimg)-TextWidth("EE") 
        text$=Left(text$,Len(text$)-1) 
      Wend 
      If text$<>GetGadgetItemText(GetDlgCtrlID_(*fdata\listhwnd),i,0) : text$+".." : EndIf 
      DrawText(4,cc**fdata\lineH+tcorr,text$,#Black,l4) 
    EndIf 
    cc+1 
  Next 
  StopDrawing() 
  SetGadgetState(*fdata\buttonimg_g, ImageID(*fdata\buttonimg)) 
  SetGadgetState(*fdata\colimg_g,ImageID(*fdata\colimg)) 
  
EndProcedure 


Procedure ImageProc(hwnd, msg, wparam, lparam) 
  
  *fdata.FIXEDICONDATA = GetProp_(hwnd, "fdata") 
  
  Select msg 
    Case #WM_NCDESTROY 
      RemoveProp_(hwnd, "fdata") 
      
    Case #WM_MOUSEMOVE 
      x = lparam&$FFFF 
      If x>=GadgetWidth(GetDlgCtrlID_(hwnd))-5 
        SetCursor_( LoadCursor_(0, #IDC_SIZEWE )) 
      Else 
        If Not *fdata\resizing_col_0 
          SetCursor_( LoadCursor_(0, #IDC_ARROW )) 
        Else 
          ResizeGadget(*fdata\list_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
          ResizeGadget(*fdata\button_0_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
          HideGadget(*fdata\list_line_g,0) 
          HideGadget(*fdata\button_0_line_g,0) 
          HideGadget(*fdata\col_0_line_g,1) 
        EndIf 
      EndIf 
      
    Case #WM_LBUTTONDOWN 
      SetFocus_(*fdata\listhwnd) 
      If lparam&$FFFF >=GadgetWidth(GetDlgCtrlID_(hwnd))-5 
        *fdata\resizing_col_0 = 1 
        SetCursor_( LoadCursor_(0, #IDC_SIZEWE )) 
        CallWindowProc_(*fdata\colimgproc, hwnd, msg, wparam, lparam) 
        SetCapture_(*fdata\listhwnd) 
        ProcedureReturn 0 
      Else 
        With hti.LVHITTESTINFO 
          \pt\x = GetGadgetItemAttribute(GetDlgCtrlID_(*fdata\listhwnd),0,#PB_ListIcon_ColumnWidth)+20 
          \pt\y = 24 
        EndWith 
        SendMessage_(*fdata\listhwnd,#LVM_SUBITEMHITTEST,0,hti) 
        firstitem = hti\iitem : If firstitem<0 : firstitem=0 : EndIf      
        localitem = lparam>>16 / *fdata\lineH 
        item = firstitem + localitem 
        SetFocus_(*fdata\listhwnd) 
        SetGadgetState(GetDlgCtrlID_(*fdata\listhwnd), item) 
      EndIf 
      
    Case #WM_SETCURSOR 
      If *fdata\resizing_col_0 
        SetCursor_(LoadCursor_(0, #IDC_SIZEWE)) 
        ProcedureReturn 0 
      EndIf 
      
  EndSelect 
  
  ProcedureReturn CallWindowProc_(*fdata\colimgproc, hwnd, msg, wparam, lparam) 
  
EndProcedure 

Procedure UpdateImages(*fdata.FIXEDICONDATA)  
  
  ; Get column 0 text 
  *buffer = AllocateMemory(255) 
  With itm.HDITEM 
    \mask = #HDI_TEXT 
    \pszText = *buffer 
    \cchTextMax = 255 
  EndWith 
  SendMessage_(*fdata\header, #HDM_GETITEM, 0, @itm.HDITEM) 
  text$=PeekS(itm\pszText)  
  FreeMemory(*buffer) 
  
  ; Get column 0 dimensions 
  SendMessage_(*fdata\header, #HDM_GETITEMRECT,0,@hr.RECT) 
  w = hr\right 
  h = hr\bottom 
  exstyle = GetWindowLongPtr_(*fdata\header, #GWL_EXSTYLE) 
  style   = GetWindowLongPtr_(*fdata\header, #GWL_STYLE) 
  
  ; Recreate the button on a new header 
  hdr = CreateWindowEx_(exstyle,"Sysheader32","",style&~#WS_VISIBLE,0,0,w,h,*fdata\listhwnd,0,GetModuleHandle_(0),0) 
  
  With itm.HDITEM 
    \mask = #HDI_TEXT | #HDI_WIDTH 
    \cxy  = w 
    \pszText = @text$ 
    \cchTextMax = 255 
    \fmt = #HDF_LEFT 
  EndWith 
  
  SendMessage_(hdr, #WM_SETFONT, GetGadgetFont(GetDlgCtrlID_(*fdata\listhwnd)), 1) 
  SendMessage_(hdr, #HDM_INSERTITEM,0,itm) 
  
  ; Draw the new button in its 3 states 
  hdc = GetWindowDC_(hdr) 
  
  ShowWindow_(hdr, #SW_SHOW) 
  
  ; normal button 
  UpdateWindow_(hdr) 
  If IsImage(*fdata\buttonimg):FreeImage(*fdata\buttonimg):EndIf 
  *fdata\buttonimg = CreateImage(#PB_Any,w,h) 
  dcout=StartDrawing(ImageOutput(*fdata\buttonimg)) 
  BitBlt_(dcout,0,0,w,h,hdc,0,0,#SRCCOPY) 
  StopDrawing() 
  
  ; pressed button 
  SendMessage_(hdr, #WM_LBUTTONDOWN, 0 ,5|5<<16) 
  UpdateWindow_(hdr) 
  If IsImage(*fdata\button_pressedimg):FreeImage(*fdata\button_pressedimg):EndIf 
  *fdata\button_pressedimg = CreateImage(#PB_Any,w,h) 
  dcout=StartDrawing(ImageOutput(*fdata\button_pressedimg)) 
  BitBlt_(dcout,0,0,w,h,hdc,0,0,#SRCCOPY) 
  StopDrawing() 
  SendMessage_(hdr, #WM_LBUTTONUP, 0 ,5|5<<16) 
  
  ; hover button 
  SendMessage_(hdr, #WM_MOUSEMOVE, 0 ,5|5<<16) 
  UpdateWindow_(hdr) 
  If IsImage(*fdata\button_hoverimg):FreeImage(*fdata\button_hoverimg):EndIf 
  *fdata\button_hoverimg = CreateImage(#PB_Any,w,h) 
  dcout=StartDrawing(ImageOutput(*fdata\button_hoverimg)) 
  BitBlt_(dcout,0,0,w,h,hdc,0,0,#SRCCOPY) 
  StopDrawing() 
  
  ReleaseDC_(hdr, hdc) 
  
  ; Finished, destroy temporary header 
  DestroyWindow_(hdr) 
  
EndProcedure 

Procedure ListProc(hwnd, msg, wparam, lparam) 
  
  *fdata.FIXEDICONDATA = GetProp_(hwnd, "fdata") 
  
  Select msg 
    Case #WM_NCDESTROY 
      If IsGadget(*fdata\list_line_g)
        FreeGadget(*fdata\list_line_g)
      EndIf
      If IsGadget(*fdata\button_0_line_g)
        FreeGadget(*fdata\button_0_line_g)
      EndIf
      If IsGadget(*fdata\col_0_line_g)
        FreeGadget(*fdata\col_0_line_g)
      EndIf
      If IsGadget(*fdata\buttonimg_g)
        FreeGadget(*fdata\buttonimg_g)
      EndIf
      If IsGadget(*fdata\colimg_g)
        FreeGadget(*fdata\colimg_g)
      EndIf
      If IsImage(*fdata\colimg)
        FreeImage(*fdata\colimg)
      EndIf
      If IsImage(*fdata\sizelin)
        FreeImage(*fdata\sizelin)
      EndIf
      
      RemoveProp_(hwnd, "fdata") 
      
      
    Case #WM_SHOWWINDOW
      If wparam
        If IsGadget(*fdata\buttonimg_g)
          HideGadget(*fdata\buttonimg_g, 0)
        EndIf
        If IsGadget(*fdata\colimg_g)
          HideGadget(*fdata\colimg_g,0)
        EndIf
      Else
        If IsGadget(*fdata\buttonimg_g)
          HideGadget(*fdata\buttonimg_g, 1)
        EndIf
        If IsGadget(*fdata\colimg_g)
          HideGadget(*fdata\colimg_g,1)
        EndIf
      EndIf
      
      
    Case #WM_PAINT 
      PaintColumn_0( *fdata ) 
      
    Case  #WM_NOTIFY 
      
      *nmHEADER.HD_NOTIFY = lParam 
      
      Select *nmHEADER\hdr\code 
        Case #HDN_BEGINTRACK, #HDN_BEGINTRACKW 
          If *nmHEADER\iItem = 0 
            ProcedureReturn #True 
          EndIf 
      EndSelect 
      
    Case #WM_MOUSEMOVE 
      
      If *fdata\resizing_col_0 
        loc = lparam&$FFFF 
        Select loc 
          Case 10 To *fdata\col_0_width 
            ResizeGadget(*fdata\list_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
            ResizeGadget(*fdata\button_0_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
            HideGadget(*fdata\list_line_g,0) 
            HideGadget(*fdata\button_0_line_g,0) 
            HideGadget(*fdata\col_0_line_g,1) 
            
          Case *fdata\col_0_width To GadgetWidth(GetDlgCtrlID_(hwnd))-10 
            ResizeGadget(*fdata\col_0_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
            HideGadget(*fdata\col_0_line_g,0) 
            HideGadget(*fdata\button_0_line_g,1) 
            HideGadget(*fdata\list_line_g,1) 
            
        EndSelect    
        
      EndIf 
      
    Case #WM_LBUTTONUP 
      
      If *fdata\resizing_col_0 
        oldwidth = *fdata\col_0_width
        ReleaseCapture_() 
        *fdata\resizing_col_0 = #False 
        SetCursor_(LoadCursor_(0,#IDC_ARROW)) 
        HideGadget(*fdata\col_0_line_g,1) 
        HideGadget(*fdata\list_line_g,1) 
        HideGadget(*fdata\button_0_line_g,1) 
        InvalidateRect_(hwnd,0, 1) 
        *fdata\col_0_width=PeekW(@lparam) 
        widthdiff = *fdata\col_0_width-oldwidth
        oldscrollpos = GetScrollPos_(hwnd, #SB_HORZ)
        If *fdata\col_0_width < 30 
          *fdata\col_0_width = 30 
        EndIf 
        If *fdata\col_0_width > GadgetWidth(GetDlgCtrlID_(hwnd)) - 30 
          *fdata\col_0_width = GadgetWidth(GetDlgCtrlID_(hwnd)) - 30 
        EndIf 
        SetGadgetItemAttribute(GetDlgCtrlID_(hwnd),0,#PB_ListIcon_ColumnWidth,*fdata\col_0_width) 
        SetWindowRgn_(hwnd, 0, 1) 
        
        CallWindowProc_(*fdata\listproc, hwnd, msg, wparam, lparam) 
        lineH = SendMessage_(hwnd,#LVM_GETITEMSPACING,#True,0)>>16 
        
        UpdateImages(*fdata) 
        
        hrgn1 = CreateRectRgn_(0,0,GadgetWidth(GetDlgCtrlID_(hwnd)),GadgetHeight(GetDlgCtrlID_(hwnd))) 
        hrgn2 = CreateRectRgn_(GetSystemMetrics_(#SM_CXEDGE),GetSystemMetrics_(#SM_CYEDGE),GetGadgetItemAttribute(GetDlgCtrlID_(hwnd),0,#PB_ListIcon_ColumnWidth,0) +GetSystemMetrics_(#SM_CYEDGE),GadgetHeight(GetDlgCtrlID_(hwnd))-(*fdata\scroll_height+GetSystemMetrics_(#SM_CYEDGE))) 
        CombineRgn_(hrgn1, hrgn1, hrgn2, #RGN_XOR) 
        SetWindowRgn_(hwnd, hrgn1, 1) 
        
        SendMessage_(*fdata\header, #HDM_GETITEMRECT,0,@hr.RECT) 
        buttonh = hr\bottom    
        
        FreeImage(*fdata\colimg) 
        *fdata\colimg = CreateImage(#PB_Any,*fdata\col_0_width, GadgetHeight(GetDlgCtrlID_(hwnd))-(buttonh+GetSystemMetrics_(#SM_CYEDGE)*2+*fdata\scroll_height)) 
        
        ResizeGadget(*fdata\colimg_g,GadgetX(GetDlgCtrlID_(hwnd))+GetSystemMetrics_(#SM_CXEDGE),GadgetY(GetDlgCtrlID_(hwnd))+buttonh+GetSystemMetrics_(#SM_CXEDGE), #PB_Ignore,#PB_Ignore) 
        ResizeGadget(*fdata\buttonimg_g,GadgetX(GetDlgCtrlID_(hwnd))+GetSystemMetrics_(#SM_CXEDGE),GadgetY(GetDlgCtrlID_(hwnd))+GetSystemMetrics_(#SM_CYEDGE),*fdata\col_0_width+GetSystemMetrics_(#SM_CXEDGE),buttonh) 
        
        If newwidth > oldwidth
          SetScrollPos_(hwnd, #SB_HORZ, oldscrollpos+widthdiff, 1)
        EndIf
        
        InvalidateRect_(hwnd, 0,1) 
      EndIf 
      
    Case #WM_SETCURSOR 
      
      If *fdata\resizing_col_0 
        SetCursor_(LoadCursor_(0, #IDC_SIZEWE)) 
        ProcedureReturn 0 
      EndIf 
      
    Case #WM_SETFONT, #WM_MOVE      
      
      SetWindowRgn_(hwnd, 0, 1) 
      
      CallWindowProc_(*fdata\listproc, hwnd, msg, wparam, lparam) 
      *fdata\lineH = SendMessage_(hwnd,#LVM_GETITEMSPACING,#True,0)>>16 
      *fdata\header = SendMessage_(hwnd,#LVM_GETHEADER,0,0) 
      UpdateWindow_(hwnd) 
      
      SendMessage_(*fdata\header, #HDM_GETITEMRECT,0,@hr.RECT) 
      buttonw = hr\right 
      buttonh = hr\bottom 
      
      UpdateImages(*fdata) 
      
      hrgn1 = CreateRectRgn_(0,0,GadgetWidth(GetDlgCtrlID_(hwnd)),GadgetHeight(GetDlgCtrlID_(hwnd))) 
      hrgn2 = CreateRectRgn_(GetSystemMetrics_(#SM_CXEDGE),GetSystemMetrics_(#SM_CYEDGE),GetGadgetItemAttribute(GetDlgCtrlID_(hwnd),0,#PB_ListIcon_ColumnWidth,0) +GetSystemMetrics_(#SM_CYEDGE),GadgetHeight(GetDlgCtrlID_(hwnd))-(*fdata\scroll_height+GetSystemMetrics_(#SM_CYEDGE))) 
      CombineRgn_(hrgn1, hrgn1, hrgn2, #RGN_XOR) 
      SetWindowRgn_(hwnd, hrgn1, 1) 
      
      FreeImage(*fdata\colimg) 
      *fdata\colimg = CreateImage(#PB_Any,*fdata\col_0_width, GadgetHeight(GetDlgCtrlID_(hwnd))-(buttonh+GetSystemMetrics_(#SM_CYEDGE)*2+*fdata\scroll_height)) 
      
      ResizeGadget(*fdata\colimg_g,GadgetX(GetDlgCtrlID_(hwnd))+GetSystemMetrics_(#SM_CXEDGE),GadgetY(GetDlgCtrlID_(hwnd))+buttonh+GetSystemMetrics_(#SM_CXEDGE), #PB_Ignore,#PB_Ignore) 
      ResizeGadget(*fdata\buttonimg_g,GadgetX(GetDlgCtrlID_(hwnd))+GetSystemMetrics_(#SM_CXEDGE),GadgetY(GetDlgCtrlID_(hwnd))+GetSystemMetrics_(#SM_CYEDGE),*fdata\col_0_width+GetSystemMetrics_(#SM_CXEDGE),buttonh) 
      
      If IsImage(*fdata\sizelin):FreeImage(*fdata\sizelin):EndIf 
      *fdata\sizelin = CreateImage(#PB_Any,3,GadgetHeight(GetDlgCtrlID_(hwnd))) 
      
      hdc = StartDrawing(ImageOutput(*fdata\sizelin)) 
      Box(1,1,1,ImageHeight(*fdata\sizelin),#Gray) 
      SetRect_(imgrect.RECT,0,0,3,ImageHeight(*fdata\sizelin)) 
      DrawFocusRect_(hdc,imgrect.RECT) 
      StopDrawing() 
      SetGadgetState(*fdata\col_0_line_g, ImageID(*fdata\sizelin)) 
      SetGadgetState(*fdata\list_line_g, ImageID(*fdata\sizelin)) 
      SetGadgetState(*fdata\button_0_line_g, ImageID(*fdata\sizelin)) 
      InvalidateRect_(hwnd, 0,1) 
  EndSelect 
  
  ProcedureReturn CallWindowProc_(*fdata\listproc, hwnd, msg, wparam, lparam) 
  
EndProcedure 

Procedure ButtonProc(hwnd, msg, wparam, lparam) 
  
  *fdata.FIXEDICONDATA = GetProp_(hwnd, "fdata") 
  
  Select msg 
    Case #WM_NCDESTROY 
      RemoveProp_(hwnd, "fdata") 
      
    Case #WM_MOUSELEAVE 
      SetGadgetState(GetDlgCtrlID_(hwnd),ImageID(*fdata\buttonimg)) 
      RedrawWindow_(hwnd,0,0,#RDW_INVALIDATE|#RDW_UPDATENOW) 
      *fdata\mouseinbutton = #False 
      ProcedureReturn 0 
      
    Case #WM_LBUTTONDOWN 
      If lparam&$FFFF >= GadgetWidth(GetDlgCtrlID_(hwnd))-5 
        *fdata\resizing_col_0 = 1 
        SetCursor_( LoadCursor_(0, #IDC_SIZEWE )) 
        CallWindowProc_(*fdata\colimgproc, hwnd, msg, wparam, lparam) 
        SetCapture_(*fdata\listhwnd) 
        ProcedureReturn 0 
      Else 
        SetGadgetState(GetDlgCtrlID_(hwnd),ImageID(*fdata\button_pressedimg)) 
        CallWindowProc_(*fdata\colimgproc, hwnd, msg, wparam, lparam) 
        SetCapture_(hwnd) 
        ProcedureReturn 0 
      EndIf 
      
    Case #WM_MOUSEMOVE 
      
      If Not *fdata\mouseinbutton 
        SetGadgetState(GetDlgCtrlID_(hwnd),ImageID(*fdata\button_hoverimg)) 
        With tm.TRACKMOUSEEVENT 
          \cbSize = SizeOf(TRACKMOUSEEVENT) 
          \dwFlags = #TME_LEAVE 
          \hwndTrack = hwnd 
        EndWith 
        TrackMouseEvent_(@tm) 
        *fdata\mouseinbutton = #True 
      EndIf 
      
      If lparam&$FFFF >= GadgetWidth(GetDlgCtrlID_(hwnd))-5 And Not GetAsyncKeyState_(#VK_LBUTTON)&32768 
        SetCursor_( LoadCursor_(0, #IDC_SIZEWE )) 
      Else 
        If Not *fdata\resizing_col_0 
          SetCursor_( LoadCursor_(0, #IDC_ARROW )) 
        Else 
          ResizeGadget(*fdata\list_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
          ResizeGadget(*fdata\button_0_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
          HideGadget(*fdata\list_line_g,0) 
          HideGadget(*fdata\button_0_line_g,0) 
          HideGadget(*fdata\col_0_line_g,1) 
        EndIf 
      EndIf 
      
    Case #WM_LBUTTONUP 
      
      If *fdata\resizing_col_0 
        SendMessage_(*fdata\listhwnd, #WM_LBUTTONUP,wparam, lparam) 
      Else 
        ReleaseCapture_() 
        SetGadgetState(GetDlgCtrlID_(hwnd),ImageID(*fdata\buttonimg)) 
        With *fdata\hdnotify 
          \iitem = 0 
          \hdr\hwndfrom = *fdata\header 
          \hdr\code = #HDN_ITEMCLICKW 
        EndWith 
        SendMessage_(*fdata\listhwnd, #WM_NOTIFY, 0, *fdata\hdnotify) 
      EndIf 
      
    Case #WM_SETCURSOR 
      If *fdata\resizing_col_0 
        SetCursor_(LoadCursor_(0, #IDC_SIZEWE)) 
        ProcedureReturn 0 
      EndIf 
      
  EndSelect 
  
  ProcedureReturn CallWindowProc_(*fdata\buttonimgproc, hwnd, msg, wparam, lparam) 
  
EndProcedure 

Procedure HeaderProc(hwnd, msg, wparam, lparam) 
  
  *fdata.FIXEDICONDATA = GetProp_(hwnd, "fdata") 
  
  Select msg 
      
    Case #WM_NCDESTROY 
      RemoveProp_(hwnd, "fdata") 
      
    Case #WM_LBUTTONUP 
      If *fdata\resizing_col_0 
        SendMessage_(*fdata\listhwnd, #WM_LBUTTONUP,wparam, lparam) 
        ProcedureReturn 0 
      EndIf 
      
    Case #WM_MOUSEMOVE 
      If *fdata\resizing_col_0 
        ResizeGadget(*fdata\col_0_line_g,lparam&$FFFF,#PB_Ignore,#PB_Ignore,#PB_Ignore) 
        HideGadget(*fdata\col_0_line_g,0) 
        HideGadget(*fdata\button_0_line_g,1) 
        HideGadget(*fdata\list_line_g,1) 
        ProcedureReturn 0 
      EndIf 
      
    Case #WM_SETCURSOR 
      If *fdata\resizing_col_0 
        SetCursor_(LoadCursor_(0, #IDC_SIZEWE)) 
        ProcedureReturn 0 
      EndIf 
      GetCursorPos_(@cp.POINT) 
      ScreenToClient_(hwnd, cp) 
      SendMessage_(hwnd, #HDM_GETITEMRECT,0,@hr.RECT) 
      hr\right+20 
      If PtInRect_(hr,cp\x|cp\y<<32) 
        SetCursor_(LoadCursor_(0,#IDC_ARROW)) 
        ProcedureReturn 0 
      EndIf 
      
  EndSelect 
  
  ProcedureReturn CallWindowProc_(*fdata\headerproc, hwnd, msg, wparam, lparam) 
  
EndProcedure 

ProcedureDLL FixedIconGadget(gadgetnumber,x,y,width,height,text$,colwidth,flags=0) 
  
  *fdata.FIXEDICONDATA = AllocateMemory(SizeOf(FIXEDICONDATA)) 
  
  *fdata\themed = ThemesEnabled() 
  
  *fdata\scroll_height = GetSystemMetrics_(#SM_CYHSCROLL) 
  *fdata\col_0_width = colwidth 
  
  If gadgetnumber = #PB_Any 
    thisgadgetnumber = ListIconGadget(gadgetnumber,x,y,width,height,text$,colwidth,flags) 
    thisGadgetID = GadgetID(thisgadgetnumber) 
    retval = thisgadgetnumber 
  Else 
    thisgadgetnumber = gadgetnumber 
    thisGadgetID = ListIconGadget(gadgetnumber,x,y,width,height,text$,colwidth,flags) 
    retval = thisgadgetID 
  EndIf 
  
  *fdata\listhwnd = thisgadgetID 
  *fdata\lineH = SendMessage_(thisgadgetID,#LVM_GETITEMSPACING,#True,0)>>16 
  
  *fdata\header = SendMessage_(thisGadgetID,#LVM_GETHEADER,0,0) 
  SendMessage_(*fdata\header, #HDM_GETITEMRECT,0,@hr.RECT) 
  buttonh = hr\bottom    
  
  UpdateImages(*fdata) 
  
  hrgn1 = CreateRectRgn_(0,0,GadgetWidth(thisGadgetnumber),GadgetHeight(thisgadgetnumber)) 
  hrgn2 = CreateRectRgn_(GetSystemMetrics_(#SM_CXEDGE),GetSystemMetrics_(#SM_CYEDGE),colwidth+GetSystemMetrics_(#SM_CYEDGE), GadgetHeight(thisgadgetnumber)-(*fdata\scroll_height+GetSystemMetrics_(#SM_CYEDGE))) 
  CombineRgn_(hrgn1, hrgn1, hrgn2, #RGN_XOR) 
  
  SetWindowRgn_(thisGadgetID, hrgn1, 1) 
  
  *fdata\colimg = CreateImage(#PB_Any,colwidth,GadgetHeight(thisgadgetnumber)-(buttonh+GetSystemMetrics_(#SM_CYEDGE)*2+*fdata\scroll_height),#PB_Image_DisplayFormat) 
  
  *fdata\buttonimg_g = ImageGadget(#PB_Any,GadgetX(thisgadgetnumber)+GetSystemMetrics_(#SM_CXEDGE), GadgetY(thisgadgetnumber)+GetSystemMetrics_(#SM_CYEDGE),*fdata\col_0_width+ GetSystemMetrics_(#SM_CXEDGE),buttonh,ImageID(*fdata\buttonimg)) 
  *fdata\colimg_g =    ImageGadget(#PB_Any,GadgetX(thisgadgetnumber)+GetSystemMetrics_(#SM_CXEDGE), buttonh+GadgetY(thisgadgetnumber)+GetSystemMetrics_(#SM_CYEDGE),*fdata\col_0_width,GadgetHeight(thisgadgetnumber)- (buttonh+*fdata\scroll_height+GetSystemMetrics_(#SM_CYEDGE)*2),0) 
  
  *fdata\listproc      = SetWindowLong_(thisGadgetID,#GWL_WNDPROC,@ListProc()) 
  *fdata\colimgproc    = SetWindowLong_(GadgetID(*fdata\colimg_g),#GWL_WNDPROC,@ImageProc()) 
  *fdata\headerproc    = SetWindowLong_(*fdata\header,#GWL_WNDPROC,@HeaderProc()) 
  *fdata\buttonimgproc = SetWindowLong_(GadgetID(*fdata\buttonimg_g),#GWL_WNDPROC,@ButtonProc()) 
  
  SetProp_(thisgadgetid,                 "fdata", *fdata) 
  SetProp_(GadgetID(*fdata\colimg_g),    "fdata", *fdata) 
  SetProp_(*fdata\header,                "fdata", *fdata) 
  SetProp_(GadgetID(*fdata\buttonimg_g), "fdata", *fdata) 
  
  *fdata\sizelin = CreateImage(#PB_Any,3,GadgetHeight(thisgadgetnumber)) 
  hdc = StartDrawing(ImageOutput(*fdata\sizelin)) 
  Box(1,1,1,ImageHeight(*fdata\sizelin),#Gray) 
  SetRect_(imgrect.RECT,0,0,3,ImageHeight(*fdata\sizelin)) 
  DrawFocusRect_(hdc,imgrect.RECT) 
  StopDrawing() 
  
  oldlist = UseGadgetList(thisgadgetid) 
  *fdata\col_0_line_g = ImageGadget(#PB_Any,*fdata\col_0_width+5,0,4,GadgetHeight(thisgadgetnumber),ImageID(*fdata\sizelin)) 
  DisableGadget(*fdata\col_0_line_g,1) 
  HideGadget(*fdata\col_0_line_g,1) 
  UseGadgetList(GadgetID(*fdata\colimg_g)) 
  *fdata\list_line_g = ImageGadget(#PB_Any,0,0,4,GadgetHeight(thisgadgetnumber),ImageID(*fdata\sizelin)) 
  DisableGadget(*fdata\list_line_g,1) 
  HideGadget(*fdata\list_line_g,1) 
  UseGadgetList(GadgetID(*fdata\buttonimg_g)) 
  *fdata\button_0_line_g = ImageGadget(#PB_Any,0,0,4,GadgetHeight(*fdata\buttonimg_g),ImageID(*fdata\sizelin)) 
  DisableGadget(*fdata\button_0_line_g ,1) 
  HideGadget(*fdata\button_0_line_g ,1) 
  UseGadgetList(oldlist)  
  
  ProcedureReturn retval 
  
EndProcedure 

;/////////////////////////////////////////////////////////////////////////////////////////////////////////
Little test program:

Code: Select all

Global old_proc

Procedure MyListProc(hwnd, msg, wparam, lparam) 

  Select msg 
    
    Case  #WM_NOTIFY 
      *nmHEADER.HD_NOTIFY = lParam 
       Select *nmHEADER\hdr\code 
        Case #HDN_ITEMCLICKW 
          Debug "Column "+Str(*nmHEADER\iitem)+" button pressed"
        EndSelect 
  EndSelect
  
  ProcedureReturn CallWindowProc_(old_proc, hwnd, msg, wparam, lparam)
EndProcedure


OpenWindow(0,0,0,1024,768,"FixedIconGadget Test",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)

List0 = FixedIconGadget(#PB_Any,10,10,600,300,"column 0", 140,#PB_ListIcon_FullRowSelect)

ResizeGadget(List0, 20,100,#PB_Ignore,600)
SetGadgetFont(List0, LoadFont(0, "Arial", 12) )

AddGadgetColumn(List0,1,"Column 1",100)
AddGadgetColumn(List0,2,"Column 2",100)
AddGadgetColumn(List0,3,"Column 3",100)
AddGadgetColumn(List0,4,"Column 4",100)
AddGadgetColumn(List0,5,"Column 5",100)
AddGadgetColumn(List0,6,"Column 6",100)
AddGadgetColumn(List0,7,"Column 7",100)

For i=0 To 100
  AddGadgetItem(List0, -1, "Line "+Str(i)+" First gadget column 0"+Chr(10)+"Line "+Str(i)+" col 1"+Chr(10)+"Line "+Str(i)+" col 2"+Chr(10)+"Line "+Str(i)+" col 3"+Chr(10)+"Line "+Str(i)+" col 4"+Chr(10)+"Line "+Str(i)+" col 5"+Chr(10)+"Line "+Str(i)+" col 6"+Chr(10)+"Line "+Str(i)+" col 7")
Next

old_proc = SetWindowLong_(GadgetID(list0),#GWL_WNDPROC,@MyListProc())


ButtonGadget(0, 400,720,100,20,"Destroy")

Repeat
  ev = WaitWindowEvent()
  Select ev
    Case #PB_Event_Gadget
      If EventGadget()=0
        FreeGadget(List0)
      EndIf
  EndSelect
Until ev = #PB_Event_CloseWindow

Last edited by netmaestro on Sun Apr 25, 2010 2:21 pm, edited 25 times in total.
BERESHEIT
User avatar
Michael Vogel
Addict
Addict
Posts: 2807
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Post by Michael Vogel »

When I try to resize column 0 I get a new, empty column between #0 and #1 - not sure if this is a bug or an intended behaviour...

Michael
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

I was right about the dirty hacks! :wink: A novel way of proceeding for sure!

Yes you can make column 1 disappear permanently with some ruthless resizing!!! I think you need to prevent column 0 from being resized netty.

Aye with a bit of work and, as you say, switch some of the hard coded values with proper system metrics etc. this could be really neat.

Thanks for sharing.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Mar 22: Update, code and details in first post
BERESHEIT
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Oops, fixed a couple bugs in the drawing.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Pretty cool netty! :)

One problem - mouse capture, or lack of it! :wink:

When resizing column 0 you haven't captured the mouse. E.g. drag the divider outside the window and then release the mouse and you will see what I mean.

You'll need to capture the mouse on left-button down and send all messages to one window or the other - the listicon itself is probably the best bet.

Thanks again, this could be useful.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Update March 26, code & descriptions in first post.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Hi,

thanks for the update. :)

The capture is certainly much better; but still not without it's problems I'm afraid. When you resize column 0 and drag the divider off screen, the resizing is cancelled - at least it is here on Vista! Is this deliberate because it is certainly non-standard behaviour? I still think, rather than use a thread here, a simple use of SetCapture_() would be much simpler and would quickly solve these problems.

Still, great utility.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

On the drag behaviour, what should it do? Say if the divider was dragged off the screen to the left and then the mousebutton released, should it resize down to it's smallest? I've set it up to cancel. It's a small thing to change that if it isn't what should be expected.

On the SetCapture, I confess that I just can't make it work. I set the capture to the listview control when the lbutton goes down, and I look for events in the listview's proc, but they don't come. If I drag the mouse off the control, off the window entirely and release, all the listview gets when it has the capture is WM_NCMOUSELEAVE, and it only gets that when the mouse moves after the release.

I appreciate the suggestion, and I'm with you- SetCapture should be the correct approach. If you could help me apply it to this code so that it gives the same performance as the thread, I'll happily adopt it.

Thanks for your feedback in this, it's appreciated!
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

An early version of EsGRID handled all of the resizing manually by setting the capture and manually drawing divider lines etc. It worked okay. Yours should be somewhat easier because you are of course moving a gadget around rather than drawing divider lines.

On capturing the mouse and giving it to your main ListIcon, you should receive through #WM_MOUSEMOVE cursor coords (in lParam) relative to the client area of the ListIcon regardless of where the cursor actually is, and this is the key.

However, I can confirm that your library is not behaving itself when you attempt to capture the cursor! Strange! The following works fine. Click on the window to capture the cursor and the x-coord is then displayed whenever you move the cursor, even when you venture into the listicon or venture off the window!

Code: Select all

Global gFlag

Procedure WinCallback(hWnd, uMsg, wParam, lParam) 
  If uMsg = #WM_MOUSEMOVE
    If gFlag
      x.w = lParam&$ffff
      Debug x
    EndIf
  EndIf 
  
  ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 
  
  
If OpenWindow(0, 0, 0, 600, 600, "Messages", #PB_Window_MinimizeGadget|#PB_Window_MaximizeGadget|#PB_Window_ScreenCentered) 
   ListIconGadget(0, 5, 5, 290, 90, "Name", 100, #PB_ListIcon_FullRowSelect|#PB_ListIcon_AlwaysShowSelection)
   AddGadgetColumn(0, 1, "Address", 250)
   AddGadgetItem(0, -1, "Harry Rannit"+Chr(10)+"12 Parliament Way, Battle Street, By the Bay")
   AddGadgetItem(0, -1, "Ginger Brokeit"+Chr(10)+"130 PureBasic Road, BigTown, CodeCity")

  SetWindowCallback(@WinCallback())    ; activate the callback
  Repeat 
    Select WaitWindowEvent() 
      Case #WM_LBUTTONDOWN
        gFlag = 1
        SetCapture_(WindowID(0))
      Case #WM_LBUTTONUP
        gFlag = 0
        ReleaseCapture_()
      Case #PB_Event_CloseWindow 
        End 
    EndSelect 
  ForEver 
EndIf 
I'll keep testing your code - there must be a reason for this!
I may look like a mule, but I'm not a complete ass.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Got it.

Do not call the original window proc when you capture the cursor; or at the very least call it before capturing the cursor.

Have tested with your code; captured the cursor in the ImageProc with SetCapture_(*fdata\listhwnd) ensuring that the call to the old win proc occurs before this. In your ListProc I then trapped #WM_MOUSEMOVE etc. works fine.

A word of note; assign lParam&$ffff to a word variable as you'll then receive negative coordinates rather than unsigned 16-bit values.

I hope this helps with this great library.

**EDIT : I also tested by directing mouse capture to your image proc and in this case I had to stop calling the old window proc for #WM_MOUSEMOVE when the mouse had been captured. Seems that the image gadget does some peculiar things! :) I guess the safest way is to redirect the capture to the listicon.
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Super! Thanks so much for your help and advice, I'll get right on this now and get rid of that thread. Then- on to the pushable column 0 button.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Just a thought, could you not add the entire header to the window region for the ListIcon, including the header item for column 0 ?
I may look like a mule, but I'm not a complete ass.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Nope. The header will scroll... yuck.
BERESHEIT
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Whoops, silly me; of course! :)
I may look like a mule, but I'm not a complete ass.
Post Reply