Jump to content


Photo

An example of using Virtual ListView in AHK


  • Please log in to reply
4 replies to this topic

#1 yonken

yonken
  • Members
  • 15 posts

Posted 10 May 2010 - 01:15 PM

Here's the story:

http://www.autohotke...ht=virtual list

And here's the example:

Posted Image

Really fast, isn't it? (even faster than nDroid, I think :oops: )

That's my first try to implement a virtual listview, it seems that it's possible to have a virtual listview in AHK, everything works fine, however the listview keeps flickering while the column is being draged, and I have no idea why that happen and how to solve it.

I might try to fix it the other time (but I'm not sure if I have time :( ).

If anyone have any idea(about how to make the listview flicker-free), please let me know! :wink:

Usage:
1. Hit the button and locate the folder.
2. Type your search patterns into the edit box.
3. That's it.

Note:
1. The search patterns should be space-separated.
2. Each pattern is not case-senstivie.
3. Any pattern prefixed with a minus "-" is considered as exclusion.
4. Any pattern prefixed with a backslash "\" is considered to be matched with (any part of) the folde, otherwise it would be matched with (any part of) the file name.

And yes, this script just do nothing but listing the files, feel free to modify it and of course share your revision :wink:

;
; AutoHotkey Version: 1.0.48.05
; Language:       English
; Platform:       Win9x/NT
; Author:         Yonken
;
; Script Function:
;	To enumerate the files of specified folder(s), filter out specified type(s), list and
; update the result in real time as the search pattern changed.
;	Use at your own risk.
;

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
SetBatchLines, -1	; Never sleep

WM_NOTIFY				:= 0x004E
LVN_FIRST               := -100

LVN_GETDISPINFOA        := (LVN_FIRST-50)	; For ANSI version
LVN_GETDISPINFOW        := (LVN_FIRST-77)	; For unicode version
LVN_GETDISPINFO			:= LVN_GETDISPINFOW

LVM_FIRST				:= 0x1000
LVM_SETITEMCOUNT		:= (LVM_FIRST + 47)
LVM_REDRAWITEMS			:= (LVM_FIRST + 21)

LVS_OWNERDATA			:= 0x1000

LPSTR_TEXTCALLBACKA		:= -1

sizeofNMHDR				:= 12
sizeofLVITEM			:= 40

LVSICF_NOINVALIDATEALL	:= 0x00000001

LVIF_TEXT				:= 0x0001
LVIF_IMAGE				:= 0x0002
LVIF_STATE				:= 0x0008

CP_ACP 					:= 0           ; default to ANSI code page

pRtlFillMemory			:= GetProcAddressInModule("RtlFillMemory")
pWideCharToMultiByte	:= GetProcAddressInModule("WideCharToMultiByte")
pMultiByteToWideChar	:= GetProcAddressInModule("MultiByteToWideChar")
pStrCpy					:= GetProcAddressInModule("strcpy", "msvcrt")

g_strAppName			:= "Smart Open File"
g_strVersion			:= "2010.5.10"
g_strTitle				:= g_strAppName A_Space g_strVersion
g_hMainWnd				:= 0

g_nFilesCount			:= 0
g_nMatchCount			:= 0
g_MatchIndices			= -1


; Allow the user to maximize or drag-resize the window:
Gui +Resize

; Create some buttons:
Gui, Add, Button, Default vBtnLoadFolder gButtonLoadFolder, &Load a folder

; Create the ListView with two columns, Name and Size:
Gui, Add, ListView, Grid xm r20 w700 vMyListView Hwndg_hMyListView +%LVS_OWNERDATA%, Name|In Folder|Modified|Size (KB)|Type

; Create an ImageList so that the ListView can display some icons:
ImageListID1 := IL_Create(10)
ImageListID2 := IL_Create(10, 10, true)  ; A list of large icons to go with the small ones.

; Attach the ImageLists to the ListView so that it can later display the icons:
LV_SetImageList(ImageListID1)
LV_SetImageList(ImageListID2)

ListView_SetItemCount(g_hMyListView, g_nMatchCount)

Gui, Add, Edit, vEditSearchString gOnChangeSearchString,

Gui, Show, , %g_strTitle% [0 of 0]

OnMessage(WM_NOTIFY, "OnNotify")

Gui, +LastFound
WinSet, ReDraw		; Invalidate the list-view

g_hMainWnd := WinExist()

return

GuiEscape:
	GuiClose:  ; Indicate that the script should exit automatically when the window is closed.
	ExitApp
Return

GuiSize:  ; Expand or shrink the ListView in response to the user's resizing of the window.
	if A_EventInfo = 1  ; The window has been minimized.  No action needed.
		return
	; Otherwise, the window has been resized or maximized. Resize the ListView to match.
	GuiControl, Move, MyListView, % "W" . (A_GuiWidth - 20) . " H" . (A_GuiHeight - 70)
	GuiControl, Move, EditSearchString, % "W" . (A_GuiWidth - 20) . " Y" . (A_GuiHeight - 30)
return

ButtonLoadFolder:
	Gui +OwnDialogs  ; Forces user to dismiss the following dialog before using main window.
	FileSelectFolder, Folder,, 3, Select a folder to read:
	if not Folder  ; The user canceled the dialog.
		return
		
	g_nMatchCount := 0
	WinSetTitle, , , %g_strTitle% [%g_nMatchCount% of %g_nFilesCount%]

	GuiControl, Disable, BtnLoadFolder
	SetTimer, FileLoadProgressTimer, 100
	
	g_arrResult := ; Free the memory
	g_nFilesCount := LoadFolder(Folder, "g_arrResult")
	
	Gosub, OnChangeSearchString
	
	Gui +LastFound
	GuiControl, Enable, BtnLoadFolder
	SetTimer, FileLoadProgressTimer, Off
return

OnChangeSearchString:
	GuiControl, -Redraw, MyListView  ; Improve performance by disabling redrawing during load.
	nMatchCount := g_nFilesCount
	bNeedSetFocus := false
	If (g_nFilesCount > 0)
	{
		GuiControlGet, strSearchPattern, , EditSearchString
		
		strSearchPattern := RegExReplace(strSearchPattern, "S)[\s]+", "|")	; Replace multiple whitespaces with a single character
		if(strSearchPattern = "" || strSearchPattern = "|")
		{
			; Nothing is entered, select the first one
			g_MatchIndices = -1
			bNeedSetFocus := true
		}
		Else
		{
			If ( SubStr(strSearchPattern, StrLen(strSearchPattern)) == "|")
				strSearchPattern := SubStr(strSearchPattern, 1, StrLen(strSearchPattern)-1)
			StringSplit, arrSearchPatterns, strSearchPattern, |
			
			g_MatchIndices =
			VarSetCapacity(g_MatchIndices, g_nFilesCount * 4)
			
			nMatchCount := 0

			Loop, %g_nFilesCount%
			{
				strName := g_arrResult%A_Index%_NameAnsi
				strFolder := g_arrResult%A_Index%_FolderAnsi
				bMatch := true
				Loop %arrSearchPatterns0%
				{
					if ( !IsMatch(strName, strFolder, arrSearchPatterns%A_Index%) )
					{
						bMatch := false
						Break
					}
				}
				if (bMatch)
				{
					NumPut(A_Index, g_MatchIndices, nMatchCount * 4)
					++nMatchCount
				}
			}
		}
	}
	Else
	{
		g_MatchIndices = -1
	}
	g_nMatchCount := nMatchCount
	ListView_SetItemCount(g_hMyListView, g_nMatchCount)
	ListView_RedrawItems(g_hMyListView, 0, -1)
	;ToolTip, Done searching %strSearchPattern%
	
	LV_ModifyCol()  ; Auto-size each column to fit its contents.
	GuiControl, +Redraw, MyListView  ; Re-enable redrawing (it was disabled above).
	
	If (bNeedSetFocus)
	{
		GuiControl, Focus, SysListView321, 
		Send, {Home}
		GuiControl, Focus, EditSearchString, 
	}

	WinSetTitle, , , %g_strTitle% [%g_nMatchCount% of %g_nFilesCount%]
Return

IsMatch(ByRef strFileName, ByRef strFolder, strSubPattern)
{
	chChar1			:= SubStr(strSubPattern, 1, 1)
	chChar2			:= SubStr(strSubPattern, 2, 1)
	bIsExclude		:= chChar1 == "-" || chChar2 == "-"
	bIsInFolder		:= chChar1 == "\" || chChar2 == "\"
	
	strSubPattern := RegExReplace(strSubPattern, "S)^[-\\]+")
	If (strSubPattern = "")
		return true
	/*
	StringReplace, strSubPattern, strSubPattern, -
	StringReplace, strSubPattern, strSubPattern, \
	*/
	strSearchText	:= bIsInFolder ? strFolder : strFileName
	bMatch			:= InStr(strSearchText, strSubPattern)
	if (bIsExclude)
		bMatch := !bMatch

	return bMatch
}

FileLoadProgressTimer:
	Gui +LastFound
	WinSetTitle, , , %g_strTitle% [%g_nMatchCount% of %g_nFilesCount%]
Return

/*
typedef struct tagNMHDR
{
    HWND  hwndFrom;
    UINT  idFrom;
    UINT  code;         // NM_ code
}   NMHDR;

 NMHDR *pnm
*/
OnNotify(idCtrl, pnmh)
{
	global g_hMyListView, LVN_GETDISPINFO
	hwndFrom := DecodeInteger( "uint4", pnmh, 0 )
	if ( hwndFrom == g_hMyListView )
	{
		idFrom := DecodeInteger( "uint4", pnmh, 4)
		code := DecodeInteger( "uint4", pnmh, 8)
		nCode := code > 0x7FFFFFFF ? -(~code) - 1 : code
		if ( nCode == LVN_GETDISPINFO )
		{
			OnGetDispInfo(pnmh)
		}
    }
}

/*
NMLVDISPINFO* pnmv

typedef struct tagNMLVDISPINFO {
    NMHDR hdr;
    LVITEM item;
} NMLVDISPINFO;

typedef struct _LVITEM { 
    UINT mask;     0
    int iItem;     4
    int iSubItem;  8
    UINT state;    12
    UINT stateMask; 16
    LPTSTR pszText; 20
    int cchTextMax; 24
    int iImage;     28
    LPARAM lParam;
#if (_WIN32_IE >= 0x0300)
    int iIndent;
#endif
#if (_WIN32_WINNT >= 0x560)
    int iGroupId;
    UINT cColumns; // tile view columns
    PUINT puColumns;
#endif
#if (_WIN32_WINNT >= 0x0600)
    int* piColFmt;
    int iGroup;
#endif
} LVITEM, *LPLVITEM; 

*/
OnGetDispInfo(pnmv)
{
	global
	iItemOffset		:= sizeofNMHDR + 4
	iItem			:= DecodeInteger( "uint4", pnmv, iItemOffset)
	
	if (iItem < 0 || iItem > g_nMatchCount)
		return	; requesting invalid item
	
	maskOffset		:= sizeofNMHDR + 0
	mask			:= DecodeInteger( "uint4", pnmv, maskOffset)
	
	if (mask & LVIF_TEXT)
	{
		iSubItemOffset	:= sizeofNMHDR + 8
		iSubItem		:= DecodeInteger( "uint4", pnmv, iSubItemOffset)
		
		pszTextOffset	:= sizeofNMHDR + 20
		pszText			:= DecodeInteger( "uint4", pnmv, pszTextOffset)
		
		If (g_MatchIndices = -1)
			nIndex := iItem + 1
		else
			nIndex := NumGet(g_MatchIndices, iItem * 4)
			
		;ToolTip, Row %iItem% Index in list %nIndex%
		
		pstrText := 0
		if (0 == iSubItem)
			pstrText := &g_arrResult%nIndex%_Name
		Else if (1 == iSubItem)
			pstrText := &g_arrResult%nIndex%_Folder
		Else if (2 == iSubItem)
			pstrText := &g_arrResult%nIndex%_Modified
		Else if (3 == iSubItem)
			pstrText := &g_arrResult%nIndex%_SizeKB
		Else if (4 == iSubItem)
			pstrText := &g_arrResult%nIndex%_Ext
		EncodeInteger( pstrText, 4, pnmv, pszTextOffset )
	}
	if (mask & LVIF_STATE)
	{
		stateOffset := sizeofNMHDR + 12
		EncodeInteger( 0, 4, pnmv, stateOffset )
	}
	if (mask & LVIF_IMAGE)
	{
		iImageOffset := sizeofNMHDR + 28
		EncodeInteger( -1, 4, pnmv, iImageOffset )
	}
}

LoadFolder(ByRef strFolder, arrResultName, ByRef strExtInclude = "", ByRef strExtExclude = "", bRecursive = true)
{
	global	; This is important for creating/accessing array
	nTotalFiles := 0
	nIndex := g_nFilesCount+1
	
	; Check if the last character of the folder name is a backslash, which happens for root
	; directories such as C:\. If it is, remove it to prevent a double-backslash later on.
	StringRight, LastChar, strFolder, 1
	if LastChar = \
		StringTrimRight, strFolder, strFolder, 1  ; Remove the trailing backslash.

	; Ensure the variable has enough capacity to hold the longest file path. This is done
	; because ExtractAssociatedIconA() needs to be able to store a new filename in it.
	VarSetCapacity(Filename, 260)
	sfi_size = 352
	VarSetCapacity(sfi, sfi_size)

	; Gather a list of file names from the folder
	Loop %strFolder%\*.*, 0, %bRecursive%
	{
		;MsgBox %A_LoopFileName%
		
		; filter out
		If (strExtExclude != "")
		{
			if A_LoopFileExt in %strExtExclude%
			{
				Continue
			}
		}
		If (strExtInclude != "")
		{
			if A_LoopFileExt not in %strExtInclude%
			{
				Continue
			}
		}
		
		; Name|In Folder|Modified|Size (KB)|Type
		FormatTime, FileTimeModified, %A_LoopFileTimeModified% LSys R D1	;, MM/dd/yyyy HH:mm
		
		ToWideChar(A_LoopFileName, %arrResultName%%nIndex%_Name)
		ToWideChar(A_LoopFileExt, %arrResultName%%nIndex%_Ext)
		ToWideChar(A_LoopFileDir, %arrResultName%%nIndex%_Folder)
		ToWideChar(FileTimeModified, %arrResultName%%nIndex%_Modified)
		ToWideChar(A_LoopFileSizeKB, %arrResultName%%nIndex%_SizeKB)
		
		; We need this for pattern matching
		%arrResultName%%nIndex%_NameAnsi := A_LoopFileName
		%arrResultName%%nIndex%_FolderAnsi := A_LoopFileDir
		
		++nIndex
		++nTotalFiles
		++g_nFilesCount
	}
	return nTotalFiles
}

#IfWinActive ahk_class AutoHotkeyGUI
~Up::
~+Up::
	IfWinNotActive, ahk_id %g_hMainWnd%,
	{
		Send, {Up}
		Return
	}
	
	GuiControl, Focus, SysListView321, 
	;ControlSend, SysListView321, {Up}, %g_hMainWnd%
Return

~Down::
~+Down::
	IfWinNotActive, ahk_id %g_hMainWnd%,
	{
		Send, {Down}
		Return
	}
	
	GuiControl, Focus, SysListView321, 
	;ControlSend, SysListView321, {Down}, %g_hMainWnd%
Return
#IfWinActive

;;;;;;;;;;;;;;;;;;; Helper Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ListView_RedrawItems(hwndLV, iFirst, iLast)
{
	global LVM_REDRAWITEMS
	SendMessage, LVM_REDRAWITEMS, iFirst, iLast, , ahk_id %hwndLV%
	return %ErrorLevel%
}

ListView_SetItemCount(hwndLV, cItems)
{
	global LVM_SETITEMCOUNT, LVSICF_NOINVALIDATEALL
    SendMessage, LVM_SETITEMCOUNT, cItems, LVSICF_NOINVALIDATEALL, , ahk_id %hwndLV%
	return %ErrorLevel%
}

TransformWideCharToMultiByte(pWideChar, nWideCharNumber)
{
	Global CP_ACP
	nRequiredSize := WideCharToMultiByte(CP_ACP, 0, pWideChar, nWideCharNumber, 0, 0, 0, 0)
	if(nRequiredSize > 0)
	{
		VarSetCapacity( pMultiByteBuffer, nRequiredSize, 0 )
		nBytesWritten := WideCharToMultiByte(CP_ACP, 0, pWideChar, nWideCharNumber, &pMultiByteBuffer, nRequiredSize, 0, 0)
		return pMultiByteBuffer
	}
	return 0
}

TransformMultiByteToWideChar(pMultiByte, ByRef pWideCharBuffer)
{
	Global CP_ACP
	nRequiredSize := MultiByteToWideChar(CP_ACP, 0, pMultiByte, -1, 0, 0)
	if(nRequiredSize > 0)
	{
		VarSetCapacity( pWideCharBuffer, nRequiredSize<<1, 0 )
		nBytesWritten := MultiByteToWideChar(CP_ACP, 0, pMultiByte, -1, &pWideCharBuffer, nRequiredSize)
		return nBytesWritten
	}
	return -1
}

ToWideChar(strAnsiText, ByRef strUnicodeText)
{
	nStrLen := StrLen(strAnsiText)
	VarSetCapacity(strUnicodeText, (nStrLen+1)*2, 0)
	TransformMultiByteToWideChar(&strAnsiText, strUnicodeText)
	Return strUnicodeText
}

WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar)
{
	global pWideCharToMultiByte
	return DllCall(pWideCharToMultiByte, "UInt", CodePage, "UInt", dwFlags, "UInt", lpWideCharStr, "Int", cchWideChar, "UInt", lpMultiByteStr, "Int", cbMultiByte, "UInt", lpDefaultChar, "UInt", lpUsedDefaultChar)
}

MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, lpWideCharStr, cchWideChar)
{
	global pMultiByteToWideChar
	return DllCall(pMultiByteToWideChar, "UInt", CodePage, "UInt", dwFlags, "UInt", lpMultiByteStr, "Int", cbMultiByte, "UInt", lpWideCharStr, "Int", cchWideChar)
}

DecodeInteger( p_type, p_address, p_offset, p_hex=true )
{
  old_FormatInteger := A_FormatInteger
  ifEqual, p_hex, 1, SetFormat, Integer, hex
  else, SetFormat, Integer, dec
  StringRight, size, p_type, 1
  loop, %size%
      value += *( ( p_address+p_offset )+( A_Index-1 ) ) << ( 8*( A_Index-1 ) )
  if ( size <= 4 and InStr( p_type, "u" ) != 1 and *( p_address+p_offset+( size-1 ) ) & 0x80 )
      value := -( ( ~value+1 ) & ( ( 2**( 8*size ) )-1 ) )
  SetFormat, Integer, %old_FormatInteger%
  return, value
}

EncodeInteger( p_value, p_size, p_address, p_offset )
{
	global pRtlFillMemory
	loop, %p_size%
		DllCall( pRtlFillMemory, "uint", p_address+p_offset+A_Index-1, "uint", 1, "uchar", p_value >> ( 8*( A_Index-1 ) ) )
}

GetProcAddressInModule(strFuncName, strModuleName = "kernel32")
{
	return DllCall("GetProcAddress", uint, DllCall("GetModuleHandle", str, strModuleName), str, strFuncName)
}


#2 majkinetor

majkinetor
  • Fellows
  • 4511 posts

Posted 10 May 2010 - 05:57 PM

Resizing column makes script freeze here.
Items are not visible in the list until scrolling.

It would be interesting to see how this script behaves when ported to AHK_L. When I did so for my problematic ListViews problems did disappear (or minimize or change) due to the "fix" Lexikos included in recent release.

I thought subclassing was an issue with listview, but since you don't use it, I guess WM_NOTIFY is problematic.

BTW, nobody, to my knowledge, solved this issue so far. Try AHK_L and tell the observations. Thats probably better solution anyway, as of Unicode so you will simplify code by removing unicode conversion dllcalls.

Anyway, perhaps the problem could be solved using something else then ListView.

On related note, I would rly like to see module/function that makes ordinary list view "virtuaL"

#3 yonken

yonken
  • Members
  • 15 posts

Posted 11 May 2010 - 02:34 AM

Resizing column makes script freeze here.
Items are not visible in the list until scrolling.

It would be interesting to see how this script behaves when ported to AHK_L. When I did so for my problematic ListViews problems did disappear (or minimize or change) due to the "fix" Lexikos included in recent release.


When this script ported to AutoHotkey_La (ANSI version), the flickering issue is gone for good, but it failed to display anthing in AutoHotkey_Lw (Unicode version), any idea?

#4 Puzzled Greatly

Puzzled Greatly
  • Members
  • 508 posts

Posted 16 May 2010 - 01:30 PM

This is very interesting. I wish I knew how to adapt it. I'd prefer the search bar at the top and a fixed location rather than using a button. Then there's the problem of how to get a click to open a file. But I do like the speed.

GP

#5 rhinox202

rhinox202
  • Members
  • 34 posts

Posted 13 March 2011 - 01:27 AM

Hey Yonken,

I was wondering if you got anywhere with getting this script to work in AutoHotKey_L (Uni). I was hoping to use this in a personal project but it doesn't work as you mentioned.

Bob