├── LICENSE.md ├── AnimatedGif.py └── README.md /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ole Jakob Skjelten 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AnimatedGif.py: -------------------------------------------------------------------------------- 1 | """ AnimatedGIF - a class to show an animated gif without blocking the tkinter mainloop() 2 | 3 | Copyright (c) 2016 Ole Jakob Skjelten 4 | Released under the terms of the MIT license (https://opensource.org/licenses/MIT) as described in LICENSE.md 5 | 6 | """ 7 | import sys 8 | import time 9 | try: 10 | import Tkinter as tk # for Python2 11 | except ImportError: 12 | import tkinter as tk # for Python3 13 | 14 | 15 | class AnimatedGif(tk.Label): 16 | """ 17 | Class to show animated GIF file in a label 18 | Use start() method to begin animation, and set the stop flag to stop it 19 | """ 20 | def __init__(self, root, gif_file, delay=0.04): 21 | """ 22 | :param root: tk.parent 23 | :param gif_file: filename (and path) of animated gif 24 | :param delay: delay between frames in the gif animation (float) 25 | """ 26 | tk.Label.__init__(self, root) 27 | self.root = root 28 | self.gif_file = gif_file 29 | self.delay = delay # Animation delay - try low floats, like 0.04 (depends on the gif in question) 30 | self.stop = False # Thread exit request flag 31 | 32 | self._num = 0 33 | 34 | def start(self): 35 | """ Starts non-threaded version that we need to manually update() """ 36 | self.start_time = time.time() # Starting timer 37 | self._animate() 38 | 39 | def stop(self): 40 | """ This stops the after loop that runs the animation, if we are using the after() approach """ 41 | self.stop = True 42 | 43 | def _animate(self): 44 | try: 45 | self.gif = tk.PhotoImage(file=self.gif_file, format='gif -index {}'.format(self._num)) # Looping through the frames 46 | self.configure(image=self.gif) 47 | self._num += 1 48 | except tk.TclError: # When we try a frame that doesn't exist, we know we have to start over from zero 49 | self._num = 0 50 | if not self.stop: # If the stop flag is set, we don't repeat 51 | self.root.after(int(self.delay*1000), self._animate) 52 | 53 | def start_thread(self): 54 | """ This starts the thread that runs the animation, if we are using a threaded approach """ 55 | from threading import Thread # We only import the module if we need it 56 | self._animation_thread = Thread() 57 | self._animation_thread = Thread(target=self._animate_thread).start() # Forks a thread for the animation 58 | 59 | def stop_thread(self): 60 | """ This stops the thread that runs the animation, if we are using a threaded approach """ 61 | self.stop = True 62 | 63 | def _animate_thread(self): 64 | """ Updates animation, if it is running as a separate thread """ 65 | while self.stop is False: # Normally this would block mainloop(), but not here, as this runs in separate thread 66 | try: 67 | time.sleep(self.delay) 68 | self.gif = tk.PhotoImage(file=self.gif_file, format='gif -index {}'.format(self._num)) # Looping through the frames 69 | self.configure(image=self.gif) 70 | self._num += 1 71 | except tk.TclError: # When we try a frame that doesn't exist, we know we have to start over from zero 72 | self._num = 0 73 | except RuntimeError: 74 | sys.exit() 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnimatedGIF 2 | ### - a Python class for animating GIFs in tkinter 3 | A very simple class (subclass of tkinter.Label) that displays an animated GIF in 4 | a label and either runs the animation in a separate thread or using tkinter's `after()`. Written in Python3 and tested successfully on 2.7 (thanks to @brenden-t-r) 5 | 6 | This allows using animated GIFs together with a normal tkinter mainloop() without blocking, but with caveats (see below) 7 | Perfect for showing small animated "please wait"/"working"/"downloading" icons while doing other tasks. 8 | 9 | *I recommend trying the non-threaded approach first. If you have stuttering in your animation, try the threaded approach* 10 | ###### Example usage (no threads): 11 | 12 | ```python 13 | 14 | from AnimatedGIF import * 15 | 16 | lbl_with_my_gif = AnimatedGif(parent, 'my_logo.gif', 0.04) # (tkinter.parent, filename, delay between frames) 17 | lbl_with_my_gif.pack() # Packing the label with the animated gif (grid works just as well) 18 | lbl_with_my_gif.start() # Shows gif at first frame and we are ready to go 19 | ... 20 | lbl_with_my_gif.stop() # Setting stop flag, which ends the update loop (animation) 21 | ``` 22 | 23 | ###### Example usage (threading - but not recommended see below): 24 | 25 | ```python 26 | 27 | from AnimatedGIF import * 28 | 29 | lbl_with_my_gif = AnimatedGif(parent, 'my_logo.gif', 0.04) # (tkinter.parent, filename, delay between frames) 30 | lbl_with_my_gif.pack() # Packing the label with the animated gif (grid works just as well) 31 | lbl_with_my_gif.start_thread() # Spawn thread which updates animation 32 | ... 33 | lbl_with_my_gif.stop_thread() # Setting stop flag, which ends the animation 34 | ``` 35 | 36 | I made this after seeing a whole lot of questions on StackExchange on how to do this, but no real working solutions that allows 37 | for usage together with mainloop(), which I guess a lot of people, myself included, need. 38 | 39 | ## Known issues 40 | 41 | If you are using the threaded aproach and get the `"RuntimeError: main thread is not in main loop"` error, you're experiencing a [known problem with tkinter](http://stackoverflow.com/questions/14694408/runtimeerror-main-thread-is-not-in-main-loop). Unfortunately, tkinter is not really thread safe. You can replace tkinter with [mtTkinter](http://tkinter.unpythonic.net/wiki/mtTkinter), but in my experience, whether or not this becomes a problem varies from case to case. It seems that if your GIF animation thread makes the main tkinter thread time out, this error occurs. This isn't really unique to Tkinter, but more a general issues with gui frameworks in Python. 42 | 43 | The "correct" way of using this class is to run a `update()` and `update_idletasks()` loop while the task you are waiting for run in a thread. This usually works better than putting the animation in a thread, at least if the task isn't making updates to the window. 44 | 45 | Of course, the simple way of doing it is using the non-threaded `stop` and `start` methods, which uses an "after()-loop", but in some cases, especially where the program is busy, the animation can get very choppy, as `after()` does not guarantee timely execution. Which is fine in some cases and not in others. Use either approach as you see fit. 46 | 47 | ## License 48 | 49 | AnimateGIF is released under the MIT license (https://opensource.org/licenses/MIT) 50 | --------------------------------------------------------------------------------