Jump to content

ObjCreateInterface example: Enumerating and Browsing the Desktop


LarsJ
 Share

Recommended Posts

Posted Image

Windows 7

I've had a chance to test the script on Windows 7.

To be able to browse system folders e.g. the Control Panel on Windows 7 you have to run the script, EnumDesktopGUI.au3, in the proper way. If you are using Windows 7 64 bit you have to run the script as a 64 bit program to be able to browse the system folders.

On Windows XP the generel look and appearance of the GUI is OK. There are problems on Windows 7: The menu bar doesn't look good. Disabled icons are looking very bad. The drop down menu of some of the buttons can erase parts of other controls. The grouping in the ListView of the items in Computer folder is looking very clumsy and the items seems to be mixed up in some way. Also for the Control Panel some items are mixed up and some items appears both as folders and files. I will try to find a solution to these issues.

Update #2: 2012-08-20

See first post dated 2012-04-19 below.

zip-files are attached to the bottom of the post.

Severe error

Fixed an error which could lead to a crash immediately before GUI was closed down.

Data for the TreeView was deleted and memory freed while the TreeView still had focus.

Sorting

_GUICtrlListView_RegisterSortCallBack() and related functions are used for sorting. The functions are modified to group files and folders, and to properly sort by size, by type and by time. The functions are provided in the file SortListView.au3.

It can be time consuming to sort a large number of files and folders. To prevent using time on both updating and sorting the ListView when a new folder is selected in the TreeView, the sorting mechanism is reset to default when a new folder is selected. The default sorting is the order of files and folders as provided in the "While $IEnumIDList.Next() ... WEnd" loops.

Virtual folders

It can be a lengthy process to expand a large number of subfolders in the TreeView. To avoid this multiple subfolders can be grouped into a number of virtual folders with a specific number of subfolders in each virtual folder. The virtual folders are only created in the TreeView and ListView. Not on the disk.

Virtual folders will be used when the number of subfolders exceeds 300. The number of subfolders in each virtual folder is set to 100. These limits can be changed in the options. And the functionality can be disabled completely.

This is testet in a folder with 4000 subfolders and 100 pictures in each subfolder. Expanding the subfolders is much much faster when using virtual folders. In this example there will be created 40 virtual folders named "1 - 100", "101 - 200", ..., "3901 - 4000". Each virtual folder will contain 100 subfolders. But the 100 subfolders will not be expanded until you click a virtual folder. That means that in stead of creating 4000 subfolders at one time (which is extremely time consuming) there will be created 40 virtual folders, and when you click a virtual folder there will be created 100 subfolders at a time.

Other updates

Right click menu in TreeView: Refresh

Added a SplitterBar control between the TreeView and ListView

Added grouping information to the ListView for My Computer folder

If an icon file EnumDesktopGUI.ico exists, this icon will be used for the GUI

Option to save GUI position and size on exit

Option to set TreeView startup folder

More info to the Details View

Fixed some minor issues

Update #1: 2012-04-25

In post #3 wraithdu pointed out that the built-in function ObjCreateInterface could be used in stead of AutoItObject.

An advantage in using ObjCreateInterface is that according to the helpfile you can use a struct as a parameter in the description string for the methods of the interface. It works in this example. No need for a lot of calls to DllStructGetPtr().

With ObjCreateInterface enumeration of a folder looks like this:

; The address that receives an IShellFolder Interface pointer
$pParentFolder = GetParentFolder( ... )

; Create an IDispatch-Object for the IShellFolder Interface
$IShellFolder = ObjCreateInterface( $pParentFolder, $sIID_IShellFolder, $dtagIShellFolder )

; $pIEnumIDList is the address that receives an IEnumIDList interface pointer of the enumeration object
If $IShellFolder.EnumObjects( $NULL, BitOR( $SHCONTF_FOLDERS, $SHCONTF_INCLUDEHIDDEN ), $pIEnumIDList ) = $S_OK Then

    ; Create an IDispatch-Object for the IEnumIDList Interface
    $IEnumIDList = ObjCreateInterface( $pIEnumIDList, $sIID_IEnumIDList, $dtagIEnumIDList )

    ; Enumerate the folders                                  ; Param 1 [in] : Step value as in a For-loop
    While $IEnumIDList.Next( 1, $pidlRel, $iFetched ) = $S_OK ; Param 2 [out]: PIDL relative to parent folder
        $iFolders += 1                                       ; Param 3 [out]: 0 if no more PIDLs, 1 else
         ...
         ...
         ...
    WEnd
EndIf

The zipfile below is updated with new au3-files (the 3 EnumDesktop-files) that uses ObjCreateInterface. This version is not depending on AutoItObject and AutoItObject.au3 is not included.

In this example ObjCreateInterface seems to be working flawlessly.

2012-04-19

I've been looking at AutoItObject for some time. I've got inspiration for this example from a similar C++ example and from this thread http://www.autoitscript.com/forum/index.php?showtopic=123365.

The GUI in the example is a Windows Explorer Viewer with a TreeView and a ListView. The root of the TreeView is the Desktop. You can browse the Desktop by expanding the folders in the TreeView. Select a folder to show the subfolders and files in the ListView. Right click on an item in the ListView to get file properties. Right click in the free area of the ListView to print a list of item names. Double click or press Enter on an item in the ListView to open a file or execute a program.

With AutoItObject a folder doesn't have to be a file system folder. It can be any folder in the Desktop e.g. the Control Panel. The Desktop in this case means the root of the Shell's namespace.

To enumerate a folder using AutoItObject you use code like this:

; The address that receives an IShellFolder Interface pointer
$pParentFolder = GetParentFolder( ... )

; Create an IDispatch-Object for the IShellFolder Interface
$IShellFolder = _AutoItObject_WrapperCreate( $pParentFolder, $dtagIShellFolder )

; The address that receives an IEnumIDList interface pointer of the enumeration object
$aRet = $IShellFolder.EnumObjects( $NULL, BitOR( $SHCONTF_FOLDERS, $SHCONTF_INCLUDEHIDDEN ), 0 )
If $aRet[0] = $S_OK Then

    $pIEnumIDList = $aRet[3]

    ; Create an IDispatch-Object for the IEnumIDList Interface
    $IEnumIDList = _AutoItObject_WrapperCreate( $pIEnumIDList, $dtagIEnumIDList )

    ; Enumerate the folders
    $aRet = $IEnumIDList.Next( 1, 0, 0 ) ; Param 1 [in] : Step value as in a For-loop
    While $aRet[3]                       ; Param 2 [out]: PIDL relative to parent folder
        $iFolders += 1                   ; Param 3 [out]: 0 if no more PIDLs, 1 else
             ...
             ...
             ...
        $aRet = $IEnumIDList.Next( 1, 0, 0 )
    WEnd
EndIf

Enumeration of a folder in this way uses PIDLs (pointers to ITEMIDLIST structures) in stead of file and folder names. See http://msdn.microsoft.com/en-us/library/windows/desktop/cc144090(v=vs.85).aspx.

The zipfile below contains a number of files:

WSP.dll is used for NM_RETURN notifications in the ListView.

The central file in the example is EnumDesktopFuncs.au3. The most important functions in the file are

  • GetParentFolder() - returns an IShellFolder Interface pointer to the parent folder
  • EnumTreeViewObjects() - expands the folders of the parent folder in the TreeView
  • EnumListViewObjects() - shows the folders and files in the ListView for the parent folder
  • InvokeCommand() - opens a file or runs a program when an item in the ListView is activated
You need AutoItObject by the AutoItObject team and APIConstants.au3 and WinAPIEx.au3 version 3.6 or 3.7 by Yashied.

If the script fails for some reason and you are left with an hourglass cursor, then run HourglassOff.au3 to get the normal cursor back.

Run EnumDesktopGUI.au3 in the zipfile.

Testet on XP SP3.

The zip-files can be opened with 7-Zip.

2012-04-19: Implemented with AutoItObject

EnumDesktop.zip

Update #2 2012-08-20: Implemented with ObjCreateInterface() (no need for AutoItObject, not included)

EnumDesktop.zip

EnumDesktop.zip

Edited by LarsJ
Link to comment
Share on other sites

Nice peace of code - I like it!

Is it running properly on Win7 x64!

Well done!

Br,

UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Link to comment
Share on other sites

Lilbert, Thank you for pointing that out.

Now I realize that UEZ was reporting an error (propably the same). I misunderstood. Sorry for that.

I have no access to Win7 X64 for the moment. Could someone with access to Win7 X64 test the following 2 lines of code for me in the script below.

$r = $IShellFolder.GetDisplayNameOf( Number($pidlRel), 0, Number(DllStructGetPtr($tSTRRET)) )   ; <<<<<<<<<<<<<<<<<<<
;$r = $IShellFolder.GetDisplayNameOf( $pidlRel, 0, DllStructGetPtr( $tSTRRET ) )                ; <<<<<<<<<<<<<<<<<<<

Both of these lines works on XP. Probably only the first line works on Win7 X64. I would like to get that confirmed.

In my project I consistently uses the second version of the lines, so that could be the reason for the errors.

The script displays the name of the running au3-file in a MsgBox.

#include <AutoItObject.au3>

Global Const $sIID_IShellFolder = "{000214E6-0000-0000-C000-000000000046}"
Global Const $dtagIShellFolder = $dtagIUnknown & _
  "ParseDisplayName hresult(hwnd;ptr;wstr;dword*;ptr*;dword*);" & _
  "EnumObjects hresult(hwnd;dword;ptr*);" & _
  "BindToObject hresult(ptr;ptr;ptr;ptr*);" & _
  "BindToStorage hresult(ptr;ptr;ptr;ptr*);" & _
  "CompareIDs hresult(lparam;ptr;ptr);" & _
  "CreateViewObject hresult(hwnd;ptr;ptr*);" & _
  "GetAttributesOf hresult(UINT;ptr;ulong*);" & _
  "GetUIObjectOf hresult(hwnd;uint;ptr;ptr;uint*;ptr*);" & _
  "GetDisplayNameOf hresult(ptr;uint;ptr);" & _
  "SetNameOf hresult(hwnd;ptr;wstr;dword;ptr*);"

_AutoItObject_StartUp()

Local $pidl = _ILCreateFromPath( @ScriptFullPath )
Func _ILCreateFromPath($sPath)
  Local $aRes = DllCall("shell32.dll", "ptr", "ILCreateFromPathW", "wstr", $sPath)
  If @error Then Return SetError(1, 0, 0)
  Return $aRes[0]
EndFunc

Local $pIShellFolder, $pidlRel, $r
Local $tRIID = _AutoItObject_CLSIDFromString( $sIID_IShellFolder )
;$r = _SHBindToParent( $pidl, DllStructGetPtr( $tRIID ), $pIShellFolder, $pidlRel )
$r = _SHBindToParent( Number($pidl), Number(DllStructGetPtr($tRIID)), $pIShellFolder, $pidlRel )
Func _SHBindToParent( $pidl, $riid, ByRef $pIShellFolder, ByRef $pidlRel )
  Local $aRes = DllCall("Shell32.dll", "long", "SHBindToParent", "ptr", $pidl, "ptr", $riid, "ptr*", 0, "ptr*", 0)
  If @error Then Return SetError(1, 0, 0)
  $pIShellFolder = $aRes[3]
  $pidlRel = $aRes[4]
  Return $aRes[0]
EndFunc

Local $tSTRRET = DllStructCreate("uint uType;ptr data;")
Local $IShellFolder = _AutoItObject_WrapperCreate( $pIShellFolder, $dtagIShellFolder )
$r = $IShellFolder.GetDisplayNameOf( Number($pidlRel), 0, Number(DllStructGetPtr($tSTRRET)) )   ; <<<<<<<<<<<<<<<<<<<
;$r = $IShellFolder.GetDisplayNameOf( $pidlRel, 0, DllStructGetPtr( $tSTRRET ) )                  ; <<<<<<<<<<<<<<<<<<<

Local $sName
;_StrRetToBuf(DllStructGetPtr($tSTRRET), 0, $sName, 512)
_StrRetToBuf(Number(DllStructGetPtr($tSTRRET)), 0, $sName, 512)
Func _StrRetToBuf($pSTRRET, $pidl, ByRef $sName, $iLength = 512)
  Local $aRes = DllCall("shlwapi.dll", "long", "StrRetToBufW", "ptr", $pSTRRET, "ptr", $pidl, "wstr", $sName, "uint", $iLength)
  If @error Then Return SetError(1, 0, 0)
  $sName = $aRes[3]
  Return $aRes[0]
EndFunc

MsgBox( 0, "Name of file", $sName )

_AutoItObject_Shutdown()
$IShellFolder = 0
$tSTRRET = 0
Edited by LarsJ
Link to comment
Share on other sites

Both lines work fine for me on Win7 64bit, running Autoit as 32bit as well as 64bit.

Edit: And your example "EnumDesktopGUI.au3" works fine for me too, running Autoit as 32bit as well as 64bit. Nice program ;)!

Edited by KaFu
Link to comment
Share on other sites

KaFu, Thank you very much for clarifying that.

Lilbert, Will you try to run the test script in post #7.

Link to comment
Share on other sites

The example is Implemented with ObjCreateInterface() in stead of AutoItObject. See top of post #1 for info.

Link to comment
Share on other sites

trancexx, Thank you. I'll hope you'll continue developing this function, ObjCreateInterface().

Lilbert, Are you running the scripts on a server? That could give some problems. When EnumDesktopGUI.au3 starts up, it expands the Desktop and "My Computer" folder to fill out a few items in the TreeView and to fill out the ListView. If there is an item in one of these folders that needs special treatment because it's a server, that could be the problem.

Whether it's a server or not, I think it's an item in the Desktop or "My Computer" folder that's the reason for the error. We have to figure out which item it is.

Will you take a look at the next post and then open and run FileListDesktop.au3 and FileListSystemFolder.au3 in Scite. One of the scripts or may be both will probably fail. But before they fails they will print some of the files and folders in the console. If you open Windows Explorer and compares the contents of the Desktop folder and "My Computer" folder with the output in the console, it should be possible to figure out which item is the reason for the error.

When I run FileListDesktop.au3 and FileListSystemFolder.au3 on my XP the output looks like this:

FileListDesktop.au3
-------------------
Desktop
C:\Documents and Settings\LJ\Desktop
    Internet Explorer
    Backup
    Calculator
    Command Prompt
    Date and Time
    Internet Explorer
    Media Player Classic
    Microsoft Office Access 2003
    Microsoft Office Outlook 2003
    Microsoft Visual FoxPro 9.0
    Microsoft Visual Studio 2008
    Windows Explorer
    Windows Media Player
    Windows PowerShell
    DAEMON Tools
    Notepad++
My Computer
My Network Places
My Documents
Recycle Bin

FileListSystemFolder.au3
------------------------
My Computer\
::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\
    Control Panel\
    ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\
    My Documents\
    D:\My Documents\
    Shared Documents\
    C:\Documents and Settings\All Users\Documents\
    SYSTEM (C:)\
    C:\
    DATA (D:)\
    D:\
    PROGRAMS (E:)\
    E:\
    BACKUP (G:)\
    G:\
    DVD-RAM Drive (H:)\
    H:\
    DVD Drive (M:)\
    M:\
Edited by LarsJ
Link to comment
Share on other sites

The zipfile below contains 5 small au3-files to print the contents of the Desktop folder, a system folder (e.g. "My Computer" or "Control Panel") and a directory (filesystem folder) in the Scite console.

  • FileListConsts.au3 - declarations
  • FileListFuncs.au3 - functions
  • FileListDesktop.au3 - print the contents of the Desktop folder
  • FileListSystemFolder.au3 - print the contents of a system folder
  • FileListDirectory.au3 - print the contents of a directory
FileListSystemFolder.au3 and FileListDirectory.au3 uses recursion to print the contents of subfolders. You can set the recursion level to specify how many recursions you want to make. The default is 1 which means that the contents of the specified folder is printed, but no subfolders.

FileList.zip

Edit: Added indentation for subfolders.

Edited by LarsJ
Link to comment
Share on other sites

Link to comment
Share on other sites

  • 3 months later...

Hello LarsJ, first of all the script is excellent, I learn a lot from it.

I´ve got a question, perhaps you can help me. Is it possible to sort the list view?

I was trying to sort the ListView by different methods but all failed.

I tried to register a callback function at the begining with: GUICtrlRegisterListViewSort(-1, "LVSort") but for some reason it doesn´t work.

Then I tied to trap the columns header clicked with WM_NOTIFY but I also failed. Follow the code:

Case $LVN_COLUMNCLICK ;ListView column clicked
     Local $tInfo = DllStructCreate($tagNMLISTVIEW, $ilParam)
     Local $iCol = DllStructGetData($tInfo, "SubItem") ;Column clicked starting from 0

     Local $tNMLVCUSTOMDRAW = DllStructCreate($tagNMLVCUSTOMDRAW, $ilParam)
     Local $dwItemSpec = DllStructGetData($tNMLVCUSTOMDRAW, "dwItemSpec") ; Item index
     Local $iSubItem   = DllStructGetData($tNMLVCUSTOMDRAW, "iSubItem")  ; Subitem index
                  
   LVSort($hLV, $dwItemSpec, $dwItemSpec, $iCol)

Just in case you can help me I attach the sort function I get from the forum:

Func LVSort($hWnd, $nItem1, $nItem2, $nColumn)
Local $val1, $val2, $nResult
; Switch the sorting direction
If $nColumn = $nCurCol Then
  If Not $bSet Then
   $nSortDir = $nSortDir * - 1
   $bSet = 1
  EndIf
Else
  $nSortDir = 1
EndIf
$nCol = $nColumn
$val1 = GetSubItemText($hWnd, $nItem1, $nColumn)
$val2 = GetSubItemText($hWnd, $nItem2, $nColumn)
; If it is the 3rd colum (column starts with 0) then compare the dates
If $nColumn = 2 Then
  $val1 = StringRight($val1, 4) & StringMid($val1, 4, 2) & StringLeft($val1, 2)
  $val2 = StringRight($val2, 4) & StringMid($val2, 4, 2) & StringLeft($val2, 2)
EndIf
$nResult = 0 ; No change of item1 and item2 positions
If $val1 < $val2 Then
  $nResult = -1 ; Put item2 before item1
ElseIf $val1 > $val2 Then
  $nResult = 1 ; Put item2 behind item1
EndIf
$nResult = $nResult * $nSortDir
Return $nResult
EndFunc   ;==>LVSort

; Retrieve the text of a listview item in a specified column
Func GetSubItemText($nCtrlID, $nItemID, $nColumn)
Local $stLvfi = DllStructCreate("uint;ptr;int;int[2];int")
Local $nIndex, $stBuffer, $stLvi, $sItemText
DllStructSetData($stLvfi, 1, $LVFI_PARAM)
DllStructSetData($stLvfi, 3, $nItemID)
$stBuffer = DllStructCreate("char[260]")
$nIndex = GUICtrlSendMsg($nCtrlID, $LVM_FINDITEM, -1, DllStructGetPtr($stLvfi));
$stLvi = DllStructCreate("uint;int;int;uint;uint;ptr;int;int;int;int")
DllStructSetData($stLvi, 1, $LVIF_TEXT)
DllStructSetData($stLvi, 2, $nIndex)
DllStructSetData($stLvi, 3, $nColumn)
DllStructSetData($stLvi, 6, DllStructGetPtr($stBuffer))
DllStructSetData($stLvi, 7, 260)
GUICtrlSendMsg($nCtrlID, $LVM_GETITEMA, 0, DllStructGetPtr($stLvi));
$sItemText = DllStructGetData($stBuffer, 1)
$stLvi = 0
$stLvfi = 0
$stBuffer = 0
Return $sItemText
EndFunc   ;==>GetSubItemText

Thanks a lot and regards.

Link to comment
Share on other sites

Hi jcpetu,

I've just seen your post. I'll look at it and come back with an answer in a few days. Please be patient.

LarsJ.

Link to comment
Share on other sites

Hi jcpetu,

I've added an answer to your question in the General Help and Support forum.

Lars.

Link to comment
Share on other sites

Hi LarsJ, I added a drag bar to your script. It works perfectly right until I resize the main window. I attach the drag routine I created.

;Drag Bar------------------------------------------------------
Global $DragBarWhidth=5, $DragBarLimit=70
Global $LPos=ControlGetPos($hGui, "", $idTV)
Global $RPos=ControlGetPos($hGui, "", $idLV)
Global $WPos = WinGetPos($hGui)
Global $DBLeftLimit=$Lpos[0]+$DragBarLimit,$DBRightLimit=$WPos[2]-$DragBarLimit
Global $DragBar=GUICtrlCreateLabel("",$LPos[2]-($DragBarWhidth/2),$LPos[1],$DragBarWhidth,$LPos[3], -1, 0x00000001) ;separator rezising bar $WS_EX_DLGMODALFRAME
GUICtrlSetCursor(-1, 13)
GUICtrlSetResizing(-1, $GUI_DOCKTOP + $GUI_DOCKBOTTOM + $GUI_DOCKWIDTH)
Global $Pos=GUIGetCursorInfo($hGui)
Global $DPos=ControlGetPos($hGui,"",$DragBar);position of the control relative to its parent window
ControlMove($hGui,"",$idTV,$LPos[0],$LPos[1],$DPos[0]-$DragBarWhidth,$LPos[3])
ControlMove($hGui,"",$idLV,$DPos[0]+$DragBarWhidth,$RPos[1], $WPos[2]-$DPos[0], $RPos[3])
;Drag Bar end------------------------------------------------------


;In the main Loop
;...........
   Case $DragBar
     $LPos=ControlGetPos($hGui, "", $idTV)
     $RPos=ControlGetPos($hGui, "", $idLV)
     $DPos=ControlGetPos($hGui,"",$DragBar)
     $WPos = WinGetPos($hGui)
     GUISetCursor(13,1,$hGui)
     GUISetState(@SW_DISABLE,$hGui)
     Do
      $Pos=GUIGetCursorInfo($hGui)
      Sleep(10)
      If $Pos[0]>$DBLeftLimit And $Pos[0]<$DBRightLimit Then ControlMove($hGui,"",$DragBar,$Pos[0],$DPos[1],$DPos[2],$DPos[3])
     Until $Pos[2]=0 ;$pos[2]=Primary down (1 if pressed, 0 if not pressed)
     $DPos=ControlGetPos($hGui,"",$DragBar)
     If $Pos[0]<$DBLeftLimit Then
      ControlMove($hGui,"",$idTV,$LPos[0],$LPos[1],$DPos[0]-$DragBarWhidth,$LPos[3])
      ControlMove($hGui,"",$idLV,$DPos[0]+$DragBarWhidth,$RPos[1],$WPos[2]-($DPos[0]+$DragBarWhidth),$RPos[3])
     Else
      ControlMove($hGui, "",$idTV,$LPos[0],$LPos[1],$DPos[0]-$DragBarWhidth,$LPos[3])
      If $Pos[0]>$DBRightLimit Then
       ControlMove($hGui,"",$idLV,$DPos[0]+$DragBarWhidth,$RPos[1],$WPos[2]-$DPos[0],$RPos[3])
      Else
       ControlMove($hGui,"",$idLV,$Pos[0]+$DragBarWhidth,$RPos[1], $WPos[2]-$Pos[0], $RPos[3])
      EndIf
     EndIf
     GUISetCursor()
     GUISetState(@SW_ENABLE, $hGui)
     GUICtrlSetState($DragBar, $GUI_FOCUS)

Another thing I noticed, the ListView scroll bar is hidden.

Perhaps you can help me.

I appreciate it.

Regards.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...