How to put a urwid.BigText into a urwid.ListBox - python

I keep getting an error AttributeError: 'BigText' object has no attribute 'rows' when trying to have a BigText at the top of a ListBox. I understand that the BigText is a "fixed" widget, while ListBox expects a "flow" widget, but I can't seem to get my program to take the BigText no matter what I try. Here's an exhaustive example of what I've attempted:
head_title = urwid.BigText(('banner', u'Header'), urwid.HalfBlock5x4Font())
head = urwid.Filler(head_title)
# head = urwid.AttrMap(head, 'banner')
# head = urwid.AttrMap(head, 'streak')
head = urwid.BoxAdapter(head, 3)
print head
# this gives me `<BoxAdapter flow widget <Filler box widget <BigText fixed widget>> height=3>`
body = [head, urwid.Divider()]
return urwid.ListBox(body)
Thanks!

BigText is a of 'fixed' sizing. That means that both the width and the height of the widget is defined by the widget. ListBox only accepts widgets of 'flow' sizing. This means that the width will be decided by the container (in this case the ListBox). So, you have to first convert he 'fixed' widget to a 'flow' widget. This can be done with the Padding decoration widget by setting the width property to 'clip'.
See here for a full example:
import urwid
def show_or_exit(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
return key
head = urwid.ListBox(urwid.SimpleFocusListWalker([
urwid.Padding(
urwid.BigText(('banner', "Hello world"), urwid.HalfBlock5x4Font()),
width='clip')
]))
loop = urwid.MainLoop(head, unhandled_input=show_or_exit)
loop.run()

Related

is there a way to jump from one entry field to another entry field in tkinter?

I was trying to make billing software with multiple entries in python, so it's hard to move from one entry field to another entry field using a mouse, I just wanted it to jump from the customer name field to customer phone no entry was filed when I press "enter" key but it's not working how can I make it work
from Tkinter import*
root = Tk()
root.geometry('1350x700+0+0')
root.resizable(width=False,height=False)
root.title('Billing App')
def jump_cursor(event):
customer_PhoneNo_entry.icursor(0)
customer_detail_frame=Frame(root,bd=10,highlightbackground="blue", highlightthickness=2)
customer_detail_frame.place(x=0,y=10,width=1350,height=100)
customer_detail_lbl=Label(customer_detail_frame,text="customer detail",font=('verdana',10,'bold'))
customer_detail_lbl.grid(row=0,column=0,pady=10,padx=20)
customer_name_lbl=Label(customer_detail_frame,text="customer name",font=('verdana',10,'bold'))
customer_name_lbl.grid(row=1,column=0,pady=10,padx=20)
customer_name_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Name)
customer_name_entry.grid(row=1,column=1,pady=10,padx=20)
customer_phoneno_lbl=Label(customer_detail_frame,text="phone no.",font=('verdana',10,'bold'))
customer_phoneno_lbl.grid(row=1,column=2,pady=10,padx=20)
customer_PhoneNo_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Ph_No)
customer_PhoneNo_entry.grid(row=1,column=3,pady=10,padx=20)
customer_PhoneNo_entry.bind("<Return>",jump_cursor)
This answer helped me with the same problem.
First, get all children of customer_detail_frame which are Entry,
using winfo_children()
Second, bind each child to <Return>, (in which lambda can be used
to create another function).
Last, define the jump_cursor function, such that it switches the
focus to the desired widget, using focus_set().
Here is what changes in your code would look like:
from tkinter import*
root = Tk()
root.geometry('1350x700+0+0')
root.resizable(width=False,height=False)
root.title('Billing App')
Customer_Name, Customer_Ph_No = StringVar(), StringVar()
customer_detail_frame=Frame(root,bd=10,highlightbackground="blue", highlightthickness=2)
customer_detail_frame.place(x=0,y=10,width=1350,height=100)
customer_detail_lbl=Label(customer_detail_frame,text="customer detail",font=('verdana',10,'bold'))
customer_detail_lbl.grid(row=0,column=0,pady=10,padx=20)
customer_name_lbl=Label(customer_detail_frame,text="customer name",font=('verdana',10,'bold'))
customer_name_lbl.grid(row=1,column=0,pady=10,padx=20)
customer_name_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Name)
customer_name_entry.grid(row=1,column=1,pady=10,padx=20)
customer_phoneno_lbl=Label(customer_detail_frame,text="phone no.",font=('verdana',10,'bold'))
customer_phoneno_lbl.grid(row=1,column=2,pady=10,padx=20)
customer_PhoneNo_entry=Entry(customer_detail_frame,width=20,textvariable = Customer_Ph_No)
customer_PhoneNo_entry.grid(row=1,column=3,pady=10,padx=20)
def jump_cursor(event, entry_list, this_index):
next_index = (this_index + 1) % len(entry_list)
entry_list[next_index].focus_set()
entries = [child for child in customer_detail_frame.winfo_children() if isinstance(child, Entry)]
for idx, entry in enumerate(entries):
entry.bind('<Return>', lambda e, idx=idx: jump_cursor(e, entries, idx))
root.mainloop()

why command function of tkinter radiobuttons in a loop only works for the last iteration properly

I hope I can explain the problem properly enough... so basically, I am in the progress of creating a Gui.
In this extracted piece of code I want to iterate over my port_str list and make a frame with the name of each element and some radio buttons in the frame;
and for three of these buttons R1_wire, R2_wire, etc. I call another function sel_wire_1 with 'command' to either make the buttons (e.g.R1_discipline) active or disabled.
The problem is now that if I want to print the value of the selected radio button in one of the first frames it always shows me the name of the last element in the list and the value(either 1 or two) of the last element of the port_str list and this depended on activation in sel_wire__1 also only works for the last frame (the last element of the list port_str). Also this dependend selection of sel_wire_1 does inly work for the last created frame.
So I have several identical frames in front of me with different 'labels'(out of the list) but the command function only works correctly for the last frame(last element).
I tried so many things but I still don't know how to fix that as I'm new to tkinter and not that good with OOP. I did exactly the same thing in functional programming and it worked perfectly. So what is the problem in this oop case then?
Thanks to everyone who can help in advance
class MSM:
def __init__(self,top):
#create main frame, holds everything
self.main_frame= Frame(self.top,width = 50, height = 100, bd = 1)
self.main_frame.pack(fill=BOTH, expand=1)
#creat a canvas
self.my_canvas = Canvas(self.main_frame)
self.my_canvas.pack_propagate(0)
self.my_canvas.pack(side = LEFT, fill = BOTH, expand = 1)
self.second_frame = Frame(self.my_canvas)
port_str = [port1,port2,port3,port4,port5]
for port in port_str:
self.pins(port) #here i call the pins() function in a for loop to create s frame with some radiobuttons for each element of the port_str list
def pins(self,port):
#here im defining a frame and the label of each frame which are elements of the port_str list
self.frame_portdetails = Frame(self.frame_portdetails_top)
self.frame_portdetails.pack(fill= 'both', expand = 1)
self.var_wiretype = IntVar()
self.R1_wiretype = Radiobutton(self.frame_portdetails, text= ("wire",port),variable=self.var_wiretype, value=1,command= self.sel_wiretype_1)
self.R1_wiretype.grid( row=1,column=1,sticky='W')
self.R2_wiretype = Radiobutton(self.frame_portdetails, text=("wreal",port),variable=self.var_wiretype, value=2,command=self.sel_wiretype_1)
self.R2_wiretype.grid( row=1,column=2,sticky= 'W' )
#discipline here just more buttons are being defined
self.var_discipline = IntVar()
self.R1_discipline = Radiobutton(self.frame_portdetails, text="logic", variable=self.var_discipline, value=1 )
self.R1_discipline.grid(row=2,column=1,sticky='W')
self.R2_discipline = Radiobutton(self.frame_portdetails, text="vreal", variable=self.var_discipline, value=2)
self.R2_discipline.grid(row=2,column=2,sticky='W')
self.R3_discipline = Radiobutton(self.frame_portdetails, text="ireal", variable=self.var_discipline, value=3)
self.R3_discipline.grid(row=2,column=3,sticky='W')
self.R4_discipline = Radiobutton(self.frame_portdetails, text="other", variable=self.var_discipline, value=4 )
self.entry1 = ttk.Entry (self.frame_portdetails, width = 10 )
self.entry1.grid(row=3,column=2)
self.R4_discipline.grid(row=3,column=1,sticky='W')
def sel_wiretype_1(self): #this should either make a button active or
#disabled but is seems this is only being applied to the last element
#of the list/ the last frame(and the buttons inside) that is being
#created
if(self.var_wiretype.get()==1):
self.R1_discipline['state']='active'
self.R2_discipline['state']='disabled'
self.R3_discipline['state']='disabled'
self.var_discipline.set(1)
else:
self.R1_discipline['state']='disabled'
self.R2_discipline['state']='active'
self.R3_discipline['state']='active'
self.var_discipline.set(2)
if __name__ == "__main__":
root = tk.Tk()
app = MSM(root)
root.mainloop(

.set in function is not being found in other function so it's creating an error tkinter python

I'm trying to create a GUI, in the nav menu you can click a cascade option to open another window where you can click roll to generate a set of numbers. It comes up with error. I think it's because the function is called from another function I just don't know how to get that function to call it/ if there is any other ways to fix this. I've tried global functions and looking it up but haven't found anything other than using classes so far, which I don't know how to do.
line 147, in totalRolls
txtresultsOut.set(totalRollResults)
NameError: name 'txtresultsOut' is not defined
Here is the code that is relevant to it. I've called the function to skip having to input all the other code for the main gui window.
def rollSix():
s = 0
numbers = [0,0,0,0]
for i in range(1,5):
numbers[s] = randrange(1,7)
s += 1
numbers.remove(min(numbers))
Result = sum(numbers)
totalRollResults.append(Result)
def totalRolls():
rollOne()
rollTwo()
rollThree()
rollFour()
rollFive()
rollSix()
txtresultsOut.set(totalRollResults)
def rollw():
rollWindow = tix.Tk()
rollWindow.title("Dice Rolls")
diceLabel = Label(rollWindow, text = "Click Roll for your Stats")
diceLabel.grid(row = 0, column = 0)
rollBtn = Button(rollWindow, text = "Roll Stats", command = totalRolls)
rollBtn.grid(row = 1, column = 0)
txtresultsOut = StringVar()
resultsOut = Entry(rollWindow, state = "readonly", textvariable = txtresultsOut)
resultsOut.grid(row = 2, column = 0)
rollw()
first of all I would NOT recommend using StringVar(). You can use the .get() method of Entry to obtain the value inside the same. Try this way and make a global declaration of the Entry whose values you want to get in other functions.
EDIT------------
#you can use the following code to make your entry active to be edited.
entry.configure(state='normal')
# insert new values after deleting old ones (down below)
entry.delete(0,END)
entry.insert(0, text_should_be_here)
# and finally make its state readonly to not let the user mess with the entry
entry.configure(state='readonly')

Delete/hide HBox and children widgets using Button inside HBox

I am trying to make an input widget for a module i have made (see this SO question).
The input widget should have a title bar and a variable number of input lines below. I had in mind to have an delete button at the end of each input line.
The delete button should ideally delete the container widget and all the children widgets, but hiding the container widget and children would also be acceptable.
I have not been able to find a useful recipe to this problem.
Currently i got this code, but i have no clue less as how to solve the problem.
import ipywidgets as w
​
def add_btn_clicked(b):
input_box.children = (input_box.children[0], line()) + input_box.children[1:]
​
def delete_btn_clicked(b):
# ???
with output:
print(b.keys)
return None
add = w.Button(icon="plus-circle")
add.on_click(add_btn_clicked)
​
title = w.HBox([w.Label(value=str(i)) for i in range(3)]+[add])
​
def line():
delete = w.Button(icon="trash")
delete.on_click(delete_btn_clicked)
return w.HBox([w.FloatText(value=i) for i in range(3)]+[delete])
​
input_box = w.VBox([title,line()])
output = w.Output()
​
display(input_box)
display(output)
Is there a way to tell what the parent element is from the button click or another way to achieve what I am attempting?
You can create the widgets and container separately, then define the .parent attribute on the children as the container, before assembling together. That way you can effectively hide the container when the button is clicked (with .parent.layout.display = 'none').
import ipywidgets as w
def add_btn_clicked(b):
input_box.children = (input_box.children[0], line()) + input_box.children[1:]
def delete_btn_clicked(b):
b.parent.layout.display = 'none'
add = w.Button(icon="plus-circle")
add.on_click(add_btn_clicked)
title = w.HBox([w.Label(value=str(i)) for i in range(3)]+[add])
def line():
delete = w.Button(icon="trash")
delete.on_click(delete_btn_clicked)
val_widgets = [w.FloatText(value=i) for i in range(3)]
container = w.HBox()
delete.parent = container
for widg in val_widgets:
widg.parent = container
children = val_widgets + [delete]
container.children = children
return container
input_box = w.VBox([title,line()])
output = w.Output()
display(input_box)
display(output)

How to justify the characters in drop-down list of a Combobox?

How to justify the values listed in drop-down part of a ttk.Combobox? I have tried justify='center' but that seems to only configure the selected item. Could use a resource link too if there is, I couldn't find it.
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
if __name__ == '__main__':
root = tk.Tk()
cbb = ttk.Combobox(root, justify='center', values=(0, 1, 2))
cbb.pack()
root.mainloop()
I have a one-liner solution. Use the .option_add() method after declaring ttk.Combobox. Example:
cbb = ttk.Combobox(root, justify='center', values=(0, 1, 2)) # original
cbb.option_add('*TCombobox*Listbox.Justify', 'center') # new line added
Here's one pure Python way that gets close to what you want. The items in the dropdown list all get justified to fit within the Combobox's width (or a default value will be used).
Update
Part of the reason the initial version of my answer wasn't quite right was because the code assumed that a fixed-width font was being used. That's not the case on my test platform at least, so I've modified the code to actually measure the width of values in pixels instead of whole characters, and do essentially what it did originally, but in those units of string-length measure.
import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk
class CenteredCombobox(ttk.Combobox):
DEFAULT_WIDTH = 20 # Have read that 20 is the default width of an Entry.
def __init__(self, master=None, **kwargs):
values = kwargs.get('values')
if values:
entry = ttk.Entry(None) # Throwaway for getting the default font.
font = tkFont.Font(font=entry['font'])
space_width = font.measure(' ')
entry_width = space_width * kwargs.get('width', self.DEFAULT_WIDTH)
widths = [font.measure(str(value)) for value in values]
longest = max(entry_width, *widths)
justified_values = []
for value, value_width in zip(values, widths):
space_needed = (longest-value_width) / 2
spaces_needed = int(space_needed / space_width)
padding = ' ' * spaces_needed
justified_values.append(padding + str(value))
kwargs['values'] = tuple(justified_values)
super().__init__(master, **kwargs)
root = tk.Tk()
ccb = CenteredCombobox(root, justify='center', width=10, values=('I', 'XLII', 'MMXVIII'))
ccb.pack()
root.mainloop()
(Edit: Note that this solution works for Tcl/Tk versions 8.6.5 and above. #CommonSense notes that some tkinter installations may not be patched yet,
and this solution will not work).
In Tcl ( I don't know python, so one of the python people can edit the question).
A combobox is an amalgamation of an 'entry' widget and a 'listbox' widget. Sometimes to make the configuration changes you want, you need to access the internal widgets directly.
Tcl:
% ttk::combobox .cb -values [list a abc def14 kjsdf]
.cb
% pack .cb
% set pd [ttk::combobox::PopdownWindow .cb]
.cb.popdown
% set lb $pd.f.l
.cb.popdown.f.l
% $lb configure -justify center
Python:
cb = ttk.Combobox(value=['a', 'abc', 'def14', 'kjsdf'])
cb.pack()
pd = cb.tk.call('ttk::combobox::PopdownWindow', cb)
lb = cb.tk.eval('return {}.f.l'.format(pd))
cb.tk.eval('{} configure -justify center'.format(lb))
Some caveats. The internals of ttk::combobox are subject to change.
Not likely, not anytime soon, but in the future, the hard-coded .f.l
could change.
ttk::combobox::PopdownWindow will force the creation of the listbox when it is called. A better method is to put the centering adjustment into
a procedure and call that procedure when the combobox/listbox is mapped.
This will run for all comboboxes, you will need to check the argument
in the proc to make sure that this is the combobox you want to adjust.
proc cblbhandler { w } {
if { $w eq ".cb" } {
set pd [ttk::combobox::PopdownWindow $w]
set lb $pd.f.l
$lb configure -justify center
}
}
bind ComboboxListbox <Map> +[list ::cblbhandler %W]
After digging through combobox.tcl source code I've come up with the following subclass of ttk.Combobox. JustifiedCombobox justifies the pop-down list's items almost precisely after1 pop-down list's been first created & customized and then displayed. After the pop-down list's been created, setting self.justify value to a valid one will again, customize the justification almost right after the pop-down list's first been displayed. Enjoy:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
from tkinter import ttk
except:
import Tkinter as tk
import ttk
class JustifiedCombobox(ttk.Combobox):
"""
Creates a ttk.Combobox widget with its drop-down list items
justified with self.justify as late as possible.
"""
def __init__(self, master, *args, **kwargs):
ttk.Combobox.__init__(self, master, *args, **kwargs)
self.justify = 'center'
def _justify_popdown_list_text(self):
self._initial_bindtags = self.bindtags()
_bindtags = list(self._initial_bindtags)
_index_of_class_tag = _bindtags.index(self.winfo_class())
# This dummy tag needs to be unique per object, and also needs
# to be not equal to str(object)
self._dummy_tag = '_' + str(self)
_bindtags.insert(_index_of_class_tag + 1, self._dummy_tag)
self.bindtags(tuple(_bindtags))
_events_that_produce_popdown = tuple([ '<KeyPress-Down>',
'<ButtonPress-1>',
'<Shift-ButtonPress-1>',
'<Double-ButtonPress-1>',
'<Triple-ButtonPress-1>',
])
for _event_name in _events_that_produce_popdown:
self.bind_class(self._dummy_tag, _event_name,
self._initial_event_handle)
def _initial_event_handle(self, event):
_instate = str(self['state'])
if _instate != 'disabled':
if event.keysym == 'Down':
self._justify()
else:
_ = self.tk.eval('{} identify element {} {}'.format(self,
event.x, event.y))
__ = self.tk.eval('string match *textarea {}'.format(_))
_is_click_in_entry = bool(int(__))
if (_instate == 'readonly') or (not _is_click_in_entry):
self._justify()
def _justify(self):
self.tk.eval('{}.popdown.f.l configure -justify {}'.format(self,
self.justify))
self.bindtags(self._initial_bindtags)
def __setattr__(self, name, value):
self.__dict__[name] = value
if name == 'justify':
self._justify_popdown_list_text()
def select_handle():
global a
_selected = a['values'][a.current()]
if _selected in ("left", "center", "right"):
a.justify = _selected
if __name__ == '__main__':
root = tk.Tk()
for s in ('normal', 'readonly', 'disabled'):
JustifiedCombobox(root, state=s, values=[1, 2, 3]).grid()
a = JustifiedCombobox(root, values=["Justify me!", "left", "center", "right"])
a.current(0)
a.grid()
a.bind("<<ComboboxSelected>>", lambda event: select_handle())
root.mainloop()
1 It basically makes use of bindtag event queue. This was mostly possible thanks to being able to creating a custom bindtag.

Categories