- Offizieller Beitrag
Vorweg: Ja, ich weiß, dass es in wNim einen Filedialog gibt!
Es ging mir darum, etwas zu lernen. Und zwar über Treeviews, Listviews, ImageList und das einlesen von Verzeichnissen/Dateien.
Beim darstellen der Verzeichnisstruktur in einem Treeview gibt es ja immer das Problem, dass man alle Verzeichnisse rekursiv durchsuchen muss, was entsprechend lange dauert, weil Festplatten und Co. dafür nunmal einige Zeit brauchen.
Diese Zeit kann man erheblich verkürzen, indem man immer nur 2 Rekursionen ausführt und diese darstellt. Erst beim anklicken/aufklappen eines Zweiges/Items vom Treeview liest man die nächste Rekursionsstufe ein.
Das habe ich hier auch so umgesetzt.
Außerdem habe ich bei Dateien/Verzeichnissen mit den Dateiattributen "s" = System, "h" = Hidden, und "p" = Reparsepoint/Link die Icons als Overlay-Icons erstellt. Bei den "System"-Dateien wird ein Ausrufezeichen eingeblendet. Die "Hidden"-Dateien sind "gedimmt" dargestellt und die "Reparsepoints" haben unten links den kleinen Pfeil.
Den Code habe ich wieder ausführlich kommentiert:
import wNim, winim, strutils, os, math, tables
let iconSize: wSize = (32, 32)
# Prozedur, um die System-ImageList zu initialisieren
proc FileIconInit*(restoreCache: WINBOOL): WINBOOL {.discardable.} =
type fii = proc(fRestoreCache: WINBOOL): WINBOOL {.gcsafe, stdcall.}
let
shell32 = LoadLibrary("shell32.dll")
pAddr = GetProcAddress(shell32, cast[LPCSTR](660))
if IsBadCodePtr(pAddr):
return FALSE
else:
let procFii = cast[fii](pAddr)
return procFii(restoreCache)
# Prozedur, um die System-ImageList auszulesen.
# Im Gegensatz zu "Shell_GetImageLists" (nur 16x16 und 32x32 Icons) bietet
# "SHGetImageList" Icon-Groessen bis 256x256 an (siehe unten).
proc getSystemImageList*(iImageList: int32): HIMAGELIST =
var riid = IID_IImageList
if SHGetImageList(iImageList, riid, cast[ptr pointer](result.addr)) != S_OK:
MessageDialog(nil, "Die System-Image-List konnte nicht geladen werden.\r\n" &
"Das Programm wird beendet.", "Fehler!", wOk or wIconStop).display()
quit()
FileIconInit(TRUE) # System-ImageList initialisieren
var hAppIml = ImageList(iconSize) # ImageList fuer die App erstellen
# "SHGetImageList" stellt verschiedene Icongroessen zur Verfuegung:
# SHIL_SYSSMALL = SM_CXSMICON x SM_CYSMICON (GetSystemMetrics)
# SHIL_SMALL = 16x16
# SHIL_LARGE = 32x32
# SHIL_EXTRALARGE = 48x48
# SHIL_JUMBO = 256x256
hAppIml.mHandle = getSystemImageList(SHIL_LARGE) # System-ImageList an die App-ImageList uebergeben
# Die Laufwerk-Icons der App-ImageList hinzufuegen
var
imlRoot = hAppIml.add(Icon("imageres.dll,104", size=iconSize))
imlUnknown = hAppIml.add(Icon("imageres.dll,70", size=iconSize))
imlFloppy = hAppIml.add(Icon("imageres.dll,23", size=iconSize))
imlCrMedia = hAppIml.add(Icon("imageres.dll,37", size=iconSize))
imlCrNoMedia = hAppIml.add(Icon("imageres.dll,158", size=iconSize))
imlHarddiskSys = hAppIml.add(Icon("imageres.dll,31", size=iconSize))
imlHarddisk = hAppIml.add(Icon("imageres.dll,27", size=iconSize))
imlNetwork = hAppIml.add(Icon("imageres.dll,28", size=iconSize))
imlCDromMedia = hAppIml.add(Icon("imageres.dll,56", size=iconSize))
imlCDromNoMedia = hAppIml.add(Icon("imageres.dll,25", size=iconSize))
imlRamDisk = hAppIml.add(Icon("imageres.dll,29", size=iconSize))
# Ermittelt den DriveType und gibt anhand dessen den
# Icon-Index aus der App-ImageList zurueck
proc getDriveTypeIdx(path: string): int =
let
sFile = newWideCString(path)
pFile = cast[LPCWSTR](sFile[0].addr)
iDrive = GetDriveTypeW(pFile)
case iDrive
of DRIVE_UNKNOWN, DRIVE_NO_ROOT_DIR: # Typ unbekannt oder kein Volume
result = imlUnknown
of DRIVE_REMOVABLE: # Bei einem Wechselmedium-Laufwerk
case (path.toLower)[0]
of 'a', 'b':
result = imlFloppy # Floppy
else:
try: # Cardreader
let fi = getFileInfo(path)
result = imlCrMedia # Medium eingelegt
except OSError:
result = imlCrNoMedia # kein Medium
of DRIVE_FIXED: # Harddisk
if (path.toLower)[0] == 'c':
result = imlHarddiskSys # Systemlaufwerk
else:
result = imlHarddisk # Normales Laufwerk
of DRIVE_REMOTE:
result = imlNetwork # Netzwerklaufwerk
of DRIVE_CDROM: # CD/DVD
try:
let fi = getFileInfo(path)
result = imlCDromMedia # Medium eingelegt
except OSError:
result = imlCDromNoMedia # kein Medium
of DRIVE_RAMDISK:
result = imlRamDisk # Ram-Disk
else: discard
# Holt den Icon-Index fuer die Verzeichnisse/Dateien aus der App-ImageList
proc getSystemIconIndex(path: string): int =
let
sFile = newWideCString(path)
pFile = cast[LPCWSTR](sFile[0].addr)
var shfi: SHFILEINFOW
if SHGetFileInfoW(pFile, 0x00000080'i32, shfi.addr, UINT sizeof(shfi),
SHGFI_SYSICONINDEX) != 0: result = shfi.iIcon
# Gibt einen String zurueck mit fuenf wichtigen Dateiattributen oder "error"
proc getFileAttrStr(path: string): string =
let
sFile = newWideCString(path)
pFile = cast[LPCWSTR](sFile[0].addr)
let iAttr = GetFileAttributesW(pFile)
if iAttr == INVALID_FILE_ATTRIBUTES: return "error"
result.add(if iAttr and 0x00000001'i32: 'r' else: '-') # r = readonly
result.add(if iAttr and 0x00000020'i32: 'a' else: '-') # a = archive
result.add(if iAttr and 0x00000004'i32: 's' else: '-') # s = system
result.add(if iAttr and 0x00000002'i32: 'h' else: '-') # h = hidden
result.add(if iAttr and 0x00000400'i32: 'p' else: '-') # p = reparse point
# Geaenderte Version von getIcon, wegen Bug in der originalen Version.
# In der Original-Version wird immer Index 0 zurueckgegeben!
proc newGetIcon*(self: wImageList, index: int): wIcon =
if index <= self.getImageCount():
var hIcon = ImageList_GetIcon(self.mHandle, cast[int32](index), ILD_TRANSPARENT)
result = Icon(hIcon, copy=false)
# Eine OrderedTable, zum speichern von Pfad und Icon-Index bei den Overlay-Icons
var IconIndexTable = initOrderedTable[string, int]()
# Prozedur, zum holen des Icon-Indexes fuer das Treeview bzw. das Listview.
# Bei den Attributen 's', 'h', 'p' wird ein entsprechendes Overlay-Icon erstellt
# und der App-ImageList hinzugefuegt. Damit das nicht bei jedem Aufruf passiert
# wird der Pfad und der neue Icon-Index in einer OrderedTable gespeichert und
# im Wiederholungsfall daraus geholt.
proc getIconIndex(path: string, attr: string): int =
# Wenn fuer den Pfad bereits ein Icon-Index existiert, diesen zurueckgeben
if IconIndexTable.hasKey(path): return IconIndexTable[path]
result = getSystemIconIndex(path)
if attr.find({'s', 'h', 'p'}) != -1:
var
hIcon = hAppIml.newGetIcon(result)
hImage = Image(hIcon)
hLink = Image(Icon("imageres.dll,154", size=iconSize))
hSystem = Image(Icon("mmcndmgr.dll,7", size=iconSize))
if attr.find('p') != -1:
hImage.paste(hLink, 0, 0)
if attr.find('s') != -1:
hImage.paste(hSystem, 0, 0)
if attr.find('h') != -1:
hImage.brightnessContrast(brightness = 100, contrast = -60)
result = hAppIml.add(Icon(hImage))
hSystem.delete
hLink.delete
hImage.delete
hIcon.delete
IconIndexTable[path] = result # Pfad und Icon-Index merken
# Zum suchen, nach einem SubItem (Unterverzeichnis)
proc findItemText(tvItem: wTreeItem, text: string): bool =
for tvChild in tvItem.getChildren:
if tvChild.getText == text: return true
return false
# Erstellt die SubItems (Verzeichnisse) beim Treeview
proc addSubItems(tree: wTreeCtrl, parent: wTreeItem, sDir: string) =
var sFolder: string = sDir
sFolder.normalizePathEnd(true) # sicherstellen, dass ein Backslash am Ende steht
for kind, path in walkDir(sFolder):
var attr = getFileAttrStr(path)
case kind
of pcDir, pcLinkToDir:
if not findItemText(parent, lastPathPart(path)): # wenn Unterverzeichnis noch nicht vorhanden,
tree.appendItem(parent, lastPathPart(path), getIconIndex(path, attr), data=1) # dann erstellen
else: discard
# Zum auslesen des Pfads (excl. Root)
proc getTree(self: wTreeItem, sepChar: char = chr(92)): string =
let tvRoot = self.getTreeCtrl.getRootItem
var tvItem = self
while tvItem != tvRoot:
result = tvItem.getText & sepChar & result
tvItem = tvItem.getParent
# Erstellt im Listview die Items fuer die Verzeichnisse/Dateien.
# Fuer die Dateiendungen kann ein Filter uebergeben werden. Dazu werden
# einfach die erlaubten Dateiendungen als String uebergeben.
# Zum Beispiel: readFolder(list, "c:\", "exe", "au3", "mp3", "pdf", ...)
# Beim uebergeben von einem Leerstring werden alle Dateien angezeigt.
proc readFolder(list: wListCtrl, sDir: string, sFileExt: varargs[string]) =
var
sFolder: string = sDir
lvIndex: int
sFolder.normalizePathEnd(true) # sicherstellen, dass ein Backslash am Ende steht
list.deleteAllItems() # alle Eintraege aus dem Listview entfernen
SendMessage(list.getHandle, WM_SETREDRAW, FALSE, 0) # Listview-Refresh verbieten
for kind, path in walkDir(sFolder): # zuerst alle Verzeichnisse einlesen
case kind
of pcDir, pcLinkToDir:
var attr = getFileAttrStr(path)
lvIndex = list.appendItem( # und ins Listview einfuegen
[lastPathPart(path), "", (if kind == pcLinkToDir: "<LNK>" else: "<DIR>"), attr],
image = getIconIndex(path & r"\", attr)
)
else: discard
for kind, path in walkDir(sFolder): # danach die Dateien
case kind
of pcFile, pcLinkToFile:
for extension in sFileExt: # anhand des Dateiendungs-Filter
if path.toLower.endsWith(extension.toLower): # nur die erlaubten Endungen zulassen
var
(dir, name, ext) = path.splitFile
attr: string = getFileAttrStr(path)
lvIndex = list.appendItem( # und ins Listview einfuegen
[name, ext.substr(1), insertSep($getFileSize(path), '.'), attr],
image = getIconIndex(path, attr)
)
else: discard
SendMessage(list.getHandle, WM_SETREDRAW, TRUE, 0) # Listview-Refresh wieder erlauben
let app = App()
let frame = Frame(title="Files and Folder", size=(1000, 640))
let panel = Panel(frame)
panel.setBackgroundColor(0x00DDDDDD)
let idPath = TextCtrl(panel, pos=(10, 5), size=(960, 30), style=wBorderSimple or wTeReadOnly)
idPath.font = Font(14, faceName="Tahoma", weight=wFontWeightNormal)
idPath.setBackgroundColor(0x00F4F4F4)
let idTreeview = TreeCtrl(panel, pos=(10, 40), size=(350, 540), style=wBorderSimple or wTrHasButtons or wTrHasLines or wClipChildren)
idTreeview.font = Font(12, faceName="Tahoma", weight=wFontWeightNormal)
idTreeview.setBackgroundColor(0x00F4F4F4)
idTreeview.setImageList(hAppIml, wImageListNormal)
let idListview = ListCtrl(panel, pos=(370, 40), size=(600, 540), style=wLcReport or wBorderSimple or wClipChildren)
idListview.font = Font(12, faceName="Arial", weight=wFontWeightNormal)
idListview.appendColumn("Dateiname", width=290, format=wListFormatLeft)
idListview.appendColumn("Ext", width=80, format=wListFormatRight)
idListview.appendColumn("Größe", width=120, format=wListFormatRight)
idListview.appendColumn("Attr.", width=85, format=wListFormatRight)
idListview.setBackgroundColor(0x00F4F4F4)
idListview.setImageList(hAppIml, wImageListSmall)
let root = idTreeview.addRoot("PC", imlRoot) # idTreeview-Root-Item erstellen
let iDrive = GetLogicalDrives() # die Laufwerke auslesen
var
iIcon: int
tvItem: wTreeItem
for i in 0..25: # Alle Laufwerksbuchstaben testen
if (iDrive and 2^i) == 2^i: # Laufwerke (Bit 0 = A:, Bit 1 = B:, Bit 2 = C:, usw.)
iIcon = getDriveTypeIdx($chr(97 + i) & r":\") # Icon-Index vom Laufwerk holen
tvItem = idTreeview.appendItem(root, $chr(65 + i) & $chr(58), iIcon) # Laufwerk-Item erstellen
idTreeview.addSubItems(tvItem, $chr(97 + i) & $chr(58)) # und die SubItems erstellen
root.expand() # Root-Item ausklappen
# Workaround, weil es "getItem" in wNim v0.11.2 nicht mehr gibt
proc myGetItem*(event: wEvent): wTreeItem =
let pnmtv = cast[LPNMTREEVIEW](event.lParam)
result.mHandle = pnmtv.itemNew.hItem
result.mTreeCtrl = event.getEventObject.wTreeCtrl
# Wenn ein Item im Treeview angeklickt wird
idTreeview.wEvent_TreeSelChanged do (event: wEvent):
let tvItem = event.myGetItem
if tvItem == root: return
frame.setCursor(Cursor(wCursorWait))
let path = tvItem.getTree
idPath.label = path
idListview.readFolder(path, "")
frame.setCursor(wNilCursor)
# Beim aufklappen eines Treeview-Item
idTreeview.wEvent_TreeItemExpanded do (event: wEvent):
let tvItem = event.myGetItem
if tvItem == root: return
frame.setCursor(Cursor(wCursorWait))
for tvChild in tvItem.getChildren:
if tvChild.getData == 1:
let path = tvChild.getTree
idTreeview.addSubItems(tvChild, path)
frame.setCursor(wNilCursor)
frame.center()
frame.show()
app.mainLoop()
Alles anzeigen