How to align text to the right in ttk Treeview widget? - python

I am using a ttk.Treeview widget to display a list of Arabic books. Arabic is a right-to-left language, so the text should be aligned to the right.
The justify option that is available for Label and other ttk widgets does not seem to work for Treeview.
Does anyone know how to do this?

The ttk.Treeview widget has an anchor option you can set for each column. To set the anchor of a column to the right side use:
ttk.Treeview.column(column_id, anchor=Tkinter.E)

In addition to the #fhdrsdg answer, here you have a simple example of use:
# for python 3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
def show_info():
messagebox.showinfo("More info", "First column represents the subject" \
" and the second represents its corresponding " \
"current number of tagged questions on Stack Overflow.")
root = tk.Tk()
tree = ttk.Treeview(root, columns=("Tags"), height=6)
subjects = {"Tkinter": "8,013",
"Python": "425,865",
"C++": "369,851",
"Java": "858,459"}
for subject in subjects.keys():
tree.insert("", "end", text=subject, values=(subjects[subject]))
tree.column("Tags", anchor="e")
tree.pack(fill="both", expand=True)
informer = tk.Button(root, text="More info", command=show_info)
informer.pack(side="bottom")
root.mainloop()
If you need more help on how to use ttk.Treeview widgets, have a look at this reference by The New Mexico Tech or this tutorial at TkDocs.

Related

How to make a questionnaire scrollable in Python tkinter?

I am new to Python and have been working on a school project. The idea is to take the Alcohol Use Disorders Identification Test (AUDIT) and let the user know whether or not he should seek professional help (if he scores 14+).
I got stuck today. I'm using tkinter, and a set of 10 questions with radioboxes. However, I can't proceed since the questions do not fit on the screen and for the love of God I can't make it scrollable. I've tried everything I could find, setting a class, trying to work with a frame or a canvas, etc.
The questions look like this:
"""imports"""
import tkinter as tk
app = tk.Tk()
app.title("AUDIT")
from tkinter import *
from tkinter import ttk
canvas = tk.Canvas(app)
scroll_y = tk.Scrollbar(app, orient="vertical", command=canvas.yview)
frame = tk.Frame(canvas)
# group of widgets
"""question 1"""
o1 = Text(app, height = 1, width =100)
o1.insert(INSERT, "Jak často pijete alkoholické nápoje (včetně piva)")
o1.insert(END, "?")
o1.pack()
OTAZKA1 = [
("Nikdy", "0"),
("Jednou za měsíc či méně často", "1"),
("2-4x za měsíc", "2"),
("2-3x týdně", "3"),
("4x nebo vícekrát týdně", "4"),
]
o1 = StringVar()
o1.set("0") #initialize
for text, mode in OTAZKA1:
a = Radiobutton(app, text = text, variable = o1, value = mode)
a.pack(anchor=W)
"""question 2"""
o2 = Text(app, height = 2, width =100)
o2.insert(INSERT, "Kolik standardních sklenic alkoholického nápoje vypijete během typického dne, kdy pijete")
o2.insert(END, "?")
o2.pack()
OTAZKA2 = [
("Nejvýše 1", "0"),
("1,5 až 2", "1"),
("2,5 až 3", "2"),
("3,5 až 5", "3"),
("5 a více", "4"),
]
o2 = StringVar()
o2.set("0") #initialize
for text, mode in OTAZKA2:
b = Radiobutton(app, text = text, variable = o2, value = mode)
b.pack(anchor=W)
All the way up to question 10. I know this may not the the most efficient way but it's the first code I've ever written.
How can I add the scroll bar to make all of the questions visible?
Thank you, have a great day.
You need to:
1) use Canvas.create_window(x, y, window=widget) method
2) set the canvas scrollregion attribute to Canvas.bbox("all") (but before you must update the window, otherwise you'll get the wrong result"
3) attach the scrollbar to the canvas the right way (see the code below)
I can also give you some advice:
1) It's better to use OOP for the GUI. For the small apps, it may not be the problem, but if your app is more complex and big, you'll be much easier to work with the OOP code, than with the procedural one.
2) Did you notice your code has a lot of similar parts? Remember: in almost all cases, there is no need to create a lot of similar variables manually (it's also called the DRY principle (don't repeat yourself). You may generate everything automatically using loops. Sometimes you need to use the enumerate function from Python built-ins. If you won't use the DRY principle, you will spend much more time for the development.
3) It's better to avoid overriding already imported things by importing another module. In your code, you do:
from tkinter import *
from tkinter.ttk import *
Both of these modules contain Button, Entry, Scrollbar, Radiobutton etc. So you may get everything mixed up. It's better to do like this:
from tkinter import *
from tkinter.ttk import only, what, you, need
or even better:
import tkinter as tk
import tkinter.ttk as ttk # or: from tkinter import ttk
4) You may adjust the size of everything in Canvas manually, and deny resizing of the window (either by width and/or height).
Here is the rewritten code:
from tkinter import *
from tkinter import messagebox
from tkinter.ttk import Button, Scrollbar, Radiobutton
# Create questions as a list of dictionaries
# The GUI will be generated automatically
questions = [
{"question": "Jak často pijete alkoholické nápoje (včetně piva)?",
"answers": ("Nikdy", "Jednou za měsíc či méně často",
"2-4x za měsíc", "2-3x týdně",
"4x nebo vícekrát týdně")},
{"question": "Kolik standardních sklenic alkoholického nápoje vypijete během typického dne, kdy pijete?",
"answers": ("Nejvýše 1", "1,5 až 2", "2,5 až 3", "3,5 až 5", "5 a více")},
]
class App(Tk):
def __init__(self):
super().__init__()
self.title("AUDIT") # set the window title
self.resizable(False, True) # make window unresizable by width
canv_frame = Frame(self) # create the canvas frame
# create the Canvas widget
# highlightthickness=0 removes the black border when the canvas gets focus
self.canv = Canvas(canv_frame, highlightthickness=0, width=420)
# add scrolling when mouse wheel is rotated
self.canv.bind_all("<MouseWheel>",
lambda event: self.canv.yview_scroll(-1 * (event.delta // 120), "units"))
self.canv.pack(fill=BOTH, expand=YES, side=LEFT) # pack the Canvas
# Create a scrollbar
# command=self.canv.yview tells the scrollbar to change the canvas yview
# and canvas's yscrollcommand=self.yscrollbar.set tells the canvas to update
# the scrollbar if canvas's yview is changed without it.
self.yscrollbar = Scrollbar(canv_frame, command=self.canv.yview)
self.canv["yscrollcommand"] = self.yscrollbar.set
self.yscrollbar.pack(fill=Y, side=LEFT) # pack the Scrollbar
for question_id, question in enumerate(questions, 1):
qaframe = Frame(self.canv) # create the question-answers (QA) frame
text = Text(qaframe, width=50, height=3) # create the Text widget for question
text.insert(END, question["question"]) # insert the question text there
text.pack(fill=X) # pack the text widget
aframe = Frame(qaframe) # create the answers frame
# Create the question variable and add it to the variables list
question_var = IntVar(self)
question["variable"] = question_var
# create the radiobuttons
for answer_id, answer in enumerate(question["answers"]):
Radiobutton(aframe, variable=question_var, text=answer, value=answer_id).pack()
aframe.pack(fill=Y) # pack the answers frame
self.canv.create_window(210, question_id * 175, window=qaframe) # insert the QA frame into the Canvas
canv_frame.pack(fill=BOTH, expand=YES) # pack the canvas frame
Button(self, text="Submit", command=self.submit).pack(fill=X) # create the "Submit" button
self.update() # update everything to get the right scrollregion.
self.canv.configure(scrollregion=self.canv.bbox("all")) # set the canvas scrollregion
def submit(self):
sum_ = 0 # initially, the sum_ equals 0
for question in questions:
sum_ += question["variable"].get() # and then, we add all the questions answers
messagebox.showinfo("Result", "Your result is %s!" % sum_) # show the result in an info messagebox
if __name__ == "__main__": # if the App is not imported from another module,
App().mainloop() # create it and start the mainloop

Python Tkinter Treeview fixed width with variable SQL query

Apologies if this was answered before but I cannot find anything out there with these specifics.
I'm finishing a query tool for the team I'm working with where they can choose the fields to query a database and a search a string (along with other options).
The result is being sent to a Tkinter TreeView widget (found this to be the best approach in terms of events control and visualization).
I have a main issue which is to constrain the size of the Treeview to a certain width, no matter how many fields the user chooses. Since the complete GUI is non scalable I want the Treeview to have a max size.
I have tried to include the width when defining the columns, but it seems to bypass that.
The Treeview is a child of a LabelFrame and the positioning is defined with grid.
Here a code sample of what I'm doing to set the TreeView (since this is a company application I have to be careful with disclosing some field names):
CoreQuery.groupResults = LabelFrame(CoreQuery.root, text="Query Result", padx=5, pady=5, height=470,width=960)
CoreQuery.tree = ttk.Treeview(CoreQuery.groupResults, selectmode='browse')
CoreQuery.tree.bind("<Double-1>", CoreQuery.OnTreeDoubleClick)
CoreQuery.scrollbar_horizontal = ttk.Scrollbar(CoreQuery.root, orient='horizontal', command=CoreQuery.tree.xview)
CoreQuery.scrollbar_vertical = ttk.Scrollbar(CoreQuery.root, orient='vertical', command=CoreQuery.tree.yview)
CoreQuery.tree.config(height=18, xscrollcommand=CoreQuery.scrollbar_horizontal.set, yscrollcommand=CoreQuery.scrollbar_vertical.set)
CoreQuery.tree.grid(row=0, sticky="w")
CoreQuery.scrollbar_horizontal.grid(row=1,column=0, sticky='ns')
CoreQuery.scrollbar_vertical.grid(row=0, column=2, sticky='ew')
CoreQuery.scrollbar_horizontal.configure(command=CoreQuery.tree.xview)
CoreQuery.scrollbar_vertical.configure(command=CoreQuery.tree.yview)
CoreQuery.tree.configure(yscroll=CoreQuery.scrollbar_vertical, xscroll=CoreQuery.scrollbar_horizontal)
The Following is the method that receives the SQL query result and places the data in the TreeView:
def ScreenPrintResults(header,rows):
columns=len(header)
minColumnsize=math.ceil(965/columns)
#Clear Treeview
CoreQuery.tree.delete(*CoreQuery.tree.get_children())
for values in rows:
values[0] = str(values[0]).replace(".0", "")
if (values[0].isdigit()):
values[0] = int(values[0])
auxCount=0
CoreQuery.tree['columns']=header
for value in header:
CoreQuery.tree.heading(value, text=str(value))
CoreQuery.tree.column(value, stretch=tk.NO)
for items in rows:
if auxCount==0:
CoreQuery.tree.column('#0', width=30, stretch=tk.NO)
else:
CoreQuery.tree.column(value, width=minColumnsize)
CoreQuery.tree.insert('',tk.END,auxCount+1,text=str(auxCount+1),
values=list(items))
auxCount=auxCount+1
CoreQuery.updateMessage.config(foreground="Black", font="Verdana 10 bold")
CoreQuery.message.set("...")
Is there any kind of limitation I can add the width of the TreeView so that it does not go beyond a certain width?
Or do I need to split the available width to the number of columns I get from the query?
I honestly don't care if all the information is on screen, hence I placed the scrollbars.
Here is a screenshots of the issue:
Thanks for the help!
EDIT: Changed the For loop code and added more clarifying screenshots of the issue
The example below demonstrate how to set width for the columns the minwith and maxwidth which takes integers. Also you can set height of the row using the rowheight attribute for your text to display well in the tree.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
tree = ttk.Treeview(root, column=("col1","col2"), show="headings")
style = ttk.Style(root)
style.configure('my.Treeview', rowheight=50)
tree.configure(style='my.Treeview')
tree.heading("#0", text="COMPANY NAME")
tree.heading("#1", text="DEPARTMENT")
tree.heading("#2", text="BRANCH")
tree.column("#0", stretch=tk.NO, minwidth=100, width=000)
tree.column("#1", stretch=tk.NO, minwidth=100, width=400)
tree.column("#2", stretch=tk.NO, minwidth=100, width=400)
tree.insert("", tk.END, values=("Am using python version 3.6.1 \n on windows machine ", "This an example Tkinter Treeview in Python, which is from \nttk class make"))
tree.insert("", tk.END, values=("This an example Tkinter Treeview in Python, which is from \nttk class make sure #import ttk\n also from tkinter import *", "Apologies if this was answered before but I cannot find "))
tree.pack()
root.mainloop()

Python Tkinter Entry pad text

How could I pad the entry widget so it does not start writing right at the border of the widget? Visually, having a little space from the entry widget border.
My progress:
entry_widget.bind('<FocusIn>', lambda f: entry_widget.insert(0, ' '))
That adds an empty space when the user clicks the widget but there are multiple issue with this solution:
When clicking out and clicking back in, it will add another space on top of the empty space or any text the user had filled in. I considered clearing the entry widget on FocusOut but that would also clear all the text that the user might have written.
The user can delete the inserted space
when getting the content, there is the additional space at the beginning. Though this is a small problem which can be solved by removing the first character of the content.
And there might be more issues which I did not account for.
I think the way where my code is heading is bad, therefore I am asking if anyone has any idea how to 'properly' pad the entry widget?
I'm not aware a native way of adjusting the Entry's padding, but here's one way to get something like it. Make the entry's border invisible by giving it a FLAT style, and embed the entry in a Frame that acts as the entry's border. Then you can specify the padding by adjusting the entry's borderwidth. Example:
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root, borderwidth=5, relief=tk.SUNKEN)
frame.pack()
entry = tk.Entry(frame, borderwidth=15, relief=tk.FLAT)
entry.pack()
root.mainloop()
Result:
I had this problem, and after a bit of digging, I found out you can internally pad ttk's Entry widget:
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
ttk.Style().configure('pad.TEntry', padding='5 1 1 1')
ent = ttk.Entry(root, style='pad.TEntry')
ent.pack()
root.mainloop()
Where '5 1 1 1' is 'ipad_left ipad_top ipad_right ipad_bottom' in pixels (default is '1 1 1 1'). Using the 'TEntry' argument does change the style of every ttk.Entry however, so if you wanted to avoid that you could create a new style:
ttk.Style().configure('pad.TEntry', padding='5 1 1 1')
ent1 = ttk.Entry(root, style='pad.TEntry')
ent2 = ttk.Entry(root)
Where ent1 would have the extra padding and ent2 would not.
I figured out the 'padding' option existed by running print(Style().configure('TEntry')) which returns all the style options for TEntry (in this case it's just 'padding'). This brings up a problem though; ttk's widgets often don't have a lot of customization options readily available (TEntry is missing background, borderwidth, relief, etc) meaning you'd have to 'create' them. See these links for more information: ttk widgets || Using and customizing ttk styles || ttk.Entry information || (example) adding fieldbackground to ttk.Entry
using tkinter 8.6U
entry.config(padx = 15)
Here is a simple example of how you can force a space to start with and then with a bind keep the space while the user types.
import tkinter as tk
root = tk.Tk()
def check_for_space(event):
if entry.get() == "":
entry.insert(0, " ")
elif entry.get()[0] != " ":
entry.insert(0, " ")
else:
pass
entry = tk.Entry(root)
entry.insert(0, " ")
entry.pack()
entry.bind("<Key>", check_for_space)
root.mainloop()
Try this Custom made entry
from tkinter import Entry,Tk
class MyEntry(Entry):
def __init__(self,root,placeholder,*args,**kw):
self.root = root
self.placeholder = placeholder
Entry.__init__(self,self.root,*args,**kw)
self.add()
self.bind("<FocusIn>",self.focusin)
self.bind("<FocusOut>",self.focusout)
def add(self):
if self.get()=='':
self.insert(0,self.placeholder)
def focusin(self,event=None):
if self.get()=='' or self.get()==self.placeholder:
self.delete(0,END)
def focusout(self,event=None):
if self.get()=='':
self.add()
root = Tk()
a = MyEntry(root,placeholder='<Name>')
a.pack()
b = MyEntry(root,placeholder='<Contact>')
b.pack()

Insert widget (Label) into Listbox in Tkinter

Hi people is it possible to insert Label widget into Listbox in tkinter instead of some str value and attach to it a scrollbar?
Based on the documentation online, No
The listbox can only contain text items...
for further details on the ListBox: http://effbot.org/tkinterbook/listbox.htm
Don't ask me to explain it because I really don't understand it yet, but this should work:
import tkinter as tk # python3
root = tk.Tk()
myList = tk.Listbox(root)
myText = "A list item"
myList.insert(tk.END, myText)
myList.pack()
lbl = tk.Label(myList, text=myText, anchor="w", font=("Helvetica", "24"))
lbl.pack(side="top", fill="x", anchor="w")
root.mainloop()

tkinter: simple scrollable text

Question
I'm trying to make a 'ttk Label' which a) is 20 pixels high by 300 pixels wide, b) is scrollable (in this case horizontally), and c) uses the simplest code possible within reason (except for the fact that the text and scrollbar are both within a frame*). I've found stackoverflow to be helpful in describing the processes I need to go through (put the label in a frame, put the frame in a canvas, put the scroll bar next to or underneath the canvas and 'bind' them together somehow), but despite looking at a fair few docs and stackoverflow questions, I can't figure out why my code isn't working properly. Please could someone a) update the code so that it satisfies the conditions above, and b) let me know if I've done anything unnecessary? Thanks
*the frame will be going in a project of mine, with text that is relevant
Current code
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
myframe_outer = ttk.Frame(root)
mycanvas = tk.Canvas(myframe_outer, height=20, width=300)
myframe_inner = ttk.Frame(mycanvas)
myscroll = ttk.Scrollbar(myframe_outer, orient='horizontal', command=mycanvas.xview)
mycanvas.configure(xscrollcommand=myscroll.set)
myframe_outer.grid()
mycanvas.grid(row=1, sticky='nesw')
myscroll.grid(row=2, sticky='ew')
mycanvas.create_window(0, 0, window=myframe_inner, anchor='nw')
ttk.Label(myframe_inner, text='test ' * 30).grid(sticky='w')
root.mainloop()
Edit:
Current result
Answer
Use a readonly 'entry' widget - it looks the same as a label, and doesn't need to be put in a canvas.
Code
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
mytext = tk.StringVar(value='test ' * 30)
myframe = ttk.Frame(root)
myentry = ttk.Entry(myframe, textvariable=mytext, state='readonly')
myscroll = ttk.Scrollbar(myframe, orient='horizontal', command=myentry.xview)
myentry.config(xscrollcommand=myscroll.set)
myframe.grid()
myentry.grid(row=1, sticky='ew')
myscroll.grid(row=2, sticky='ew')
root.mainloop()
Result

Categories