Checkbuttons not properly aligning in Tkinter - python-2.7

I am a Biological Engineering student working on a Capstone project in my university, and part of this project requires a little bit of coding. None of our group members knew how to code, so I've taken it upon myself to learn python and attempt to create a GUI using Tkinter. Given the reason for coding, I am extremely new to programming and thus, as a forewarning, my code is probably rather poorly written.
So to get to the point; I am having a problem getting my Checkbuttons to properly align. Essentially what happens is that the first Checkbutton group is aligned on rows 3-6 (I think, not 100% sure but they are where I want them) in column 0. I then created a second Checkbutton group in column 1, and I want the Checkbuttons to begin back at row 3-whatever, but instead they start at row 7 and span until whenever. This happens for each subsequent Checkbutton group I make.
The code is as follows:
import sys
from Tkinter import *
import tkMessageBox
import tkFileDialog
from PIL import ImageTk, Image
root = Tk()
root.geometry('900x700')
root.title('IBID 2.0')
ilabel1 = Label(root, text=' Measurement',font=("Bold",18)).grid(row=1,column=0)
datatype = {'Joint Angle' : 0,
'Joint Acceleration' : 0,
'Ground Reaction Force' : 0,
'Muscle Activation' : 0
}
for measure in datatype:
datatype[measure] = IntVar()
dt_cb = Checkbutton(root, text=measure, variable=datatype[measure])
dt_cb.grid(column=0, sticky='W', padx=20)
ilabel2 = Label(root, text='Muscle Group(s)',font= ("Bold",18),padx=30).grid(row=1,column=1)
emg_groups = {'Quadriceps' : 0,
'Hamstrings' : 0,
'Calves' : 0
}
for measure in emg_groups:
emg_groups[measure] = IntVar()
emg_cb = Checkbutton(root, text=measure, variable=emg_groups[measure])
emg_cb.grid(column=1, sticky='W', padx=20)
ilabel3 = Label(root, text='Ground Reaction Force',font=("Bold",18),padx=30).grid(row=1,column=2)
grf_groups = {'Ground Reaction Force' : 0,
'Gait' : 0,
}
for measure in grf_groups:
grf_groups[measure] = IntVar()
grf_cb = Checkbutton(root, text=measure, variable=grf_groups[measure])
grf_cb.grid(column=2, sticky='W', padx=40)
I did find a workaround where instead of using lists to create my Checkbuttons, I went ahead and made each button individually. I thought I might be able to clean up the code a little bit using lists, though I cannot find a way to put them all on the same line, I suppose. If I specify a row in the .grid(), then the options overlap one another.

Every time you call grid() without supplying a row Tkinter is automatically assuming you want to add it to a new row at the bottom:
wdgt.grid(row=0, column=0)
wdgt.grid(column=1) # row=1 is assumed here
Instead, you'll need to supply the row numbers to the new widgets you're trying to grid:
for i, measure in enumerate(emg_groups):
emg_groups[measure] = IntVar()
emg_cb = Checkbutton(root, text=measure, variable=emg_groups[measure])
emg_cb.grid(column=1, row=i+2, sticky='W', padx=20)
and:
for i, measure in enumerate(grf_groups):
grf_groups[measure] = IntVar()
grf_cb = Checkbutton(root, text=measure, variable=grf_groups[measure])
grf_cb.grid(column=2, row=i+2, sticky='W', padx=40)
Using enumerate() we get a tuple on each loop of the iteration, i being a 0 indexed counter, and of course measure being the object itself. Since you started your Label() at row 1 (you may want to change to start at row 0), we'll need to add 2 to the index each time through and then grid to that row number.

Related

Change text colour in Tkinter combobox, based on origin

I have two lots of computer names found in AD, all of which are sorted together and entered into a tkinter drop-down combobox. I would like to be able to change the text colour of the entries in the drop-down depending on which original list of computers it came from.
OPSpclist = []
OPS_pcs = active_directory.AD_object ("LDAP://OU=Locations - ...")
for OPSpc in OPS_pcs.search (objectCategory='Computer'):
OPSpc = str(OPSpc).upper()
OPSpc = OPSpc.split(",")[0].split("=")[1]
OPSpclist.append(OPSpc)
OSpclist = []
OS_pcs = active_directory.AD_object ("LDAP://OU=Locations - ...")
for OSpc in OS_pcs.search (objectCategory='Computer'):
OSpc = str(OSpc).upper()
OSpc = OSpc.split(",")[0].split("=")[1]
OSpclist.append(OSpc)
bothSchools = sorted(OSpclist) + sorted(OPSpclist)
optionList = sorted(bothSchools)
var1 = StringVar()
var1.set(optionList[0])
pcnameEntry = ttk.Combobox(entryframe, textvariable = var1, values = optionList, width=25)
pcnameEntry.focus_set()
pcnameEntry.grid(row=1, column=0, sticky=W, pady=(0, 10), padx=5)
Is it possible to have the items from the first list to appear in a different colour, all within the same, sorted, combobox drop-down list?
Thanks,
Chris.
Yes this is possible, the drop down is a listbox and therefore the items can be configured separately with the itemconfigure method. However, I don't know how to retrieve the combobox's listbox via Python but this can be done through tcl commands:
import Tkinter as tk
import ttk
root = tk.Tk()
l1 = [(name, 'computer 1') for name in 'ABCD']
l2 = [(name, 'computer 2') for name in 'ACEG']
l = sorted(l1 + l2)
combo = ttk.Combobox(root, values=[name for name, computer in l])
combo.pack()
combo.update_idletasks()
# I need to display the drop down once before setting the items' colors otherwise
# I get an error telling me the items don't exist, so I generate a click on the combobox button
combo.event_generate('<1>', x=combo.winfo_width() - 4, y=1)
colors = {'computer 1': 'blue', 'computer 2': 'red'}
# define the tcl variable corresponding to the drop down listbox
combo.tk.eval('set popdown [ttk::combobox::PopdownWindow %s]' % combo)
for i, (name, computer) in enumerate(l):
# set the color of each item (the background color can be changed too by adding '-background <color>')
combo.tk.eval('$popdown.f.l itemconfigure %i -foreground %s' % (i, colors[computer]))
root.mainloop()

Listbox Python Columns

I am trying to develop a script that allows me to keep my formatting within my listbox.
from Tkinter import *
from tabulate import tabulate
master = Tk()
listbox = Listbox(master)
listbox.pack()
table = [["spam",42],["eggs",451],["bacon",0]]
headers = ["item", "qty"]
tb = tabulate(table, headers, tablefmt="plain")
listbox.insert(END,tb)
mainloop()
End Result the listbox populated with the tb formatting:
QUESTION: HOW DO I GET MY LISTBOX TO APPEAR LIKE THE PICTURE ABOVE THAT I USED TABULATE TO FORMAT?
I've noticed treeview seems to have some limitations with the horizontal box and expanding the columns without adjusting the entire GUI so I'd decided this might be a more shake-shift way that will suit my needs just fine.
One option may be to use str.format() to align each insert into the listbox:
from Tkinter import *
import tkFont
master = Tk()
master.resizable(width=False, height=False)
master.geometry('{width}x{height}'.format(width=300, height=100))
my_font = tkFont.Font(family="Monaco", size=12) # use a fixed width font so columns align
listbox = Listbox(master, width=400, height=400, font=my_font)
listbox.pack()
table = [["spam", 42, "test", ""],["eggs", 451, "", "we"],["bacon", "True", "", ""]]
headers = ["item", "qty", "sd", "again"]
row_format ="{:<8}{sp}{:>8}{sp}{:<8}{sp}{:8}" # left or right align, with an arbitrary '8' column width
listbox.insert(0, row_format.format(*headers, sp=" "*2))
for items in table:
listbox.insert(END, row_format.format(*items, sp=" "*2))
mainloop()
Which appears to match the output you got using tabulate:
Another option could be use a Grid layout.

Bokeh: dashboard not updating axis range based on widget value

I am trying to implement an interactive dashboard in Bokeh with a "play" function that loops through all value pairs for two indicators selected by widgets.
Screen cap of dashboard
While the loop works, the dashboard resets the axis values for each step of the loop. So what I need is to set axis values based on the widget.value selected. To this end, I have built a data frame "ranges" that has the name of the indicator as index and the min/max value for each indicator as columns.
The updates for controls work thusly (x_axis,etc. are the names of the widgets):
controls = [x_axis, y_axis, start_yr, end_yr, years]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
The update function is supposed to update the ranges upon change in the controls like this:
def update():
p.x_range = Range1d(start = ranges.loc[x_axis.value,"Min"],
end = ranges.loc[x_axis.value,"Max"])
p.y_range = Range1d(start = ranges.loc[y_axis.value,"Min"],
end = ranges.loc[y_axis.value,"Max"])
What should happen: Whenever I change the value of the widget, the ranges should update, but other than that, they should remain constant
What does happen: The ranges are set based on the value of the widget initially set and don't change on update.
I've tried to find examples trying to achieve something similar but no luck.
This is a working example:
import numpy as np
from bokeh.plotting import figure
from bokeh.models import Range1d
from bokeh.io import curdoc
x = np.linspace(0, 100, 1000)
y = np.sin(x)
p = figure(x_range=(0, 100))
p.circle(x, y)
def cb():
# this works:
p.x_range.start += 1
p.x_range.end += 1
# this also works:
#p.x_range = Range1d(p.x_range.start+1, p.x_range.end+1)
curdoc().add_periodic_callback(cb, 200)
curdoc().add_root(p)

Rewriting some functions for xlsxwriter box borders from Python 2 to Python 3

I am having some problem getting xlsxwriter to create box borders around a number of cells when creating a Excel sheet. After some searching I found a thread here where there was a example on how to do this in Python 2.
The link to the thread is:
python XlsxWriter set border around multiple cells
The answer I am trying to use is the one given by aubaub.
I am using Python 3 and is trying to get this to work but I am having some problems with it.
The first thing I did was changing xrange to range in the
def box(workbook, sheet_name, row_start, col_start, row_stop, col_stop),
and then I changed dict.iteritems() to dict.items() in
def add_to_format(existing_format, dict_of_properties, workbook):
Since there have been some changes to this from Python 2 to 3.
But the next part I am struggling with, and kinda have no idea what to do, and this is the
return(workbook.add_format(dict(new_dict.items() + dict_of_properties.items())))
part. I tried to change this by adding the two dictionaries in another way, by adding this before the return part.
dest = dict(list(new_dict.items()) + list(dict_of_properties.items()))
return(workbook.add_format(dest))
But this is not working, I have not been using dictionaries a lot before, and am kinda blank on how to get this working, and if it there have been some other changes to xlsxwriter or other factors that prevent this from working. Does anyone have some good ideas for how to solve this?
Here I have added a working example of the code and problem.
import pandas as pd
import xlsxwriter
import numpy as np
from xlsxwriter.utility import xl_range
#Adding the functions from aubaub copied from question on Stackoverflow
# https://stackoverflow.com/questions/21599809/python-xlsxwriter-set-border-around-multiple-cells/37907013#37907013
#And added the changes I thought would make it work.
def add_to_format(existing_format, dict_of_properties, workbook):
"""Give a format you want to extend and a dict of the properties you want to
extend it with, and you get them returned in a single format"""
new_dict={}
for key, value in existing_format.__dict__.items():
if (value != 0) and (value != {}) and (value != None):
new_dict[key]=value
del new_dict['escapes']
dest = dict(list(new_dict.items()) + list(dict_of_properties.items()))
return(workbook.add_format(dest))
def box(workbook, sheet_name, row_start, col_start, row_stop, col_stop):
"""Makes an RxC box. Use integers, not the 'A1' format"""
rows = row_stop - row_start + 1
cols = col_stop - col_start + 1
for x in range((rows) * (cols)): # Total number of cells in the rectangle
box_form = workbook.add_format() # The format resets each loop
row = row_start + (x // cols)
column = col_start + (x % cols)
if x < (cols): # If it's on the top row
box_form = add_to_format(box_form, {'top':1}, workbook)
if x >= ((rows * cols) - cols): # If it's on the bottom row
box_form = add_to_format(box_form, {'bottom':1}, workbook)
if x % cols == 0: # If it's on the left column
box_form = add_to_format(box_form, {'left':1}, workbook)
if x % cols == (cols - 1): # If it's on the right column
box_form = add_to_format(box_form, {'right':1}, workbook)
sheet_name.write(row, column, "", box_form)
#Adds dataframe with some data
frame1 = pd.DataFrame(np.random.randint(0,100,size=(10, 4)), columns=list('ABCD'))
writer = pd.ExcelWriter('test.xlsx', engine='xlsxwriter')
#Add frame to Excel sheet
frame1.to_excel(writer, sheet_name='Sheet1', startcol= 1, startrow= 2)
# Get the xlsxwriter workbook and worksheet objects.
workbook = writer.book
worksheet = writer.sheets['Sheet1']
#Add some formating to the table
format00 = workbook.add_format()
format00.set_bold()
format00.set_font_size(14)
format00.set_bg_color('#F2F2F2')
format00.set_align('center')
worksheet.conditional_format(xl_range(2, 1, 2, 5),
{'type': 'no_blanks',
'format': format00})
box(workbook, 'Sheet1', 3, 1, 12, 5)
writer.save()
I stumbled on this when trying to see if anyone else had posted a better way to deal with formats. Don't use my old way; whether you could make it work with Python 3 or not, it's pretty crappy. Instead, grab what I just put here: https://github.com/Yoyoyoyoyoyoyo/XlsxFormatter.
If you use sheet.cell_writer() instead of sheet.write(), then it will keep a memory of the formats you ask for on a cell-by-cell basis, so writing something new in a cell (or adding a border around it) won't delete the cell's old format, but adds to it instead.
A simple example of your code:
from format_classes import Book
book = Book(where_to_save)
sheet = book.add_book_sheet('Sheet1')
sheet.box(3, 1, 12, 5)
# add data to the box with sheet.cell_writer(...)
book.close()
Look at the code & the README to see how to do other things, like format the box's borders or backgrounds, write data, apply a format to an entire worksheet, etc.

Unable to set the Entry box to correct position in python

I am trying to learn creating GUI using Tkinter .I created a window which includes text,Messagebox,Entry widget,labels and Radio buttons.
I used grid method for frames and tried to make entry boxes in row0 and row1 .And a message Box with Some text.But these are not properly aligned even though i gave correct rows and columns but output is not in order.
Entry box is created very far though i mentioned column1 .And message box is created as per the column specified.Can anyone help me how to solve this.If i am missing anything please let me now .
from Tkinter import*
import tkMessageBox
class Example:
def __init__(self,root):
root.title("Sample")
#Entry functions ---------------------------------------
Label(root, text="First Name").grid(row=0)
Label(root, text="Last Name").grid(row=1)
self.e1 = Entry(root)
self.e1.bind("<Return>",self.ShowChoice_radio)
self.e2 = Entry(root)
self.e2.bind("<Return>",self.ShowChoice_radio)
self.e1.grid(row=0,column=1)
self.e2.grid(row =1,column = 1)
#------------------------------------------------------------------------
self.frame=Frame(root)
self.frame.grid(row=3,sticky=W)
self.label=Label(self.frame, text="mine", width=12,bg="green",fg="white",justify=LEFT)
self.label.grid(row=3,column=4,sticky=W,pady=4)
root.minsize(width=666, height=220)
self.v=IntVar()
role=[("ENGLISH",1),("SPANISH",2),("GERMAN",3)]
Label(self.frame,text="Choose your role of target:",justify=LEFT,padx=2,pady=2).grid(row=4,sticky=W)
i=0
for txt,val in role:
i=i+1
self.rad_bt=Radiobutton(self.frame,text=txt,padx=20,variable=self.v,
command=self.ShowChoice_radio,value=val)
self.rad_bt.grid(row=4,column=i+1)
self.bottomframe = Frame(root)
self.bottomframe.grid(row=12,sticky=W)
self.hello(12)
T=Text(self.bottomframe,height=2,width=30)
T.pack(padx=100,side=TOP)
T.insert(END,"just a normal text to display!\n")
self.mbutton=Button(self.bottomframe,text='Quit',command=self.callback,state='normal')
self.mbutton.pack(padx=3,pady=3,side='left')
self.help=Button(self.bottomframe,text='Help',command=self.help_msg,width=5,justify=CENTER)
self.help.pack(padx=93,pady=3,side='left')
def ShowChoice_radio(self):
print self.v.get()
def help_msg(self):
tkMessageBox.showinfo("Help to print ",message="Not yet implemented")
root.minsize(width=666, height=666)
self.show_entry_fields()
self.help.config(state=DISABLED)
def callback(self):
if tkMessageBox.askyesno('verify','Really Quit?'):
root.destroy()
def hello(self,name):
w=Label(root,text="Hello Tkinter!",width=15).grid(row=10)
whatever_you_do = "Whatever . it is my test that \n i can anble to display manner in this case find out whether it is correct one or wrong \n)"
msg=Message(root, anchor='s',width=200,text = whatever_you_do)
msg.config(bg='lightgreen', font=('times', 14, 'italic'))
msg.grid(row=10,column=1,sticky=W)
def show_entry_fields(self):
print "First Name: %s\nLast Name: %s" % (self.e1.get(), self.e2.get())
if __name__=="__main__":
root=Tk()
app=Example(root)
root.mainloop()
Even the quit and Help buttons are not proper...!!!
I initially voted to close this because there is not a clear question, but mostly only a series of statements and opinions, at least one of which is incorrect. After looking more, I think I can answer your implied question "Why is tkinter behaving in a way that seems wrong to me?". The answer, I believe, is that you do not understand that grid coordinates are independent (start over) for each container gridded. Also, coordinates not used are ignored. In particular:
Root has a grid of 5 rows and 2 columns. Renumbering the rows 0, 1, 2, 3, 4 instead of the confusing labeling you used, there is no entry in column 1 for rows 2 and 4. The width of column 0 is determined by the width of self.frame in row 2, column 0. The entry boxes are far to the right because column 0 is very wide.
Self.frame has a grid of 2 rows and 4 columns. The first 3 columns of row 0 are empty. Self.bottomframe is packed instead of gridded. The buttons are to the left of where you want because you packed them to the left. In other words, tkinter did just what you said, which is apparently not what you want.
You might list the result better if you got rid of self.frame, put 'mine' in (2,0) or (2,0), 'Choose...' in (3, 0), and a frame with 3 radio buttoms in (3,1). Then root column 0 would not be so overly wide.