Python 如何在Tkinter中分别运行两个函数?

Python 如何在Tkinter中分别运行两个函数?,python,tkinter,Python,Tkinter,我是PythonGUI编程新手,我有一个按钮,当你点击它时,由于计算时间的原因,运行它需要时间。我只想在我的主函数运行时在标签中间显示一个“等待”字。 这是我的代码,其中Macula(文件路径):函数需要时间运行,我尝试在调用M.makula_检测(图像,阈值,n,大小)之前放置一个标签 我还尝试在on命令中使用两个函数,但由于它们一起运行,所以它也不起作用 这是我的密码: from tkinter import * from PIL import ImageTk, Image from tki

我是PythonGUI编程新手,我有一个按钮,当你点击它时,由于计算时间的原因,运行它需要时间。我只想在我的主函数运行时在标签中间显示一个“等待”字。 这是我的代码,其中
Macula(文件路径):
函数需要时间运行,我尝试在调用
M.makula_检测(图像,阈值,n,大小)
之前放置一个
标签

我还尝试在on命令中使用两个函数,但由于它们一起运行,所以它也不起作用

这是我的密码:

from tkinter import *
from PIL import ImageTk, Image
from tkinter import filedialog
import cv2 as cv
import MaskGeneration as mg
import MakulaDetection as M

# =================================== statics and configuration ===================================
color = '#20536C'
root = Tk()
root.title('Opticdisk and Macula detector')
root.configure(bg= color)
root.geometry('1070x700')
root.resizable(width=False, height=False)
root.iconbitmap('J:\Projects\Bachelor Project\download.ico')


# =================================== functions and body ===================================

filename_path = {}
# label = Label(LEFT,text = 'fff')
def open_image(file_path):
   global select_lbl
   bigIm_path = {}# the new path of resized big image
   file_path['image'] = filedialog.askopenfilename(initialdir="J://uni//final project//Data set",
                                              title="select an image",
                                              filetypes=(('all files', '*.*'), ('jpg files', '*.jpg'), ('tif file','*.tif')))

   IM = cv.imread(file_path['image'])
   im_wid = len(IM)
   im_len =len(IM[1])
   if (im_len > 749 and im_wid > 630) or (im_len>749) or (im_wid>630):

       IM = cv.resize(IM, (629, 629))
       cv.imwrite('J://uni//final project//res_image//big_img.jpg', IM)
       bigIm_path['image'] = ('J://uni//final project//res_image//big_img.jpg')
       #top line save the new resized big image in bigIM_path{}
       mainImage = ImageTk.PhotoImage(Image.open(bigIm_path['image']))
       select_lbl = Label(left, image=mainImage,
                   width=749,
                   height=630,
                   bg='#020101')  # .place(x=20, y=0)
       select_lbl.image = mainImage  # keep a reference! to show the image
       select_lbl.place(x=0, y=0)

   else:
       mainImage = ImageTk.PhotoImage(Image.open(filename_path['image']))
       select_lbl = Label(left, image=mainImage,
                   width= 749,
                   height=630,
                   bg='#020101')#.place(x=20, y=0)
       select_lbl.image = mainImage  # keep a reference! to show the image
       select_lbl.place(x=0, y=0)

def Macula(file_path):
   path={}#
   # messagebox = Message(left).place(x=20,y=10)
   try:
       # select_lbl.pack_forget()
       image = cv.imread(file_path['image'])
       image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
       # top line solve the problem of blue background color by changing color space  from BGR to RGB
       Size = 0.2
       thresh = 25
       n = 80

       M.makula_detection(image, thresh, n, Size)
       path['image'] = ('J://uni//final project//res_image//marked_M.png')
       ''' this line is a tec for dispaly the file '''
       mainImage = ImageTk.PhotoImage(Image.open(path['image']))
       lbl = Label(left, image=mainImage,
                   width=749,
                   height=630,
                   bg='#020101')  # .place(x=20, y=0)
       lbl.image = mainImage  # keep a reference! to show the image
       lbl.place(x=0, y=0)

   except:
       print('error')
       lbl = Label(left,text = "No image file selected !!!",
                   font = ('Times', 25, 'italic', 'bold'),
                   fg = '#ffe874',
                   bg = color)
       lbl.place(x=200,y=270)

def Mask(file_path):
   path = {}
   # messagebox = Message(left).place(x=20,y=10)
   try:
       Im = cv.imread(file_path['image'])
       mask = mg.mask_generation(Im)
       # dispaly function
       I = cv.resize(mask, (400, 400))
       cv.imwrite('J://uni//final project//res_image//finalresult.jpg', I)
       path['image'] = ('J://uni//final project//res_image//finalresult.jpg')
       ''' this line is a tec for dispaly the file '''
       mainImage = ImageTk.PhotoImage(Image.open(path['image']))
       lbl = Label(left, image=mainImage,
                   width=749,
                   height=630,
                   bg='#020101')  # .place(x=20, y=0)
       lbl.image = mainImage  # keep a reference! to show the image
       lbl.place(x=0, y=0)

   except:
       print('error')
       lbl = Label(left, text="No image file selected !!!",
                   font=('Times', 25, 'italic', 'bold'),
                   fg='#03283a',
                   bg=color)
       lbl.place(x=200, y=270)
       # lbl.grid(row=5,column=10)

# =================================== Buttons ===================================

btnBrowse = Button(top, width=93,
                  text='select file',
                  fg='#58859a',
                  font=('Times', 15, 'italic', 'bold'),
                  bg='#03283a',
                  command = lambda :open_image(filename_path))
btnBrowse.pack(side=BOTTOM)

btnMask = Button(right, text='Image Mask',
                fg= '#58859a',
                font=('Times', 20, 'italic', 'bold'),
                bg="#03283a",
                width=19,
                height=6,
                command=lambda:Mask(filename_path) )
btnMask.pack(side=TOP)

btnMacula = Button(right, text='Macula',
                  fg= '#58859a',
                  font=('Times', 20, 'italic', 'bold'),
                  bg="#03283a",
                  width=19,
                  height=6,
                  command=lambda :Macula(filename_path))
btnMacula.pack(side=TOP)

btnClear = Button(right, text='exit',
                 fg= '#58859a',
                 font=('Times', 20, 'italic', 'bold'),
                 bg="#03283a",
                 width=19,
                 height=6,
                 command=root.quit)
btnClear.pack(side=TOP)

root.mainloop()


这可能吗?

这是绝对可能的这只是溶液的清洁度的问题

这里有4个主要选项,它们的复杂性越来越高:

  • 在开始大量工作之前,让
    Macula
    同步更新标签。这是通过
    root.update()
    等来完成的
  • 让按钮触发一个更新标签的回调,然后安排
    Makula
    在事件循环中很快运行。这将在(0,Makula)之后使用
  • 在单独的线程中运行复杂代码,并让主线程轮询完成
  • 分块运行繁重的代码,当长时间运行的代码与预期的代码只有一行时,这是不可能的。(我认为这比使用线程复杂得多,因为创建一个线程来运行一个函数是非常直接的,而且做轮询的代码非常通用)
  • 根据您对编程的舒适程度,您可能需要其中任何一种,因此我将对每一种进行概述

    第一种方法是长时间运行的函数只更新标签,然后在运行之前调用
    .update()
    将更新推送到GUI。所以代码看起来像这样:

    def Macula(file_path=None):
       # probably want to have a status label already existing
       # maybe create it here? makes cleaning it and deciding where to put it harder.
       status_label.config(text = "loading")
       root.update()
    
       # long running code here
       # for i in range(1000000):
       #     print("A", end="")
    
       # after function, clear label (or delete)
       status_label.config(text = "ready")
    
    这确实可行,但在函数运行时应用程序仍将挂起,GUI更新最终会与应用程序逻辑耦合,这在长期来看是不可取的

    更好的解决方案是更多地使用
    .after()
    来计划执行,而不是依赖
    .update()
    来执行同步更新,在这种情况下,您将拥有一个更新ui的功能:

    def handle_long_running_function(callback):
        # update label
        status_label.config(text="loading")
        # define a wrapper to run the long function and then update the label again
        def run_func():
            try:
                callback()
            finally:
                status_label.config(text="ready")
        # number here is millisecond delay, setting to 0 or 1 doesn't make much difference
        root.after(1,run_func) 
    
    def Macula(file_path=None):
       # long running code here
       for i in range(1000000):
           print("A", end="")
    
    
    button = tk.Button(root, text="run func", 
                  command=lambda: handle_long_running_function(Macula))
    
    通过这种方式,回调被调度为执行,而不是完全同步发生,这使事件循环有了更多的能力,尽可能尝试处理任何其他未完成的事件,尽管在与事件循环相同的线程中运行长代码仍然会导致程序在运行时挂起。这种将回调包装到处理函数中的方法还将逻辑与UI更新分离,这是需要的。它还提供了一个处理对StasuSLabel的同步更新的地方,可能允许您至少考虑竞争条件。

    注意:在使用
    .update()
    时,这种方法同样可以很容易地用于解耦更新,但我在这里尝试提供具有不同复杂性的不同解决方案


    为了使解决方案在昂贵的函数运行时不让GUI挂起,我们需要一种不同的方法,即不在与事件循环相同的线程上运行长函数

    假设我们希望我们的应用程序在没有多个线程的情况下工作,您可以想象通过使用
    yield
    或某种形式的
    wait
    来分解长执行代码,以指示应该暂停代码,让事件循环在恢复代码之前赶上,然后我们可以创建包装器或装饰器来处理此类函数的调度:

    import functools
    
    def tkinter_managed_long_running_task(gen_func):
        """wraps a generator function to pause execution at each yield and wait a bit
        before resuming, allowing the mainloop to update gui.
        the function can yield a number of milliseconds to delay until it resumes,
        otherwise the next chunk is run almost immidiately."""
        @functools.wraps(gen_func)
        def wrapper(*args, **kw):
            # call original function and schedule
            gen = gen_func()
            def run_next_chunk():
                try:
                    delay_time = next(gen)
                except StopIteration as e:
                    # could potentially resolve a Future here if we were doing asyncio stuff.
                    # this grabs the value in `return value` inside a generator,
                    # most cases this is not used for this kind of application
                    returned_value = e.value
                    return # no schedule
                else:
                    # hit a yield, will schedule next execution
                    if delay_time is None:
                        delay_time = 10 # 10 milliseconds is pretty small, but probably reasonable default?
                    root.after(delay_time, run_next_chunk)
            # run the first chunk immidiately
            run_next_chunk()
            
        return wrapper
    
    @tkinter_managed_long_running_task
    def Macula(file_path=None):
       # long running code here
       for i in range(1000000):
           yield 1 # have yields along with millisecond delay.
           print("A", end="")
    
    这使我们可以简单地添加
    yield
    语句(在本例中,指定在恢复之前的延迟,以毫秒为单位),并且它可以无缝地工作。当然,这只在长时间的执行时间可以分割的情况下起作用,但是长时间的任务是对库的单个调用,因此实际上没有任何地方可以插入这些类型的
    yield
    s来降低速度。在这种情况下,在GUI运行时维护GUI的唯一选项是在单独的线程上运行它

    使用线程的解决方案将使用上述技术创建一个新线程,在单独的线程中执行繁重的工作,然后使用
    tkinter\u managed\u long\u running\u task
    或其他技术定期检查主线程的完成情况,以便触发相应的UI更新

    在复杂性的这一点上,尽管您可能没有使用tkinter,因为它对于GUI应用程序来说相对较慢,或者您正在考虑“为什么要使用
    yield
    ,为什么不只实现适当的
    async
    等待
    函数与tkinter并行运行?”我高度怀疑是否有人真的这么想,但我不太清楚我在做什么,最终放弃了它,因为它太慢了,无法用它做任何有用的事情

    不管怎么说,这个答案已经变得很长很杂乱了。我希望这里有些东西是有用的。干杯:)

    看看。如本网站上的问题所示。