O JOE
O JOE

Reputation: 597

How to disable tkinter 'X' when function is running

I have these few lines of code here which i want to disable X button when function is running and set it back to normal state after the function has ended.

I can disable the X button when the function is running but want to set it to normal so that it can be closed when the function is has finished running.

import tkinter as tk
from tkinter import messagebox
import tkinter.ttk as ttk



def my_message_box(event=None):
    my_m = messagebox.askyesno("Close window", "Are you sure ?")
    if my_m > 0:
        root.destroy()


def do_nothing():
   # messagebox.showinfo("ASSS", "U can't close the window wait for the process to finish")
    pass


def run_range_func():
    for bn in range(1, 100):
        print(bn)
        rt = label.config(text=bn)
        label.after(1000, rt)

        root.protocol("WM_DELETE_WINDOW", do_nothing)


root = tk.Tk()
root.geometry("500x500")


button = tk.Button(root, text="START", command=run_range_func)
button.pack()


label = tk.Label(root, text="numbers to be displayed here")
label.place(x=200, y=200)

root.protocol("WM_DELETE_WINDOW", my_message_box)
root.mainloop()

Upvotes: 1

Views: 269

Answers (1)

Roland Smith
Roland Smith

Reputation: 43495

Whenever I have a long-running job in an Tkinter program, I divide it into three parts:

  1. a start callback, coupled to a START button or menu-item.
  2. a step callback, that does a piece of the work.
  3. a stop callback, coupled to a STOP button.

The start callback disables the START button with e.g. self.runbutton['state'] = tk.DISABLED. It also enables the stop button. It sets up the step callback using the after method, and sets a "running" state variable to True.

The step callback does a small piece of work, updates the state and re-submits itself with after as long as "running" is True.

The stop callback sets "running" to False, re-enables the start button and disables itself.

I'm embedding a complete example from one of my github repos below. It is a small find-and-replace program for files.

#!/usr/bin/env python3
# file: far.py
# vim:fileencoding=utf-8:fdm=marker:ft=python
#
# Copyright © 2018 R.F. Smith <[email protected]>.
# SPDX-License-Identifier: MIT
# Created: 2018-02-27T23:38:17+0100
# Last modified: 2018-04-17T00:11:57+0200

from tkinter import filedialog
from tkinter import ttk
from tkinter.font import nametofont
import argparse
import os
import shutil
import sys
import tkinter as tk

__version__ = '0.1'


class FarUI(tk.Tk):

    def __init__(self, rootdir='', findname='', replacement=''):
        tk.Tk.__init__(self, None)
        self.running = False
        self.finditer = None
        self.create_window()
        self.tree['text'] = rootdir
        self.find.insert(0, findname)
        self.replace['text'] = replacement

    def create_window(self):
        """Create the GUI"""
        # Set the font.
        default_font = nametofont("TkDefaultFont")
        default_font.configure(size=12)
        self.option_add("*Font", default_font)
        # General commands and bindings
        self.bind_all('q', self.quit_cb)
        self.wm_title('Find and Replace v' + __version__)
        self.columnconfigure(4, weight=1)
        self.rowconfigure(4, weight=1)
        # First row
        ftxt = ttk.Label(self, text='Find:')
        ftxt.grid(row=0, column=0, sticky='w')
        fe = ttk.Entry(self, justify='left')
        fe.grid(row=0, column=1, columnspan=4, sticky='ew')
        self.find = fe
        # Second row
        treetxt = ttk.Label(self, text='In tree:')
        treetxt.grid(row=1, column=0, sticky='w')
        te = ttk.Label(self, justify='left')
        te.grid(row=1, column=1, columnspan=4, sticky='ew')
        tb = ttk.Button(self, text="browse...", command=self.tree_cb)
        tb.grid(row=1, column=5, columnspan=2, sticky='ew')
        self.tree = te
        # Third row
        reptxt = ttk.Label(self, text='Replace with:')
        reptxt.grid(row=2, column=0, sticky='w')
        re = ttk.Label(self, justify='left')
        re.grid(row=2, column=1, columnspan=4, sticky='ew')
        rb = ttk.Button(self, text="browse...", command=self.replace_cb)
        rb.grid(row=2, column=5, columnspan=2, sticky='ew')
        self.replace = re
        # Fourth row
        run = ttk.Button(self, text="run", command=self.start_replace_cb)
        run.grid(row=3, column=0, sticky='ew')
        stop = ttk.Button(self, text="stop", command=self.stop_replace_cb, state=tk.DISABLED)
        stop.grid(row=3, column=1, sticky='w')
        self.runbutton = run
        self.stopbutton = stop
        qb = ttk.Button(self, text="quit", command=self.destroy)
        qb.grid(row=3, column=2, sticky='w')
        ttk.Label(self, justify='left', text='Progress: ').grid(row=3, column=3, sticky='w')
        progress = ttk.Label(self, justify='left', text='None')
        progress.grid(row=3, column=4, columnspan=2, sticky='ew')
        self.progress = progress
        # Fifth row
        message = tk.Text(self, height=4)
        message.grid(row=4, column=0, columnspan=6, sticky='nsew')
        s = ttk.Scrollbar(self, command=message.yview)
        s.grid(row=4, column=6, sticky='nse')
        message['yscrollcommand'] = s.set
        self.message = message

    def quit_cb(self, event):
        """
        Callback to handle quitting.

        This is necessary since the quit method does not take arguments.
        """
        self.running = False
        self.quit()

    def tree_cb(self):
        rootdir = filedialog.askdirectory(
            parent=self, title='Directory where to start looking', mustexist=True
        )
        self.tree['text'] = rootdir

    def replace_cb(self):
        replacement = filedialog.askopenfilename(parent=self, title='Replacement file')
        self.replace['text'] = replacement

    def start_replace_cb(self):
        rootdir = self.tree['text']
        filename = self.find.get()
        replacement = self.replace['text']
        if self.running or not rootdir or not filename or not replacement:
            self.message.delete('1.0', tk.END)
            self.message.insert(tk.END, 'Missing data!')
            return
        self.running = True
        self.message.delete('1.0', tk.END)
        self.message.insert(tk.END, 'Starting replacement\n')
        self.runbutton['state'] = tk.DISABLED
        self.stopbutton['state'] = tk.NORMAL
        self.finditer = os.walk(rootdir)
        self.after(1, self.replace_step)

    def replace_step(self):
        if not self.running:
            return
        try:
            path, _, files = self.finditer.send(None)
            rootlen = len(self.tree['text']) + 1
            # Skip known revision control systems directories.
            for skip in ('.git', '.hg', '.svn', '.cvs', '.rcs'):
                if skip in path:
                    self.progress['text'] = 'skipping ' + path[rootlen:]
                    return
            if len(path) > rootlen and path[rootlen] != '.':
                self.progress['text'] = 'processing ' + path[rootlen:]
                filename = self.find.get()
                if filename in files:
                    original = path + os.sep + filename
                    replacement = self.replace['text']
                    repfile = os.path.basename(replacement)
                    dest = path + os.sep + repfile
                    self.message.insert(tk.END, "Removing '{}'\n".format(original))
                    os.remove(original)
                    self.message.insert(tk.END, "Copying '{}' to '{}'\n".format(replacement, dest))
                    shutil.copy2(replacement, dest)
            self.after(1, self.replace_step)
        except StopIteration:
            self.stop()
            self.message.insert(tk.END, 'Finished replacement.\n')

    def stop(self):
        self.running = False
        self.finditer = None
        self.runbutton['state'] = tk.NORMAL
        self.stopbutton['state'] = tk.DISABLED
        self.progress['text'] = 'None'

    def stop_replace_cb(self):
        self.stop()
        self.message.insert(tk.END, 'Replacement stopped by user.\n')


def main():
    """Main entry point for far.py"""
    # Parse the arguments.
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        '-d', '--rootdir', type=str, default=os.getcwd(), help='Directory to start looking in.'
    )
    parser.add_argument('-f', '--findname', type=str, default='', help='Name of the file to find.')
    parser.add_argument(
        '-r', '--replacement', type=str, default='', help='Path of the replacement file.'
    )
    parser.add_argument('-v', '--version', action='version', version=__version__)
    args = parser.parse_args(sys.argv[1:])
    if not args.rootdir.startswith(os.sep):
        args.rootdir = os.getcwd() + os.sep + args.rootdir
    # Create the UI.
    root = FarUI(args.rootdir, args.findname, args.replacement)
    root.mainloop()


if __name__ == '__main__':
    # Detach from the terminal on POSIX systems.
    if os.name == 'posix':
        if os.fork():
            sys.exit()
    # Run the program.
    main()

Upvotes: 1

Related Questions