Не могу получить ttk.Progressbar для правильного запуска - PullRequest
0 голосов
/ 30 января 2019

Я не могу заставить работать виджет ttk.Progressbar.Могу ли я узнать, в чем проблема и как я могу ее исправить?

Я знаю, что виджет Progressbar работает;когда я закомментирую self.sp_pbar.stop(), индикатор выполнения запускается, но это происходит после завершения time.sleep(5), что не является желаемым поведением.

import tkinter as tk
import tkinter.ttk as ttk
import time

class App(ttk.Frame):


    def __init__( self, master=None, *args, **kw ):

        super().__init__( master,style='App.TFrame')

        self.master = master
        self.espconnecting = False

        self._set_style()
        self._create_widgets()


    def _set_style( self ):
        print( '\ndef _set_style( self ):' )
        self.style = ttk.Style()
        self.style.configure( 'App.TFrame',  background='pink')
        self.style.configure( 'sp.TFrame',  background='light green')


    def _create_widgets( self ):
        print( '\ndef _create_widgets( self ):' )
        self.sp_frame = ttk.Frame( self, style='sp.TFrame' )
        self.sp_frame.grid(row=0, column=0)

        #self.sp_frame widgets
        self.sp_label1 = ttk.Label( self.sp_frame, text='SP(s):')
        self.sp_label2 = ttk.Label( self.sp_frame, text='ESP(s):')
        self.sp_label3 = ttk.Label( self.sp_frame, )

        self.sp_combox = ttk.Combobox( self.sp_frame, state="readonly",
                                       values=['a','b','c']  )
        self.sp_combox.bind('<<ComboboxSelected>>', self._connect_esp)

        self.sp_pbar = ttk.Progressbar( self.sp_frame, length=200,
                                        mode='indeterminate',
                                        orient=tk.HORIZONTAL, )

        self.sp_label1.grid( row=0, column=0 )
        self.sp_combox.grid( row=0, column=1, padx=[10,0] )
        self.sp_pbar.grid(   row=1, column=0, columnspan=2, sticky='ew' )
        self.sp_label2.grid( row=2, column=0)
        self.sp_label3.grid( row=2, column=1)


    def _connect_esp( self, event=None):
        print( '\ndef connect_esp( self, event=None ):' )
        self._show_conn_progress()
        print("START Connection")
        time.sleep(5) # The code is running a function here which can take some time.  
        print("END Connection")
        self.espconnecting = False


    def _show_conn_progress( self ):
        print( '\ndef _show_conn_progress( self ):' )
        self.espconnecting = True
        self.sp_label3['text']='Connecting.....'
        self.sp_label3.update_idletasks()
        self.sp_pbar.start()
        self._update_conn_progress()


    def _update_conn_progress( self ):
        print( '\ndef _update_conn_progress( self ):' )
        if not self.espconnecting:
            print('connected')
            self.sp_pbar.stop()
            self.sp_label3['text']='Connected'
        else:
            print('connecting')
            self.sp_pbar.update_idletasks()
            self.after(500, self._update_conn_progress) # Call this method after 500 ms.


def main():
    root = tk.Tk()
    root.geometry('300x100+0+24')
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)

    app = App( root )
    app.grid(row=0, column=0, sticky='nsew')

    root.mainloop()

if __name__ == '__main__':
    main()

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Метод tkinter .after() нельзя использовать для реализации неопределенного виджета ttk.Progressbar() одновременно с другим текущим процессом.Это связано с тем, что текущий процесс, моделируемый методом time.sleep (5), останавливает приложение tkinter от выдачи другого процесса.Во время остановки даже метод .after() не может работать, несмотря на то, что у него очень короткий интервал ожидания.

Как упоминалось в комментариях @Lukas и в ссылках, которыми он поделился, подход к реализации неопределенного ttk.Progressbar() одновременное выполнение с другим процессом приложения означает использование thread.daemon из модуля threading Python для управления параллелизмом.

В качестве альтернативы инфраструктура Python asyncio может использоваться для реализации неопределенного ttk.Progressbar() работает одновременно с другим процессом приложения.Я недавно исследовал эту возможность .Предостережение в этом подходе состоит в том, что «процесс остановки», а также активация и завершение ttk.Progressbar должны быть записаны в отдельных сопрограммах .

Ниже приведен мой сценарий, показывающий, какреализовать asyncio с tkinter 8.6 и его ttk.Progressbar() виджетом в Python 3.6.

import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox as tkMessageBox

import asyncio

INTERVAL = 0.05 #seconds

class App(ttk.Frame):


    def __init__( self, master, loop, interval=0.05, *args, **kw ):
        super().__init__( master,style='App.TFrame')
        self.master = master
        self.loop = loop
        self._set_style()
        self._create_widgets()


    def _set_style( self ):
        self.style = ttk.Style()
        self.style.configure( 'App.TFrame',  background='pink')
        self.style.configure( 'sp.TFrame',  background='light green')


    def _create_widgets( self ):
        self.sp_frame = ttk.Frame( self, style='sp.TFrame' )
        self.sp_frame.grid(row=0, column=0)

        #sp_frame widgets
        self.sp_label1 = ttk.Label( self.sp_frame, text='SP(s):')
        self.sp_combox = ttk.Combobox(
            self.sp_frame, state="readonly", values=['a','b','c']  )
        self.sp_combox.bind('<<ComboboxSelected>>', self._connect_esp)
        self.sp_pbar = ttk.Progressbar( self.sp_frame, length=200,
                                        mode='indeterminate',
                                        orient=tk.HORIZONTAL, )
        self.sp_label1.grid( row=0, column=0 )
        self.sp_combox.grid( row=0, column=1, padx=[10,0] )
        self.sp_pbar.grid(   row=1, column=0, columnspan=2, sticky='ew' )


    def _connect_esp( self, event):

        async def dojob( loop, start_time, duration=1 ):
            print( '\nasync def dojob( loop, end_time):' )
            while True:
                duration = 3 #seconds
                t = loop.time()
                delta = t - start_time
                print( 'wait time = {}'.format( delta ) )
                if delta >= duration:
                    break
                await asyncio.sleep( 1 )

        async def trackjob( loop ):
            print( '\nasync def trackjob( loop ):' )
            start_time = loop.time()
            self.sp_pbar.start( 50 )
            self.sp_pbar.update_idletasks()
            print( 'Job: STARTED' ) 
            result = await dojob( loop, start_time )
            print( 'result = ', result, type(result) )
            print( 'Job: ENDED' ) 
            self.sp_pbar.stop()
            self.sp_pbar.update_idletasks()

        try:
            task = self.loop.create_task( trackjob( self.loop ) )
            print( 'task = ', task, type(task))
        except Exception:
            raise


async def tk_update( root, interval=INTERVAL ):
    print( '\nasync def tk_update( interval ):' )
    try:
        while True:
            root.update() #tk update 
            await asyncio.sleep( interval )
    except tk.TclError as err:
        if "application has been destroyed" not in err.args[0]:
            raise


def ask_quit( root, loop, interval=INTERVAL ):
    '''Confirmation to quit application.'''
    if tkMessageBox.askokcancel( "Quit","Quit?" ):
        root.update_task.cancel() #Cancel asyncio task to update Tk()
        root.destroy() #Destroy the Tk Window instance.
        loop.stop() # Stop asyncio loop. This is needed before a run_forever type loop can be closed.


def main():
    loop = asyncio.get_event_loop()

    root = tk.Tk()
    root.geometry('300x100+0+24')
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)
    root.update_task = loop.create_task( tk_update( root ) ) 

    app = App( root, loop )
    app.grid(row=0, column=0, sticky='nsew')
    #root.mainloop() #DO NOT IMPLEMENT; this is replaced by running
                     # tk's update() method in a asyncio loop called loop.
                     # See tk_update() method and root.update_task.

    #Tell Tk window instance what to do before it is destroyed.
    root.protocol("WM_DELETE_WINDOW",
                  lambda :ask_quit( root, loop ) ) 

    try:
        print('start loop.run_forever()')
        loop.run_forever()
    finally:
        loop.run_until_complete( loop.shutdown_asyncgens() )
        loop.close()


if __name__ == '__main__':
    main()

С точки зрения макроса, кажется, что реализация tkinter в цикле событий Python asyncio может облегчить разработку лучших параллельных приложений с графическим интерфейсом.Я обнаружил это сам и надеюсь, что прилагаемый скрипт может помочь другим пользователям tkinter в обучении.

0 голосов
/ 30 января 2019

Это то, что вы в настоящее время имеете в своем коде:

, который вы установили self.espconnecting = False

, на который вы звоните _connect_esp()

, который вызывает _show_conn_progress()

, который устанавливает self.espconnecting = True и запускает индикатор выполнения self.sp_pbar.start()

, а затем вызывает _update_conn_progress()

, который проверяет значение self.espconnecting.Если self.espconnecting равно True (что на данный момент), соединение продолжается и индикатор выполнения продолжает катиться, как и ожидалось.Если self.espconnecting равен False, индикатор выполнения останавливается self.sp_pbar.stop()

Прежде чем .after() сможет выполнить обратный вызов за 500 мс, управление передается обратно в _connect_esp, который устанавливает self.espconnecting = False.Затем .after() вызывает _update_conn_progress(), что означает, что планка должна двигаться,

, но (вот ваша проблема): каково последнее значение self.espconnecting?=False следовательно, управление переходит к self.sp_pbar.stop(), что останавливает полосу прогресса.Вот почему, когда вы комментируете эту строку, ваш код работает должным образом, потому что, даже если там есть ответвление элемента управления, ничто не помешает работе индикатора выполнения.

SOLUTION

Не устанавливайте self.espconnecting = False в _connect_esp(), потому что до того, как .after() сделает обратный вызов за 500 мс, управление будет передано обратно в _connect_esp(), который устанавливает self.espconnecting = False, что препятствует работе вашего индикатора выполнения.

Это означает, что вам нужно найти другое средство, чтобы «завершить соединение», как только оно начнется.

Примечание: Я действительно не вижу необходимости в time.sleep(5) вкод.

Вот один из возможных способов его решения:

...
def __init__( self, master=None, *args, **kw ):

    super().__init__( master,style='App.TFrame')

    self.master = master
    self.espconnecting = False
    self.count=0

    self._set_style()
    self._create_widgets()


def _set_style( self ):
    print( '\ndef _set_style( self ):' )
    self.style = ttk.Style()
    self.style.configure( 'App.TFrame',  background='pink')
    self.style.configure( 'sp.TFrame',  background='light green')


def _create_widgets( self ):
    print( '\ndef _create_widgets( self ):' )
    self.sp_frame = ttk.Frame( self, style='sp.TFrame' )
    self.sp_frame.grid(row=0, column=0)

    #self.sp_frame widgets
    self.sp_label1 = ttk.Label( self.sp_frame, text='SP(s):')
    self.sp_label2 = ttk.Label( self.sp_frame, text='ESP(s):')
    self.sp_label3 = ttk.Label( self.sp_frame, )

    self.sp_combox = ttk.Combobox( self.sp_frame, state="readonly",
                                   values=['a','b','c']  )
    self.sp_combox.bind('<<ComboboxSelected>>', self._connect_esp)

    self.sp_pbar = ttk.Progressbar( self.sp_frame, length=200,
                                    mode='indeterminate',
                                    orient=tk.HORIZONTAL, )

    self.sp_label1.grid( row=0, column=0 )
    self.sp_combox.grid( row=0, column=1, padx=[10,0] )
    self.sp_pbar.grid(   row=1, column=0, columnspan=2, sticky='ew' )
    self.sp_label2.grid( row=2, column=0)
    self.sp_label3.grid( row=2, column=1)


def _connect_esp( self, event=None):
    print( '\ndef connect_esp( self, event=None ):' )
    self._show_conn_progress()
    print("START Connection")
    time.sleep(5)

def end_connection(self):
    print("END Connection")
    self.espconnecting = False


def _show_conn_progress( self ):
    print( '\ndef _show_conn_progress( self ):' )
    self.espconnecting = True
    self.sp_label3['text']='Connecting.....'
    self.sp_label3.update_idletasks()
    self.sp_pbar.start()
    self._update_conn_progress()


def _update_conn_progress( self ):
    print( '\ndef _update_conn_progress( self ):' )
    if not self.espconnecting:
        print('connected')
        self.sp_pbar.stop()
        self.sp_label3['text']='Connected'
    else:
        print('connecting')
        #self.sp_pbar.update_idletasks()
        self.after(500, self._update_conn_progress) # Call this method after 500 ms.
        self.count=self.count + 1
        if self.count==10:
            self.end_connection()


def main():
    root = tk.Tk()
    root.geometry('300x100+0+24')
    root.rowconfigure(0, weight=1)
    root.columnconfigure(0, weight=1)

app = App( root )
app.grid(row=0, column=0, sticky='nsew')

root.mainloop()

if __name__ == '__main__':
    main()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...