Python - 动态创建带有图像的按钮时遇到问题

问题描述 投票:0回答:1

我对 Python 非常陌生(我的自学之旅才几周),但我遇到了困难。

目标:

要让脚本读取运行脚本的文件夹中的文件,请为其找到的任何 .lnk 文件动态创建按钮,从目标 .exe 文件中提取图标并将其添加到该链接的按钮中。单击时,该按钮将启动创建时指向的快捷方式.lnk 文件。有效地在 Windows 任务栏中创建快捷方式的弹出菜单。

问题:

我可以让它在没有图像的情况下工作,但是一旦我尝试将图像添加到 tk.Button(image) ,它就只适用于它创建的最后一个按钮。我确信我获取图像的方式做错了,但我就是不知道是什么。

result 上图显示了结果,单击前两个按钮时绝对不会执行任何操作,第三个按钮按预期工作。如果我注释掉将图像添加到按钮的部分,所有三个按钮都可以打开各自的链接。

代码:

import tkinter as tk
import os
import pyautogui
from win32api import GetSystemMetrics
from PIL import Image, ImageTk
import win32com.client
import win32gui
import win32ui


def execlink(linkpath):
    # print(linkpath)
    os.startfile(linkpath)


class IconExtractor:
    def __init__(self):
        self.photo = None
        self.root = None  # Keep a reference to the Tkinter root window
        self.label = None  # Keep a reference to the Label widget

    def extract_icon(self, exe_path, ico_path):
        # Load the executable file
        icon_handles, num_icons = win32gui.ExtractIconEx(exe_path, 0)

        # Check if any icons were found
        if icon_handles:
            # Select the first icon from the list
            h_icon = icon_handles[0]

            # Create a device context
            hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0))

            # Create a bitmap and draw the icon into it
            hbmp = win32ui.CreateBitmap()
            hbmp.CreateCompatibleBitmap(hdc, 32, 32)  # You may adjust the size (32x32 in this case)
            hdc = hdc.CreateCompatibleDC()
            hdc.SelectObject(hbmp)
            hdc.DrawIcon((0, 0), h_icon)

            # Save the bitmap as an icon file
            hbmp.SaveBitmapFile(hdc, ico_path)

            # Release the icon resources
            win32gui.DestroyIcon(h_icon)
        else:
            print("No icons found in the executable.")

    def on_closing(self):
        # This function is called when the user closes the Tkinter window
        self.root.destroy()

    def main(self, lnk_path, ico_path):
        # Explicitly initialize the Tkinter event loop
        # root = tk.Tk()
        # root.withdraw()  # Hide the root window

        # lnk_path = r''
        # ico_path = r''

        # Get the target path from the .lnk file
        shell = win32com.client.Dispatch("WScript.Shell")
        shortcut = shell.CreateShortCut(lnk_path)
        target_path = shortcut.TargetPath

        if os.path.exists(target_path):
            # Extract the icon from the executable
            self.extract_icon(target_path, ico_path)

            # Check if the .ico file was generated successfully
            if os.path.exists(ico_path):
                # Open the ICO file using Pillow
                with Image.open(ico_path) as img:
                    # Convert the image to a format compatible with PhotoImage
                    img = img.convert("RGBA")
                    self.photo = ImageTk.PhotoImage(img)

                # # Create the main window
                # self.root = tk.Toplevel(root)
                #
                # # Create a button to activate the .lnk file with the icon
                # button = tk.Button(self.root, text="Activate", compound=tk.LEFT, image=self.photo,
                #                    command=self.activate_lnk)
                # button.pack(side=tk.LEFT)
                #
                # # Bind the closing event
                # self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
                #
                # # Run the Tkinter event loop
                # self.root.mainloop()
            # else:
            #     print("Failed to generate .ico file.")
        else:
            print("Failed to retrieve the target path.")


class LinksMenu:

    def __init__(self):

        self.MainWindow = tk.Tk()
        self.MainWindow.geometry("100x40+" + str(pyautogui.position()[0]) + "+100")
        self.MainWindow.overrideredirect(True)
        self.MainWindow.bind("<KeyPress>", self.closing)

        self.btnframe = tk.Frame(self.MainWindow)
        self.btnframe.configure(bg='')
        self.btnframe.columnconfigure(0, weight=1)

        count = 1

        files = [File for File in os.listdir('.') if os.path.isfile(File)]

        for File in files:
            if File[-4:] == ".lnk":
                GetImage.main(os.path.realpath(File),
                              os.path.dirname(os.path.realpath(File)) + '\\' + File[0:-4] + '.ico')
                newbutton = (tk.Button
                             (self.btnframe,
                              name=File[0:-4].lower() + 'btn',
                              text=File[0:-4],
                              compound=tk.LEFT,
                              image=GetImage.photo,
                              font=('Arial', 14),
                              bg='DodgerBlue4',
                              command=lambda m=os.path.realpath(File): [execlink(m), self.close_after_choice()]))
                newbutton.grid(padx=2,
                               pady=2,
                               row=count,
                               column=0,
                               sticky=tk.W+tk.E)
                count += 1

        self.btnframe.pack(fill='x')

        self.MainWindow.geometry("300x"
                                 + str(count*45)
                                 + "+"
                                 + str(pyautogui.position()[0])
                                 + "+"
                                 + str(GetSystemMetrics(1)-(count*45)))
        self.MainWindow.configure(bg='')
        self.MainWindow.mainloop()

    def closing(self, event):
        if event.state == 8 and event.keysym == "Escape":
            self.MainWindow.destroy()

    def close_after_choice(self):
        self.MainWindow.destroy()


if __name__ == "__main__":
    GetImage = IconExtractor()
    LinksMenu()

我已经尝试过:

  • 将图标保存为 png,然后通过文件路径调用它
  • 将其添加到新对象,然后将新对象添加到 tk.Button(如果链接持续存在)
  • 在添加到 tk.Button 之前将其转换为内存中的 png
  • 不动态创建按钮(以防与重新使用相同的对象名称“newbutton”有关
  • 首先销毁(通过类)调用的对象以获取图像

完全公开,我使用 ChatGPT 来帮助完成图标提取的代码。

python tkinter icons dynamically-generated shortcut-file
1个回答
0
投票

因为你使用了同一个变量来存储这些图像的引用,所以只有最后一张会被保留,其他的都会被垃圾收集。

使用每个按钮的属性保存该按钮的图像引用:

newbutton.photo = GetImage.photo
© www.soinside.com 2019 - 2024. All rights reserved.