How to automatically resize the last column of a ListIcon?

Windows specific forum
Jihugen
User
User
Posts: 45
Joined: Mon Jun 07, 2010 11:36 pm
Location: Normandy, France

How to automatically resize the last column of a ListIcon?

Post by Jihugen »

Hi,
I'm trying to have the last column of a ListIcon automatically resized.

(In my case, it would be only when adding items to the list, or modifying the witdh of the first column).
The final goal is to avoid the horizontal scrolling bar that appear only to allow the vertical bar to be displayed...

It would be the equivalent of PureLVSORT_SetLastColumnAutoResize() in one of the excellent Gnozal's libs (PureLVSORT).
But I don't want to be dependent of this lib, because I don't need sorting features, and I'm using Set/GetGadgetItemData().

We can see in the code below what's wrong with scrolling bars:

Code: Select all

Enumeration
  #Window
EndEnumeration

Enumeration
  #ListIcon
  #Button
EndEnumeration

If OpenWindow(#Window, 450, 200, 400, 185, "test", #PB_Window_SystemMenu|#PB_Window_TitleBar)
  ListIconGadget(#ListIcon, 15, 20, 200, 150, "1", 160, #PB_ListIcon_AlwaysShowSelection|#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect)
  AddGadgetColumn(#ListIcon, 1, "2", 100)
  SendMessage_(GadgetID(#ListIcon), #LVM_SETCOLUMNWIDTH, 1, #LVSCW_AUTOSIZE_USEHEADER)
  ButtonGadget(#Button, 240, 20, 140, 30, "Add items", #PB_Button_Toggle)

  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Button
            ClearGadgetItems(#ListIcon)
            If GetGadgetState(#Button)
              For i = 0 To 20
                AddGadgetItem(#ListIcon, -1, Str(i))
              Next
            EndIf
            ; SendMessage_(GadgetID(#ListIcon), #LVM_SETCOLUMNWIDTH, 1, #LVSCW_AUTOSIZE_USEHEADER) ; <---- I'd like to have this line in a callback proc.
        EndSelect
      Case #PB_Event_CloseWindow
        CloseWindow(#Window)
        Break
    EndSelect
  ForEver
EndIf
What should be the way to proceed?

For now, I believe I have to use the command:
SendMessage_(GadgetID(#ListIcon), #LVM_SETCOLUMNWIDTH, 1, #LVSCW_AUTOSIZE_USEHEADER)
but it should probably be in a callback procedure associated with the ListIcon to work automatically, shouln't it?
Unfortunately, I'm getting a bit lost when it comes to callback...
A little help would be very appreciated. :)
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: How to automatically resize the last column of a ListIco

Post by luis »

Based on what you asking, probably something like this ?

Code: Select all


Enumeration
  #Window
EndEnumeration

Enumeration
  #ListIcon
  #Button
EndEnumeration

Procedure.i CallBack_ListView (hWnd, iMessage, wParam, lParam)

Protected *oldWinProc = GetProp_(hWnd, "OldList")

Select iMessage               
    Case #WM_NCDESTROY
        RemoveProp_(hWnd, "OldList") 
    Case #WM_SIZE
        SendMessage_(GadgetID(#ListIcon), #LVM_SETCOLUMNWIDTH, 1, #LVSCW_AUTOSIZE_USEHEADER)
        ProcedureReturn 0         
EndSelect   

ProcedureReturn CallWindowProc_(*oldWinProc, hWnd, iMessage, wParam, lParam)

EndProcedure

Procedure CallBack_MainWin (iWindowID, iMessage, wParam, lParam)
 Protected iRetVal = #PB_ProcessPureBasicEvents
 Protected *tNMH.NMHEADER
 
 Select iMessage
    Case #WM_NOTIFY        
                
         *tNMH = lParam
         
         If *tNMH\hdr\code = #HDN_ITEMCHANGING              
            Debug "resizing"            
         EndIf                            
  EndSelect
      
  ProcedureReturn iRetVal
 
EndProcedure

Procedure SubclassWindow (hWnd, *fpNewWinProc, sPropID.s)
SetProp_(hWnd, sPropID, GetWindowLongPtr_(hWnd, #GWL_WNDPROC))   
SetWindowLongPtr_(hWnd, #GWL_WNDPROC, *fpNewWinProc)
EndProcedure 

If OpenWindow(#Window, 450, 200, 400, 185, "test", #PB_Window_SystemMenu|#PB_Window_TitleBar)
  ListIconGadget(#ListIcon, 15, 20, 200, 150, "1", 150, #PB_ListIcon_AlwaysShowSelection|#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect)
 
  SubclassWindow (GadgetID(#ListIcon), @CallBack_ListView(), "OldList")
 
  AddGadgetColumn(#ListIcon, 1, "2", 50) 
  ButtonGadget(#Button, 240, 20, 140, 30, "Add items", #PB_Button_Toggle)

  SetWindowCallback(@CallBack_MainWin())
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Button
            ClearGadgetItems(#ListIcon)
            If GetGadgetState(#Button)
              For i = 0 To 20
                AddGadgetItem(#ListIcon, -1, Str(i))
              Next
            EndIf           
        EndSelect
      Case #PB_Event_CloseWindow
        CloseWindow(#Window)
        Break
    EndSelect
  ForEver
EndIf
But it's not foolproof, unless you prevent manual column resizing too...
Last edited by luis on Wed Jun 30, 2010 11:00 pm, edited 1 time in total.
"Have you tried turning it off and on again ?"
A little PureBasic review
Jihugen
User
User
Posts: 45
Joined: Mon Jun 07, 2010 11:36 pm
Location: Normandy, France

Re: How to automatically resize the last column of a ListIco

Post by Jihugen »

Well, thanks, luis. Thanks to your code, I have dicovered some still unknow stuff for me, like the use of GetProp_().
luis wrote:But it's not foolproof, unless you prevent manual column resizing too...
Unfortunately, as I said in 1st post, I'd like to keep the possibility to resize column...

Can we intercept the message when columns are resized, the same way we intercept #WM_SIZE ?
(I tried to seach in the Win API SDK help, but, well, hem... I think I don't even know what to look for).

And I'm just wondering if the source code of Gnozal's libraries is available ? (I don't think so, but it's just to be sure).
USCode
Addict
Addict
Posts: 923
Joined: Wed Mar 24, 2004 11:04 pm
Location: Seattle

Re: How to automatically resize the last column of a ListIco

Post by USCode »

I might be misunderstanding your issue, but won't SetGadgetItemAttribute with #PB_ListIcon_ColumnWidth do what you want?
http://www.purebasic.com/documentation/ ... ibute.html
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: How to automatically resize the last column of a ListIco

Post by luis »

Jihugen wrote: Can we intercept the message when columns are resized, the same way we intercept #WM_SIZE ?
(I tried to seach in the Win API SDK help, but, well, hem... I think I don't even know what to look for).
I edited the code to show you a way to intercept the resize of the heading columns.

From there don't know exactly what you like to obtain, but you can use that as a starting point maybe ?
"Have you tried turning it off and on again ?"
A little PureBasic review
Jihugen
User
User
Posts: 45
Joined: Mon Jun 07, 2010 11:36 pm
Location: Normandy, France

Re: How to automatically resize the last column of a ListIco

Post by Jihugen »

@USCode
You can't get the colunm to be automatically resized with a simple SetGadgetItemAttribute().

@Luis
I edited the code to show you a way to intercept the resize of the heading columns.
Ok, thanks again.
Now I have to digest and understand all that...
You may call me stupid, but I can't understand why you once use a gadget callback, and once a windows callback?
<edit> After reading the WinAPI doc, I think I get it: it's simply because the HDN_ITEMCHANGING message is sent to the parent Windows, not to the ListIcon Gadget </edit>
Can't we only use a gadget callback?
From there don't know exactly what you like to obtain, but you can use that as a starting point maybe ?
Sure, it a good starting point.
I'm just afraid I'll write some dirty noobs code here because I curently don't understand exactly all the involved things...
And well, what I like to obtain is simply that the last column always uses all the available space that remains at the right side of the Listicon. So, it should get resized when the other columns size change, or when the vertical scroolbal appears in the gadget (because of the place needed to display the scroolbar).
But I though it was clear in my 1st post... or is there a point I forgot to clarify? :?
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: How to automatically resize the last column of a ListIco

Post by luis »

Yes the reason for the other callback it's the one you already discovered.

About what you want to obtain.. it seem not very easy, but I'm not very adept with those kind of things, maybe it's easy for someone else :)

You want to keep every field resizable and the last field to autosize to fill the available space, right ?

Suppose you have three fields: A, B, C, in a listview of W width.

A+B+C must be <= W or you get a horizontal scroll bar.

If the user can resize B, it can resize it to a width > W, so you'll have the scroll bar back.

C, when resized with the sendmessage used above, seems to always have a minum width, when the minimal width kick in you'll have again the horizontal scrollbar.

So you should be able to limit the resize of the items checking every instant the A+B+C <= W condition

But how much A+B can be near to W without the autosize of C hitting the minimum value and going above W is not clear to me and so I don't know hot to treat that.

Or you can throw the autosize feature away and size the last column manually computing its width with C = W - (A + B), and adding the scroll bar width to the equation if the vertical scrollbar is visible (you can get both the width and the scrollbar status through api).

Anyway, considering changing programmatically the size of one column will fire a resize event message like changing it with the mouse (I believe), you should try to differentiate these and ignore the ones caused by your code (maybe using a flag?) or you'll end up with endless looping.

Don't know... I would probably try along these routes but maybe some one else can give you a better suggestion.
"Have you tried turning it off and on again ?"
A little PureBasic review
Jihugen
User
User
Posts: 45
Joined: Mon Jun 07, 2010 11:36 pm
Location: Normandy, France

Re: How to automatically resize the last column of a ListIco

Post by Jihugen »

In fact (about A+B+C column size...) the goal is not to avoid an horizontal scrolling bar at all cost.
In case the last column has reach its minimum size (which I think depends of the column header text width), then of course a scrollbar can appear.
The goal is to have the last column automatically sized up to fill all the available space. And in case you increase the size of any other columns, the last column should be sized down, until it reaches its min width, then in the later case, a horizontal scroolbar will appear.
I imagine all of this should be done properly if the SendMessage_(GadgetID(#ListIcon), #LVM_SETCOLUMNWIDTH, 1, #LVSCW_AUTOSIZE_USEHEADER) could be sended at the right time.
luis wrote:Anyway, considering changing programmatically the size of one column will fire a resize event message like changing it with the mouse (I believe), you should try to differentiate these and ignore the ones caused by your code (maybe using a flag?) or you'll end up with endless looping.
Yes, you're right, I have actually faced this endless loop issue.

Well, thanks for your time, Luis. I also hope someone else know a clean way to proceed. :|
Until then, I have placed several SendMessage_(GadgetID(#ListIcon), #LVM_SETCOLUMNWIDTH, 1, #LVSCW_AUTOSIZE_USEHEADER) in my application, each time I change the number of items in the list. It's not very smart, but the result is acceptable.
Jihugen
User
User
Posts: 45
Joined: Mon Jun 07, 2010 11:36 pm
Location: Normandy, France

Re: How to automatically resize the last column of a ListIco

Post by Jihugen »

Gnozal has kindly provided me a snippet of code used in his PureLVSORT library. And he seems to agree the task was not as simple as it could look first.
Unfortunately, the code is too interlinked in the whole library too be reused, at least for me.

But I have found another way to proceed, intercepting the #WM_PAINT message.
I'm posting it here if someone is interested.

Code: Select all

Enumeration
  #Window
EndEnumeration

Enumeration
  #ListIcon
  #Button
EndEnumeration

Global *oldListIconCallback

Procedure.i CallBack_ListView (hWnd, iMessage, wParam, lParam)
  Select iMessage  
    Case #WM_PAINT
      Debug "paint"
      LastColumn = SendMessage_(SendMessage_(hWnd, #LVM_GETHEADER, 0, 0), #HDM_GETITEMCOUNT, 0, 0) - 1
      SendMessage_(hWnd, #LVM_SETCOLUMNWIDTH, LastColumn, #LVSCW_AUTOSIZE_USEHEADER)
  EndSelect   

  ProcedureReturn CallWindowProc_(*oldListIconCallback, hWnd, iMessage, wParam, lParam)
EndProcedure

If OpenWindow(#Window, 450, 200, 400, 185, "test", #PB_Window_SystemMenu|#PB_Window_TitleBar)
  ListIconGadget(#ListIcon, 15, 20, 200, 150, "1", 150, #PB_ListIcon_AlwaysShowSelection|#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect)
  
  *oldListIconCallback = SetWindowLongPtr_(GadgetID(#ListIcon), #GWL_WNDPROC, @CallBack_ListView())

  AddGadgetColumn(#ListIcon, 1, "2", 50)
  ButtonGadget(#Button, 240, 20, 140, 30, "Add items", #PB_Button_Toggle)

  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Button
            ClearGadgetItems(#ListIcon)
            If GetGadgetState(#Button)
              For i = 0 To 20
                AddGadgetItem(#ListIcon, -1, Str(i))
              Next
            EndIf           
        EndSelect
      Case #PB_Event_CloseWindow
        CloseWindow(#Window)
        Break
    EndSelect
  ForEver
EndIf
It's working nice, but I'm quite sure that intercepting the #WM_PAINT message is not the proper way to do.
For example, sending the #LVM_SETCOLUMNWIDTH command fires several other #WM_PAINT messages, so it's not very efficient (but at least there is no endless loop).

But I shamefully admit that I'm not mastering the subject at all, and that it's working almost by luck.
So please feel free to correct this code if you find it's too much a nonsense...
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: How to automatically resize the last column of a ListIco

Post by netmaestro »

I think this is probably going to be a bit cleaner. The problem with responding to WM_PAINT is that any sizing has already happened, and any adjustments made thereafter will result in sizes changed in a doubly-visible manner (read: flicker) See how this looks:

Code: Select all

Enumeration
  #Window
EndEnumeration

Enumeration
  #ListIcon
  #Button
EndEnumeration

Procedure HeaderProc(hwnd, msg, wparam, lparam)
  oldproc = GetProp_(hwnd, "oldproc")
  
  Select msg
    Case #WM_NCDESTROY
      RemoveProp_(hwnd, "oldproc")
      
    Case #WM_WINDOWPOSCHANGING
      
      NewList widths()
      
      hostgadget = GetDlgCtrlID_(GetParent_(hwnd))
      If GetWindowLongPtr_(GadgetID(hostgadget), #GWL_STYLE) & #WS_VSCROLL
        maxwidth = GadgetWidth(hostgadget) - 25
      Else
        maxwidth = GadgetWidth(hostgadget) - 7
      EndIf
      
      numitems = SendMessage_(hwnd, #HDM_GETITEMCOUNT, 0, 0)
      lastcol = numitems-1
      
      For i=0 To lastcol
        SendMessage_(hwnd, #HDM_GETITEMRECT, i, @p.RECT)
        AddElement(widths())
        widths() = p\right - p\left
      Next 
      curwidth=0
      ForEach widths()
        curwidth+widths()
      Next
      lastcol_curwidth = widths()
      
      lastcol_maxwidth = lastcol_curwidth-(curwidth-maxwidth)
      If lastcol_maxwidth >= 18
        With hdi.HDITEM
          \mask = #HDI_WIDTH
          \cxy = lastcol_maxwidth
        EndWith
        SendMessage_(hwnd, #HDM_SETITEM, lastcol, hdi)
      EndIf
      
      ProcedureReturn 0
      
  EndSelect   
  
  ProcedureReturn CallWindowProc_(oldproc, hwnd, msg, wparam, lparam)
  
EndProcedure

If OpenWindow(#Window, 450, 200, 400, 185, "test", #PB_Window_SystemMenu|#PB_Window_TitleBar)
  ListIconGadget(#ListIcon, 15, 20, 200, 150, "1", 100, #PB_ListIcon_AlwaysShowSelection|#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect)
  AddGadgetColumn(#ListIcon, 1, "2", 70)
  ButtonGadget(#Button, 240, 20, 140, 30, "Add items", #PB_Button_Toggle)
  
  hdr = SendMessage_(GadgetID(#ListIcon), #LVM_GETHEADER,0,0)
  
  SetProp_(hdr, "oldproc", SetWindowLongPtr_(hdr, #GWL_WNDPROC, @HeaderProc()))
  
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Button
            ClearGadgetItems(#ListIcon)
            If GetGadgetState(#Button)
              For i = 0 To 20
                AddGadgetItem(#ListIcon, -1, Str(i))
              Next
            EndIf
            
        EndSelect
      Case #PB_Event_CloseWindow
        CloseWindow(#Window)
        Break
    EndSelect
  ForEver
EndIf
BERESHEIT
Jihugen
User
User
Posts: 45
Joined: Mon Jun 07, 2010 11:36 pm
Location: Normandy, France

Re: How to automatically resize the last column of a ListIco

Post by Jihugen »

Thanks Netmaestro your help with this issue, and for your explaination about the WM_PAINT message.

Unfortunately, I had several glitches when trying your example...

1. There is a small dummy column created at the right.
I'm using XP with no Themes (classical), maybe depending on your theme it could/couldn't appear.
IMHO, using #LVSCW_AUTOSIZE_USEHEADER instead of computing ourselves the columns size should be preferred to avoid that.
So I have replaced the whole 'Case #WM_WINDOWPOSCHANGING' code in your example with the code below.
It fixes the issue, and the code is much simpler that way.

Code: Select all

    Case #WM_WINDOWPOSCHANGING
      LastColumn = SendMessage_(hwnd, #HDM_GETITEMCOUNT, 0, 0) - 1
      SendMessage_(GetParent_(hwnd), #LVM_SETCOLUMNWIDTH, LastColumn, #LVSCW_AUTOSIZE_USEHEADER)
      ProcedureReturn 0
2. If you try to resize the first column to be bigger than the displayed List width (by dragging the column separator outside the list, to the right side), making the horizontal scrolling bar appears, then the column separator is not displayed anymore between column 1 and 2 (I have the same issue with my code using WM_PAINT).
If you then reduce back the first column witdh (dragging the column separator outside the list, to the left side), making the horizontal scrolling bar disappears, the display of the list header gets messed up.

3. When we add items (using the 'add items' button), the last column with is nicely adjusted, but when we remove items, witdh is not adjusted.

4. If the first column size is large enough to make the horizontal scrolling bar displayed, then when moving this scrollbar, the resizing code callback gets constantly called.
So it's not very efficient (could be noticeable when working with quite big List ?).


I tried to find better messages to intercept, or to adapt examples (I found on the net) from other languages to PB.
But until now, all my tries were failures...
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Re: How to automatically resize the last column of a ListIco

Post by PB »

> The final goal is to avoid the horizontal scrolling bar that appear only to allow the vertical bar to be displayed

If you don't care about the user manually resizing the columns, then this is what I've been doing for years:

Code: Select all

; Creates a ListIconGadget with 3 x columns. The first 2 will have hard-coded
; widths of between 50 and 150 pixels each, and the 3rd will be a dynamic size
; that is short enough NOT to induce the horizontal scrollbar from appearing.

; Does NOT take into account that the user can later resize the columns!

scrollwidth=GetSystemMetrics_(#SM_CXVSCROLL)+GetSystemMetrics_(#SM_CXDRAG)

If OpenWindow(0,200,200,400,150,"No horizonal scrollbar!",#PB_Window_SystemMenu)
  col1=Random(100)+50
  col2=Random(100)+50
  ListIconGadget(0,10,10,380,115,"1",col1)
  For a=1 To 10 : AddGadgetItem(0,-1,Str(a)) : Next
  AddGadgetColumn(0,1,"2",col2)
  AddGadgetColumn(0,2,"3  (Dynamic)",380-col1-col2-scrollwidth)
  Repeat : Until WaitWindowEvent()=#PB_Event_CloseWindow
EndIf
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
Jihugen
User
User
Posts: 45
Joined: Mon Jun 07, 2010 11:36 pm
Location: Normandy, France

Re: How to automatically resize the last column of a ListIco

Post by Jihugen »

@PB
Well, I do care about the columns resizing, as I stated in 1st post.
Jihugen wrote:(In my case, it would be only when adding items to the list, or modifying the witdh of the first column).
But anyway, thanks for the tips. Could be useful. I may reuse it when trying to keep things simple.
Honestly, if I had knew it at the beginning, I may have not opened this thread... ;)
But now, I'm aiming at a more clean and robust (or let's say 'professional') design.
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Re: How to automatically resize the last column of a ListIco

Post by PB »

> I do care about the columns resizing, as I stated in 1st post

I missed that bit. But you can still work my calculations into any ListIconGadget column resizing to achieve your goal.
That's pretty much what I was trying to demonstrate to you; ie. the algo used to prevent the scrollbar appearing. ;)
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
GeBonet
Enthusiast
Enthusiast
Posts: 135
Joined: Fri Apr 04, 2008 6:20 pm
Location: Belgium

Re: How to automatically resize the last column of a ListIco

Post by GeBonet »

Hi,
I thing you can try the "PureLVSORT_SetLastColumnAutoResize(GadgetNumber.l, TrueOrFalse.l)"
of Gnozal Library... ? :?:
Sorry for my english :wink: ! (Windows Xp, Vista and Windows 7, Windows 10)
Post Reply