Python 2.7: Tkinter/ttk Linked ComboBoxes - combobox

I've made a simple GUI using a GUI editor called PAGE. It contains two Tkinter.ttk combo boxes. My first combo box gets its values as column names from a connected sqlite database table. When I choose a column name from first combo box, second combo box should update its values automatically related to first values.
BTW, my codes below work properly in this current condition. If I choose a value from first combo box, It'll print the values to interactive shell. But these values should insert into second combo box. Does anyone know how can I figure it out?
Any help is greatly appreciated. Thanks in advance...
Here are my GUI codes:
from pysqlite2 import dbapi2 as db
from Tkinter import *
import ttk
def getdata():
global colnames
conn = db.connect("blabla.sqlite")
cur = conn.execute("select * from states")
col = cur.description
colnames = [abu[0] for abu in col]
initcombo = getdata()
def vp_start_gui():
global val, w, root
root = Tk()
root.title('Linked Comboboxes')
root.geometry('301x230+556+208')
set_Tk_var()
w = New_Toplevel_1 (root)
init()
root.mainloop()
w = None
def create_New_Toplevel_1 (root):
global w, w_win
if w:
return
w = Toplevel (root)
w.title('New_Toplevel_1')
w.geometry('301x230+556+208')
set_Tk_var()
w_win = New_Toplevel_1 (w)
init()
return w_win
def destroy_New_Toplevel_1 ():
global w
w.destroy()
w = None
def set_Tk_var():
global combobox
combobox = StringVar()
def init():
pass
class New_Toplevel_1:
def __init__(self, master=None):
style = ttk.Style()
theme = style.theme_use()
default = style.lookup(theme, 'background')
master.configure(background=default)
def choose1(event=None):
conn2 = db.connect("blabla.sqlite")
cur2 = conn2.execute("select %s from states" % self.TCombobox1.get())
results = cur2.fetchall()
for row in results:
print row
self.TCombobox1 = ttk.Combobox (master, state='readonly')
self.TCombobox1.place(relx=0.03,rely=0.13,relheight=0.09,relwidth=0.48)
self.TCombobox1["values"] = colnames
self.TCombobox1.set("Choose one...")
self.TCombobox1.bind("<<ComboboxSelected>>", choose1)
self.TCombobox2 = ttk.Combobox (master, state='readonly')
self.TCombobox2.place(relx=0.03,rely=0.33,relheight=0.09,relwidth=0.48)
self.TLabel1 = ttk.Label (master)
self.TLabel1.place(relx=0.03,rely=0.83,height=19,width=28)
self.TLabel1.configure(relief="flat")
self.TLabel1.configure(text='''Info:''')
self.TButton1 = ttk.Button (master)
self.TButton1.place(relx=0.63,rely=0.22,height=25,width=76)
self.TButton1.configure(takefocus="")
self.TButton1.configure(text='''Run''')
if __name__ == '__main__':
vp_start_gui()

Set the 'results' as the value of the 'values' config of the second combobox.
def choose1(event=None):
conn2 = db.connect("blabla.sqlite")
cur2 = conn2.execute("select %s from states" % self.TCombobox1.get())
results = cur2.fetchall()
self.TCombobox2['values'] = results
for row in results:
print row

Related

PySide6 QTreeWidget Checkbox SingleSelection

have a PySide6 QTreeWidget with some Elements and Checkboxes, very simple. What i cannot getting to work is how can i make s Single Selection with the Checkboxes? What works is SingleSelection without the Checkboxes, but not when i only use the Checkboxes itself. I wann use only Checkboxes and not Mouseclicks on the row, i did that with
tv.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
tv.setFocusPolicy(QtCore.Qt.NoFocus)
My Idea was set a itemChanged handler function for it and when a checkbox is clicked i run through all items, set all item checkboxes to uncheck with
child.setCheckState(0, QtCore.Qt.Unchecked)
and after that set the item that is selected from the handler to checked.
def handle_statechange(item):
selected_item = item.text(0)
for i in range(tv.invisibleRootItem().childCount()):
child = tv.invisibleRootItem().child(i)
child.setCheckState(0, QtCore.Qt.Unchecked)
item.setCheckState(0, QtCore.Qt.Checked)
But that doesnt work. How can i make this behavior? Thanks!
Here is small sample Code
import sys
from PySide6 import QtGui, QtCore, QtWidgets
testdict = {'TEST1': 'Testname1',
'TEST2': 'Testname2',
'TEST3': 'Testname3',
'TEST4': 'Testname4',
}
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget()
widget.setWindowTitle("test")
widget.grid = QtWidgets.QGridLayout(widget)
widget.grid.setContentsMargins(5, 5, 5, 5)
widget.hide()
tv = QtWidgets.QTreeWidget()
tv.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
tv.setIndentation(0)
tv.setColumnCount(4)
tv.setFocusPolicy(QtCore.Qt.NoFocus)
tv.hideColumn(2)
tv.hideColumn(3)
tv.setHeaderLabels(['NORM', 'NAME'])
tv.header().setDefaultAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignLeft)
strlen_list = []
for k, v in testdict.items():
strlen_list.append(len(v))
TreeNodeItem = QtWidgets.QTreeWidgetItem
treeNode = TreeNodeItem(tv, k)
treeNode.setText( 0, k )
treeNode.setText( 1, v )
treeNode.setText( 2, '512' )
treeNode.setText( 3, '513' )
treeNode.setCheckState(0, QtCore.Qt.Unchecked)
widget.grid.addWidget(tv, 0, 0, QtCore.Qt.AlignTop)
label = QtWidgets.QLabel()
label.setText("Testlabel")
widget.grid.addWidget(label, 1, 0, QtCore.Qt.AlignTop)
button = QtWidgets.QPushButton('Testbutton', widget)
button.clicked.connect(test)
widget.grid.addWidget(button, 2, 0, QtCore.Qt.AlignLeft)
widget.show()
The check state change of items has absolutely nothing to do with the selection and is not directly correlated to mouse clicks, since the user could click on an item but not on its checkbox, or could press the space-bar to toggle the check state of the current item.
Assuming that you toggle the check state of items ONLY using mouse/keyboard and you will always have only one checked item at most, you can connect to the itemChanged signal and verify if any of its columns has a checked state.
Then you have to store the checked item and column so that you can later verify if any other item has changed and it's different from the previously set checked item.
class ToggleTree(QtWidgets.QTreeWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lastToggled = None
self.itemChanged.connect(self.checkToggled)
def checkToggled(self, item):
for column in range(item.columnCount()):
if item.checkState(column):
break
else:
if self.lastToggled and self.lastToggled[0] == item:
self.lastToggled = None
return
if self.lastToggled:
oldItem, oldColumn = self.lastToggled
if oldItem != item:
# prevent recursion
self.itemChanged.disconnect(self.checkToggled)
oldItem.setCheckState(oldColumn, 0)
self.itemChanged.connect(self.checkToggled)
self.lastToggled = item, column
# ...
tv = ToggleTree()
Note: if you only need a 2-dimensional model like in your example, don't use QTreeWidget, but QTableWidget.

Is there a way to use/get the value from a current entry window

I'm trying to get the variable that's entered in an entry widget on the Return key pressed event, but struggling a bit. What I have tried has always produced a blank result.
This code may look messy and hap-hazard, but it's only going to be a template that I'll be using on a current project!
I've tried that many things to get it to work, I can't remember what I have tried!
from collections import OrderedDict
try:
import tkinter as tk
except:
import Tkinter as tk
root = tk.Tk()
labelLIST = OrderedDict([
('Temp ID', 'tempID'),
('PO Number', "poNumber"),
('Reference', "reference"),
('Cut/Sample Date', "csDate"),
('Cut Number', "cut")
])
i = 0
e_loops = len(labelLIST)
print (e_loops)
def bval1(event=None):
for i in range(e_loops):
print (entries[i].get())
entries[0].delete(0, tk.END)
entries[0].insert(0, 'DISABLED')
entries[0].configure(state='disabled')
def bval2():
entries[0].configure(state='normal')
for i in range(e_loops):
entries[i].delete(0, tk.END)
entries[0].focus()
def onClick(event):
ent = event.widget # event.widget is the widget that called the event
print(ent.cget("text")) # Print the text for the selected button
event.widget.tk_focusNext().focus()
def enterEV(event):
# print(entries[].get())
event.widget.tk_focusNext().focus()
entries = []
for key, value in labelLIST.items():
label = tk.Label(root, text=key)
label.grid(row=i, column=0, sticky="ew", padx=1, pady=1)
entry = tk.Entry(root, width=10)
entry.grid(row=i, column=1, sticky="ew", padx=5, pady=5)
if value == "cut":
entry.bind('<Return>', bval1)
else:
# entry.bind('<Return>', enterEV)
entry.bind('<Return>', onClick)
entries.append(entry)
i = i+1
button = tk.Button(root, text="Submit", command=bval1)
button.grid(row=0, column=2, columnspan=9, sticky="ew")
button = tk.Button(root, text="Clear", command=bval2)
button.grid(row=1, column=2, columnspan=9, sticky="ew")
entries[0].focus()
tk.mainloop()
When enter/return is pressed, I want the value that is the entry box to be printed to terminal via the onClick event. But the output is always empty.
def onClick(event):
ent = event.widget # event.widget is the widget that called the event
print(ent.cget("text")) # Print the text for the selected button
event.widget.tk_focusNext().focus()
You don't use the text attribute to get the value in an Entry widget. Using cget("text") returns the value for the textvariable attribute. Since you haven't set that attribute, it will always be the empty string.
Instead, you need to call the get method:
print(ent.get())

Bokeh MultiSelect plotting in infinite loop, distorting plot

I'm trying to plotting multiple lines into a graph based on a user's "MultiSelect" options. I read in two separate excel files of data and and plot their axis based on the user's request. I'm using Python 3.5 and running on a MAC.
1). As soon as I make a multiselection the figure gets distorted
2). It seems the plot is running in an infinite loop.
3). The plot doses not properly update when user changes selections. It just adds more plots without removing the previous plot.
from os.path import dirname, join
from pandas import *
import numpy as np
import pandas.io.sql as psql
import sqlite3 as sql
import sys, os
from bokeh.plotting import figure
from bokeh.layouts import layout, widgetbox
from bokeh.models import ColumnDataSource, HoverTool, Div
from bokeh.models.widgets import Slider, Select, TextInput, MultiSelect
from bokeh.io import curdoc
import matplotlib.pyplot as plt
files = list()
path = os.getcwd()
for x in os.listdir(path):
if x.endswith(".xlsx"):
if x != 'template.xlsx' :
files.append(x)
axis_map = {
"0% void": "0% void",
"40% void": "40% void",
"70% void": "70% void",
}
files_list = MultiSelect(title="Files", value=["dummy2.xlsx"],
options=open(join(dirname(__file__), 'files.txt')).read().split())
voids = MultiSelect(title="At what void[s]", value=["0% void"], options=sorted(axis_map.keys()))
p = figure(plot_height=600, plot_width=700, title="", toolbar_location=None)
pline = figure(plot_height=600, plot_width=700, title="")
path = os.getcwd()
data_dict = {}
for file in os.listdir(path):
if file.endswith(".xlsx"):
xls = ExcelFile(file)
df = xls.parse(xls.sheet_names[0])
data = df.to_dict()
data_dict[file] = data
# converting dictionary to dataframe
newdict = {(k1, k2):v2 for k1,v1 in data_dict.items() \
for k2,v2 in data_dict[k1].items()}
xxs = DataFrame([newdict[i] for i in sorted(newdict)],
index=MultiIndex.from_tuples([i for i in sorted(newdict.keys())]))
master_data = xxs.transpose()
def select_data():
for vals in files_list.value:
for vox in voids.value:
pline.line(x=master_data[vals]['Burnup'], y= master_data[vals][vox])
pline.circle(x=master_data[vals]['Burnup'], y= master_data[vals][vox])
return
def update():
select_data()
controls = [ files_list, voids]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
sizing_mode = 'fixed' # 'scale_width' also looks nice with this example
inputs = widgetbox(*controls, sizing_mode=sizing_mode)
l = layout([
[inputs, pline],
], sizing_mode=sizing_mode)
update()
curdoc().add_root(l)
curdoc().title = "Calculations"
I am not 100% certain, since the code above is not self-contained and cannot be run and investigated, but there are some issues (as of Bokeh 0.12.4) with adding new components to documents being problematic in some situations. These issues are high on the priority list for the next two point releases.
Are the data sizes reasonable such that you could create all the combinations up front? If so, I would recommend doing that, and then having the multi-select values toggle the visibility on/off appropriately. E.g., here's a similar example using a checkbox:
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row
from bokeh.palettes import Viridis3
from bokeh.plotting import figure
from bokeh.models import CheckboxGroup
p = figure()
props = dict(line_width=4, line_alpha=0.7)
x = np.linspace(0, 4 * np.pi, 100)
l0 = p.line(x, np.sin(x), color=Viridis3[0], legend="Line 0", **props)
l1 = p.line(x, 4 * np.cos(x), color=Viridis3[1], legend="Line 1", **props)
l2 = p.line(x, np.tan(x), color=Viridis3[2], legend="Line 2", **props)
checkbox = CheckboxGroup(labels=["Line 0", "Line 1", "Line 2"], active=[0, 1, 2], width=100)
def update(attr, old, new):
l0.visible = 0 in checkbox.active
l1.visible = 1 in checkbox.active
l2.visible = 2 in checkbox.active
checkbox.on_change('active', update)
layout = row(checkbox, p)
curdoc().add_root(layout)
If the data sizes are not such that you can create all the combinations up front, then I would suggest making an issue on the project issue trackerhttps://github.com/bokeh/bokeh/issues) that has a complete, minimal, self-contained, runnable as-is code to reproduce the problem (i.e. generates random or synthetic data but it otherwise identical). This it the number one thing that would help the core devs address the issue more promptly.
#bigreddot Thanks for your response.
I edited the code to now make it self contained.
1). The plot does not reset. The new selected plots over the previous plot.
2). When the user makes multiple selections (ctrl+shift) the plot axis gets distorted and it seems to be running in an infinite loop
from pandas import *
import numpy as np
import sys, os
from bokeh.plotting import figure
from bokeh.layouts import layout, widgetbox
from bokeh.models.widgets import MultiSelect
from bokeh.io import curdoc
from bokeh.plotting import reset_output
import math
axis_map = {
"y1": "y3",
"y2": "y2",
"y3": "y1",
}
x1 = np.linspace(0,20,62)
y1 = [1.26 * math.cos(x) for x in np.linspace(-1,1,62) ]
y2 = [1.26 * math.cos(x) for x in np.linspace(-0.95,.95,62) ]
y3 = [1.26 * math.cos(x) for x in np.linspace(-.9,.90,62) ]
TOOLS = "pan,wheel_zoom,box_zoom,reset,save,hover"
vars = MultiSelect(title="At what void[s]", value=["y1"], options=sorted(axis_map.keys()))
master_data = { 'rate' : x1,
'y1' : y1,
'y2' : y2,
'y3' : y3
}
p = figure(plot_height=600, plot_width=700, title="", toolbar_location=None)
pline = figure(plot_height=600, plot_width=700, title="", tools=TOOLS)
def select_data():
for vox in vars.value:
pline.line(x=master_data['rate'], y= master_data[vox], line_width=2)
pline.circle(x=master_data['rate'], y=master_data[vox], line_width=2)
return
controls = [ vars]
for control in controls:
control.on_change('value', lambda attr, old, new: select_data())
sizing_mode = 'fixed'
inputs = widgetbox(*controls)
l = layout([
[inputs, pline],
])
select_data()
curdoc().add_root(l)
curdoc().title = "Plot"

GetSelection ComboBox

I can't figure out what else I need for the combobox to select
from my list as I type. Eventually I'm going to add SetInsertionPoint.
But for now, my item selected is allways -1
self.filter = wx.ComboBox(self, wx.ID_ANY, choices = '', style=wx.CB_DROPDOWN)
def OnTextChanged(self, event):
sel = self.filter.GetSelection()
print 'OnItemSelected %s' % sel
This other SO answer has a custom control that might work for you:
Auto-Completion In wxPython wxComboBox
You may also get ideas from this wxPython wiki article
http://wiki.wxpython.org/TextCtrlAutoComplete
I also noticed that the masked combo box may have this feature itself, according to the docs: http://www.wxpython.org/docs/api/wx.lib.masked.combobox-module.html which says the following
BaseMaskedComboBox - Base class for generic masked edit comboboxes; allows auto-complete of values.
To use the GetSelection() alone, you need to set the ComboBox to read only. It's a nice way to
query by hitting one charactor. Using SetInsertionPoint and SetMark keeps the curser to the next string of your query. I used the example Mike suggested •Auto-Completion In wxPython wxComboBox
and modified my code to used these instances. Since I always use boxsizers and open functions I needed to do away with the wx.EVT_TEXT event. Here's how it works:
## copy/paste to text file
'''
73,"N WASHINGTON ST"
95,"BRIAR CREEK RD"
97,"N INDEPENDENCE AVE"
09,"N ADAMS ST"
13,"N JEFFERSON ST"
19,"N MADISON ST"
21,"QUAIL CREEK DR"
24,"INDIAN DR"
12,"CHEROKEE TRAIL"
50,"CORONADO TRAIL"
'''
import wx, os
from cStringIO import StringIO
import csv
class MainFrame(wx.Frame):
def __init__(self, parent, choices=[], style=0):
wx.Frame.__init__(self,None,wx.ID_ANY,title='test combo autocomplete',size=(225, 70))
self.vbox= wx.BoxSizer(wx.VERTICAL)
self.background = wx.Panel(self)
self.OpenDir = wx.TextCtrl(self,style=wx.PROCESS_ENTER|wx.TE_CENTRE)
self.filter = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_DROPDOWN)
self.OpenDir.Bind(wx.EVT_LEFT_UP,self.OnChooseRoot)
self.filter.Bind(wx.EVT_TEXT, self.OnTextChanged)
hsizer1 = wx.BoxSizer(wx.HORIZONTAL)
hsizer1.Add(self.OpenDir,1)
hsizer2 = wx.BoxSizer(wx.HORIZONTAL)
hsizer2.Add(self.filter,1)
self.vbox.Add(hsizer1,proportion = 0,flag = wx.EXPAND)
self.vbox.Add(hsizer2,proportion = 0,flag = wx.EXPAND)
self.SetSizer(self.vbox)
self.Show()
self.OpenDir.SetValue("click to open directory")
def OnTextChanged(self, event):
def refresh():
wnd = self.filter
currentText = event.GetString()
while wnd:
print currentText
wnd.Layout()
print wnd.Layout()
wnd = wnd.GetParent()
self.filter.SetInsertionPoint(len(currentText))
self.filter.SetMark(len(currentText), len(self.choices))
self.filter.Refresh()
refresh()
event.Skip()
def OnChooseRoot(self, event):
self.dirname=""
dlg = wx.FileDialog(self, "choose a file to open", self.dirname,
"", "*.*", wx.OPEN)
if dlg.ShowModal() == wx.ID_OK:
self.filename = dlg.GetFilename()
self.dirname = dlg.GetDirectory()
self.pathname = dlg.GetPath()
self.f = open(os.path.join(self.dirname, self.filename), 'r')
self.text = self.f.read()
labeltop = self.dirname + '\\'
self.OpenDir.SetValue(labeltop + self.filename)
sources = [StringIO(self.text)]
for i, source in enumerate(sources):
c = list(csv.reader(source))
self.choices = [x[1] for x in c]
self.filter.SetItems(self.choices)
app = wx.App(redirect=False)
frame = MainFrame(None)
app.MainLoop()

selected item of comboBox in custom Delegate from QTableView

I use a custom delegate to display a column of comboBoxes in my QTableView.
The values are the same for all the comboBoxes so it's not really the population part that gives me trouble.
I want them to show as the selected item, some value that I can retrieve from a database. I have access to the database from the delegate, but in order to send my request, I need the row of the comboBox.
So I guess my question is : how can you iterate over all the rows of the table and do some action from inside the custom delegate ?
If it can help here is my custom delegate class :
class ComboBoxDelegate(QtGui.QItemDelegate):
def __init__(self, parent, itemslist):
QtGui.QItemDelegate.__init__(self, parent)
self.itemslist = itemslist
self.parent = parent
def paint(self, painter, option, index):
# Get Item Data
value = index.data(QtCore.Qt.DisplayRole).toInt()[0]
# value = self.itemslist[index.data(QtCore.Qt.DisplayRole).toInt()[0]]
# fill style options with item data
style = QtGui.QApplication.style()
opt = QtGui.QStyleOptionComboBox()
opt.currentText = str(self.itemslist[value])
opt.rect = option.rect
# draw item data as ComboBox
style.drawComplexControl(QtGui.QStyle.CC_ComboBox, opt, painter)
self.parent.openPersistentEditor(index)
def createEditor(self, parent, option, index):
##get the "check" value of the row
# for row in range(self.parent.model.rowCount(self.parent)):
# print row
self.editor = QtGui.QComboBox(parent)
self.editor.addItems(self.itemslist)
self.editor.setCurrentIndex(0)
self.editor.installEventFilter(self)
self.connect(self.editor, QtCore.SIGNAL("currentIndexChanged(int)"), self.editorChanged)
return self.editor
# def setEditorData(self, editor, index):
# value = index.data(QtCore.Qt.DisplayRole).toInt()[0]
# editor.setCurrentIndex(value)
def setEditorData(self, editor, index):
text = self.itemslist[index.data(QtCore.Qt.DisplayRole).toInt()[0]]
pos = self.editor.findText(text)
if pos == -1:
pos = 0
self.editor.setCurrentIndex(pos)
def setModelData(self,editor,model,index):
value = self.editor.currentIndex()
model.setData(index, QtCore.QVariant(value))
def updateEditorGeometry(self, editor, option, index):
self.editor.setGeometry(option.rect)
def editorChanged(self, index):
check = self.editor.itemText(index)
id_seq = self.parent.selectedIndexes[0][0]
update.updateCheckSeq(self.parent.db, id_seq, check)
And I call it fromthe QTableView like this :
self.setEditTriggers(QtGui.QAbstractItemView.CurrentChanged)
self.viewport().installEventFilter(self)
self.setItemDelegateForColumn(13,ComboBoxDelegate(self, self.checkValues))
Hope I was clear enough, thanks for your attention
Not sure if accessing the database from the delegate is a right thing to do. Your delegate can contain reference to the instance of QAbstractTableModel which the QTableView refers to. You can then use methods in the model to iterate over rows of the table.

Resources