Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate

Can you move a listview column programmatically?


  • Please log in to reply
15 replies to this topic
wtg
  • Guests
  • Last active:
  • Joined: --
I have a need for a checkbox on a listview column other than the first. I've found I can create a listview with the checkbox and then move the column to a different position via my mouse, dragging Col 1 to the 4th column position, for instance. However, I can't find a way to accomplish the same thing programmatically.

Is the only way to accomplish this going to be feeding the GUI an emulated mouse click and drag, or am I missing something obvious?

Sorry if this is covered somewhere. I've read the help file extensively and searched the forums with no luck.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
http://msdn.microsof... ... rarray.asp
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

wtg
  • Members
  • 251 posts
  • Last active: Dec 19 2012 03:54 PM
  • Joined: 04 Oct 2006
Thanks for pointing that out to me PhiLho. It's a bit more than I was ready for, but I think I'm getting close.

Can you see what's wrong with my test program below? I've never used SendMessage before (and have only used AHK for about a week) and so I've had to scrounge the help file and the forums to figure out what to do. I'm sure I'm probably making a newbie mistake, but I can't see why this won't reorder column 1 and 2.


Gui, Add, ListView, grid Checked vTestList, Column 1|Column 2|Column 3
Gui, Add, Button, gSwap, Swap
Gui, Add, Statusbar

Gui, Show, Autosize Center +Resize,Swap Test

lv_Add("", "r1c1", "r1c2", "r1c3")
lv_Add("", "r2c1", "r2c2", "r2c3")
lv_Add("", "r3c1", "r3c2", "r3c3")
Return

   
Swap:   
   cap = 3*4
   VarSetCapacity(ColOrder, cap, 0) 
   InsertInteger(1, ColOrder, 0*4) 
   InsertInteger(0, ColOrder, 1*4) 
   InsertInteger(2, ColOrder, 2*4) 
   
   SendMessage, 0x103A, cap, &ColOrder, TestList, ahk_class AutoHotkeyGUI 
   MsgBox Errorlevel = %ErrorLevel%
return

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity.  To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
	Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
		DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
} 

GuiClose:
   ExitApp


PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Some errors: wParam is the number of elements in the array, not the size of the buffer in bytes; cap = 3*4 is a classical error, it should be cap := 3*4; AFAIK, SendMessage doesn't take a GUI control variable name (it is not a GUI command), you have to put the ClassNN.

I have made a function out of your code:
Gui Add, ListView, grid Checked vTestList, C1|C2|C3|C4|C5
Gui Add, Button, gSwap, Swap
Gui Add, Statusbar

Gui Show, Autosize Center +Resize, Swap Test

LV_Add("", "r1c1", "r1c2", "r1c3", "r1c4", "r1c5")
LV_Add("", "r2c1", "r2c2", "r2c3", "r2c4", "r2c5")
LV_Add("", "r3c1", "r3c2", "r3c3", "r3c4", "r3c5")
LV_ModifyCol()
Return


Swap:
	colNb := LV_GetCount("Column")
	Random col1, 1, colNb
	Loop
	{
		Random col2, 1, colNb
		If (col2 != col1)
			Break
	}
	Swap(col1, col2, colNb, 1)
	LV_ModifyCol()
return

; Swap two columns of the list view # _lvID, given by their index, starting at 1
Swap(_col1, _col2, _colNb, _lvID)
{
	local colOrder, pos

	VarSetCapacity(colOrder, _colNb * 4, 0)
	Loop %_colNb%
	{
		pos := A_Index - 1
		If (A_Index = _col1)
			InsertInteger(_col2 - 1, colOrder, pos * 4)
		Else If (A_Index = _col2)
			InsertInteger(_col1 - 1, colOrder, pos * 4)
		Else
			InsertInteger(pos, colOrder, pos * 4)
	}
	SendMessage 0x1000 + 58	; LVM_SETCOLUMNORDERARRAY
			, _colNb, &colOrder, SysListView32%_lvId%, A
	SendMessage 0x1000 + 21	; LVM_REDRAWITEMS
			, 0, _colNb - 1, SysListView32%_lvId%, A
}
[EDIT]
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

wtg
  • Members
  • 251 posts
  • Last active: Dec 19 2012 03:54 PM
  • Joined: 04 Oct 2006
PhiLho,

Thank you so much... you really helped clarify things.

I didn't understand how to provide the handle to the list control. If I understand correctly, it's SysListView321 because this was the first list added to the screen, and the second added would be SysListView322 and so on. Is there a function of any kind, or can one be written, that would return SysListView32n using just the list variable name? I mean, AHK commands that manipulate the list using it's variable name are essentially converting those references to the appropriate SysListView32n reference behind the scenes, right?

The cap assignment problem was something I introduced after the fact cleaning up code to post, and using it as the # of elements in the SendMessage command is a bug I introduced while trying to figure out why my call wasn't working. It's one of those changes that could have taken me another day to figure out once I got the list controls handle right, or may not have gotten them both right at the same time without your help.

By the way, you used the LVM_REDRAWITEMS command to make the columns redraw. Is there an advantage to doing this over a simple GuiControl, +Redraw, TestList?

Thanks again!

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005

By the way, you used the LVM_REDRAWITEMS command to make the columns redraw. Is there an advantage to doing this over a simple GuiControl, +Redraw, TestList?

Probably no... I just had the nose in MSDN, so I searched there instead of AHK doc. I wouldn't have thought of it anyway, as the doc. presents it as a pair with -Redraw (block updates, then re-allow them).

Mapping a GUI control ClassNN to its variable name is a bit tricky.
One way (the only one?) is to use:
GuiControlGet var, FocusV
so you have to do a GuiControl Focus first (can be done once just after the Gui building).
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

wtg
  • Members
  • 251 posts
  • Last active: Dec 19 2012 03:54 PM
  • Joined: 04 Oct 2006

[Mapping a GUI control ClassNN to its variable name is a bit tricky.
One way (the only one?) is to use:
GuiControlGet var, FocusV
so you have to do a GuiControl Focus first (can be done once just after the Gui building).


Doing some more reading after your suggestion I believe you meant GuiControlGet,var,Focus instead of FocusV. That's really helpful. Since the ListView variable isn't actually used to store a value by the control, one could as a matter of practice use:
 GuiControl,Focus,MyList
 GuiControlGet,MyList,Focus
right after building the screen, storing the ClassNN value in the control's variable. Seems as good a thing as anything to store in the variable.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Yes, I got it the way around... (I shown how to get a variable name, not the ClassNN.) Sorry. but at least it pushed you in the right direction. :-)
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

adamrgolf
  • Members
  • 442 posts
  • Last active: May 22 2017 09:16 PM
  • Joined: 28 Dec 2006

Some errors: wParam is the number of elements in the array, not the size of the buffer in bytes; cap = 3*4 is a classical error, it should be cap := 3*4; AFAIK, SendMessage doesn't take a GUI control variable name (it is not a GUI command), you have to put the ClassNN.

I have made a function out of your code:

Gui Add, ListView, grid Checked vTestList, C1|C2|C3|C4|C5
Gui Add, Button, gSwap, Swap
Gui Add, Statusbar

Gui Show, Autosize Center +Resize, Swap Test

LV_Add("", "r1c1", "r1c2", "r1c3", "r1c4", "r1c5")
LV_Add("", "r2c1", "r2c2", "r2c3", "r2c4", "r2c5")
LV_Add("", "r3c1", "r3c2", "r3c3", "r3c4", "r3c5")
LV_ModifyCol()
Return


Swap:
	colNb := LV_GetCount("Column")
	Random col1, 1, colNb
	Loop
	{
		Random col2, 1, colNb
		If (col2 != col1)
			Break
	}
	Swap(col1, col2, colNb, 1)
	LV_ModifyCol()
return

; Swap two columns of the list view # _lvID, given by their index, starting at 1
Swap(_col1, _col2, _colNb, _lvID)
{
	local colOrder, pos

	VarSetCapacity(colOrder, _colNb * 4, 0)
	Loop %_colNb%
	{
		pos := A_Index - 1
		If (A_Index = _col1)
			InsertInteger(_col2 - 1, colOrder, pos * 4)
		Else If (A_Index = _col2)
			InsertInteger(_col1 - 1, colOrder, pos * 4)
		Else
			InsertInteger(pos, colOrder, pos * 4)
	}
	SendMessage 0x1000 + 58	; LVM_SETCOLUMNORDERARRAY
			, _colNb, &colOrder, SysListView32%_lvId%, A
	SendMessage 0x1000 + 21	; LVM_REDRAWITEMS
			, 0, _colNb - 1, SysListView32%_lvId%, A
}
[EDIT]


I have a need for this as well, however i cannot get PhiLho's code, quoted here, to work so I can see a demonstration.

Am I doing something wrong?

adamrgolf
  • Members
  • 442 posts
  • Last active: May 22 2017 09:16 PM
  • Joined: 28 Dec 2006
My bad -- the code needed to be as follows to test (duh):

Gui Add, ListView, grid Checked vTestList, C1|C2|C3|C4|C5
Gui Add, Button, gSwap, Swap
Gui Add, Statusbar

Gui Show, Autosize Center +Resize, Swap Test

LV_Add("", "r1c1", "r1c2", "r1c3", "r1c4", "r1c5")
LV_Add("", "r2c1", "r2c2", "r2c3", "r2c4", "r2c5")
LV_Add("", "r3c1", "r3c2", "r3c3", "r3c4", "r3c5")
LV_ModifyCol()
Return


Swap:
   colNb := LV_GetCount("Column")
   Random col1, 1, colNb
   Loop
   {
      Random col2, 1, colNb
      If (col2 != col1)
         Break
   }
   Swap(col1, col2, colNb, 1)
   LV_ModifyCol()
return

; Swap two columns of the list view # _lvID, given by their index, starting at 1
Swap(_col1, _col2, _colNb, _lvID)
{
   local colOrder, pos

   VarSetCapacity(colOrder, _colNb * 4, 0)
   Loop %_colNb%
   {
      pos := A_Index - 1
      If (A_Index = _col1)
         InsertInteger(_col2 - 1, colOrder, pos * 4)
      Else If (A_Index = _col2)
         InsertInteger(_col1 - 1, colOrder, pos * 4)
      Else
         InsertInteger(pos, colOrder, pos * 4)
   }
   SendMessage 0x1000 + 58   ; LVM_SETCOLUMNORDERARRAY
         , _colNb, &colOrder, SysListView32%_lvId%, A
   SendMessage 0x1000 + 21   ; LVM_REDRAWITEMS
         , 0, _colNb - 1, SysListView32%_lvId%, A
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity.  To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
   Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
      DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}

GuiClose:
   ExitApp


wtg
  • Members
  • 251 posts
  • Last active: Dec 19 2012 03:54 PM
  • Joined: 04 Oct 2006
Glad you got it sorted out.

skwire
  • Moderators
  • 279 posts
  • Last active: Aug 12 2014 05:16 PM
  • Joined: 18 Jan 2006
In researching how to save/load listview column orders for Trout, I extended the example to use the built-in NumPut()/NumGet() now available as well as showing how to use LVM_GETCOLUMNORDERARRAY to retrieve the current column order.

LVM_FIRST               := 0x1000
LVM_REDRAWITEMS         := 21
LVM_SETCOLUMNORDERARRAY := 58
LVM_GETCOLUMNORDERARRAY := 59

Gui, Add, ListView, grid Checked vTestList, C1|C2|C3|C4|C5
Gui, Add, Button  , gShift                , Shift
Gui, Add, Button  , gGet                  , Get
Gui, Add, Statusbar

Gui Show, Autosize Center +Resize, Column re-order test

LV_Add( "", "r1c1", "r1c2", "r1c3", "r1c4", "r1c5" )
LV_Add( "", "r2c1", "r2c2", "r2c3", "r2c4", "r2c5" )
LV_Add( "", "r3c1", "r3c2", "r3c3", "r3c4", "r3c5" )
LV_ModifyCol()
Return


Shift:
{
    New_Column_Order := "3|5|1|2|4"

    LV_Set_Column_Order( LV_GetCount( "Column" ), New_Column_Order )
}
return


Get:
{
    MsgBox, % LV_Get_Column_Order( LV_GetCount( "Column" ) )
}
Return


LV_Set_Column_Order( _Num_Of_Columns, _New_Column_Order, _lvID="1", Delim="," )
{
    local colOrder, pos
    VarSetCapacity( colOrder, _Num_Of_Columns * 4, 0 )
    
    Loop, Parse, _New_Column_Order, %Delim%
    {
        pos := A_Index - 1
        NumPut( A_LoopField - 1, colOrder, pos * 4, "UInt" )
    }
    
    SendMessage, LVM_FIRST + LVM_SETCOLUMNORDERARRAY
               , _Num_Of_Columns, &colOrder, SysListView32%_lvId%, A   ; LVM_SETCOLUMNORDERARRAY
    
    SendMessage, LVM_FIRST + LVM_REDRAWITEMS        
               , 0, _Num_Of_Columns - 1, SysListView32%_lvId%, A   ; LVM_REDRAWITEMS
    
    VarSetCapacity( colOrder, 0 ) ; Clean up.
}


LV_Get_Column_Order( _Num_Of_Columns, _lvID="1", Delim="," )
{
    local colOrder, pos
    Output := ""
    VarSetCapacity( colOrder, _Num_Of_Columns * 4, 0 )
    
    SendMessage, LVM_FIRST + LVM_GETCOLUMNORDERARRAY
               , _Num_Of_Columns, &colOrder, SysListView32%_lvID%, A   ; LVM_GETCOLUMNORDERARRAY

    Loop, % _Num_Of_Columns
    {
        pos := A_Index - 1
        Col := NumGet( colOrder, pos * 4, "UInt"  ) + 1 ; Array is zero-based so we add one.
        Output .= Col . Delim
    }
    StringTrimRight, Output, Output, 1 ; Trim trailing delimiter.
    VarSetCapacity( colOrder, 0 ) ; Clean up.
    Return, Output
}


GuiEscape:
GuiClose:
{
    ExitApp
}
Return



HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Thanks everyone, that helped me :)

JnLLnd
  • Members
  • 193 posts
  • Last active: Jul 23 2015 02:15 AM
  • Joined: 30 Dec 2007

Thank you PhiLho, adamrgolf and skwire. This helped me too.

 

But, if I'm not wrong, even after columns ar reordered on the "visible" side using the technique described above, fields remains in the orignal order if I query the listview programatically like this:

strCurrentHeader := ""
Loop, % LV_GetCount("Column")
{
	LV_GetText(strThisHeader, 0, A_Index)
	strCurrentHeader := strCurrentHeader . strThisHeader . strDelimiter
}
StringTrimRight, strCurrentHeader, strCurrentHeader, 1 ; remove extra delimiter

As for physical reordering, the ListView doc says:

... the physical reordering of columns does not affect the column order seen by the script. For example, the first column will always be column 1 from the script's point of view, even if the user has physically moved it to the right of other columns.

 

 

From my experience (again, I hope I'm wrong), this seems to be true also  if fields are programmatically reordered.

 



VxE
  • Moderators
  • 3622 posts
  • Last active: Dec 24 2015 02:21 AM
  • Joined: 07 Oct 2006

You are quite correct. Rearranging listview columns (either manually or programmatically) does not effect how the script sees them.

 

If this isn't what you're hoping for, you can work around it by renaming the columns and swapping the column contents.