How to create a loading wheel in a Tkinter Text Widget?
e.g. run through the sequence of -, \, |, / to create the illusion of a spinning bar.
The only examples I could find were for commandline/console applications
Insert text into the console by using console.write('text here')
If you wish for a spinning bar/wheel on the end of the line simple add the additional parameter console.write('text here', 'loader')
The spinning wheel will stop and disappear once you write another line of text.
class Console(Frame):
def __init__(self, master, **kwargs):
Frame.__init__(self, master)
self.text = Text(self, wrap='word', **kwargs)
self.text.pack()
self.text.config(state='disabled')
self.sequence = ['-', '\\', '|', '/']
self.load = False
self.queue = Queue.Queue()
self.update_me()
def write(self, line, link=None):
self.queue.put((line,link))
def clear(self):
self.queue.put((None, None))
def update_me(self):
try:
while 1:
line, link = self.queue.get_nowait()
self.text.config(state='normal')
if line is None:
self.text.delete(1.0, END)
elif link and link == 'loader':
self.load = True
self.text.delete(self.text.index("end-2c"))
self.text.insert(self.text.index("end-1c"), str(line))
else:
if self.load:
self.text.delete(self.text.index("end-2c"))
self.text.insert(self.text.index("end-1c"), str(line))
else:
self.text.insert(END, str(line))
self.load = False
self.text.see(END)
self.update_idletasks()
self.text.config(state='disabled')
except Queue.Empty:
pass
self.after(100, self.update_me)
if self.load:
self.queue.put((self.sequence[0], 'loader'))
self.sequence.append(self.sequence.pop(0))
if __name__ == '__main__':
# testing application
import time
root = Tk()
console = Console(root)
console.pack()
def load_it():
console.write('Loading World...', 'loader')
time.sleep(3)
console.write('Done')
import threading
t = threading.Thread(target=load_it)
t.daemon = True
t.start()
root.mainloop()
exit()
Related
I have been working on a music player app that uses VLC to play songs directly from the internet and has features like seeking and a progress bar. But as the normal tkinter progress bar looks kinda old so I used customtkinter.CTkSlider widget but using this causes buffering. The song plays smoothly on using Tk.Scale.
Here, is the code:
# import external libraries
import vlc
import tkinter as Tk
from tkinter import ttk
import pyautogui
import customtkinter
import pafy
# import standard libraries
import os
from threading import Thread, Event
import time
import platform
import requests
class ttkTimer(Thread):
"""a class serving same function as wxTimer... but there may be better ways to do this
"""
def __init__(self, callback, tick):
Thread.__init__(self)
self.callback = callback
self.stopFlag = Event()
self.tick = tick
self.iters = 0
def run(self):
while not self.stopFlag.wait(self.tick):
self.iters += 1
self.callback()
def stop(self):
self.stopFlag.set()
def get(self):
return self.iters
class Player(Tk.Frame):
"""The main window has to deal with events.
"""
def __init__(self, parent, title=None):
Tk.Frame.__init__(self, parent)
self.parent = parent
if title == None:
title = "tk_vlc"
self.parent.title(title)
# Menu Bar
# File Menu
menubar = Tk.Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = Tk.Menu(menubar)
fileMenu.add_command(label="Open", underline=0, command=self.OnOpen)
fileMenu.add_command(label="Exit", underline=1, command=_quit)
menubar.add_cascade(label="File", menu=fileMenu)
# The second panel holds controls
self.player = None
self.videopanel = ttk.Frame(self.parent)
self.canvas = Tk.Canvas(self.videopanel).pack(fill=Tk.BOTH,expand=1)
self.videopanel.pack(fill=Tk.BOTH,expand=1)
ctrlpanel = ttk.Frame(self.parent)
pause = ttk.Button(ctrlpanel, text="Pause", command=self.OnPause)
play = ttk.Button(ctrlpanel, text="Play", command=self.OnPlay)
stop = ttk.Button(ctrlpanel, text="Stop", command=self.OnStop)
volume = ttk.Button(ctrlpanel, text="Volume", command=self.OnSetVolume)
pause.pack(side=Tk.LEFT)
play.pack(side=Tk.LEFT)
stop.pack(side=Tk.LEFT)
volume.pack(side=Tk.LEFT)
self.volume_var = Tk.IntVar()
self.volslider = Tk.Scale(ctrlpanel, variable=self.volume_var, command=self.volume_sel,
from_=0, to=100, orient=Tk.HORIZONTAL, length=100)
self.volslider.pack(side=Tk.LEFT)
ctrlpanel.pack(side=Tk.BOTTOM)
ctrlpanel2 = ttk.Frame(self.parent)
self.scale_var = Tk.DoubleVar()
self.timeslider_last_val = ""
self.timeslider = customtkinter.CTkSlider(ctrlpanel2, variable=self.scale_var, command=self.scale_sel,
from_=0, to=1000, orient=Tk.HORIZONTAL) # This causes buffer
self.timeslider.pack(side=Tk.BOTTOM, fill=Tk.X,expand=1)
self.timeslider_last_update = time.time()
ctrlpanel2.pack(side=Tk.BOTTOM,fill=Tk.X)
# VLC player controls
self.Instance = vlc.Instance()
self.player = self.Instance.media_player_new()
self.timer = ttkTimer(self.OnTimer, 1.0)
self.timer.start()
self.parent.update()
#self.player.set_hwnd(self.GetHandle()) # for windows, OnOpen does does this
def Extract(self,topic):
"""Will play video on following topic, takes about 10 to 15 seconds to load"""
url = 'https://www.youtube.com/results?q=' + topic
count = 0
cont = ''
try:
cont = requests.get(url)
except:
print('Error','Cannot Connect.. Internet not connected or invalid URL or id.')
cont = ''
data = cont.content
data = str(data)
lst = data.split('"')
for i in lst:
count+=1
if i == 'WEB_PAGE_TYPE_WATCH':
break
if lst[count-5] == "/results":
print("Error","No video found.")
return "https://www.youtube.com"+lst[count-5]
def OnExit(self, evt):
"""Closes the window.
"""
self.Close()
def OnOpen(self):
"""Pop up a new dialow window to choose a file, then play the selected file.
"""
# if a file is already running, then stop it.
self.OnStop()
fullname = pafy.new(self.Extract(pyautogui.password(mask="", title="Enter Song Name:", text="Enter Song Name:"))).getbest().url
self.Media = self.Instance.media_new(fullname)
self.player.set_media(self.Media)
# set the window id where to render VLC's video output
if platform.system() == 'Windows':
self.player.set_hwnd(self.GetHandle())
else:
self.player.set_xwindow(self.GetHandle()) # this line messes up windows
# FIXME: this should be made cross-platform
self.OnPlay()
# set the volume slider to the current volume
#self.volslider.SetValue(self.player.audio_get_volume() / 2)
self.volslider.set(self.player.audio_get_volume())
def OnPlay(self):
"""Toggle the status to Play/Pause.
If no file is loaded, open the dialog window.
"""
# check if there is a file to play, otherwise open a
# Tk.FileDialog to select a file
if not self.player.get_media():
self.OnOpen()
else:
# Try to launch the media, if this fails display an error message
if self.player.play() == -1:
self.errorDialog("Unable to play.")
def GetHandle(self):
return self.videopanel.winfo_id()
#def OnPause(self, evt):
def OnPause(self):
"""Pause the player.
"""
self.player.pause()
def OnStop(self):
"""Stop the player.
"""
self.player.stop()
# reset the time slider
self.timeslider.set(0)
def OnTimer(self):
"""Update the time slider according to the current movie time.
"""
if self.player == None:
return
# since the self.player.get_length can change while playing,
# re-set the timeslider to the correct range.
length = self.player.get_length()
dbl = length * 0.001
self.timeslider.config(to=dbl)
# update the time on the slider
tyme = self.player.get_time()
if tyme == -1:
tyme = 0
dbl = tyme * 0.001
self.timeslider_last_val = ("%.0f" % dbl) + ".0"
# don't want to programatically change slider while user is messing with it.
# wait 2 seconds after user lets go of slider
if time.time() > (self.timeslider_last_update + 2.0):
self.timeslider.set(dbl)
def scale_sel(self, evt):
if self.player == None:
return
nval = self.scale_var.get()
sval = str(nval)
if self.timeslider_last_val != sval:
self.timeslider_last_update = time.time()
mval = "%.0f" % (nval * 1000)
self.player.set_time(int(mval)) # expects milliseconds
def volume_sel(self, evt):
if self.player == None:
return
volume = self.volume_var.get()
if volume > 100:
volume = 100
if self.player.audio_set_volume(volume) == -1:
self.errorDialog("Failed to set volume")
def OnToggleVolume(self, evt):
"""Mute/Unmute according to the audio button.
"""
is_mute = self.player.audio_get_mute()
self.player.audio_set_mute(not is_mute)
# update the volume slider;
# since vlc volume range is in [0, 200],
# and our volume slider has range [0, 100], just divide by 2.
self.volume_var.set(self.player.audio_get_volume())
def OnSetVolume(self):
"""Set the volume according to the volume sider.
"""
volume = self.volume_var.get()
# vlc.MediaPlayer.audio_set_volume returns 0 if success, -1 otherwise
if volume > 100:
volume = 100
if self.player.audio_set_volume(volume) == -1:
self.errorDialog("Failed to set volume")
def errorDialog(self, errormessage):
"""Display a simple error dialog.
"""
Tk.tkMessageBox.showerror(self, 'Error', errormessage)
def Tk_get_root():
if not hasattr(Tk_get_root, "root"): #(1)
Tk_get_root.root= Tk.Tk() #initialization call is inside the function
return Tk_get_root.root
def _quit():
print("_quit: bye")
root = Tk_get_root()
root.quit() # stops mainloop
root.destroy() # this is necessary on Windows to prevent
# Fatal Python Error: PyEval_RestoreThread: NULL tstate
os._exit(1)
if __name__ == "__main__":
# Create a Tk.App(), which handles the windowing system event loop
root = Tk_get_root()
root.protocol("WM_DELETE_WINDOW", _quit)
player = Player(root, title="tkinter vlc")
# show the player window centred and run the application
root.mainloop()
Kindly help
Regards.
I have some code working that displays line numbers along the left side of a text widget. For the most part, it works well, but there are a couple of issues that I need some pointers on how to fix.
I have posted the code below.
Here is a link to a test file with 200 lines (each line is numbered).
test file here
Issues:
(1) You can mouse wheel (scroll) up and down the file. However, this causes the line numbers to get out of sync with the text widget line numbers. If someone can point me in the right direction to look at, I probably can figure it out and fix it.
(2) The PgDn/PgUp keys have a similar effect. I can hit PgDn a few times and notice the Text widget lines get out of sync with the line numbers, as well as losing their horizontal alignment. Again, if someone can point me in the right direction to look at, I'm sure I can figure out what's happening in the code and fix it.
from tkinter import *
class LineNumbers(Text):
def __init__(self, master, text_widget, **kwargs):
super().__init__(master, **kwargs)
self.text_widget = text_widget
self.text_widget.bind('<KeyRelease>', self.on_key_release)
self.text_widget.bind('<FocusIn>', self.on_key_release)
self.text_widget.bind('<MouseWheel>', self.on_key_release)
self.insert(1.0, '1')
self.configure(state='disabled')
def on_key_release(self, event=None):
p, q = self.text_widget.index("#0,0").split('.')
p = int(p)
final_index = str(self.text_widget.index(END))
num_of_lines = final_index.split('.')[0]
line_numbers_string = "\n".join(str(p + no) for no in range(int(num_of_lines)))
width = len(str(num_of_lines))
self.configure(state='normal', width=width)
self.delete(1.0, END)
self.insert(1.0, line_numbers_string)
self.configure(state='disabled')
if __name__ == '__main__':
win = Tk()
win.title("Line Numbers Test")
win.geometry("800x600+1000+300")
txt = Text(win)
ln = LineNumbers(win, txt, width=2)
f = open("line_test.txt", 'r')
lines = f.readlines()
for line in lines:
txt.insert(END, line)
f.close()
ln.pack(side=LEFT, fill=BOTH)
txt.pack(expand=True, fill=BOTH)
txt.focus()
win.mainloop()
class LineNumbers(Text):
def __init__(self, master, text_widget, **kwargs):
super().__init__(master, **kwargs)
self.busy = False
self.master = master
self.text_widget = text_widget
self.text_widget.bind('<KeyRelease>', self.on_key_release)
self.text_widget.bind('<FocusIn>', self.on_key_release)
self.text_widget.bind('<MouseWheel>', self.on_mousew)
self.text_widget.bind('<Button-4>', self.on_mousew)
self.text_widget.bind('<Button-5>', self.on_mousew)
self.insert(1.0, '1')
self.configure(state='disabled')
def on_mousew(self, event=None):
self.after( 50, self.bogus)
def on_key_release(self, event=None):
self.after( 50, self.bogus)
return "break"
def bogus( self):
self.text_widget.update()
p, q = self.text_widget.index("#0,0").split('.')
np = int(p)
self.text_widget.see( "#" + str( np+1) + ","+ q)
final_index = str(self.text_widget.index(END))
num_of_lines = final_index.split('.')[0]
line_numbers_string = "\n".join( str(np+no) for no in range(0, int(num_of_lines)))
width = len(str(num_of_lines))
self.configure(state='normal', width=width)
self.delete(1.0, END)
self.insert(1.0, line_numbers_string)
self.configure(state='disabled')
I am trying to start a function with a new thread and everytime i start the function, the tkinter application opens up again. For me to see the function running i need to quit the new application that opened and then it starts. Is there something in my code that is calling the application again and not allowing my startReceive function to run instantly when i start the thread?
import tkinter as tk
from udpTransfer1 import UdpClass
from threading import Thread
from tkinter import messagebox
import multiprocessing, time, signal
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.alert = ''
self.pack()
self.alive = False
self.create_widgets()
self.c = UdpClass()
self.threadExists= False
self.st = None
def create_widgets(self):
# 1 button
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Save UDP"
self.hi_there["command"] = self.run
self.hi_there.pack(side="top")
# 2 button
self.b2 = tk.Button(self)
self.b2["text"] = "Stop UDP"
self.b2["command"] = self.b2Action
self.b2.pack(side="top")
# 3 button
self.quit = tk.Button(self, text="QUIT", fg="red")
self.quit["command"] = self.kill
self.quit.pack(side="bottom")
def kill(self):
if self.threadExists == True:
# Alert Box
# self.alert = messagebox.showinfo("ALERT").capitalize()
self.alert = messagebox._show(None,'UDP has to be stopped first!')
else:
root.destroy()
def run(self):
# self.st = Thread(target=self.c.startReceive)
# self.st = multiprocessing.Process(target=self.c.startReceive)
self.st = multiprocessing.Process(target=startReceive)
self.st.start()
if (self.st.is_alive()):
print("Thread open")
self.threadExists = True
def b2Action(self):
if self.threadExists:
# self.c.terminate()
terminate()
self.st.terminate()
time.sleep(.1)
if not(self.st.is_alive()):
print("Thread Killed")
self.threadExists = False
else :
self.alert = messagebox._show(None, 'No Detection of UDP Running!')
def startReceive():
print('started')
try:
isRunning = True
# csvfile = open(self.filename, 'w')
# spamwriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
# start_time = time.time()
while isRunning:
print('done')
time.sleep(1)
except:
print('out')
def terminate():
# self.socket.close()
# isRunning = False
print('stopped')
root = tk.Tk()
app = Application(master=root)
app.master.title("UDP Saving")
app.master.geometry('200x200')
app.master.maxsize(200, 500)
app.mainloop()
Not 100% sure, but this might be the issue:
def run(self):
# self.st = Thread(target=self.c.startReceive)
# self.st = multiprocessing.Process(target=self.c.startReceive)
self.st = multiprocessing.Process(target=startReceive)
self.st.start()
if (self.st.is_alive()):
print("Thread open")
self.threadExists = True
I think You should put self.threadExists = True inside previous if.
def run(self):
# self.st = Thread(target=self.c.startReceive)
# self.st = multiprocessing.Process(target=self.c.startReceive)
self.st = multiprocessing.Process(target=startReceive)
self.st.start()
if (self.st.is_alive()):
print("Thread open")
self.threadExists = True
I have a Tkinter GUI application that I need to enter text in. I cannot assume that the application will have focus, so I implemented pyHook, keylogger-style.
When the GUI window does not have focus, text entry works just fine and the StringVar updates correctly. When the GUI window does have focus and I try to enter text, the whole thing crashes.
i.e., if I click on the console window or anything else after launching the program, text entry works. If I try entering text immediately (the GUI starts with focus), or I refocus the window at any point and enter text, it crashes.
What's going on?
Below is a minimal complete verifiable example to demonstrate what I mean:
from Tkinter import *
import threading
import time
try:
import pythoncom, pyHook
except ImportError:
print 'The pythoncom or pyHook modules are not installed.'
# main gui box
class TestingGUI:
def __init__(self, root):
self.root = root
self.root.title('TestingGUI')
self.search = StringVar()
self.searchbox = Label(root, textvariable=self.search)
self.searchbox.grid()
def ButtonPress(self, scancode, ascii):
self.search.set(ascii)
root = Tk()
TestingGUI = TestingGUI(root)
def keypressed(event):
key = chr(event.Ascii)
threading.Thread(target=TestingGUI.ButtonPress, args=(event.ScanCode,key)).start()
return True
def startlogger():
obj = pyHook.HookManager()
obj.KeyDown = keypressed
obj.HookKeyboard()
pythoncom.PumpMessages()
# need this to run at the same time
logger = threading.Thread(target=startlogger)
# quits on main program exit
logger.daemon = True
logger.start()
# main gui loop
root.mainloop()
I modified the source code given in the question (and the other one) so that the pyHook
related callback function sends keyboard event related data to a
queue. The way the GUI object is notified about the event may look
needlessly complicated. Trying to call root.event_generate in
keypressed seemed to hang. Also the set method of
threading.Event seemed to cause trouble when called in
keypressed.
The context where keypressed is called, is probably behind the
trouble.
from Tkinter import *
import threading
import pythoncom, pyHook
from multiprocessing import Pipe
import Queue
import functools
class TestingGUI:
def __init__(self, root, queue, quitfun):
self.root = root
self.root.title('TestingGUI')
self.queue = queue
self.quitfun = quitfun
self.button = Button(root, text="Withdraw", command=self.hide)
self.button.grid()
self.search = StringVar()
self.searchbox = Label(root, textvariable=self.search)
self.searchbox.grid()
self.root.bind('<<pyHookKeyDown>>', self.on_pyhook)
self.root.protocol("WM_DELETE_WINDOW", self.on_quit)
self.hiding = False
def hide(self):
if not self.hiding:
print 'hiding'
self.root.withdraw()
# instead of time.sleep + self.root.deiconify()
self.root.after(2000, self.unhide)
self.hiding = True
def unhide(self):
self.root.deiconify()
self.hiding = False
def on_quit(self):
self.quitfun()
self.root.destroy()
def on_pyhook(self, event):
if not queue.empty():
scancode, ascii = queue.get()
print scancode, ascii
if scancode == 82:
self.hide()
self.search.set(ascii)
root = Tk()
pread, pwrite = Pipe(duplex=False)
queue = Queue.Queue()
def quitfun():
pwrite.send('quit')
TestingGUI = TestingGUI(root, queue, quitfun)
def hook_loop(root, pipe):
while 1:
msg = pipe.recv()
if type(msg) is str and msg == 'quit':
print 'exiting hook_loop'
break
root.event_generate('<<pyHookKeyDown>>', when='tail')
# functools.partial puts arguments in this order
def keypressed(pipe, queue, event):
queue.put((event.ScanCode, chr(event.Ascii)))
pipe.send(1)
return True
t = threading.Thread(target=hook_loop, args=(root, pread))
t.start()
hm = pyHook.HookManager()
hm.HookKeyboard()
hm.KeyDown = functools.partial(keypressed, pwrite, queue)
try:
root.mainloop()
except KeyboardInterrupt:
quit_event.set()
I tried to open mutiple frames by mutiple threads.
Here is my code.
'''
This is the module for test and studying.
Author:Roger
Date: 2010/10/10
Python version: 2.6.5
'''
import threading, Tkinter
class Application(Tkinter.Frame):
def __init__(self, master=None):
Tkinter.Frame.__init__(self, master)
self.columnconfigure(50)
self.rowconfigure(50)
self.grid()
self.createWidgets()
self.mainloop()
def createWidgets(self):
self.quitButton = Tkinter.Button (self, text='Quit', command=self.quit )
self.quitButton.grid()
class lab_404(threading.Thread):
'''
Is this the doc_string of lab_404?
Can there be mutiple_window?
I do know why is it like this?
Why is the button still on the frame?
'''
myWindow = None
def __init__(self, computer = 10, server = None, table = 1, chair = 1, student = 2, myWindow = None):
threading.Thread.__init__(self)
self.__computer = computer
self.__server = server
self.__table = table
self.__chair = chair
self.__student = student
self.myWindow = Application()
#self.myWindow.mainloop() #mainloop method is here, I don't where to put it.
def getComputer(self):
return self.__computer
def getServer(self):
return self.__server
def getMyWindow(self):
return self.myWindow
def setServer(self, Server):
self.__server = Server
def run(self):
print super(lab_404, self).getName(), 'This thread is starting now!'
print super(lab_404, self).getName(), 'This thread is ending.'
if __name__ == '__main__':
for n in xrange(1, 10, 1):
tn = lab_404(server = n) #Try to make a loop.
tn.start()
The code above has been running as a frame, then stop (mainloop?). It won't continue to the next frame until I close the formmer window. It's fitful.
How could I make it open new frames automatically?
I don't think you can do what you want. Generally speaking, there's no need to run multiple frames and multiple event loops. You can have multiple frames within a single thread and within a single event loop.