January 19, 2006

Set Desktop Icon Positions

Filed under: Scripting — Marcus Tettmar @ 9:42 am

Cosmo asked in his comment on How to Start Writing an Automation Script how he could set the position of a desktop shortcut. This is an interesting question that hasn’t come up before and it took me a while to realise how it could be done. You may wonder why you’d even want to do it. Well if you’re automating software installations for your end-user or customer machines you may want to make sure the desktop icons are always in the same place. You may want to put a link to your support site in the top right of the screen, for example, so that your customer can always find it.

The solution wasn’t immediately obvious. You can move icons around with the mouse, but trying to automate that would be a bit of a nightmare and you can’t drag icons with the keyboard. Even then how do you know what position it’s at? So how do you do it? Well I realised that the desktop icons are actually elements of a hidden ListView control belonging to the desktop window. So we need to manipulate this ListView control. Macro Scheduler 8.0 has a function called GetListItem which will return the index of a list item by its caption. So immediately we can find the index of a desktop icon with just this line:

GetListItem>Program Manager,SysListView32,0,RealPlayer,0,0,0,Result,Handle

This line returns the index of the “RealPlayer” shortcut. Note that the main window title is Program Manager and the object class name is SysListView32. You can see this in the View System Windows tool (under the Tools menu in Macro Scheduler). Conveniently, GetListItem also returns the Handle of the listview object. We need this for the next bit.

We need to go a level deeper to figure out how to position the item. We get down and dirty with the Win32 API. The Win32API is a heap of functions deep within Windows. There’s a message called LVI_SETITEMPOSITION which is sent to a ListView control when it needs to be positioned. Messages always have two parameters, known as wParam and lParam. In this case wParam is the index of the ListView item, which we got from the GetListItem command, and lParam is a POINT structure. A POINT structure specifies an X and Y coordinate. So we can send the X and Y coordinate of where we want the shortcut to end up, to the ListView control by way of the LVI_SETITEMPOSITION command.

To send the LVI_SETITEMPOSITION message we need to use the Win32 API function SendMessage. First we need to convert the X and Y coordinates into an integer value. This involves a bit of wizardry using a couple of VBScript functions which are in the full script. So this allows us to do this:

Let>xpos=100
Let>ypos=500
VBEval>MAKELPARAM(%xpos%,%ypos%),lparam

//Reposition
Let>LVI_SETITEMPOSITION=4111
LibFunc>user32,SendMessageA,r,Handle,LVI_SETITEMPOSITION,Result,lparam

So using the VBscript function MAKELPARAM (it’s in the full script) we convert the target x,y coordinates to an lParam value which we can send to the ListView with the message. We declare the LVI_SETITEMPOSITION message (message 4111 in Windows – LVI_SETITEMPOSITION is just constant name) and then use the LibFunc command to run the SendMessageA Win32 API function which sends the message. We send it to the ListView control using the handle returned by GetListItem and we send the index, also returned by GetListItem, and our lparam which contains the new x,y coordinates.

So here’s the full script:

//Functions needed for working with windows messages
VBSTART
Function LoWord(wInt)
LoWord = wInt AND &HFFFF&
End Function

Function HiWord(wInt)
HiWord = wInt  &H10000 AND &HFFFF&
End Function

Function MAKELPARAM(wLow, wHigh)
MAKELPARAM = LoWord(wLow) Or (&H10000 * LoWord(wHigh))
End Function
VBEND

//Refresh icon view
SetFocus>Program Manager
Press F5

//Get index of RealPlayer shortcut on desktop
GetListItem>Program Manager,SysListView32,0,RealPlayer,0,0,0,Result,Handle

//Set position of icon.
//If Align to Grid is on this will slot icon in where it fits best.
//If Align to Grid is off it will place icon at this absolute position.
Let>xpos=100
Let>ypos=500
VBEval>MAKELPARAM(%xpos%,%ypos%),lparam

//Reposition
Let>LVI_SETITEMPOSITION=4111
LibFunc>user32,SendMessageA,r,Handle,LVI_SETITEMPOSITION,Result,lparam

Apologies for the way the script is formatted. HTML doesn’t lend itself well to showing script code the way it was intended. But you can download the full script file at the end of the article.

Note the VBScript at the top which creates an LParam value out of two integers, in this case the x,y values. Besides declaring the VBScript functions, the very first thing the script does is focus the desktop and hit F5 to force a refresh. This ensures that the icons are in their correct places so that the GetListItem command returns the correct value. Note that the desktop window title is “Program Manager”. So we can use that to focus the desktop window.

If “Align to Grid” is enabled the icon will snap to the position in the list nearest the x,y position we provide. If Align to Grid is off it will use that absolute x,y position. I should also say that the x,y coordinates are the top left position of the shortcut. I’ve included some code to toggle Align to Grid in the downloadable script file.

Windows message names are constants. Macro Scheduler doesn’t have these variables pre-defined so to use Windows messages you’ll need to find out their integer values. If you have a development environment such as C++ you’ll find them declared in winuser.h. If you don’t, I’ve uploaded a text file with a large number here.

So there you are. I hope this is useful.

Download the Script File
AllApi.Net API List Windows 32 API Functions – this site is aimed at users of Visual Basic but I find it a handy reference of API functions and the examples can easily be translated to LibFunc calls.