Related
I am trying to create my own python code editor. For that I tried to use the tkcode module. Tkcode partially covers my needs (colors in the text for example), but it does not have the keyboard events that Idle has (automatic indentation when pressing enter, when pressing tab puts 4 spaces, etc). Some of them I can try to recreate, but automatic indentation is difficult. Is there any way to associate the Idle events to my code editor using the idlelib without creating another window (since I am making a notebook)? I went through the source code of Idle and couldn't find the way.
I know that this site is not to ask for recommendations, but if you recommend a better module to create a text widget that allows me to create this editor, it would be great too.
This is the code I have made:
from tkcode import CodeEditor
from tkinter import ttk
import tkinter as tk
import re
class CodeEditor(CodeEditor):
def __init__(Self, *args, **kargs):
super().__init__(*args, **kargs)
Self.bind("<Return>", Self.enter)
def get_current_line(Self):
return Self.get("insert linestart", "insert lineend")
def enter(Self, event):
for index, char in enumerate(Self.get_current_line()):
if(char != " "):
break
else:
index += 1
Self.insert("insert", "\n")
Self.insert("insert", " "*index)
return "break"
class FileHandler:
def __init__(Self, progs_path, filetabs):
Self.files = {}
Self.progs_path = progs_path
Self.filetabs = filetabs
def askopen(Self):
v = tk.Toplevel()
v.transient()
v.resizable(0, 0)
prog = ttk.Entry(v)
prog.pack(padx=10, pady=10)
prog.bind("<Return>", lambda Event:(Self.open(prog.get()), v.destroy()))
def open(Self, prog):
progfile = str(prog)[0]
progfile = f"prog{progfile}00A{progfile}99.py"
if(progfile in Self.files):
text = Self.files[progfile][prog]
else:
functions = {}
name = None
with open(f"{Self.progs_path}/{progfile}") as file:
for line in file:
match = re.match("(def|class) prog(\w+)", line)
if(match):
name = match[2]
functions[name] = line
if(line.startswith(" ") and name):
functions[name] += line
Self.files[progfile] = functions
text = functions[prog]
frame = ttk.Frame(Self.filetabs)
code_editor = CodeEditor(frame, language="python", highlighter="mariana", font="TkFixedFont", autofocus=True, padx=10, pady=10)
code_editor.pack(fill="both", expand=True)
code_editor.content = text
Self.filetabs.add(frame, text="prog"+prog)
v = tk.Tk()
filetabs = ttk.Notebook()
fh = FileHandler(".", filetabs)
menubar = tk.Menu(tearoff=0)
file = tk.Menu(tearoff=0)
menubar.add_cascade(label="Archivo", menu=file)
file.add_command(label="Abrir prog", command=fh.askopen)
v["menu"] = menubar
filetabs.pack(fill="both", expand=True)
fh.open("833")
In the end I was able to fix it. I was able to recreate almost all but one of the functions. For the last one, I made an impersonator class for idlelib.editor.EditorWindow to be able to do the automatic indentation, in addition to adding two new functions to the tkcode.CodeEditor.
However, I find this solution to be very unstable. Any better answer is still appreciated c:
This is the code:
from tkcode import CodeEditor
from tkinter import ttk
from idlelib.editor import EditorWindow
import tkinter as tk
import re
class FakeEditorWindow(EditorWindow):
def __init__(Self, text):
Self.text = text
Self.indentwidth = 4
Self.tabwidth = 4
Self.prompt_last_line = ''
Self.num_context_lines = 50, 500, 5000000
Self.usetabs = False
def is_char_in_string(Self, text_index):
return 1
class CodeEditor(CodeEditor):
def __init__(Self, *args, **kargs):
super().__init__(*args, **kargs)
Self.fake_editor_window = FakeEditorWindow(Self)
Self.bind("<Tab>", Self.tab)
Self.bind("<Control-Shift_L>", Self.dedent)
Self.bind("<BackSpace>", Self.backspace)
Self.bind("<Home>", Self.inicio)
Self.bind("<Return>", Self.enter)
def undo_block_start(Self):
pass
def undo_block_stop(Self):
pass
def get_current_line(Self):
return Self.get("insert linestart", "insert lineend")
def selection_get(Self):
if(Self.tag_ranges("sel")):
return Self.get("sel.first", "sel.last")
else:
return ""
def get_selection_zone(Self):
return (map(int, Self.index('sel.first').split(".", 1)),
map(int, Self.index('sel.last').split(".", 1)))
def tab(Self, event):
selection = Self.selection_get()
if(selection):
(startline, startcolumn), (endline, endcolumn) = Self.get_selection_zone()
if(startcolumn == 0):
for line in range(startline, endline+1):
Self.insert(f"{line}.0", " "*4)
Self.tag_add("sel", f"{startline}.0", "sel.last")
Self.mark_set("insert", f"{endline+1}.0")
else:
Self.insert("insert", " "*4)
return "break"
def dedent(Self, event):
if(Self.tag_ranges("sel")):
(startline, startcolumn), (endline, endcolumn) = Self.get_selection_zone()
if(startcolumn == 0):
for line in range(startline, endline+1):
if(Self.get(f"{line}.0", f"{line}.4") == " "*4):
Self.delete(f"{line}.0", f"{line}.4")
def backspace(Self, event):
if(not Self.tag_ranges("sel") and Self.get("insert linestart", "insert").isspace()):
cursor_line, cursor_col = map(int, Self.index("insert").split(".", 1))
Self.delete(f"{cursor_line}.{cursor_col-4}", "insert")
return "break"
def inicio(Self, event):
cursor_line, cursor_column = map(int, Self.index('insert').split(".", 1))
if(not Self.get("insert linestart", f"{cursor_line}.{cursor_column}").isspace()):
for i in range(cursor_column, -1, -1):
if(Self.get("insert linestart", f"{cursor_line}.{i}").isspace()):
Self.mark_set("insert", f"{cursor_line}.{i}")
return "break"
def enter(Self, event):
return EditorWindow.newline_and_indent_event(Self.fake_editor_window, event)
class FileHandler:
def __init__(Self, progs_path, filetabs):
Self.files = {}
Self.progs_path = progs_path
Self.filetabs = filetabs
def askopen(Self):
v = tk.Toplevel()
v.transient()
v.resizable(0, 0)
prog = ttk.Entry(v)
prog.pack(padx=10, pady=10)
prog.bind("<Return>", lambda Event:(Self.open(prog.get()), v.destroy()))
def open(Self, prog):
progfile = str(prog)[0]
progfile = f"prog{progfile}00A{progfile}99.py"
if(progfile in Self.files):
text = Self.files[progfile][prog]
else:
functions = {}
name = None
with open(f"{Self.progs_path}/{progfile}") as file:
for line in file:
match = re.match("(def|class) prog(\w+)", line)
if(match):
name = match[2]
functions[name] = line
if(line.startswith(" ") and name):
functions[name] += line
Self.files[progfile] = functions
text = functions[prog]
frame = ttk.Frame(Self.filetabs)
code_editor = CodeEditor(frame, language="python", highlighter="mariana", font="TkFixedFont", autofocus=True, padx=10, pady=10)
code_editor.pack(fill="both", expand=True)
code_editor.content = text
Self.filetabs.add(frame, text="prog"+prog)
v = tk.Tk()
filetabs = ttk.Notebook()
fh = FileHandler(".", filetabs)
menubar = tk.Menu(tearoff=0)
file = tk.Menu(tearoff=0)
menubar.add_cascade(label="Archivo", menu=file)
file.add_command(label="Abrir prog", command=fh.askopen)
v["menu"] = menubar
filetabs.pack(fill="both", expand=True)
fh.open("833")
It was working fine. It would open window with 3 columns, with text I could enter. After adding some code, it suddenly stopped showing a screen, and it just says Username: in the terminal. I used a completely newly typed code, but still same result:
(I left out the importing from the top)
class ConnectPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2 # used for our grid
with open("prev_details.txt","r") as f:
d = f.read().split(",")
prev_ip = d[0]
prev_port = d[1]
prev_username = d[2]
self.add_widget(Label(text='IP:')) # widget #1, top left
self.ip = TextInput(text=prev_ip, multiline=False) # defining self.ip...
self.add_widget(self.ip) # widget #2, top right
self.add_widget(Label(text='Port:'))
self.port = TextInput(text=prev_port, multiline=False)
self.add_widget(self.port)
self.add_widget(Label(text='Username:'))
self.username = TextInput(text=prev_username, multiline=False)
self.add_widget(self.username)
# add our button.
self.join = Button(text="Join")
self.join.bind(on_press=self.join_button)
self.add_widget(Label()) # just take up the spot.
self.add_widget(self.join)
def join_button(self, instance):
port = self.port.text
ip = self.ip.text
username = self.username.text
with open("prev_details.txt","w") as f:
f.write(f"{ip},{port},{username}")
info = f"Joining {ip}:{port} as {username}"
chat_app.info_page.update_info(info)
chat_app.screen_manager.current = 'Info'
Clock.schedule_once(self.connect, 1)
def connect(self, _):
port = int(self.port.text)
ip = self.ip.text
username = self.username.text
if not socket_client.connect(ip, port, username, show_error):
return
chat_app.create_chat_page()
chat_app.screen_manager.current = 'Chat'
class ChatPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.add_widget(Label(text='Fancy stuff here to come!!!', font_size=30))
class InfoPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.message = Label(halign="center", valign="middle", font_size=30)
self.message.bind(width=self.update_text_width)
self.add_widget(self.message)
def update_info(self, message):
self.message.text = message
def update_text_width(self, *_):
self.message.text_size = (self.message.width * 0.9, None)
class Epicapp(App):
def build(self):
self.screen_manager = ScreenManager()
self.connect_page = ConnectPage()
screen = Screen(name='Connect')
screen.add_widget(self.connect_page)
self.screen_manager.add_widget(screen)
# Info page
self.info_page = InfoPage()
screen = Screen(name='Info')
screen.add_widget(self.info_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
def create_chat_page(self):
self.chat_page = ChatPage()
screen = Screen(name='Chat')
screen.add_widget(self.chat_page)
self.screen_manager.add_widget(screen)
def show_error(message):
chat_app.info_page.update_info(message)
chat_app.screen_manager.current = 'Info'
Clock.schedule_once(sys.exit, 10)
This is the result (no screen)
Username:
Process finished with exit code -1
Problem has been solved, i removed the import socket_client.py which is not shown in the code above. Now the prev_details.txt is making an error. FileNotFoundError: [Errno 2] No such file or directory: 'prev_details.txt'
I am trying to figure out how to pass data from one class into another. My knowledge of python is very limited and the code I am using has been taken from examples on this site.
I am trying to pass the User name from "UserNamePage" class into "WelcomePage" class. Can someone please show me how to achieve this. I will be adding more pages and I will need to pass data between the different pages
Below is the full code - as mentioned above most of this code has come from other examples and I am using these examples to learn from.
import tkinter as tk
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import datetime
import re
def Chk_String(mystring):
Allowed_Chars = re.compile('[a-zA-Z_-]+$')
return Allowed_Chars.match(mystring)
def FnChkLogin(Page):
booAllFieldsCorrect = False;
myFName = Page.FName.get()
myFName = myFName.replace(" ", "")
myLName = Page.LName.get()
myLName = myLName.replace(" ", "")
if myFName == "":
messagebox.showinfo('Login Ifo is Missing', "Please type in your First Name")
elif not Chk_String(myFName):
messagebox.showinfo('First Name Error:', "Please only use Leter or - or _")
elif myLName == "":
messagebox.showinfo('Login Info is Missing', "Please type in your Last Name")
elif not Chk_String(myLName):
messagebox.showinfo('Last Name Error:', "Please only use Leter or - or _")
else:
booAllFieldsCorrect = True;
if booAllFieldsCorrect == True:
app.geometry("400x200")
app.title("Welcome Screen")
PageController.show_frame(app,"WelcomePage")
def FnAddButton(Page,Name,Label,Width,X,Y,FnAction):
Name = ttk.Button (Page, text=Label,width=int(Width),command=FnAction)
Name.place(x=int(X),y=int(Y))
class PageController(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top",fill="both",expand="True")
container.grid_rowconfigure(0,weight=1)
container.grid_columnconfigure(0,weight=1)
self.frames={}
for F in (UserNamePage,WelcomePage):
page_name = F.__name__
frame = F(container,self)
self.frames[page_name] = frame
frame.grid(row=0,column=0,sticky="nsew")
self.show_frame("UserNamePage")
def show_frame(self,page_name):
frame= self.frames[page_name]
frame.tkraise()
class UserNamePage(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller = controller
lblFName = Label(self,text="First Name ",relief=GROOVE,width=12,anchor=E).place(x=50,y=50)
lblLName = Label(self,text="Last Name ",relief=GROOVE,width=12,anchor=E).place(x=50,y=75)
self.FName = StringVar()
inputFName = Entry(self,textvariable=self.FName,width=25).place(x=142,y=50)
self.LName = StringVar()
inputLName = Entry(self,textvariable=self.LName,width=25).place(x=142,y=75)
cmdContinue = ttk.Button (self, text='Continue',width=9,command=lambda:FnChkLogin(self)).place(x=320,y=70)
class WelcomePage(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller = controller
UserNamePageData = UserNamePage(parent,controller)
UserFName = str(UserNamePageData.FName)
UserLName = str(UserNamePageData.LName)
strWelcome = "Welcome " + UserFName + " " + UserLName
lblWelcome = Label(self,text=strWelcome,relief=FLAT,width=50,anchor=W).place(x=25,y=25)
if __name__ == "__main__":
app = PageController()
app.geometry("400x200")
app.title("User Page")
app.eval('tk::PlaceWindow %s center' % app.winfo_pathname(app.winfo_id()))
app.mainloop()
In the method show_frame(self,page_name) in class PageController, add the following lines
if page_name == 'WelcomePage':
self.frames['WelcomePage'].UserFName = self.frames['UserNamePage'].FName.get()
self.frames['WelcomePage'].UserLName = self.frames['UserNamePage'].LName.get()
and remove the two lines UserFName = str(UserNamePageData.FName)and UserLName = str(UserNamePageData.LName).
Explanation: it must be done in a place that has references to both frames (i.e. class PageController).
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
OK, I wanted to make an python application that allows me to send a message over LAN.
Here's the code that "works" "locally" (imagine that I'm forming 2 of my fingers to make an " on those 2 words)
username = input("username: ")
run = 1
while run == 1:
message = input(username + ": ")
if message == "exit":
print(username + " left")
run = 0
else:
print(username + ": " + message)
My question how do I send the variable "message" over LAN?
I wan't it to send the variable message over LAN to another PC and print it on it and the other way around.
Here are several files that may help you with developing a messaging system for your LAN.
Simple_Server.py
#! /usr/bin/env python3
import socket, select
def main():
a = [socket.socket(socket.AF_INET, socket.SOCK_STREAM)] # socket array
a[0].bind(('', 8989))
a[0].listen(5)
while True:
for b in select.select(a, [], [])[0]: # ready socket
if b is a[0]:
a.append(b.accept()[0])
else:
try:
c = b.recv(1 << 12) # sent message
except socket.error:
b.shutdown(socket.SHUT_RDWR)
b.close()
a.remove(b)
else:
for d in (d for d in a[1:] if d is not b): # message sink
d.sendall(c)
if __name__ == '__main__':
main()
MultichatClient.py
#! /usr/bin/env python3
from safetkinter import *
from tkinter.constants import *
import socket
import sys
class MultichatClient(Frame):
after_handle = None
def __init__(self, master, remote_host):
super().__init__(master)
self.message_area = ScrolledText(self, width=81, height=21,
wrap=WORD, state=DISABLED)
self.message_area.grid(sticky=NSEW, columnspan=2)
self.send_area = Entry(self)
self.send_area.bind('<Return>', self.keyPressed)
self.send_area.grid(sticky=EW)
b = Button(self, text='Send', command=self.mouseClicked)
b.grid(row=1, column=1)
self.send_area.focus_set()
try:
self.remote = socket.create_connection((remote_host, 8989))
except socket.gaierror:
print('Could not find host {}.'.format(remote_host))
except socket.error:
print('Could not connect to host {}.'.format(remote_host))
else:
self.remote.setblocking(False)
self.after_handle = self.after_idle(self.dataready)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#classmethod
def main(cls, args):
root = Tk()
root.title('MultichatClient version 1.0')
m = cls(root, args[0])
m.grid(sticky=NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
return 1
def dataready(self):
try:
s = self.remote.recv(1 << 12).decode()
except socket.error:
pass
else:
self.message_area['state'] = NORMAL
self.message_area.insert(END, s)
self.message_area['state'] = DISABLED
self.message_area.see(END)
self.after_handle = self.after(100, self.dataready)
def destroy(self):
if self.after_handle:
self.after_cancel(self.after_handle)
super().destroy()
def mouseClicked(self, e=None):
self.remote.sendall(self.send_area.get().encode() + b'\r\n')
self.send_area.delete(0, END)
keyPressed = mouseClicked
if __name__ == '__main__':
sys.exit(MultichatClient.main(sys.argv[1:]))
Simple_Client.pyw
#! /usr/bin/env python3
"""Provide a GUI for easy interactions with Multichat servers.
This program is an example of a first attempt at implementing a client
for interacting with a Multichat server through purely graphical means."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower#gmail.com>'
__date__ = '11 October 2012'
__version__ = 1, 0, 0
################################################################################
from tkinter.messagebox import *
from tkinter.constants import *
from safetkinter import *
import logging
import traceback
import _thread
import socket
import os
import traceback
import sys
import threadbox
################################################################################
class SimpleClient(Frame):
"SimpleClient(master, **kw) -> SimpleClient instance"
#classmethod
def main(cls):
"Create a GUI root and demonstrate the SimpleClient widget."
root = Tk()
root.title('Chat Client')
root.minsize(675, 450)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.bind_all('<Control-Key-a>', cls.handle_control_a)
frame = cls(root)
frame.grid(sticky=NSEW)
root.mainloop()
#staticmethod
def handle_control_a(event):
"Process Ctrl-A commands by widget type."
widget = event.widget
if isinstance(widget, Text):
widget.tag_add(SEL, 1.0, END + '-1c')
return 'break'
if isinstance(widget, Entry):
widget.selection_range(0, END)
return 'break'
def __init__(self, master, **kw):
"Initialize the SimpleClient instance with the widgets it contains."
super().__init__(master, **kw)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# Build Widgets
self.output_area = ScrolledText(self, width=25, height=4, wrap=WORD)
self.input_area = Entry(self)
self.corner = Sizegrip(self)
# Place Widgets
self.output_area.grid(row=0, column=0, columnspan=2, sticky=NSEW)
self.input_area.grid(row=1, column=0, sticky=EW)
self.corner.grid(row=1, column=1, sticky=SE)
# Setup Widgets
self.output_area['state'] = DISABLED
self.input_area.bind('<Return>', self.send)
self.after_idle(self.connect)
def connect(self):
"Try connecting to a server to begin chatting."
self.connection = Connector(self, 'Chat Client').connection
if self.connection is None:
self._root().destroy()
else:
self.connection.setblocking(False)
self.after_idle(self.update)
def send(self, event):
"Send a message across the connection from the given widget."
self.connection.sendall(event.widget.get().encode() + b'\r\n')
event.widget.delete(0, END)
def update(self):
"Update the output area with any incoming messages."
self.output_area['state'] = NORMAL
try:
self.output_area.insert(END, self.connection.recv(1 << 12).decode())
except socket.error:
pass
else:
self.output_area.see(END)
finally:
self.output_area['state'] = DISABLED
self.after(100, self.update)
################################################################################
def start_thread(function, *args, **kwargs):
"Start a new thread of execution while logging any errors."
_thread.start_new_thread(log_errors, (function, args, kwargs))
def log_errors(function, args=(), kwargs={}):
"Execute a function with its arguments and log any exceptions."
try:
function(*args, **kwargs)
except SystemExit:
pass
except:
basename = os.path.basename(sys.argv[0])
filename = os.path.splitext(basename)[0] + '.log'
logging.basicConfig(filename=filename)
logging.error(traceback.format_exc())
################################################################################
class Dialog(Toplevel): # Copies tkinter.simpledialog.Dialog
"Dialog(parent, title=None) -> Dialog instance"
def __init__(self, parent, title=None):
"Initialize a Dialog window that takes focus away from the parent."
super().__init__(parent)
self.withdraw()
if parent.winfo_viewable():
self.transient(parent)
if title:
self.title(title)
self.parent = parent
self.result = None
body = Frame(self)
self.initial_focus = self.body(body)
body.grid(sticky=NSEW, padx=5, pady=5)
self.buttonbox()
if not self.initial_focus:
self.initial_focus = self
self.protocol('WM_DELETE_WINDOW', self.cancel)
if self.parent is not None:
self.geometry('+{}+{}'.format(parent.winfo_rootx() + 50,
parent.winfo_rooty() + 50))
self.deiconify()
self.initial_focus.focus_set()
try:
self.wait_visibility()
except tkinter.TclError:
pass
else:
self.grab_set()
self.wait_window(self)
def destroy(self):
"Destruct the Dialog window."
self.initial_focus = None
super().destroy()
def body(self, master):
"Create the body of this Dialog window."
pass
def buttonbox(self):
"Create the standard buttons and Dialog bindings."
box = Frame(self)
w = Button(box, text='OK', width=10, command=self.ok, default=ACTIVE)
w.grid(row=0, column=0, padx=5, pady=5)
w = Button(box, text='Cancel', width=10, command=self.cancel)
w.grid(row=0, column=1, padx=5, pady=5)
self.bind('<Return>', self.ok)
self.bind('<Escape>', self.cancel)
box.grid()
def ok(self, event=None):
"Validate and apply the changes made by this Dialog."
if not self.validate():
self.initial_focus.focus_set()
return
self.withdraw()
self.update_idletasks()
try:
self.apply()
finally:
self.cancel()
def cancel(self, event=None):
"Close the Dialong window and return to its parent."
if self.parent is not None:
self.parent.focus_set()
self.destroy()
def validate(self):
"Verify that the Dialog is in a valid state."
return True
def apply(self):
"Make any changes the Dialog wishes to accomplish."
pass
################################################################################
class Connector(Dialog):
"Connector(parent, title=None) -> Connector instance"
def body(self, master):
"Customize the Dialog window with some custom widgets."
self.connection = None
self.resizable(False, False)
# Build Widgets
self.prompt = Label(master, text='Enter server IP address:')
self.address = Entry(master)
# Place Widgets
self.prompt.grid(sticky=W, padx=30, pady=2)
self.address.grid(sticky=W, padx=30)
def buttonbox(self):
"Redefine the buttons at the bottom of the window."
w = Button(self, text='Connect', width=10, command=self.ok,
default=ACTIVE)
w.grid(sticky=E, padx=5, pady=5)
self.bind('<Return>', self.ok)
self.bind('<Escape>', self.cancel)
def validate(self):
"Ask a Consumator to make a connection with the given address."
c = Consumator(self, 'Chat Client', (self.address.get(), 8989))
if c.connection is None:
Message(self, icon=WARNING, type=OK, title='Warning',
message='Could not connect to address!').show()
return False
self.connection = c.connection
return True
################################################################################
class Consumator(Dialog):
"Consumator(parent, title, address) -> Consumator instance"
def __init__(self, parent, title, address):
"Initialize the Consumator with the server's address."
self.server_address = address
super().__init__(parent, title)
def body(self, master):
"Create the widgets for this Dialog and start the connection process."
self.connection = None
self.resizable(False, False)
# Build Widgets
self.message = Label(master, text='Trying to connect to address ...')
self.progress = Progressbar(master, orient=HORIZONTAL)
# Place Widgets
self.message.grid(sticky=W, padx=10, pady=2)
self.progress.grid(sticky=EW, padx=10, pady=2)
# Setup Widgets
self.progress.configure(mode='indeterminate', maximum=30)
self.progress.start()
result = []
start_thread(self.connect, result)
self.after_idle(self.poll, result)
def buttonbox(self):
"Cancel the creation of the buttons at the bottom of this Dialog."
pass
#threadbox.MetaBox.thread
def connect(self, result):
"Try connecting to the server address that was given."
try:
result.append(socket.create_connection(self.server_address, 10))
except socket.timeout:
result.append(None)
def poll(self, result):
"Find out if the any connection information is available yet."
if result:
self.connection = result[0]
self.cancel()
else:
self.after(100, self.poll, result)
################################################################################
if __name__ == '__main__':
log_errors(SimpleClient.main)
affinity.py
"""Allow a simple way to ensure execution is confined to one thread.
This module defines the Affinity data type that runs code on a single thread.
An instance of the class will execute functions only on the thread that made
the object in the first place. The class is useful in a GUI's main loop."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower#gmail.com>'
__date__ = '4 June 2012'
__version__ = 1, 0, 0
################################################################################
import sys
import _thread
import queue
################################################################################
def slots(names=''):
"Sets the __slots__ variable in the calling context with private names."
sys._getframe(1).f_locals['__slots__'] = \
tuple('__' + name for name in names.replace(',', ' ').split())
################################################################################
class Affinity:
"Affinity() -> Affinity instance"
slots('thread, action')
def __init__(self):
"Initializes instance with thread identity and job queue."
self.__thread = _thread.get_ident()
self.__action = queue.Queue()
def __call__(self, func, *args, **kwargs):
"Executes function on creating thread and returns result."
if _thread.get_ident() == self.__thread:
while not self.__action.empty():
self.__action.get_nowait()()
return func(*args, **kwargs)
delegate = _Delegate(func, args, kwargs)
self.__action.put_nowait(delegate)
return delegate.value
################################################################################
class _Delegate:
"_Delegate(func, args, kwargs) -> _Delegate instance"
slots('func, args, kwargs, mutex, value, error')
def __init__(self, func, args, kwargs):
"Initializes instance from arguments and prepares to run."
self.__func = func
self.__args = args
self.__kwargs = kwargs
self.__mutex = _thread.allocate_lock()
self.__mutex.acquire()
def __call__(self):
"Executes code with arguments and allows value retrieval."
try:
self.__value = self.__func(*self.__args, **self.__kwargs)
self.__error = False
except:
self.__value = sys.exc_info()[1]
self.__error = True
self.__mutex.release()
#property
def value(self):
"Waits for value availability and raises or returns data."
self.__mutex.acquire()
if self.__error:
raise self.__value
return self.__value
threadbox.py
"""Provide a way to run instance methods on a single thread.
This module allows hierarchical classes to be cloned so that their instances
run on one thread. Method calls are automatically routed through a special
execution engine. This is helpful when building thread-safe GUI code."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower#gmail.com>'
__date__ = '9 October 2012'
__version__ = 1, 0, 1
################################################################################
import functools
import affinity
################################################################################
class _object: __slots__ = '_MetaBox__exec', '__dict__'
################################################################################
class MetaBox(type):
"MetaBox(name, bases, classdict, old=None) -> MetaBox instance"
__REGISTRY = {object: _object}
__SENTINEL = object()
#classmethod
def clone(cls, old, update=()):
"Creates a class preferring thread affinity after update."
classdict = dict(old.__dict__)
classdict.update(update)
return cls(old.__name__, old.__bases__, classdict, old)
#classmethod
def thread(cls, func):
"Marks a function to be completely threaded when running."
func.__thread = cls.__SENTINEL
return func
def __new__(cls, name, bases, classdict, old=None):
"Allocates space for a new class after altering its data."
assert '__new__' not in classdict, '__new__ must not be defined!'
assert '__slots__' not in classdict, '__slots__ must not be defined!'
assert '__module__' in classdict, '__module__ must be defined!'
valid = []
for base in bases:
if base in cls.__REGISTRY:
valid.append(cls.__REGISTRY[base])
elif base in cls.__REGISTRY.values():
valid.append(base)
else:
valid.append(cls.clone(base))
for key, value in classdict.items():
if callable(value) and (not hasattr(value, '_MetaBox__thread') or
value.__thread is not cls.__SENTINEL):
classdict[key] = cls.__wrap(value)
classdict.update({'__new__': cls.__new, '__slots__': (), '__module__':
'{}.{}'.format(__name__, classdict['__module__'])})
cls.__REGISTRY[object() if old is None else old] = new = \
super().__new__(cls, name, tuple(valid), classdict)
return new
def __init__(self, name, bases, classdict, old=None):
"Initializes class instance while ignoring the old class."
return super().__init__(name, bases, classdict)
#staticmethod
def __wrap(func):
"Wraps a method so execution runs via an affinity engine."
#functools.wraps(func)
def box(self, *args, **kwargs):
return self.__exec(func, self, *args, **kwargs)
return box
#classmethod
def __new(meta, cls, *args, **kwargs):
"Allocates space for instance and finds __exec attribute."
self = object.__new__(cls)
if 'master' in kwargs:
self.__exec = kwargs['master'].__exec
else:
valid = tuple(meta.__REGISTRY.values())
for value in args:
if isinstance(value, valid):
self.__exec = value.__exec
break
else:
self.__exec = affinity.Affinity()
return self
safetkinter.py
"""Register tkinter classes with threadbox for immediate usage.
This module clones several classes from the tkinter library for use with
threads. Instances from these new classes should run on whatever thread
the root was created on. Child classes inherit the parent's safety."""
__author__ = 'Stephen "Zero" Chappell <Noctis.Skytower#gmail.com>'
__date__ = '4 June 2012'
__version__ = 1, 0, 0
################################################################################
import time
import tkinter.filedialog
import tkinter.font
import tkinter.messagebox
import tkinter.scrolledtext
import tkinter.ttk
import threadbox
################################################################################
tkinter.NoDefaultRoot()
#threadbox.MetaBox.thread
def mainloop(self):
"Creates a synthetic main loop so that threads can still run."
while True:
try:
self.update()
except tkinter.TclError:
break
else:
time.sleep(tkinter._tkinter.getbusywaitinterval() / 1000)
threadbox.MetaBox.clone(tkinter.Misc, {'mainloop': mainloop})
################################################################################
OldButton = threadbox.MetaBox.clone(tkinter.Button)
Canvas = threadbox.MetaBox.clone(tkinter.Canvas)
OldFrame = threadbox.MetaBox.clone(tkinter.Frame)
Menu = threadbox.MetaBox.clone(tkinter.Menu)
PhotoImage = threadbox.MetaBox.clone(tkinter.PhotoImage)
Spinbox = threadbox.MetaBox.clone(tkinter.Spinbox)
StringVar = threadbox.MetaBox.clone(tkinter.StringVar)
Text = threadbox.MetaBox.clone(tkinter.Text)
Tk = threadbox.MetaBox.clone(tkinter.Tk)
Toplevel = threadbox.MetaBox.clone(tkinter.Toplevel)
################################################################################
Button = threadbox.MetaBox.clone(tkinter.ttk.Button)
Checkbutton = threadbox.MetaBox.clone(tkinter.ttk.Checkbutton)
Entry = threadbox.MetaBox.clone(tkinter.ttk.Entry)
Frame = threadbox.MetaBox.clone(tkinter.ttk.Frame)
Label = threadbox.MetaBox.clone(tkinter.ttk.Label)
Labelframe = threadbox.MetaBox.clone(tkinter.ttk.Labelframe)
Progressbar = threadbox.MetaBox.clone(tkinter.ttk.Progressbar)
Radiobutton = threadbox.MetaBox.clone(tkinter.ttk.Radiobutton)
Scale = threadbox.MetaBox.clone(tkinter.ttk.Scale)
Scrollbar = threadbox.MetaBox.clone(tkinter.ttk.Scrollbar)
Sizegrip = threadbox.MetaBox.clone(tkinter.ttk.Sizegrip)
Treeview = threadbox.MetaBox.clone(tkinter.ttk.Treeview)
################################################################################
Directory = threadbox.MetaBox.clone(tkinter.filedialog.Directory)
Font = threadbox.MetaBox.clone(tkinter.font.Font)
Message = threadbox.MetaBox.clone(tkinter.messagebox.Message)
ScrolledText = threadbox.MetaBox.clone(tkinter.scrolledtext.ScrolledText)
So I am trying to build a GUI where I enter some information, clear the entry fields, and then add new entry fields. However, when I try to clear the frame from the root via grid_remove, the application freezes. The relevant code is below.
import tkinter
from threading import Thread
class PinGui(tkinter.Frame):
def __init__(self, client):
self.client = client
self.root = client.root
self.add_pin = client.add_pin
self.end = client.end
tkinter.Frame.__init__(self, self.root)
self.grid_widgets()
self.grid_buttons()
self.bind_keys()
self.grid(padx=32, pady=32)
def grid_buttons(self, b1='Add', b2='Reset', b3='Quit'):
self.addButton = tkinter.Button(self, text=b1, command=self.validate)
self.resetButton = tkinter.Button(self, text=b2, command=self.reset)
self.quitButton = tkinter.Button(self, text=b3, command=self.end)
self.buttons = [self.addButton, self.resetButton, self.quitButton]
for i in range(3): self.buttons[i].grid(row=i, column=11)
def grid_widgets(self):
widths = [3,3,4,4,6]
self.pin_vars = []
self.pin_fields = []
for i in range(5):
self.pin_vars.append(tkinter.StringVar())
self.pin_fields.append(
tkinter.Entry(self,width=widths[i], textvariable=self.pin_vars[i])
)
self.pin_fields[i].grid(row=0, column=2*i, padx=3)
self.pin_fields[0].focus_set()
def bind_keys(self):
self.root.bind_all("<Return>", self.validate)
self.root.bind_all("<Escape>", self.end)
def validate(self, args=None):
self.client.pin = []
for field in self.pin_fields:
self.client.pin.append(field.get())
Thread(target=self.add_pin).start()
def ungrid(self):
for field in self.pin_fields: field.grid_remove()
for button in self.buttons: button.grid_remove()
self.display.grid_remove()
And:
class PinClient:
def __init__(self):
self.root = tkinter.Tk()
self.gui = PinGui(self)
self.pins = []
def add_pin(self):
self.gui.reset()
if 'display' in self.__dict__:
self.pins.append(self.pin)
self.display.add_pin(self.pin)
self.ping("Enter PIN for Comp %s:" % len(self.display.col1))
if len(self.display.col1) > 5:
self.end() # THIS IS WHERE IT FREEZES
else:
self.subject = self.pin
self.display = Display(self.root, self.pin)
self.display.grid(row=1, padx=32, pady=32)
self.ping("Enter PIN for Comp 1:")
def ping(self, msg):
self.gui.d_var.set(msg)
def end(self, args=None):
self.gui.ungrid()
class Display(tkinter.Frame):
def __init__(self, master, pin):
tkinter.Frame.__init__(self, master)
self.pin = pin
self.col1 = []
self.col2 = []
self.col1.append(tkinter.Label(self, text="Subject:"))
self.col2.append(tkinter.Label(self, text=self.pin))
self.grid_widgets()
def grid_widgets(self):
self.ungrid()
for i in range(len(self.col1)):
self.col1[i].grid(row=i, column=0)
self.col2[i].grid(row=i, column=1)
def ungrid(self):
for i in range(len(self.col1)):
self.col1[i].grid_remove()
self.col2[i].grid_remove()
def add_pin(self, pin):
self.col1.append(tkinter.Label(self, text="Comp %s:" % len(self.col1)))
self.col2.append(tkinter.Label(self, text=pin))
i = len(self.col1)
self.col1[i-1].grid(row=i, column=0)
self.col2[i-1].grid(row=i, column=1)
It seems to be somehow related to the threading, but I haven't been able to find any reason this should freeze. Any help is greatly appreciated!
Tkinter is not thread safe. If you do anything in a thread other than the main thread that touches a GUI object then you will get unpredictable results. Almost certainly, it is threading that is causing your problem, since you are trying to access widgets from the worker thread.