Python Forum
[Tkinter] tkinter freezes by clicking button
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] tkinter freezes by clicking button
#21
(Apr-02-2018, 12:58 PM)Zatox11 Wrote: making some kind of .exe file so that someone cand launch the program without using cmd or IDE
I think in windows, you only need to write a file with extension .pyw. On computers with python installed, clicking a .pyw file will launch the program without cmd window.
Reply
#22
Thanks, renaming the mainfuntion to .pyw works just fine. However I wanted to create an .exe file which runs on computers that don't have Python installed. I found "pyinstaller" and it basically did exactly what I wanted: I have one single .exe file which starts my program. Unfortunately the "solve" button is broken again: By clicking solve, another instance of tkinter is opened and it is stuck in an endless loop. It seems to be the exact same problem which you had already solved before. I didn't make any changes to the working code. When I run the .pyw fie, the problem doesn't occur, it's just the .exe file.
Reply
#23
(Apr-02-2018, 04:07 PM)Zatox11 Wrote: Unfortunately the "solve" button is broken again: By clicking solve, another instance of tkinter is opened and it is stuck in an endless loop.

I know very little of the issues of the multiprocessing module in windows, but you could try to call freeze_support() in your code
# SudokuSolver_main.py
from multiprocessing import freeze_support
 
if __name__ == '__main__':
    freeze_support()
    import SudokuSolver_class as Sudokuolver
    from tkinter import Tk
 
    def main():
        root = Tk()
        app = Sudokuolver.SudokuSolver(root)
        root.mainloop()
 
    main()
Reply
#24
Thank you very much. calling freeze_support() function prevents tkinter from being stuck in an endless loop when running the program via .exe file.
Reply
#25
*** UPDATE v2 ***

- .exe file working
- added backtrack counter
- added "stopwatch"

# SudokuSolver_solver.py

BACKTRACK_COUNTER = 0

def do_solve(sudoku):
    status = wsolve(sudoku)
    return status, sudoku, BACKTRACK_COUNTER

def consistent(matrix, row, col, value):
    for i in range(9):
        if matrix[row][i]==value: return False
        if matrix[i][col]==value: return False
    rowStart = row - row%3
    colStart = col - col%3
    for m in range(3):
        for k in range(3):
            if matrix[rowStart+k][colStart+m]==value: return False
    return True
 
class SolutionFound(Exception):
    pass
 
def wsolve(sudoku):
    try:
        solve(sudoku, 0)
    except SolutionFound:
        return True
    except StopSolving:
        return False
    return False
 
def solve(sudoku, num):
    global BACKTRACK_COUNTER
    BACKTRACK_COUNTER += 1
    if num==81:
        raise SolutionFound
        return True
    else:
        row = int(num / 9)
        col = num % 9
        if sudoku[row][col]!=' ':
            solve(sudoku, num+1)
        else:
            for value in range(1,10):
                if consistent(sudoku, row, col, str(value)):
                    sudoku[row][col] = str(value)
                    if solve(sudoku, num+1): return True
                sudoku[row][col]=' '
            return False
  
if __name__ == '__main__':
    printSUDOKU(sudoku)
    solve(sudoku, 0)
# SudokuSolver_class.py

import os
import SudokuSolver_solver as Solver
from random import randint
from tkinter import ttk
from tkinter import *
from tkcallasync import tk_call_async, MULTIPROCESSING


class SudokuSolver(Frame):

    sudoku = [[" " for col in range(9)] for row in range(9)]
    solution = [[" " for col in range(9)] for row in range(9)]
    exampleSudoku = [[' ',' ',' ','2','1',' ',' ',' ',' '],
                     [' ',' ','7','3',' ',' ',' ',' ',' '],
                     [' ','5','8',' ',' ',' ',' ',' ',' '],
                     ['4','3',' ',' ',' ',' ',' ',' ',' '],
                     ['2',' ',' ',' ',' ',' ',' ',' ','8'],
                     [' ',' ',' ',' ',' ',' ',' ','7','6'],
                     [' ',' ',' ',' ',' ',' ','2','5',' '],
                     [' ',' ',' ',' ',' ','7','3',' ',' '],
                     [' ',' ',' ',' ','9','8',' ',' ',' ']]

    def __init__(self, parent):
        Frame.__init__(self, parent, name='frame')
        self.parent = parent
        self.initUI()
        self.blankSUDOKU()
        self.sudokudir = os.path.join(os.getcwd(), 'sudokus')

    def initUI(self):
        self.message_txt = StringVar()
        self.time_txt = StringVar()
        self.seconds = 0
        self.minutes = 0
        self.time_txt.set('0{}:0{}'.format(self.minutes, self.seconds))
        self.timer_mode = 'stopped'
        self.disabled = False
        self.values = {}

        self.parent.title('Sudoku Solver')
        self.pack(fill=BOTH, expand=True)

        self.message = Label(self, textvariable=self.message_txt)
        self.message.grid(row=10,column=0,columnspan=8, sticky=E+W)

        self.timer = Label(self,textvariable=self.time_txt)
        self.timer.grid(row=10,column=8,sticky=E+W)

        self.solveBtn = Button(self, text="solve",width=6,command=self.execute_solver)
        self.solveBtn.grid(row=0,column=0)

        self.newBtn = Button(self, text="new",width=6,command=self.blankSUDOKU)
        self.newBtn.grid(row=0,column=1)

        self.initBtn = Button(self, text="init",width=6,command=self.initSUDOKU)
        self.initBtn.grid(row=0,column=2)

        self.startBtn = Button(self, text="start",width=6,command=self.start_timer)
        self.startBtn.grid(row=0,column=3)

        self.checkBtn = Button(self, text="check",width=6,command=self.checkSOLUTION)
        self.checkBtn.grid(row=0,column=6)

        self.saveBtn = Button(self, text="save",width=6,command=self.saveSUDOKU)
        self.saveBtn.grid(row=0,column=7)

        self.printBtn = Button(self, text="print",width=6,command=self.printALL)
        self.printBtn.grid(row=0,column=8)

        self.pbar = ttk.Progressbar(self, mode='indeterminate')
        self.pbar.grid(row=11,column=0, columnspan=9, sticky=W+E)

    # --- clear all fields and delete all values in sudoku[][] and solution[][] --- #
    def blankSUDOKU(self):
        self.message_txt.set('')
        self.reset_timer()
        for row in range(9):
            for col in range(9):
                self.sudoku[row][col] = ' '
                self.solution[row][col] = ' '
                self.numEntry = Entry(self, width=5, justify=CENTER,
                                 font="Helvetica 12 bold")
                self.values[row,col] = self.numEntry
                self.numEntry.grid(row=row+1,column=col, ipady=5)

    # ----- get a random Sudoku from textfiles ----- #
    def initSUDOKU(self):
        self.blankSUDOKU()
        self.message_txt.set('')
        self.reset_timer()
        Files = []
        valid = True
        for filename in os.listdir(self.sudokudir):
            if filename.startswith('sudoku') and filename.endswith('.txt'):
                Files.append(filename)
        L = len(Files)
        if L < 1:
            self.message_txt.set('No Sudokus saved, yet! Try this one')
            self.sudoku = self.copySUDOKU(self.exampleSudoku)
        else:
            # ----- fill sudoku array with values from textfile ----- #
            R = randint(0, L-1)
            sudokuFile = open(os.path.join(self.sudokudir,Files[R]))
            row, col = 0, 0
            for value in sudokuFile.read():
                if value.isdigit() or value==' ':
                    if value=='0': value = ' '      # zeros can also be used as blanks
                    self.sudoku[row][col] = value
                    col += 1
                    if col==9:
                        if row==8: break
                        row += 1
                        col = 0
            sudokuFile.close()
            valid = self.checkSUDOKU(self.sudoku)[0]
        if not valid:
            self.message_txt.set('%s does not contain a valid sudoku' % Files[R])
        else:
        # ----- make values from random sudoku appear in the GUI ----- #
            for row in range(9):
                for col in range(9):
                    value = self.sudoku[row][col]
                    if value!=' ':
                        self.values[row,col].insert(0,value)
        self.reset_message()

    def printSUDOKU(self, matrix):
        for row in range(9):
            print("|-----------------------------------|")
            for col in range(9):
                print("| " + matrix[row][col] + " ",end='')
            print("|")
        print("|-----------------------------------|")

    def printALL(self):
        print('Sudoku:')
        self.printSUDOKU(self.sudoku)
        print('Solution:')
        self.printSUDOKU(self.solution)

    def saveSUDOKU(self):
       CHECK = self.checkSUDOKU(self.solution)
       if CHECK[0] and CHECK[1]:    # check if Sudoku is valid and complete
           filecount = 1    # variable for how many sudoku files are already existing
           if not os.path.exists(self.sudokudir): os.makedirs(self.sudokudir)
           while os.path.isfile(self.path_to("sudoku%s.txt" % filecount)):
               filecount += 1
           sudokuFile = open(self.path_to("sudoku%s.txt" % filecount), 'w')
           solutionFile = open(self.path_to("solution%s.txt" % filecount), 'w')
           # ----- create textfiles for sudoku and the solution ----- #
           for row in range(9):
               for col in range(9):
                   sudokuFile.write(self.sudoku[row][col])
                   solutionFile.write(self.solution[row][col])
               sudokuFile.write('\n')
               solutionFile.write('\n')
           sudokuFile.close()
           solutionFile.close()
           self.message_txt.set('Files saved...')
       else:
           self.message_txt.set('Sudoku must be valid and complete!')
       self.reset_message()

    def copySUDOKU(self, matrix):
        matrix_copy = [[" " for col in range(9)] for row in range(9)]
        for row in range(9):
            for col in range(9):
                matrix_copy[row][col] = matrix[row][col]
        return matrix_copy

    # ----- check if user input makes sense -----#
    def checkSUDOKU(self, matrix):
        valid, complete, ctr = True, True, 0
        for row in range(9):
            for col in range(9):
                value = matrix[row][col]
                if value!=' ':
                    matrix[row][col]=' '
                    if not self.consistent(matrix, row, col, value):
                        valid, complete = False, True
                        return valid, complete, row, col
                    matrix[row][col] = value
                    ctr += 1
                else: complete = False
        if ctr<17: valid = False        # Sudoku must include at least 17 clues
        return valid, complete

    # ----- check if solution is valid ----- #
    def checkSOLUTION(self):
        self.getinput(self.solution)
        CHECK = self.checkSUDOKU(self.solution)
        if CHECK[0] and CHECK[1]: self.message_txt.set("Solution correct!")
        elif not CHECK[1]: self.message_txt.set("Sudoku is not complete!")
        elif not CHECK[0]: self.message_txt.set("Mistake at row %s, col %s"
                                      % (CHECK[2], CHECK[3]))
        self.reset_message()

    # ----- write user input into values{} ----- #
    def getinput(self, matrix):
        for row in range(9):
            for col in range(9):
                value = self.values[row,col].get()
                if value=='': matrix[row][col] = ' '
                elif len(value)!=1: matrix[row][col] = ' '
                elif not value.isdigit(): matrix[row][col] = ' '
                elif int(value)<1 or int(value)>9: matrix[row][col] = ' '
                else : matrix[row][col] = value

    # ----- check if this input leads to a valid solution ----- #
    def consistent(self, matrix, row, col, value):
        for i in range(9):
            if matrix[row][i]==value: return False
            if matrix[i][col]==value: return False
        rowStart = row - row%3
        colStart = col - col%3
        for m in range(3):
            for k in range(3):
                if matrix[rowStart+k][colStart+m]==value: return False
        return True

    def path_to(self, name):
        return os.path.join(self.sudokudir, name)

    def execute_solver(self):
        self.getinput(self.sudoku) # user input from GUI -> sudoku array
        if not self.checkSUDOKU(self.sudoku)[0]:    # check if user input makes sense
            self.message_txt.set("This sudoku does not have a solution")
            self.reset_message()
        else:
            if self.disabled:
                self.message_txt.set("warning, It's still calculating...")
                self.reset_message()
                return
        
            def callback(result):
                self.disabled = False
                status, self.solution, N = result
                self.pbar.stop()
                self.printSUDOKU(self.sudoku)
                self.printSUDOKU(self.solution)
                self.message_txt.set('Solution found! %s backtracks needed' % N if status else 'No Solution found!')
                self.fill_solution()
                self.reset_message(10000)

            self.disabled = True
            self.pbar.start(20)
            tk_call_async(self, Solver.do_solve, args=(self.sudoku,),
                      callback=callback, method=MULTIPROCESSING)

    def fill_solution(self):
        for row in range(9):
            for col in range(9):
                self.values[row,col].delete(0,END)
                self.values[row,col].insert(0,self.solution[row][col])

    def start_timer(self):
        if self.timer_mode == 'stopped':
            self.startBtn.configure(text='stop')
            self.timer_mode = 'running'
        elif self.timer_mode == 'running':
            self.startBtn.configure(text='start')
            self.timer_mode = 'stopped'

        def timer():
            if self.timer_mode == 'stopped': return
            if self.seconds == 60:
                self.seconds = 0
                self.minutes += 1
            if self.seconds > 9 and self.minutes > 9:
                self.time_txt.set('{}:{}'.format(self.minutes, self.seconds))
            elif self.seconds > 9 and self.minutes <= 9:
                self.time_txt.set('0{}:{}'.format(self.minutes, self.seconds))
            elif self.seconds <= 9 and self.minutes <= 9:
                self.time_txt.set('0{}:0{}'.format(self.minutes, self.seconds))
            elif self.seconds <= 9 and self.minutes > 9:
                self.time_txt.set('{}:0{}'.format(self.minutes, self.seconds))
            self.seconds += 1
            self.after(1000, timer)

        timer()

    def reset_timer(self):
        self.startBtn.configure(text='start')
        self.minutes = 0
        self.seconds = 0
        self.timer_mode = 'stopped'
        self.time_txt.set('0{}:0{}'.format(self.minutes, self.seconds))

    def reset_message(self,DELAY=5000):
        def reset():
            self.message_txt.set('')
        self.after(DELAY, reset)
# SudokuSolver_main.py

from multiprocessing import freeze_support

if __name__ == '__main__':
    freeze_support()
    import SudokuSolver_class as Sudokuolver
    from tkinter import Tk

    def main():
        root = Tk()
        app = Sudokuolver.SudokuSolver(root)
        root.mainloop()

    main()
Next:
- Button that stops backtracking process. I have already tried a few things, but none auf them worked
- Timecounter (how long did backtracking take)
Reply
#26
I tried your files with Atom. On ..._class_.py I commented line 8. Elsewhere I had to replace "Sudokuolvers" by "Sudokusolvers"; that twice. Now with "python ..._main.py" the whole works well.
Reply
#27
I put your 3 files in a folder named SUDO. I renamed your main file to "__main__.py"... So I run the whole project by typing : python SUDO
Reply
#28
@Gribouillis. You are very strong in python. Could you not help to finish this very beautiful project. I tried to find "tk_call_async" or tkcallasync",that python cannot import, but impossible.
Reply
#29
(Apr-08-2018, 09:32 AM)sylas Wrote: I tried to find "tk_call_async" or tkcallasync",that python cannot import, but impossible.
@sylas tk_call_async()is defined in this recipe from activestate.com. You need to download the code and save it in a file named tkcallasync.py
Reply
#30
I put "tkcallasync.py", 4th file, in my SUDO project. The top of ...class.py, lokks like:
# SudokuSolver_class.py

import os
import SudokuSolver_solver as Solver
from random import randint
from tkinter import ttk
from tkinter import *
#from tkcallasync import tk_call_async, MULTIPROCESSING #origin
import tkcallasync


class SudokuSolver(Frame):
The error message I get, is:
Error:
|-----------------------------------| | | | | | | | | | | |-----------------------------------| | | | | | | | | | | |-----------------------------------| | | | | | | | | | | |-----------------------------------| C:\Users\Sylvain λ python SUDO Exception in Tkinter callback Traceback (most recent call last): File "C:\Python35\lib\tkinter\__init__.py", line 1558, in __call__ return self.func(*args) File "SUDO\SudokuSolver_class.py", line 250, in execute_solver tk_call_async(self, Solver.do_solve, args=(self.sudoku), NameError: name 'tk_call_async' is not defined
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] TKinter Remove Button Frame Nu2Python 8 929 Jan-16-2024, 06:44 PM
Last Post: rob101
  tkinter - touchscreen, push the button like click the mouse John64 5 813 Jan-06-2024, 03:45 PM
Last Post: deanhystad
  Centering and adding a push button to a grid window, TKinter Edward_ 15 4,666 May-25-2023, 07:37 PM
Last Post: deanhystad
  [Tkinter] Clicking on the button crashes the TK window ODOshmockenberg 1 2,232 Mar-10-2022, 05:18 PM
Last Post: deanhystad
  Can't get tkinter button to change color based on changes in data dford 4 3,410 Feb-13-2022, 01:57 PM
Last Post: dford
  Creating a function interrupt button tkinter AnotherSam 2 5,503 Oct-07-2021, 02:56 PM
Last Post: AnotherSam
  [Tkinter] Have tkinter button toggle on and off a continuously running function AnotherSam 5 4,992 Oct-01-2021, 05:00 PM
Last Post: Yoriz
  tkinter showing image in button rwahdan 3 5,596 Jun-16-2021, 06:08 AM
Last Post: Yoriz
  tkinter button image Nick_tkinter 4 4,020 Mar-04-2021, 11:33 PM
Last Post: deanhystad
  tkinter python button position problem Nick_tkinter 3 3,554 Jan-31-2021, 05:15 AM
Last Post: deanhystad

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020