Listbox Python Columns - python-2.7

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.

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

How to use hover with multi-line in bokeh?

I'm facing issues with multi-line figure in bokeh. i can't get the values when i show my graph. i'm using series data type.
Code:
df = pandas.read_csv("Data.csv", parse_dates=["time"])
result = df.groupby(['time','up','down'], as_index = False)['up', 'down'].sum()
p = Figure(width=500, height=250,logo =None,
sizing_mode='scale_width',
tools="pan, box_zoom, wheel_zoom, save, reset, box_select",
x_axis_type="datetime",
title="Graph:",
x_axis_label="Time Frame",
y_axis_label="Utilization (GB)",
toolbar_location="below",
toolbar_sticky=False)
up = result["up"]
time = result["time"]
down = result["down"]
p.multi_line(xs = [time, time], ys = [up, down], color=['#2828B0', '#BC0096'], line_width=1, legend='graph_1')
hover = HoverTool(tooltips = [('Time', '#time'),
('data', '#up')])
p.add_tools(hover)
p.show()
The # fields of hover tooltips refer to columns in Bokeh ColumnDataSource objects. Since you have not created a CDS explicitly with column names of your choice, Bokeh makes one for you with the standard column name xs and ys in this case. So:
HoverTool(tooltips = [
('Time', '#xs'),
('data', '#ys')]
)
That will put a hover that displays over all segments in the multi-line. There is no way to have a hover work for just one or the other. If you need that, you will have to use separate calls to line instead of multi_line.

Bokeh tool tip displaying all the values for the line

I am trying to plot a line chart which includes tooltip, but the code below results in displaying all the values of the line in a tooltip instead displaying a single value for those co ordinates
#Import the library
import pandas
import itertools
import bokeh
import MySQLdb
from bokeh.plotting import figure, output_file, show
from bokeh.models import HoverTool
TOOLS='hover'
wells=['F1','F2','F3','F4','F5','F6','F7','F8','F9','F10','F11','F12','G1','G2','G3','G4','G5','G6','G7','G8','G9','G10','G11','G12']
p = figure(plot_width=800, plot_height=640,x_axis_type="datetime", tools=TOOLS)
p.title.text = 'Click on legend entries to hide the corresponding lines'
# Open database connection
db = MySQLdb.connect("localhost","user","password","db" )
#pallete for the lines
my_palette=bokeh.palettes.inferno(len(wells))
#create a statement to get the data
for name, color in zip(wells,my_palette):
stmnt='select date_time,col1,wells,test_value from db where wells="%s"'%(name)
#creating dataframe
df=pandas.read_sql(stmnt,con=db)
p.scatter(df['date_time'], df['test_value'], line_width=2, color=color, alpha=0.8, legend=name,)
#Inserting tool tip
hover = p.select(dict(type=HoverTool))
hover.tooltips = [("Wells","#wells"),("Date","#%s"%(df['date_time'])),("Values","#%s"%(df['test_value']))]
hover.mode = 'mouse'
#Adding a legend
p.legend.location = "top_right"
output_file("interactive_legend.html", title="interactive_legend.py example")
show(p)
Given below is the resultant screenshot
I am trying to get only one well,Date_time,Test_value at given mouse over instance
This code:
hover.tooltips = [
("Wells","#wells"),
("Date","#%s"%(df['date_time'])),
("Values","#%s"%(df['test_value']))
]
Does not do what you think. Let's suppose df['date_time'] has the value [10, 20, 30, 40]. Then after your string substitution, your tooltip looks like:
("Date", "#[10, 20, 30, 40]")
Which exactly explains what you are seeing. The #[10 part looks for a column named "[10" in your ColumnDataSource (because of the # in front). There isn't a column with that name, so the tooltip prints ??? to indicate it can't find data to look up. The rest 20, 30, 40 is just plain text, so it gets printed as-is. In your code, you are actually passing a Pandas series and not a list, so the string substitution also prints the Name and dtype info in the tooltip text as well.
Since you are passing sequence literals to scatter, it creates a Column Data Source for you, and the default names in the CDS it are 'x' and 'y'. My best guess, is that you actually want:
hover.tooltips = [
("Wells","#wells"),
("Date","#x"),
("Values","#y")
]
But note that you would want to do this outside the loop. As it is you are simply modifying the same hover tool over and over.

Get widget name of label in frame using mouse

Win7 SP1, Python 2.7,Tkinter.
I make a frame, and I place an array of labels within. The array is 10w x 24h.
I want to click on one of these labels and return the name of the label so I may alter it's variable.
I know how to do using a listbox, but how to do using just 'label in a frame'?
Thanks, Mark.
You don't need the label name, and besides labels don't have useful names. When the event fires you are given a reference to the widget, which you can use to query or modify the widget attributes.
Here's an example of how you can change a label by clicking on it. Run the program, and then click on any label as often as you want.
import Tkinter as tk
import time
def on_click(event):
now = time.strftime("%H:%M:%S")
event.widget.configure(text="you clicked me at %s" % now)
root = tk.Tk()
for row in range(4):
for col in range(4):
label = tk.Label(root, width=25, borderwidth=1, relief="sunken")
label.grid(row=row, column=col, padx=2, pady=2)
label.bind("<1>", on_click)
root.mainloop()

pyqt - Automatically update widget on user input

Total pyqt novice here. Trying to automatically modify the widget to display different options when user selects certain option in combobox 2. IE if user selects 'Cliff Erosion' or 'Dune Erosion', I want the widget to refresh with additional comboboxes. Likewise, if they select back to 'Rising Tides' or 'Coastal Flooding' I'd like the widget to go back to original call. How do I refresh the widget?
from PyQt4.QtGui import *
# Create window
class Window(QWidget):
#This block adds features into the window init
def __init__(self):
QWidget.__init__(self)
self.setWindowTitle('Monterey Bay Sea Level Rise')
self.resize(300, 240)
self.addWidgets1()
def addWidgets1(self):
#Add drop-down list for selecting forecast year
self.year_lbl = QLabel("1. Select Forecast Year", self)
self.year_lbl.move(5,0)
year = QComboBox(self)
year.addItem('2030')
year.addItem('2060')
year.addItem('2100')
year.move(5,20)
#Add drop-down list for selecting hazard
self.hazard_lbl = QLabel("2. Select Coastal Hazard", self)
self.hazard_lbl.move(5,50)
hazard = QComboBox(self)
hazard.addItem('Rising Tides')
hazard.addItem('Coastal Storm Flooding')
hazard.addItem('Cliff Erosion')
hazard.addItem('Dune Erosion')
hazard.activated[str].connect(self.addWidget2)
hazard.move(5,70)
#Add drop-down list for inputing model intensity (s1,s2,s3)
self.intensity_lbl = QLabel("3. Select Intensity", self)
self.intensity_lbl.move(5,100)
intensity = QComboBox(self)
intensity.addItem('Low')
intensity.addItem('Mid')
intensity.addItem('High')
intensity.move(5,120)
def addWidget2(self,text):
#if hazard is cliff erosion or dune erosion we want to update the widget
#... to include wstorm,long_term AND no_change,stormier
if text == 'Cliff Erosion' or text == 'Dune Erosion':
print 'Hi'
self.type_lbl = QLabel("3. Select type of changes", self)
self.type_lbl.move(5,150)
types = QComboBox(self)
types.addItem('Long-term')
types.addItem('Storm induced')
types.move(5,180)
self.storm_lbl = QLabel("4. Select for stormier", self)
self.storm_lbl.move(5,150)
storm = QComboBox(self)
storm.addItem('No Change')
storm.addItem('Stormier')
storm.move(5,180)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
#window.resize(100, 60)
window.show()
sys.exit(app.exec_())
The normal way of adding widgets to a Qt application is to use layouts. They will calculate the preferred size and location of your widgets and update this when needed, e.g. when widgets are added or when the window is resized (note what happens when you make your window smaller during execution and compare it with my solution below). I'm certain that it's possible to do all the moving and resizing yourself, but QLayouts are really the way to go and I strongly recommend you use them too.
There are a few types of QLayouts but in your case I would use a QFormLayout. To my surprise the QFormLayout does have an addRow method but no corresponding removeRow. However I find that just showing/hiding the combo boxes when needed does the trick as well. I've adapted your example below.
Finally, even just showing or hiding the last two combo boxes will cause the layout to slightly move the first three. This is caused by the fact that the 4th label is the longest of them all. I find that annoying. Perhaps a better solution is to enable/disable the combo boxes when needed. This has the additional benefit of showing the user that these options even exist. See also the code below. A second alternative may be to use a QGridLayout (instead of a QFormLayout) and use setColumnMinimumWidth to set the first column to a size that will hold all possible labels.
from PyQt4 import QtGui
# Create window
class Window(QtGui.QWidget):
#This block adds features into the window init
def __init__(self):
QtGui.QWidget.__init__(self)
self.setWindowTitle('Monterey Bay Sea Level Rise')
self.resize(300, 240)
self.addWidgets1()
def addWidgets1(self):
self.layout = QtGui.QFormLayout()
self.setLayout(self.layout)
#Add drop-down list for selecting forecast year
# You don't need to set to parent of the widgets to self anymore, the
# layout will set the parent automatically when you add the widgets
self.year_lbl = QtGui.QLabel("1. Select Forecast Year")
# self.year_lbl.move(5,0) # Can be removed. The layout takes care of it.
year = QtGui.QComboBox()
year.addItem('2030')
year.addItem('2060')
year.addItem('2100')
self.layout.addRow(self.year_lbl, year)
#Add drop-down list for selecting hazard
self.hazard_lbl = QtGui.QLabel("2. Select Coastal Hazard")
self.hazard = QtGui.QComboBox()
self.hazard.addItem('Rising Tides')
self.hazard.addItem('Coastal Storm Flooding')
self.hazard.addItem('Cliff Erosion')
self.hazard.addItem('Dune Erosion')
self.hazard.activated[str].connect(self.updateComboboxes)
self.layout.addRow(self.hazard_lbl, self.hazard)
#Add drop-down list for inputing model intensity (s1,s2,s3)
self.intensity_lbl = QtGui.QLabel("3. Select Intensity")
intensity = QtGui.QComboBox()
intensity.addItem('Low')
intensity.addItem('Mid')
intensity.addItem('High')
self.layout.addRow(self.intensity_lbl, intensity)
self.types_lbl = QtGui.QLabel("3. Select type of changes")
self.types = QtGui.QComboBox()
self.types.addItem('Long-term')
self.types.addItem('Storm induced')
self.layout.addRow(self.types_lbl, self.types)
self.storm_lbl = QtGui.QLabel("4. Select for stormier")
self.storm = QtGui.QComboBox()
self.storm.addItem('No Change')
self.storm.addItem('Stormier')
self.layout.addRow(self.storm_lbl, self.storm)
# show initial state
self.updateComboboxes()
def updateComboboxes(self, text=None):
#if hazard is cliff erosion or dune erosion we want to update the widget
#... to include wstorm,long_term AND no_change,stormier
if text is None:
text = self.hazard.currentText()
usable = (text == 'Cliff Erosion' or text == 'Dune Erosion')
if True: # change to False to use enabling/disabling widgets
# May cause other widgets to be relocated
self.types_lbl.setVisible(usable)
self.types.setVisible(usable)
self.storm_lbl.setVisible(usable)
self.storm.setVisible(usable)
else:
# This option doesn't relocate widgets
# Also may give additional clue to the uses that this exsits
self.types_lbl.setEnabled(usable)
self.types.setEnabled(usable)
self.storm_lbl.setEnabled(usable)
self.storm.setEnabled(usable)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
#window.resize(100, 60)
window.show()
sys.exit(app.exec_())