【问题标题】:How to create Thread in GUI-application?如何在 GUI 应用程序中创建线程?
【发布时间】:2020-01-11 00:47:31
【问题描述】:

问题:

我通过 unsplash API 设置了一个图像下载 GUI 应用程序(tkinter)。 每件事都在进行中。没有任何错误,但是当我运行应用程序时 它显示“未响应”导致图像在后台下载。如何创建两个线程。一个将处理 tkinter GUI,第二个将处理背景图像下载过程。

我的代码:

from tkinter import *
import requests
import os, pprint
from time import sleep
from threading import *
from urllib.request import urlretrieve


class Gui(Tk):
    def __init__(self):
        self.do_splash_call = False
        Tk.__init__(self)
        self.title('unsplash image download')
        self.geometry('900x500')
        # main canvas
        main = Canvas(self, bg='#0099e6')
        main.place(relx=0, rely=0, relwidth=1, relheight=1)

        # frame1 for upper left
        name_frame = Frame(main)
        name_frame.place(relx=.1, rely=.1)
        # name input and label
        name_label = Label(name_frame, text='Image Name:')
        name_label.pack(side=LEFT, pady=3)
        self.name = Entry(name_frame, bd=0, font=2, width=23)
        self.name.pack(side=RIGHT, pady=3)

        # frame2 for upper right
        page_frame = Frame(main)
        page_frame.place(relx=.5, rely=.1, relwidth=.3)
        # landing page
        Label(page_frame, text='Landing page:').pack(side=LEFT, pady=3)
        self.page = Spinbox(page_frame, from_=1, to=50, width=30)
        self.page.pack(side=RIGHT, pady=3)

        # frame3 for down left
        size_frame = Frame(main)
        size_frame.place(relx=.1, rely=.2)
        # name input and label
        size_label = Label(size_frame, text='Android, pc, tablet:')
        size_label.pack(side=LEFT, pady=3)
        self.size = Entry(size_frame, bd=0, font=2)
        self.size.pack(side=RIGHT, pady=3)

        # button
        download = Button(main, bd=1, font=4, text='Download', width=10, command=lambda :self.splash())
        download.place(relx=.6, rely=.2)

        # output
        self.output = Text(main, bd=2)
        self.output.place(relx=0, rely=.5, relheight=1, relwidth=1)

        # path to save images
        save_frame = Frame(main)
        save_frame.place(relx=.1, rely=.3, relwidth=.7)
        Label(save_frame, text='Save:').pack(side=LEFT, padx=5)
        self.save = Entry(save_frame, bd=1)
        self.save.place(relx=.1, relwidth=1)

        # insert path for save images
        self.save.insert(0, str('c:\\users\\abc\\Desktop\\photos'))



    def path(self):
        if os.path.exists(self.save.get()):
            os.chdir(self.save.get())
        else:
            os.mkdir(self.save.get())
            os.chdir(self.save.get())
    def splash(self):
        self.do_splash_call = True
        self.call_splash()

    def call_splash(self):
        if self.do_splash_call:
            # getting input from user
            image_name = self.name.get()
            pages = self.page.get()
            size = self.size.get()
            # set the size of photos
            if size == 'pc' or 'desktop':
                size = 1080
                orientation = 'landscape'
            elif size == 'hd' or 'full hd' or 'clear' or 'normal' or 'HD':
                size = 1500
                orientation = 'landscape'
            elif size == 'android' or 'mini' or 'mobile':
                size = 400
                orientation = 'portrait'
            else:
                size = 1500
                orientation = 'landscape'

            try:
                api = f'''https://api.unsplash.com/photos/search?query=
                          {image_name}&resolution={size}&orientation= 
                          {orientation}&client_id=API Key
                           &page={pages}&w=1500&dpi=2'''
                res = requests.get(api).json()
                for i in range(2):
                    url = res[i]['links']['download']

                    name_of_image = str(res[i]['alt_description'])
                    img_name = '_'.join(name_of_image[:40].split(' '))
                    # create folder for download images
                    self.path()

                    # print current images downloading
                    self.output.insert(INSERT, '\nDownloading img.... %s.png' % img_name)
                    self.output.tag_add('fine', '1.0', '3.0')
                    self.output.tag_config('fine', background='lightGreen', foreground='#196619')
                    # Downloading image in pc
                    urlretrieve(url, '%s.png' % img_name)

            except Exception as e:
                self.output.insert(INSERT, e)
                self.output.tag_add('error', '1.0', '3.0')
                self.output.tag_config('error', background='#ff4d4d', foreground='black')
                print(e)

if __name__ == '__main__':
    app = Gui()
    app.mainloop()

我除外:

有一个类,里面有一个名为splash()的方法,所以我如何创建Thread,一个用于constructor,第二个用于splash() method?

【问题讨论】:

  • 你只需要线程来运行call_splash,但你应该在运行线程threading.Thread(taget=self.call_splash, args=(self.name.get(), self.page.get(), self.size.get()))和def call_splash(self, image_name, pages, size):之前从GUI获取值
  • 拜托,你能在代码中实现它吗?这样会更好理解。
  • 有很多关于如何在 python 中创建线程的文档。你做过研究吗?

标签: python python-3.x multithreading tkinter


【解决方案1】:

代码中的线程和其他更改或 cmets 示例,因为存在错误。

我没有更改from tkinter import *,因为它需要更多的工作。在某些系统上,线程可能无法访问 GUI,因此 call_spash 中的 self.output 可能有问题,但我没有更改它 - 工作太多。

顺便说一句:PEP 8 -- Style Guide for Python Code

from tkinter import *  # PEP8 - don't use *
#import tkinter as tk
import requests
import os
import pprint  # PEP8 - imports in separated lines
from time import sleep
# from threading import *   # PEP8 - don't use *
import threading
from urllib.request import urlretrieve


class Gui(Tk):
    def __init__(self):
        #Tk.__init__(self)
        super().__init__() # instead of Tk.__init__(self)

        self.title('unsplash image download')
        self.geometry('900x500')

        self.do_splash_call = False
        self.thread = None

        # main canvas
        main = Canvas(self, bg='#0099e6')
        main.place(relx=0, rely=0, relwidth=1, relheight=1)

        # frame1 for upper left
        name_frame = Frame(main)
        name_frame.place(relx=.1, rely=.1)
        # name input and label
        name_label = Label(name_frame, text='Image Name:')
        name_label.pack(side=LEFT, pady=3)
        self.name = Entry(name_frame, bd=0, font=2, width=23)
        self.name.pack(side=RIGHT, pady=3)

        # frame2 for upper right
        page_frame = Frame(main)
        page_frame.place(relx=.5, rely=.1, relwidth=.3)
        # landing page
        Label(page_frame, text='Landing page:').pack(side=LEFT, pady=3)
        self.page = Spinbox(page_frame, from_=1, to=50, width=30)
        self.page.pack(side=RIGHT, pady=3)

        # frame3 for down left
        size_frame = Frame(main)
        size_frame.place(relx=.1, rely=.2)
        # name input and label
        size_label = Label(size_frame, text='Android, pc, tablet:')
        size_label.pack(side=LEFT, pady=3)
        self.size = Entry(size_frame, bd=0, font=2)
        self.size.pack(side=RIGHT, pady=3)

        # button
        #download = Button(main, bd=1, font=4, text='Download', width=10, command=lambda :self.splash())
        download = Button(main, bd=1, font=4, text='Download', width=10, command=self.splash) # no need lambda
        download.place(relx=.6, rely=.2)

        # output
        self.output = Text(main, bd=2)
        self.output.place(relx=0, rely=.5, relheight=1, relwidth=1)

        # path to save images
        save_frame = Frame(main)
        save_frame.place(relx=.1, rely=.3, relwidth=.7)
        Label(save_frame, text='Save:').pack(side=LEFT, padx=5)
        self.save = Entry(save_frame, bd=1)
        self.save.place(relx=.1, relwidth=1)

        # insert path for save images
        #self.save.insert(0, str('c:\\users\\abc\\Desktop\\photos'))
        self.save.insert(0, 'c:\\users\\abc\\Desktop\\photos') # it is already string 

    def path(self):
        # you could use `val = self.save.get()` once and later use `val`
        #if os.path.exists(self.save.get()):
        #    os.chdir(self.save.get())
        #else:
        #    os.mkdir(self.save.get())
        #    os.chdir(self.save.get())

        val = self.save.get()

        if not os.path.exists(val): 
            #os.mkdir(val)   # it makes only first dir in path `dir1/dir2/dir3`
            os.makedirs(val) # it makes all dirs in path `dir1/dir2/dir3`

        #os.makedirs(val, exist_ok=True) # Python 3.7+, it doesn't need `os.path.exists`

        os.chdir(val)

    def splash(self):
        #if not self.thread or not self.thread.is_alive(): # method without `self.do_splash_call` 
        if not self.do_splash_call:
            print('[DEBUG] run splash')
            self.do_splash_call = True

            # getting input from user
            image_name = self.name.get()
            pages = self.page.get()
            size = self.size.get()

            self.thread = threading.Thread(target=self.call_splash, args=(image_name, pages, size) )
            self.thread.start()

    def call_splash(self, image_name, pages, size):
        # set the size of photos
        #if size == 'pc' or 'desktop': # wrong
        #if size == 'pc' or size = 'desktop': # good
        if size in ('pc', 'desktop'): # good
            size = 1080
            orientation = 'landscape'
        #elif size == 'hd' or 'full hd' or 'clear' or 'normal' or 'HD': # wrong
        elif size in ('hd', 'full hd', 'clear', 'normal', 'HD'):
            size = 1500
            orientation = 'landscape'
        #elif size == 'android' or 'mini' or 'mobile': # wrong
        elif size in ('android', 'mini', 'mobile'):
            size = 400
            orientation = 'portrait'
        else:
            size = 1500
            orientation = 'landscape'

        try:
        api = f'''https://api.unsplash.com/photos/search?query=
                  {image_name}&resolution={size}&orientation= 
                  {orientation}&client_id=API Key
                   &page={pages}&w=1500&dpi=2'''
        res = requests.get(api).json()
        print('[DEBUG] res:', res)

        for i in range(2):
            url = res[i]['links']['download']

            name_of_image = str(res[i]['alt_description'])
            img_name = '_'.join(name_of_image[:40].split(' '))
            # create folder for download images
            self.path()

            # print current images downloading
            self.output.insert(INSERT, '\nDownloading img.... %s.png' % img_name)
            self.output.tag_add('fine', '1.0', '3.0')
            self.output.tag_config('fine', background='lightGreen', foreground='#196619')
            # Downloading image in pc
            urlretrieve(url, '%s.png' % img_name)

        except Exception as e:
            self.output.insert('end', e)
            self.output.tag_add('error', '1.0', '3.0')
            self.output.tag_config('error', background='#ff4d4d', foreground='black') # you could define it once 
            print('[DEBUG] e:', e)

        self.do_splash_call = False

if __name__ == '__main__':
    app = Gui()
    app.mainloop()

【讨论】:

  • 非常感谢。我明白了。
  • 这里print('[DEBUG] e:', e)DEBUG是什么意思?
  • 如何通过一行代码检查API是否有效,例如:assert、raise等。
  • 我能理解这一点吗:Thread(target=self.call_splash, args=(image_name, pages, size) ) 为什么我们在这里传递 image_name, pages, size 参数是因为我们使用 API?
  • DEBUG 只是文本。我用它来查看我的打印件,这些打印件仅对测试很重要,稍后我可以删除它们或创建变量来执行if self.debug: print("[DEBUG...")
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-20
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多