0

I have the following problem with this easy script:

from Tkinter import *
root = Tk()
while 1:
   pass

I think, after the 2nd line everyone would expect a Tkinter window would appear. But it does not! If I put this line into the Python console (without the endless-while-loop), it works.

[I wanted to add an image here, but as I'm new I'm to allowed to :-(]

But running the script (double-clicking on the *.py file in the Windows Explorer) results only in an empty Python console!

Background: Actually I want to use Snack for Python. This is based on Tkinter. That means I have to create an Tk() instance first. Everything works fine in the Python console. But I want to write a bigger program with at least one Python script thus I cannot type the whole program into the console everytime :-)

I have installed Python 2.7 and Tcl/Tk 8.5 (remember: it works in the console)


EDIT: So here's my solution:

First, I create a class CSoundPlayer:

from Tkinter import*
import tkSnack

class CSoundPlayer:
    def __init__(self, callbackFunction):
        self.__activated = False
        self.__callbackFunction = callbackFunction
        self.__sounds = []
        self.__numberOfSounds = 0
        self.__root = Tk()
        self.__root.title("SoundPlayer")
        tkSnack.initializeSnack(self.__root)

    def __mainFunction(self):
        self.__callbackFunction()
        self.__root.after(1, self.__mainFunction)
        pass

    def activate(self):
        self.__activated = True
        self.__root.after(1, self.__mainFunction)
        self.__root.mainloop()

    def loadFile(self, fileName):
        if self.__activated:
            self.__sounds.append(tkSnack.Sound(load=fileName))
            self.__numberOfSounds += 1
            # return the index of the new sound
            return self.__numberOfSounds - 1
        else:
            return -1

    def play(self, soundIndex):
        if self.__activated:
            self.__sounds[soundIndex].play()
        else:
            return -1

Then, the application itself must be implemented in a class thus the main() is defined when handed over to the CSoundPlayer() constructor:

class CApplication:
    def __init__(self):
        self.__programCounter = -1
        self.__SoundPlayer = CSoundPlayer(self.main)
        self.__SoundPlayer.activate()

    def main(self):
        self.__programCounter += 1
        if self.__programCounter == 0:
            self.__sound1 = self.__SoundPlayer.loadFile("../mysong.mp3")
            self.__SoundPlayer.play(self.__sound1)
        # here the cyclic code starts:
        print self.__programCounter

CApplication()

As you can see, the mainloop() is called not in the constructor but in the activate() method. This is because the CApplication won't ever get the reference to CSoundPlayer object because that stucks in the mainloop. The code of the class CApplication itself does a lot of overhead. The actual "application code" is placed inside the CApplication.main() - code which shall be executed only once is controlled by means of the program counter.

Now I put it to the next level and place a polling process of the MIDI Device in the CApplication.main(). Thus I will use MIDI commands as trigger for playing sound files. I hope the performance is sufficient for appropriate latency.

Have you any suggestions for optimization?

momjovi89
  • 89
  • 1
  • 1
  • 6
  • Instead of your own loop, use the mainloop provided by tkinter. Remove your `while` loop, and insert this code: `root.mainloop()`. You should look at a good tutorial... – nbro Apr 28 '15 at 08:07
  • That shows the window. But it's not what I want. I need the while loop to poll my trigger input for playing sound files by Snack. And the Snack docu doesn't say that the mainloop() is needed. I think Snack just needs an instance of the Tk() and it's not important that it is displayed. But as no sound is played I assume that the Tk() doesn't work at all. So I tried that easy script mentioned above without any Snack stuff and the window also doesn't appears. Besides I wonder why I don't need the mainloop() in the console. – momjovi89 Apr 28 '15 at 08:09
  • 1
    What do you want then? – nbro Apr 28 '15 at 08:10
  • See above. I edited the comment. I hit enter accidentally before I finished :-) – momjovi89 Apr 28 '15 at 08:15
  • You will need to call the mainloop, more info about the mainloop and why you don't need to call it the console see [this](http://stackoverflow.com/q/8683217/3714930) and [this](http://stackoverflow.com/q/20891710/3714930). To run an infinite loop alongside your Tkinter mainloop you usually use after(), see [this](http://stackoverflow.com/q/21667713/3714930) and [this](http://stackoverflow.com/q/25731997/3714930) for more info. – fhdrsdg Apr 28 '15 at 08:32
  • Ok I get it running with mainloop. It's not so nice but it works: I want to encapsulate the Snack stuff in a class. So I add a parameter to the __init__ method as a callback to my main application and one private function that calls that callback and is rescheduled by after() again and again. Futhermore I need a activate method for the "Snack class" which calls the mainloop() after the main application has got the reference from the constructor... – momjovi89 Apr 28 '15 at 10:06

1 Answers1

1

You must start the event loop. Without the event loop, tkinter has no way to actually draw the window. Remove the while loop and replace it with mainloop:

from Tkinter import *
root = Tk()
root.mainloop()

If you need to do polling (as mentioned in the comments to the question), write a function that polls, and have that function run every periodically with after:

def poll():
    <do the polling here>

    # in 100ms, call the poll function again
    root.after(100, poll)

The reason you don't need mainloop in the console depends on what you mean by "the console". In IDLE, and perhaps some other interactive interpreters, tkinter has a special mode when running interactively that doesn't require you call mainloop. In essence, the mainloop is the console input loop.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Yes I did it already this way. But I didn't want the method poll() to know about the root. So I found out a solution for encapsulating the whole thing inside a class and give the poll() as a callback. I'll add the solution as code to the question above! – momjovi89 Apr 28 '15 at 12:54