I am making a python program that moves the desktop icons using LVM_SETITEMPOSITION
from winapi
but I have a problem with commctrl.LVM_SETITEMPOSITION
and it gives me an error 'int' object is not callable
. Here is my code:
import win32gui
import commctrl
from time import sleep
from ctypes import wintypes
hd = wintypes.HWND
hd = win32gui.FindWindow("Progman", None)
hd = win32gui.FindWindowEx(hd, 0, "SHELLDLL_DefView", None)
hd = win32gui.FindWindowEx(hd, 0, "SysListView32", None)
i = 0
while i < 1000:
commctrl.LVM_SETITEMPOSITION(hd, 0, i, i)
i+100
sleep(1)
The proper way (using COM) is way more painful than the shortcut I took (still at the end of the answer).
Resources:
[SO]: How to move desktop icons using winapi on C? (@IInspectable's answer)
[SO]: Return a list of all files from the selected Explorer Window with pywin32 (@zett42's answer)
[SO]: win32com get the list of available applications (@fuglede's answer)
MS doc (lots of). Mentioning:
PyWin32 doc:
I started "translating" code from the 1st URL. Everything went fine til the point of getting IFolderView, which is not wrapped by PyWin32's COM extensions, hence yielding:
TypeError: There is no interface object registered that supports this IID.
I tried to work around it by [SO]: Implementing a COM interface in Python (@SimonMourier's answer), but no success.
So, I was only left with the option of adding IFolderView (partial) support myself. PR is [GitHub]: mhammond/pywin32 - IFolderView COM client (merged to main on 240220, and present in official builds since v307).
After building the shell.pyd locally (containing the above changes), I was able to get the same behavior using the following script (which works fine).
code01.py:
#!/usr/bin/env python
import msvcrt
import sys
import time
import pythoncom
import win32com.client as wcomcli
from win32com.shell import shell, shellcon
SWC_DESKTOP = 0x08
SWFO_NEEDDISPATCH = 0x01
CLSID_ShellWindows = "{9BA05972-F6A8-11CF-A442-00A0C90A8F39}"
IID_IFolderView = "{CDE725B0-CCC9-4519-917E-325D72FAB4CE}"
IID_IShellView = "{000214E3-0000-0000-C000-000000000046}"
def main(*argv):
shell_windows = wcomcli.Dispatch(CLSID_ShellWindows)
hwnd = 0
dispatch = shell_windows.FindWindowSW(
wcomcli.VARIANT(pythoncom.VT_I4, shellcon.CSIDL_DESKTOP),
wcomcli.VARIANT(pythoncom.VT_EMPTY, None),
SWC_DESKTOP, hwnd, SWFO_NEEDDISPATCH,
)
service_provider = dispatch._oleobj_.QueryInterface(pythoncom.IID_IServiceProvider)
browser = service_provider.QueryService(shell.SID_STopLevelBrowser, shell.IID_IShellBrowser)
shell_view = browser.QueryActiveShellView()
print(shell_view.GetCurrentInfo())
folder_view = shell_view.QueryInterface(IID_IFolderView)
#print(folder_view.QueryInterface(shell.IID_IShellView))
items_len = folder_view.ItemCount(shellcon.SVGIO_ALLVIEW)
print(f"ItemCount: {items_len}")
for i in range(items_len):
item = folder_view.Item(i)
print(f"Item {i:2d}\n {item}")
print(f"Spacings: {folder_view.GetSpacing(300, 300)}, {folder_view.GetDefaultSpacing()}")
item = b"\x1fx@\xf0_d\x81P\x1b\x10\x9f\x08\x00\xaa\x00/\x95N" # "Recycle Bin" equivalent (in my case)
#item = 0 # May also be the index (still "Recicle Bin" equivalent)
#item = 1
print(f"Item {item} position: {folder_view.GetItemPosition(item)}")
for i in range(0, 1080, 16):
print(i)
folder_view.SelectAndPositionItem(item, (i, i), shellcon.SVSI_POSITIONITEM)
time.sleep(0.5)
if msvcrt.kbhit():
break
if __name__ == "__main__":
print(
"Python {:s} {:03d}bit on {:s}\n".format(
" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32,
sys.platform,
)
)
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Output:
[cfati@CFATI-W10PC064:e:\Work\Dev\StackExchange\StackOverflow\q071905594]> "c:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code01.py Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 (1, 1075839524) ItemCount: 8 Item 0 b'\x1fx@\xf0_d\x81P\x1b\x10\x9f\x08\x00\xaa\x00/\x95N' Item 1 b':\x00\r\x08\x00\x00NX\x95\xa4 \x00ADOBEA~2.LNK\x00\x00T\x00\t\x00\x04\x00\xef\xbemV\xa2\xbcRX\xe1h.\x00\x00\x00\xd8\x96\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00k\xc7(\x00A\x00d\x00o\x00b\x00e\x00 \x00A\x00c\x00r\x00o\x00b\x00a\x00t\x00.\x00l\x00n\x00k\x00\x00\x00\x1c\x00' Item 2 b':\x00\xae\x00\x00\x00\x87O\x96I&\x00desktop.ini\x00H\x00\t\x00\x04\x00\xef\xbe\x87O\xdcITX.m.\x00\x00\x00{\xb2\x03\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x86)\xbf\x00d\x00e\x00s\x00k\x00t\x00o\x00p\x00.\x00i\x00n\x00i\x00\x00\x00\x1a\x00' Item 3 b':\x00\xe4\x08\x00\x00TXtm \x00MICROS~1.LNK\x00\x00V\x00\t\x00\x04\x00\xef\xbesQ\xd1=TXtm.\x00\x00\x00s\xb8\x04\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\xf0Q\x00M\x00i\x00c\x00r\x00o\x00s\x00o\x00f\x00t\x00 \x00E\x00d\x00g\x00e\x00.\x00l\x00n\x00k\x00\x00\x00\x1c\x00' Item 4 b'2\x00\x19\x04\x00\x00wSiN \x00CONNEC~1.LNK\x00\x00\x86\x00\t\x00\x04\x00\xef\xbewSiNRX\xe1h.\x00\x00\x00\xa7\x8d\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X\xe5>\x00C\x00o\x00n\x00n\x00e\x00c\x00t\x00i\x00o\x00n\x00 \x00t\x00o\x00 \x00Q\x00C\x00_\x00A\x00I\x00C\x001\x000\x000\x00_\x006\x004\x00.\x001\x008\x007\x00.\x002\x001\x003\x00.\x001\x009\x004\x00.\x00l\x00n\x00k\x00\x00\x00\x1c\x00' Item 5 b'2\x00\x1a\x01\x00\x00\xafR+\xae&\x00desktop.ini\x00H\x00\t\x00\x04\x00\xef\xbe\xaeR\xeb\x01TX.m.\x00\x00\x00\xd9k\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95\xa35\x00d\x00e\x00s\x00k\x00t\x00o\x00p\x00.\x00i\x00n\x00i\x00\x00\x00\x1a\x00' Item 6 b'2\x00\x9c\x04\x00\x00mV\x94\xb0 \x00TOTALC~1.LNK\x00\x00f\x00\t\x00\x04\x00\xef\xbemV\x94\xb0RX\xe1h.\x00\x00\x00\x9bo\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\xbf\x01\x01T\x00o\x00t\x00a\x00l\x00 \x00C\x00o\x00m\x00m\x00a\x00n\x00d\x00e\x00r\x00 \x006\x004\x00 \x00b\x00i\x00t\x00.\x00l\x00n\x00k\x00\x00\x00\x1c\x00' Item 7 b'2\x00\x8e\x04\x00\x00mV\x94\xb0 \x00TOTALC~2.LNK\x00\x00X\x00\t\x00\x04\x00\xef\xbemV\x94\xb0RX\xe1h.\x00\x00\x00\x9co\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001\xbf\x01\x01T\x00o\x00t\x00a\x00l\x00 \x00C\x00o\x00m\x00m\x00a\x00n\x00d\x00e\x00r\x00.\x00l\x00n\x00k\x00\x00\x00\x1c\x00' Spacings: (75, 101), (96, 16) Item b'\x1fx@\xf0_d\x81P\x1b\x10\x9f\x08\x00\xaa\x00/\x95N' position: (13, 2) 0 16 32 48 Done.
Notes:
I don't know how Win divides the screen into tiles, so I don't know how to make the icon move at any iteration (but that's beyond the question scope, and it was similar in the old approach). I'm incrementing the X / Y coordinates by 16 for no particular reason
An item can be specified by its data (that is used in the underlying WinAPI implementation). But since that data (that gibberish string in the above output) makes no sense to the user, I added functionality to also use its index. If you want to use it by name, check this code (code02.py, "stolen" from [GitHub]: mhammond/pywin32 - (main) com/win32comext/shell/test/testShellFolder.py) out:
#!/usr/bin/env python
import sys
from win32com.shell import shell, shellcon
def main(*argv):
sf = shell.SHGetDesktopFolder()
print("Shell Folder is", sf, dir(sf))
for i, e in enumerate(sf):
print(f"Item: {i}\n Name: {sf.GetDisplayNameOf(e, shellcon.SHGDN_NORMAL)}\n Data: {e}")
# And get the enumerator manually
objs = sf.EnumObjects(0, shellcon.SHCONTF_FOLDERS | shellcon.SHCONTF_NONFOLDERS | shellcon.SHCONTF_INCLUDEHIDDEN)
print(objs, dir(objs))
if __name__ == "__main__":
print(
"Python {:s} {:03d}bit on {:s}\n".format(
" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32,
sys.platform,
)
)
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Check [SO]: How to change username of job in print queue using python & win32print (@CristiFati's answer) (at the end) for ways of moving forward with the above patch
Didn't try it on new Win 11 builds as I don't have it
Sorry, I misread WORD (as DWORD) in [MS.Docs]: LVM_SETITEMPOSITION message, so I shifted too much to the left.
Here's a working example (which should move the item on the leading diagonal on the desktop).
code00.py:
#!/usr/bin/env python
import msvcrt
import sys
import time
import commctrl as cc
import win32gui as wgui
def main(*argv):
search_criteria = (
(0, "Progman", None),
(0, "SHELLDLL_DefView", None),
(0, "SysListView32", None),
)
wnd = 0
for crit in search_criteria:
wnd = wgui.FindWindowEx(wnd, *crit)
if wnd == 0:
print("Could not find child matching criteria: {:}".format(crit))
return
idx = 0
for i in range(0, 1000, 16):
lparam = (i << 16) | i
print("{:d} - 0x{:08X}".format(i, lparam))
wgui.SendMessage(wnd, cc.LVM_SETITEMPOSITION, idx, lparam)
time.sleep(0.5)
if msvcrt.kbhit():
break
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.")
sys.exit(rc)