Python/Cookbook

【Python/File Explorer】탐색기처럼 만들고 싶었으나...

1Q74 2023. 2. 22. 14:23

어느 날 갑자기 python으로 Windows GUI프로그래밍에 관심이 동한 바, 폭풍같이 구글링을 하여 다음과 같은 코드를 작성하여 보았다. 꿈은 장대하였으나, 현실의 벽은 높다는 것을 나에게 알려 주었다.


1. 코드

실행하면 「바탕화면」이 표시되고, 「바탕화면」을 더블클릭하면 하위 개체들이 표시됩니다, 2-depth까지. 딱 거기까지만 됩니다.

import codecs
import tkinter as tk
import tkinter.ttk as ttk
from PIL import Image, ImageTk

import win32api
import win32con
import win32gui
import win32ui
from numpy import uint32
from win32comext.shell.shellcon import *
from win32comext.shell import shell
import numpy


class LunchBox:

    def __init__(self):
        self._tree = None
        self._sh_current_parent_folder = None
        self._nodes = []
        self._node_index = 0

    def _get_fileinfo(self, location):
        _, fileinfo = shell.SHGetFileInfo(
            location,
            0,
            SHGFI_DISPLAYNAME | SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_ATTRIBUTES | SHGFI_SMALLICON | SHGFI_ICON
        )
        return fileinfo

    def _get_desktop_folder_info(self):
        desktop_location = shell.SHGetSpecialFolderLocation(0, CSIDL_DESKTOP)
        return self._get_fileinfo(desktop_location)

    def _get_icon(self, info):
        hicon, iicon, dwattr, name, typename = info

        ico_x = win32api.GetSystemMetrics(win32con.SM_CXICON)
        hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))
        hbmp = win32ui.CreateBitmap()
        hbmp.CreateCompatibleBitmap(hdc, ico_x, ico_x)
        hdc = hdc.CreateCompatibleDC()
        hdc.SelectObject(hbmp)
        hdc.DrawIcon((0, 0), hicon)
        win32gui.DestroyIcon(hicon)

        bmpinfo = hbmp.GetInfo()
        bmpstr = hbmp.GetBitmapBits(True)
        img = Image.frombuffer(
            "RGBA",
            (bmpinfo["bmWidth"], bmpinfo["bmHeight"]),
            bmpstr, "raw", "BGRA", 0, 1
        )

        img = img.resize((16, 16), Image.ANTIALIAS)

        return img

    def _get_treenode(self, sh_folder):
        info = self._get_fileinfo(sh_folder)
        icon = self._get_icon(info)
        iconimage = ImageTk.PhotoImage(icon)
        hassubfolder = (uint32(info[2]) & uint32(SFGAO_HASSUBFOLDER)) == uint32(SFGAO_HASSUBFOLDER)
        return {'name': info[3], 'icon': iconimage, 'hassubfolder': hassubfolder}

    def _on_double_click(self, event):
        item = self._tree.identify('item', event.x, event.y)
        children = self._tree.get_children(item)
        self._tree.delete(children)

        sh_current_folder = [eval(self._tree.item(item, "values")[0])]

        sh_folders = self._sh_current_parent_folder.BindToObject(sh_current_folder, None, shell.IID_IShellFolder)

        for sh_folder in sh_folders:
            self._node_index = self._node_index + 1
            name = sh_folders.GetDisplayNameOf(sh_folder, SHGDN_NORMAL)
            print("name = ", name)
            self._nodes.append(self._get_treenode([sh_current_folder[0], sh_folder[0]]))

            subtree = self._tree.insert(
                item,
                "end",
                text=self._nodes[self._node_index]['name'],
                image=self._nodes[self._node_index]['icon'],
                values=sh_folder
            )
            if self._nodes[self._node_index]['hassubfolder']:
                self._sh_current_parent_folder = sh_folders
                self._tree.insert(subtree, "end", text="__EMPTY__")
                self._tree.bind("<Double-1>", self._on_double_click)

    def _set_desktop_folder(self):
        fldinfo = self._get_desktop_folder_info()
        icn = self._get_icon(fldinfo)
        icnimg = ImageTk.PhotoImage(icn)
        self._nodes.append({'name': fldinfo[3], 'icon': icnimg, 'hassubfolder': True})
        self.root_tree_node = self._tree.insert(
            '',
            'end',
            text=self._nodes[self._node_index]['name'],
            image=self._nodes[self._node_index]['icon']
        )

    def load(self):
        root = tk.Tk()
        root.geometry('500x500')

        self._tree = ttk.Treeview(root)
        self._tree.pack(fill='both', expand=True)
        # Treeview의 헤더를 숨긴다.
        self._tree.configure(show="tree")

        # Desktop폴더(바탕화면)를 표시한다.
        self._set_desktop_folder()

        self._sh_current_parent_folder = shell.SHGetDesktopFolder()

        for sh_folder in self._sh_current_parent_folder:
            self._node_index = self._node_index + 1
            self._nodes.append(self._get_treenode(sh_folder))
            subtree = self._tree.insert(
                self.root_tree_node, "end",
                text=self._nodes[self._node_index]['name'],
                image=self._nodes[self._node_index]['icon'],
                values=sh_folder
            )
            if self._nodes[self._node_index]['hassubfolder']:
                self._tree.insert(subtree, "end", text="__EMPTY__")
                self._tree.bind("<Double-1>", self._on_double_click)

        root.mainloop()


lb = LunchBox()
lb.load()

2. 결과

2-1. 실행하면 「바탕화면」항목만 표시된다.

2-2. 바탕화면을 더블클릭하면 하위 항목들이 표시된다.

2-3. 표시된 하위 항목을 더블클릭하면 그 하위의 항목들이 표시된다.

2-4. 표시된 하위 항목을 더블클릭하면 아무것도 표시되지 않는다.


3. 소스

본 소스를 실행해 보기 위해서는 JET BRAIN사에서 만든 PyCharm같은 프로그램을 사용하는 것이 정신건강에 좋습니다.

lunchbox.py
0.00MB


4. Reference

http://timgolden.me.uk/pywin32-docs/contents.html