Tkinter polling example: file copy with progress bar

Discussion in 'Python' started by JohnWShipman, Dec 12, 2010.

  1. JohnWShipman

    JohnWShipman Guest

    Attached below is a Tkinter script that demonstrates polling, that is,
    performing a long-running process in parallel with the GUI. The
    script asks for an input file name and an output file name and copies
    the input file to the output file. The copy operation is done in a
    child process managed with pexpect, and the GUI reports the progress
    of the file copy using a Scale widget as a progress bar.

    Cordially,
    John W. Shipman, NM Tech Computer Center, Socorro, NM;
    ================
    #!/usr/bin/env python
    #================================================================
    # copyprogress: File copy with a progress bar for Tkinter 8.4.
    # - Demonstrates Tkinter .after() and the pexpect module.
    # Written by John W. Shipman (), New Mexico Tech
    # Computer Center, Socorro, NM 87801 USA. This script is in
    # the public domain.
    #----------------------------------------------------------------

    # - - - - - I m p o r t s

    import sys, os, stat
    import Tkinter as tk
    import tkFileDialog, tkMessageBox
    import pexpect

    # - - - - - M a n i f e s t c o n s t a n t s

    BUTTON_FONT = ("Helvetica", 17)
    LABEL_FONT = ("Helvetica", 14)
    ENTRY_FONT = ("DejaVu Sans Mono", 12)
    POLL_TIME = 50 # Polling frequency in milliseconds


    # - - - - - m a i n

    def main():
    """
    """
    app = App()
    app.master.title("Copy with progress bar")
    app.mainloop()

    # - - - - - c l a s s A p p

    class App(tk.Frame):
    '''Copies a file with a progress bar.

    Widgets:
    .fromFileVar: StringVar for source file name
    .fromFileEntry: Entry for source file name
    .fromFileBrowse: Browse button for source file name
    .fromFileLabel: Label for above
    .toFileVar: StringVar for destination file name
    .toFileEntry: Entry for destination file name
    .toFileBrowse: Browse button for destination file name
    .toFileLabel: Label for above
    .copyButton: Button to start copying
    .progressVar: DoubleVar for progress scale
    .progressScale: Scale to show progress

    Grid plan:
    0 1 2
    +----------------+-----------------+----------------+
    0 | .fromFileEntry | .fromFileBrowse | .fromFileLabel |
    +----------------+-----------------+----------------+
    1 | .toFileEntry | .toFileBrowse | .toFileLabel |
    +----------------+-----------------+----------------+
    2 | .progress | .copyButton | .quitButton |
    +----------------+-----------------+----------------+

    Internal state:
    .fromFileSize: Source file size in bytes
    .child: pexpect child process to do the copy
    '''

    # - - - A p p . _ _ i n i t _ _

    def __init__(self, master=None):
    tk.Frame.__init__(self, master)
    self.grid()
    self.__createWidgets()

    # - - - A p p . _ _ c r e a t e w i d g e t s

    def __createWidgets(self):
    '''Create all widgets and associated variables.
    '''
    self.fromFileVar = tk.StringVar()
    self.fromFileEntry = tk.Entry ( self,
    textvariable=self.fromFileVar,
    font=ENTRY_FONT, width=50 )
    rowx, colx = 0, 0
    self.fromFileEntry.grid(row=rowx, column=colx, sticky=tk.E)

    self.fromFileBrowse = tk.Button ( self,
    command=self.__browseFrom,
    font=BUTTON_FONT, text="Browse" )
    colx += 1
    self.fromFileBrowse.grid(row=rowx, column=colx)

    self.fromFileLabel = tk.Label ( self,
    font=LABEL_FONT, text="Source file" )
    colx += 1
    self.fromFileLabel.grid(row=rowx, column=colx, sticky=tk.W)

    self.toFileVar = tk.StringVar()
    self.toFileEntry = tk.Entry ( self,
    textvariable=self.toFileVar,
    font=ENTRY_FONT, width=50 )
    rowx, colx = rowx+1, 0
    self.toFileEntry.grid(row=rowx, column=colx, sticky=tk.E)

    self.toFileBrowse = tk.Button ( self,
    command=self.__browseTo,
    font=BUTTON_FONT, text="Browse" )
    colx += 1
    self.toFileBrowse.grid(row=rowx, column=colx)

    self.toFileLabel = tk.Label ( self,
    font=LABEL_FONT, text="Destination file")
    colx += 1
    self.toFileLabel.grid(row=rowx, column=colx, sticky=tk.W)

    self.progressVar = tk.DoubleVar()
    self.progressScale = tk.Scale ( self,
    length=400, orient=tk.HORIZONTAL,
    from_=0.0, to=100.0, resolution=0.1, tickinterval=20.0,
    variable=self.progressVar,
    label="Percent completion", font=LABEL_FONT )
    rowx, colx = rowx+1, 0
    self.progressScale.grid(row=rowx, column=colx, sticky=tk.E)

    self.copyButton = tk.Button ( self,
    command=self.__copyHandler,
    font=BUTTON_FONT, text="Copy" )
    colx += 1
    self.copyButton.grid(row=rowx, column=colx )

    self.quitButton = tk.Button ( self, command=self.quit,
    font=BUTTON_FONT, text="Quit" )
    colx += 1
    self.quitButton.grid(row=rowx, column=colx, sticky=tk.W)


    # - - - A p p . _ _ b r o w s e F r o m

    def __browseFrom(self):
    '''Handler for Browse button for the source file.
    '''
    # [ if the user enters an existing file name in a popup ->
    # self.fromFileVar := that name
    # else ->
    # f := an empty string ]

    f = tkFileDialog.askopenfilename(title="Source file name")
    if len(f) == 0:
    return
    else:
    self.fromFileVar.set(f)


    # - - - A p p . _ _ b r o w s e T o

    def __browseTo(self):
    '''Handler for Browse button for the source file.
    '''
    # [ if the user enters a nonexistent existing file name in
    # a popup, or enters an existing name and then says it's
    # okay to overwrite it ->
    # self.toFileVar := that name
    # else ->
    # f := an empty string ]
    f = tkFileDialog.asksaveasfilename(title="Destination file
    name")
    if len(f) == 0:
    return
    else:
    self.toFileVar.set(f)

    # - - - A p p . _ _ c o p y H a n d l e r

    def __copyHandler(self):
    '''Start the file copy process.
    '''
    # [ if the source file name is empty ->
    # display a popup error message
    # return
    # else -> I ]
    if len(self.fromFileVar.get()) == 0:
    tkMessageBox.showerror("Error",
    "Please enter a source file name." )
    return

    # [ if the destination file name is empty ->
    # show an error popup
    # return
    # else if the destination file exists and the user's reply
    # to a popup indicates they do not want to proceed ->
    # return
    # else -> I ]
    toFileName = self.toFileVar.get()
    if len(toFileName) == 0:
    tkMessageBox.showerror("Error",
    "Please enter a destination file name." )
    return
    elif os.path.exists(toFileName):
    message = ( "File '%s' exists.\nDo you want to overwrite "
    "it?" % toFileName )
    answer = tkMessageBox.askokcancel("Destination file
    exists",
    message, default=tkMessageBox.CANCEL)
    if not answer:
    return

    # [ if the source file exists ->
    # self.fromFileSize := that file's size in bytes
    # else ->
    # display a popup and return ]
    if not self.__copySetup():
    return

    # [ self.child := a pexpect.spawn child process that copies
    # the source file to the destination file with -f
    # self := self with a callback to self.__poll after
    # POLL_TIME ]
    self.__startCopy()

    # - - - A p p . _ _ c o p y S e t u p

    def __copySetup(self):
    '''Operations done before the copy is started.

    [ if the source file exists ->
    self.fromFileSize := that file's size in bytes
    return True
    else ->
    display an error popup
    return False ]
    '''
    fromFileName = self.fromFileVar.get()
    try:
    self.fromFileSize = self.__measureFile(fromFileName)
    except OSError, details:
    tkMessageBox.showerror ( "Source file error",
    "File %s: %s" % (fromFileName, str(details)) )
    return False

    return True

    # - - - A p p . _ _ s t a r t C o p y

    def __startCopy ( self ):
    '''Start up a file copy operation.

    [ (self.fromFileVar contains the name of a readable file)
    and
    (self.toFileVar contains the name of a writeable file) ->
    self.child := a pexpect.spawn child process that
    copies
    the source file to the destination file with -f
    self := self with a callback to self.__poll after
    POLL_TIME ]
    '''
    # [ command := a copy command from the source file to the
    # destination file, with a force option ]
    command = ( "cp -f %s %s" %
    (self.fromFileVar.get(), self.toFileVar.get()) )

    # [ self.progressVar := 0
    # self := self with a callback after POLL_TIME to
    # self.__poll ]
    self.progressVar.set(0.0)
    self.after(POLL_TIME, self.__poll)

    # [ self.child := a pexpect.spawn process to run command ]
    self.child = pexpect.spawn(command)


    # - - - A p p . _ _ m e a s u r e F i l e

    def __measureFile(self, fileName):
    '''Determine the current length of a file, if it exists.

    [ if fileName can be statted ->
    return the current length of that file
    else -> raise OSError ]
    '''
    status = os.stat ( fileName )
    return status[stat.ST_SIZE]

    # - - - A p p . _ _ p o l l

    def __poll(self):
    '''Periodic check of the copy progress.

    [ if self.child has terminated ->
    self.progressVar := 100.0
    self.child := (closed)
    show a status popup
    else if we can stat the output file ->
    self.progressVar := (destination file size /
    self.fromFileSize) as a percentage
    self := self with a callback to self.__poll after
    POLL_TIME ]
    '''
    # [ if self.child has terminated ->
    # self.progressVar := 100.0
    # return
    # else -> I ]
    if not self.child.isalive():
    self.child.close()
    self.progressVar.set(100.0)
    tkMessageBox.showinfo ( "Success",
    "File %s has been copied to %s, size %s." %
    (self.fromFileVar.get(), self.toFileVar.get(),
    self.fromFileSize) )
    return

    # [ if we can stat the output file ->
    # outFileSize := its size in bytes
    # else ->
    # display an error popup
    # return ]
    toFileName = self.toFileVar.get()
    try:
    toFileSize = self.__measureFile ( toFileName )
    except OSError, details:
    tkMessageBox.showerror ( "Destination file error",
    "File %s: %s" % (toFileName, str(details)) )
    return

    # [ self.progressVar := toFileSize / self.fromFileSize
    # as a percentage
    # self := self with a callback to self.__poll after
    # POLL_TIME ]
    self.progressVar.set ( 100.0 * float(toFileSize) /
    float(self.fromFileSize) )
    self.after(POLL_TIME, self.__poll)



    # - - - - - E p i l o g u e

    if __name__ == "__main__":
    main()
     
    JohnWShipman, Dec 12, 2010
    #1
    1. Advertising

  2. JohnWShipman

    baloan Guest

    Unfortunately you use command('cp...') to copy the file instead of
    Pythons portable library methods. This choice
    effectively makes your program work on Unix only (not Windows).

    See http://modcopy.sourceforge.net for a more portable version.

    Regards,
     
    baloan, Dec 14, 2010
    #2
    1. Advertising

  3. On Tue, 14 Dec 2010 07:35:45 -0800 (PST)
    baloan <> wrote:
    > Unfortunately you use command('cp...') to copy the file instead of
    > Pythons portable library methods. This choice
    > effectively makes your program work on Unix only (not Windows).
    >
    > See http://modcopy.sourceforge.net for a more portable version.


    I guess I missed the beginning of this thread but can someone tell me
    why one needs to download a whole other program in order to do this?

    open(out_fn, 'w').write(open(in_fn).read())

    --
    D'Arcy J.M. Cain <> | Democracy is three wolves
    http://www.druid.net/darcy/ | and a sheep voting on
    +1 416 425 1212 (DoD#0082) (eNTP) | what's for dinner.
     
    D'Arcy J.M. Cain, Dec 14, 2010
    #3
  4. JohnWShipman

    Harishankar Guest

    On Tue, 14 Dec 2010 10:57:40 -0500, D'Arcy J.M. Cain wrote:
    > I guess I missed the beginning of this thread but can someone tell me
    > why one needs to download a whole other program in order to do this?
    >
    > open(out_fn, 'w').write(open(in_fn).read())


    Or what about shutil? Isn't that the higher level file operation module?

    --
    Harishankar (http://harishankar.org http://lawstudentscommunity.com)
     
    Harishankar, Dec 14, 2010
    #4
  5. On Tue, 14 Dec 2010 16:25:54 +0000 (UTC)
    Harishankar <> wrote:
    > On Tue, 14 Dec 2010 10:57:40 -0500, D'Arcy J.M. Cain wrote:
    > > open(out_fn, 'w').write(open(in_fn).read())

    > Or what about shutil? Isn't that the higher level file operation module?


    At least that's in the standard library but even then it can be
    overkill for a simple copy. It does do some error checking that the
    above doesn't do if you need that.

    --
    D'Arcy J.M. Cain <> | Democracy is three wolves
    http://www.druid.net/darcy/ | and a sheep voting on
    +1 416 425 1212 (DoD#0082) (eNTP) | what's for dinner.
     
    D'Arcy J.M. Cain, Dec 14, 2010
    #5
  6. JohnWShipman

    JohnWShipman Guest

    On Dec 14, 8:57 am, "D'Arcy J.M. Cain" <> wrote:
    > On Tue, 14 Dec 2010 07:35:45 -0800 (PST)
    >
    > baloan <> wrote:
    > > Unfortunately you use command('cp...') to copy the file instead of
    > > Pythons portable library methods. This choice
    > > effectively makes your program work on Unix only (not Windows).

    >
    > > Seehttp://modcopy.sourceforge.netfor a more portable version.

    >
    > I guess I missed the beginning of this thread but can someone tell me
    > why one needs to download a whole other program in order to do this?
    >
    >   open(out_fn, 'w').write(open(in_fn).read())


    I posted this example because I got several queries on how to do
    polling in Tkinter, specifically how to use the .after() universal
    widget method. The points about using the portable library methods
    are all well taken. I used file copy as the example long-running
    process because a reader wanted to know how to do that specifically.
    Please forgive me for not thinking about portability and stuff; you
    know how us ancient Unix weenies are.
     
    JohnWShipman, Dec 15, 2010
    #6
  7. JohnWShipman

    JohnWShipman Guest

    On Dec 14, 8:57 am, "D'Arcy J.M. Cain" <> wrote:
    > On Tue, 14 Dec 2010 07:35:45 -0800 (PST)
    >
    > baloan <> wrote:
    > > Unfortunately you use command('cp...') to copy the file instead of
    > > Pythons portable library methods. This choice
    > > effectively makes your program work on Unix only (not Windows).

    >
    > > Seehttp://modcopy.sourceforge.netfor a more portable version.

    >
    > I guess I missed the beginning of this thread but can someone tell me
    > why one needs to download a whole other program in order to do this?
    >
    >   open(out_fn, 'w').write(open(in_fn).read())


    I posted this example because I got several queries on how to do
    polling in Tkinter, specifically how to use the .after() universal
    widget method. The points about using the portable library methods
    are all well taken. I used file copy as the example long-running
    process because a reader wanted to know how to do that specifically.
    Please forgive me for not thinking about portability and stuff; you
    know how us ancient Unix weenies are.
     
    JohnWShipman, Dec 15, 2010
    #7
  8. JohnWShipman

    Steve Holden Guest

    Steve Holden, Dec 15, 2010
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Charlie Zhang
    Replies:
    3
    Views:
    1,318
    Paul Lutus
    Aug 16, 2004
  2. benjamin schollnick

    Need a Progress Bar/Meter for Tkinter

    benjamin schollnick, Mar 4, 2004, in forum: Python
    Replies:
    4
    Views:
    9,656
    benjamin schollnick
    Mar 5, 2004
  3. Gurpreet Singh

    Simple Tkinter Progress Bar

    Gurpreet Singh, May 11, 2007, in forum: Python
    Replies:
    0
    Views:
    671
    Gurpreet Singh
    May 11, 2007
  4. Stefan Sonnenberg-Carstens

    Re: Simple Tkinter Progress Bar

    Stefan Sonnenberg-Carstens, May 11, 2007, in forum: Python
    Replies:
    0
    Views:
    699
    Stefan Sonnenberg-Carstens
    May 11, 2007
  5. Rob

    progress bar or guage bar

    Rob, Jul 11, 2003, in forum: ASP General
    Replies:
    6
    Views:
    218
    Chris Barber
    Jul 12, 2003
Loading...

Share This Page