Associate idle keyboard events to widget text tkinter - python

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")

Related

Creating a bulleted list on a label widget using python tkinter

Is there any way to create a hanging indented list using the tkinter label widget? Note: Using propper standard bullets not * or -.
I have made a class that do what you want:
class BLabel(object):
b = "•"
def __init__(self,master):
import tkinter as tk
self.l = tk.Label(master)
def add_option(self,text):
if self.l.cget("text") == "":
self.l.config(text=self.b+" "+text)
else:
self.l.config(text=self.l.cget("text") +"\n"+ self.b + " "+text)
You can use it like:
lbal = BLabel(master=master)
lbal.add_option("Bullet1") #<-- adding item
lbal.add_option("Bullet2") #<-- adding item
lbal.l.pack() #<-- Packing
Here is an example code:
import tkinter as tk
root = tk.Tk()
class BLabel(object):
b = "•"
def __init__(self,master):
import tkinter as tk
self.l = tk.Label(master)
def add_option(self,text):
if self.l.cget("text") == "":
self.l.config(text=self.b+" "+text)
else:
self.l.config(text=self.l.cget("text") +"\n"+ self.b + " "+text)
lbal = BLabel(master=root)
lbal.add_option("Bullet1")
lbal.add_option("Bullet2")
lbal.l.pack()
Here is the output of above code:
In that way, you can use pack , place or grid. Example:
Grid:
lbal.l.grid(row=0,column=0)
Place:
lbal.l.place(x=0,y=0)
Pack:
lbal.l.pack()
you can use unicode code points, as a rough implementation:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class BulletLabel(tk.Label):
def __init__(self, master, *args, **kwargs):
text = kwargs.pop('text', '')
kwargs['text'] = self.bulletise(text)
tk.Label.__init__(self, master, *args, **kwargs)
def bulletise(self, text):
if len(text) == 0: # no text so no bullets
return ''
lines = text.split('\n')
parts = []
for line in lines: # for each line
parts.extend(['\u2022', line, '\n']) # prepend bullet and re append newline removed by split
return ''.join(parts)
def configure(self, *args, **kwargs):
text = kwargs.pop('text', '')
if text != '':
kwargs['text'] = self.bulletise(text)
tk.Label.configure(*args, **kwargs)
root = tk.Tk()
blabel = BulletLabel(root, text='one\ntwo\nthree')
blabel.pack()
root.mainloop()
You'd be better off with a message widget, which is a label widget designed to show text with multiple lines. For your bullets you can use a unicode string. For example:
import tkinter as tk
root = tk.Tk()
point = '\u2022'
msg = tk.Message(root, text='Hello\n%s World.' % point)
msg.pack()

Tkinter how to catch this particular key event

Problem Description
I have an application in Tkinter that uses a Listbox that displays search results. When I press command + down arrow key, I am putting the focus from the search field to the first item in the Listbox. This is exactly how I want the behaviour but instead for just the down arrow.
However, I am already binding the down arrow to this Listbox by self.bind("<Down>", self.moveDown). I can not understand why command + down works while simply down (to which I literally bind'ed it to) does not. Specifically the result of pressing the down arrow is the following
While pressing command + down gives the intended result:
How can I let down behave just like command + down, and what is the reason why command is required at all?
Code snippets
def matches(fieldValue, acListEntry):
pattern = re.compile(re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
root = Tk()
img = ImageTk.PhotoImage(Image.open('imgs/giphy.gif'))
panel = Label(root, image=img)
panel.grid(row=1, column=0)
entry = AutocompleteEntry(autocompleteList, panel, root, matchesFunction=matches)
entry.grid(row=0, column=0)
root.mainloop()
With AutocompleteEntry being:
class AutocompleteEntry(Tkinter.Entry):
def __init__(self, autocompleteList, df, panel, rdi, *args, **kwargs):
self.df = df
self.product_row_lookup = {key:value for value, key in enumerate(autocompleteList)}
temp = df.columns.insert(0, 'Product_omschrijving')
temp = temp.insert(1, 'grams')
self.result_list = pd.DataFrame(columns=temp)
self.panel = panel
self.rdi = rdi
# self.bind('<Down>', self.handle_keyrelease)
# Listbox length
if 'listboxLength' in kwargs:
self.listboxLength = kwargs['listboxLength']
del kwargs['listboxLength']
else:
self.listboxLength = 8
# Custom matches function
if 'matchesFunction' in kwargs:
self.matchesFunction = kwargs['matchesFunction']
del kwargs['matchesFunction']
else:
def matches(fieldValue, acListEntry):
pattern = re.compile('.*' + re.escape(fieldValue) + '.*', re.IGNORECASE)
return re.match(pattern, acListEntry)
self.matchesFunction = matches
Entry.__init__(self, *args, **kwargs)
self.focus()
self.autocompleteList = autocompleteList
self.var = self["textvariable"]
if self.var == '':
self.var = self["textvariable"] = StringVar()
self.var.trace('w', self.changed)
self.bind("<Right>", self.selection)
self.bind("<Up>", self.moveUp)
self.bind("<Down>", self.moveDown)
self.bind("<Return>", self.selection)
self.listboxUp = False
self._digits = re.compile('\d')
def changed(self, name, index, mode):
if self.var.get() == '':
if self.listboxUp:
self.listbox.destroy()
self.listboxUp = False
else:
words = self.comparison()
if words:
if not self.listboxUp:
self.listbox = Listbox(width=self["width"], height=self.listboxLength)
self.listbox.bind("<Button-1>", self.selection)
self.listbox.bind("<Right>", self.selection)
self.listbox.bind("<Down>", self.moveDown)
self.listbox.bind("<Tab>", self.selection)
self.listbox.place(x=self.winfo_x(), y=self.winfo_y() + self.winfo_height())
self.listboxUp = True
self.listbox.delete(0, END)
for w in words:
self.listbox.insert(END, w)
else:
if self.listboxUp:
self.listbox.destroy()
self.listboxUp = False
else:
string = self.get()
if '.' in string:
write_to_file(self, string)
def contains_digits(self, d):
return bool(self._digits.search(d))
def selection(self, event):
if self.listboxUp:
string = self.listbox.get(ACTIVE)
self.var.set(string + ' ')
self.listbox.destroy()
self.listboxUp = False
self.icursor(END)
def moveDown(self, event):
self.focus()
if self.listboxUp:
if self.listbox.curselection() == ():
index = '0'
print "ok"
else:
index = self.listbox.curselection()[0]
print "blah"
if index != END:
self.listbox.selection_clear(first=index)
print "noo"
if index != '0':
index = str(int(index) + 1)
self.listbox.see(index) # Scroll!
self.listbox.selection_set(first=index)
self.listbox.activate(index)
else:
print "not up"
def comparison(self):
return [w for w in self.autocompleteList if self.matchesFunction(self.var.get(), w)]
Both command+down and down should produce the same output excepted that down also types question mark  onto the entry which made the last letter typed is the question mark box.
This is because pressing command, your computer checks the option menu to see if there's a shortcut with that key, if there isn't any, it will not do anything. While tkinter registered the down button as being pressed, so the event was triggered.
In contrast, With out pressing command, the Entry first displays the value of "down", which there isn't any, then executes the event binding, what you can do is, in the event, remove the last letter of the Entry. You can do so by self.delete(len(self.get())-1) in your event. Or add a return 'break' at the end of your event to prevent it from being typed.
Unfortunately, it's really hard to understand your real problem because you've posted too much unrelated code and not enough related code. It seems to me that what you're trying to accomplish is to let the user press the down or up arrow while an entry has focus, and have that cause the selection in a listbox to move down or up. Further, it seems that part of the problem is that you're seeing characters in the entry widget that you do not want to see when you press down or up.
If that is the problem, the solution is fairly simple. All you need to do is have your binding return the string "break" to prevent the default binding from being processed. It is the default binding that is inserting the character.
Here is an example. Run the example, and press up and down to move the selection of the listbox. I've left out all of the code related to autocomplete so you can focus on how the event binding works.
import Tkinter as tk
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.entry = tk.Entry(self.root)
self.listbox = tk.Listbox(self.root, exportselection=False)
for i in range(30):
self.listbox.insert("end", "Item #%s" % i)
self.entry.pack(side="top", fill="x")
self.listbox.pack(side="top", fill="both", expand=True)
self.entry.bind("<Down>", self.handle_updown)
self.entry.bind("<Up>", self.handle_updown)
def start(self):
self.root.mainloop()
def handle_updown(self, event):
delta = -1 if event.keysym == "Up" else 1
curselection = self.listbox.curselection()
if len(curselection) == 0:
index = 0
else:
index = max(int(curselection[0]) + delta, 0)
self.listbox.selection_clear(0, "end")
self.listbox.selection_set(index, index)
return "break"
if __name__ == "__main__":
Example().start()
For a fairly thorough explanation of what happens when an event is triggered, see this answer: https://stackoverflow.com/a/11542200/7432
Again, leaving aside the autocomplete requirement, I came up with a solution that uses that standard available commands and events for Listbox and Scrollbar. <<ListboxSelect>> lets you capture changes in selection from any of the lists and align the others. In addition, the Scrollbar and Listbox callbacks are directed to a routing function that passes things on to all of the listboxes.
# updownmultilistbox.py
# 7/24/2020
#
# incorporates vsb to propagate scrolling across lists
#
import tkinter as tk
class Example(object):
def __init__(self):
self.root = tk.Tk()
self.listOfListboxes = []
# self.active_lb = None
self.vsb = tk.Scrollbar(orient='vertical', command=self.OnVsb)
self.vsb.pack(side='right', fill='y')
self.lb1 = tk.Listbox(self.root, exportselection=0,
selectmode= tk.SINGLE, yscrollcommand=self.vsb_set)
self.lb2 = tk.Listbox(self.root, exportselection=0,
selectmode=tk.SINGLE, yscrollcommand=self.vsb_set)
self.lb3 = tk.Listbox(self.root, exportselection=0,
selectmode=tk.SINGLE, yscrollcommand=self.vsb_set)
self.listOfListboxes.append(self.lb1)
self.listOfListboxes.append(self.lb2)
self.listOfListboxes.append(self.lb3)
for i in range(30):
self.lb1.insert("end", "lb1 Item #%s" % i)
self.lb2.insert("end", "lb2 Item #%s" % i)
self.lb3.insert("end", "lb3 Item #%s" % i)
self.lb1.pack(side="left", fill="both", expand=True)
self.lb2.pack(side="left", fill="both", expand=True)
self.lb3.pack(side="left", fill="both", expand=True)
for lb in self.listOfListboxes:
lb.bind('<<ListboxSelect>>', self.handle_select)
for lb in self.listOfListboxes:
lb.selection_set(0)
lb.activate(0)
self.listOfListboxes[0].focus_force()
def start(self):
self.root.title('updownmultilistbox')
self.root.mainloop()
def OnVsb(self, *args):
for lb in self.listOfListboxes:
lb.yview(*args)
def vsb_set(self, *args):
print ('vsb_set args: ', *args)
self.vsb.set(*args)
for lb in self.listOfListboxes:
lb.yview_moveto(args[0])
def handle_select(self, event):
# set evey list to the same selection
print ('select handler: ', event, event.widget.curselection())
# self.active_lb = event.widget
for lb in self.listOfListboxes:
if lb != event.widget:
lb.selection_clear(0, 'end') # have to avoid this for the current widget
lb.selection_set(event.widget.curselection())
lb.activate(event.widget.curselection())
if __name__ == "__main__":
Example().start()

python 3.5 pass object from one class to another

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).

Why are my spaces registered as { }

I made this simple program that translates from English to a completely different one (randomized English), when it displays the translation it shows spaces as { }. Any suggestions?
from tkinter import *
english = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t',
'u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','!',
'\"','#','$','%','&','\'','\'','(',')','*','+',',','-','.','/',':',';','?','#','[','\\',']','^','_','`','{','|','}','~',' ', ',','\t','\n','\r','\x0b','\x0c']
potatish =['0','1','2','3','4','5','6','7','8','9','a','v','l','d','e','z','j','s','i','x','q','u','f','b','o','y','c','m','t',
'k','p','g','h','n','r','w','A','V','L','D','E','Z','J','S','I','X','Q','U','F','B','O','Y','C','M','T','K','P','G','H','N','R','W','!',
'\"','#','$','%','&','\'','\'','(',')','*','+',',','-','.','/',':',';','?','#','[','\\',']','^','_','`','{','|','}','~',' ', ',','\t','\n','\r','\x0b','\x0c']
class Program(Frame):
""" A Program to translate """
def __init__ (self, master):
"""Initialise the frame"""
Frame.__init__(self, master)
self.grid()
self.create_widgets()
def create_widgets(self):
"""Buttons!!!"""
#Text
self.text = Label(self, text = "Please enter some text:")
self.text.grid()
#Field
self.user = Entry(self)
self.user.grid()
#Button
self.translate = Button(self, text = "Translate")
self.translate.grid()
self.translate["command"] = self.update_console
#Output
self.output = Label(self, text = "Output:\n")
self.output.grid()
#finished
self.finished = Label(self, text = "")
self.finished.grid()
def update_console(self):
"""Updates output"""
self.trans = self.translatedef()
self.finished["text"] = self.trans
def translatedef(self):
f = []
i = -1
j = -1
text = self.user.get()
while i != -2:
i+=1
try:
l = english.index(text[i])
f.append(potatish[l])
except:
while j != -2:
j+=1
try:
return f
except:
break
break
root = Tk()
root.title("Potatish")
root.geometry("500x300")
app = Program(root)
root.mainloop()
You are setting the text of the Label to a list, not a string. The empty element of that list is being displayed as {} by Tkinter.
To correct it, form a string from your list before you send it to the Label:
def update_console(self):
"""Updates output"""
self.trans = ''.join(self.translatedef())
self.finished["text"] = self.trans

Having multiple-page GUI in Python with several buttons

I am currently trying to implement a GUI for a task at work. I have found some open source code (see below) where we make a GUI having 3 pages where one can go between the pages using next/previous buttons. If you run the code you'll see what each button and so on is indended to do.
However, when you run the code and click the "count++" button, the overall count increases by one and not the count for the individual page (e.g. being on page 1 and clicking the count++ 4 times, still makes the count for page 2 or 3, 4 as well, and not zero). The same problem occurs when I try to update the text in each of the textboxes on each page (supposed to be number of clicks), as it won't update. I am not sure how to actually get to the textframe for each individual page.
Any suggestions on where to go from here? In the long run I would like to have scroll-down menus where the selections will be put onto each individual text-frame.
Thanks,
import ttk
from Tkinter import *
import tkMessageBox
class Wizard(object, ttk.Notebook):
def __init__(self, master=None, **kw):
npages = kw.pop('npages', 3)
kw['style'] = 'Wizard.TNotebook'
ttk.Style(master).layout('Wizard.TNotebook.Tab', '')
ttk.Notebook.__init__(self, master, **kw)
self._children = {}
self.click_count = 0
self.txt_var = "Default"
for page in range(npages):
self.add_empty_page()
self.current = 0
self._wizard_buttons()
def _wizard_buttons(self):
"""Place wizard buttons in the pages."""
for indx, child in self._children.iteritems():
btnframe = ttk.Frame(child)
btnframe.pack(side='left', fill='x', padx=6, pady=4)
txtframe = ttk.Frame(child)
txtframe.pack(side='right', fill='x', padx=6, pady=4)
nextbtn = ttk.Button(btnframe, text="Next", command=self.next_page)
nextbtn.pack(side='top', padx=6)
countbtn = ttk.Button(txtframe, text="Count++..", command=self.update_click)
countbtn.grid(column=0,row=0)
txtBox = Text(txtframe,width = 50, height = 20, wrap = WORD)
txtBox.grid(column=1,row=0)
txtBox.insert(0.0, self.txt_var)
rstbtn = ttk.Button(btnframe, text="Reset count!", command=self.reset_count)
rstbtn.pack(side='top', padx=6)
if indx != 0:
prevbtn = ttk.Button(btnframe, text="Previous",
command=self.prev_page)
prevbtn.pack(side='right', anchor='e', padx=6)
if indx == len(self._children) - 1:
nextbtn.configure(text="Finish", command=self.close)
def next_page(self):
self.current += 1
def prev_page(self):
self.current -= 1
def close(self):
self.master.destroy()
def add_empty_page(self):
child = ttk.Frame(self)
self._children[len(self._children)] = child
self.add(child)
def add_page_body(self, body):
body.pack(side='top', fill='both', padx=6, pady=12)
def page_container(self, page_num):
if page_num in self._children:
return self._children[page_num]
else:
raise KeyError("Invalid page: %s" % page_num)
def _get_current(self):
return self._current
def _set_current(self, curr):
if curr not in self._children:
raise KeyError("Invalid page: %s" % curr)
self._current = curr
self.select(self._children[self._current])
current = property(_get_current, _set_current)
def update_click(self):
self.click_count += 1
message = "You have clicked %s times now!" % str(self.click_count)
tkMessageBox.showinfo("monkeybar", message)
self.txt_var = "Number of clicks: %s" % str(self.click_count) #this will not change the text in the textbox!
def reset_count(self):
message = "Count is now 0."
#ctypes.windll.user32.MessageBoxA(0, message, "monkeybar", 1)
tkMessageBox.showinfo("monkeybar", message)
self.click_count = 0
def combine_funcs(*funcs):
def combined_func(*args, **kwargs):
for f in funcs:
f(*args, **kwargs)
return combined_func
def demo():
root = Tk()
nbrpages = 7
wizard = Wizard(npages=nbrpages)
wizard.master.minsize(400, 350)
wizard.master.title("test of GUI")
pages = range(nbrpages)
for p in pages:
pages[p] = ttk.Label(wizard.page_container(p), text='Page %s'%str(p+1))
wizard.add_page_body(pages[p])
wizard.pack(fill='both', expand=True)
root.mainloop()
if __name__ == "__main__":
demo()
Your update_click method operate on the attribute click_count of your wizard. If you want different count, you could either create a class for your pages and thus, each object would manage its own count, or manage several counter, for instance in a list, the same way you handle your _children list.
For the former case, you can create a page class inheriting ttk.Frame with the body of your _wizard_buttons loop as constructor. In the later case, you could try something like this
class Wizard(object, ttk.Notebook):
def __init__(self, master=None, **kw):
[...]
#replace self.click_count = 0 with
self.click_counters = [0 for i in range(npages)]
def update_click(self):
self.click_counters[self.current] += 1
# and so on...
Regarding the Text widget update, you can not handle it though a variable, it works with Entry(one line text field) but not on Text (multiline, rich text field). If you continue with Text, the usual recipe to what you seems to want is
text.delete(1.0, END)
text.insert(END, content)

Categories

Resources