I have a function that inserts multiple QcomboBoxes in the first row of QtableWidget and then updates an excel file starting in row 2. This is at the end of my function:
for i in range(df.shape[1]):
combo = comboCompanies(self)
self.tableWidget.setCellWidget(0, i, combo)
What I would like to do is know when one of the indexes is changed on a combobox but they currently have the same name so I need to figure out how to uniquely identity them. I found the following but it isnt in Python
QComboBox* myComboBox = new QComboBox(); // making a new dropdown box
myComboBox->setObjectName(QString::number(i)); // pass column number as object name
connect(myComboBox, SIGNAL(currentIndexChanged(QString)),
SLOT(onComboChanged(QString)));
ui->tableWidget->setCellWidget(0,i,myComboBox); // put box into table
How do I code in Python?
You can identify Your Combobox eihter by setting it's objectname or just by passing it's column as a parameter to the callback.
currentIndexChanged signal only gives You new changed index, but not the Combobox itself. That's why we have to use inline lambda function.
Here is the code with both options used:
import sys
from PyQt5.QtWidgets import QApplication, QTableWidget, QComboBox
def index_changed_id_by_column(column: int, selected_index: int) -> None:
"""combobox in column changed selected_index"""
print(f"Combobox in column: {column}, changed to index:{selected_index}")
def index_changed_id_by_combobox(combobox: QComboBox, selected_index: int) -> None:
"""combobox changed selected_index"""
print(f"Combobox: {combobox.objectName()}, changed to index:{selected_index}")
if __name__ == "__main__":
app = QApplication(sys.argv)
# Create some Table
table = QTableWidget()
table.setRowCount(2)
table.setColumnCount(4)
# Solution 1, using column index to identify Combobox
for i in range(table.columnCount()):
combobox = QComboBox()
combobox.addItems(["Option 1", "Option 2", "Option 3"])
'''
This connects currentIndexChanged signal with lambda function (selected_index, column),
calling index_changed_id_by_column slot
'''
combobox.currentIndexChanged.connect(
lambda selected_index, column=i: index_changed_id_by_column(column, selected_index))
table.setCellWidget(0, i, combobox)
# Solution 2, using objectName to identify Combobox
for i in range(table.columnCount()):
combobox = QComboBox()
combobox.setObjectName(f"combobox_row1_coll{i}")
combobox.addItems(["Option 1", "Option 2", "Option 3"])
'''
This connects currentIndexChanged signal with lambda function (selected_index, combobox),
calling index_changed_id_by_combobox slot
'''
combobox.currentIndexChanged.connect(
lambda selected_index, combobox=combobox: index_changed_id_by_combobox(combobox, selected_index))
table.setCellWidget(1, i, combobox)
table.show()
app.exec()
In first row we have Comboboxes calling index_changed_id_by_column callback.
In second row Comboboxes calling index_changed_id_by_combobox callback.
Pick the one that You need for Your project.
Related
I'm writing a simple script that create a ttk treeview (that act as a table) and, when you double-click it, it opens a file (with the path saved in the dictionary). Double click opening is possible by this method:
t.bind("<Double-1>", lambda f=nt[x]["URIallegato"]: os.startfile(str(f)))
However, this doesn't gave me the ID of the row (stored in the #0 column). With the ID I can get the path of the file saved in a dictionary.
Here is the full Treeview code:
t=Treeview(w)
t.pack(padx=10,pady=10)
for x in list(nt.keys()):
t.insert("",x,text=nt[x]["allegati"])
if nt[x]["allegati"]!="":
t.bind("<Double-1>",
lambda f=nt[x]["URIallegato"]: os.startfile(str(f)))
Thanks!
The normal way to do this is to bind a single binding on the treeview for a double click. The default binding for single-click will select the item, and in your double-click binding you can ask the treeview for the selected item.
If you associate values with the treeview item, you can fetch them so that you don't have to store them in a dictionary.
Here's an example:
import tkinter as tk
from tkinter import ttk
def on_double_click(event):
item_id = event.widget.focus()
item = event.widget.item(item_id)
values = item['values']
url = values[0]
print("the url is:", url)
root = tk.Tk()
t=ttk.Treeview(root)
t.pack(fill="both", expand=True)
t.bind("<Double-Button-1>", on_double_click)
for x in range(10):
url = "http://example.com/%d" % x
text = "item %d" % x
t.insert("", x, text=text, values=[url])
root.mainloop()
I want to implement a feature which will allow user to navigate in Gtk.TreeView widget by arrow keys, unfortunately select_iter() method is not doing what I was expecting from it, i. e. it fails to select parent node of selected node :P
And now I need explanation why it's not working or hint on some kind of workaround of this issue.
Below is ready to run test program which demonstrates this problem. Problematic line of code is tagged with #FIXME.
from gi.repository import Gtk
from gi.repository import Gdk
class WizardManager(Gtk.Dialog):
'''Dialog window which makes possible to choose type of resource to create by editor.'''
def __init__(self, parent):
super().__init__('Wizard manager', parent, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT)
self.set_default_response(Gtk.ResponseType.OK)
self.set_decorated(False)
self.set_size_request(640, 480)
vbox = self.get_content_area()
self.__tree_store = Gtk.TreeStore(str)
self.__tree_view = Gtk.TreeView(self.__tree_store)
self.__tree_view.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
self.__tree_view.connect('key-press-event', self.__on_tree_view_key_press)
self.__tree_view.set_headers_visible(False)
text_renderer = Gtk.CellRendererText()
text_column1 = Gtk.TreeViewColumn(None, text_renderer)
text_column1.add_attribute(text_renderer, 'text', 0)
self.__tree_view.append_column(text_column1)
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.add(self.__tree_view)
vbox.pack_start(scrolled_window, True, True, 0)
self.__populate_tree_store()
self.show_all()
def __on_tree_view_key_press(self, tree_view, event):
# TODO Implement tree navigation with arrow keys
tree_selection = tree_view.get_selection()
selected_iter = tree_selection.get_selected()[1]
if selected_iter:
selected_tree_path = self.__tree_store.get_path(selected_iter)
# Right arrow and Return should expand selected node.
if event.keyval == Gdk.KEY_Right or event.keyval == Gdk.KEY_Return:
tree_view.expand_row(selected_tree_path, False)
# Left arrow should collapse node or select it parent.
elif event.keyval == Gdk.KEY_Left:
if not tree_view.collapse_row(selected_tree_path):
# Unable to collapse node it must be empty. select it's parent.
parent_iter = selected_iter.copy()
if self.__tree_store.iter_parent(parent_iter):
# FIXME Why select_iter() executes without error and is not able to select parent node?
# same goes for select_path() :P
tree_selection.select_iter(parent_iter)
def __populate_tree_store(self):
# Ordinary resources
self.__tree_store.append(None, ('File',))
self.__tree_store.append(None, ('Directory',))
# Python files
python_dir = self.__tree_store.append(None, ('Python',))
self.__tree_store.append(python_dir, ('Python module',))
self.__tree_store.append(python_dir, ('Python package',))
# Django files
django_dir = self.__tree_store.append(python_dir, ('Django',))
self.__tree_store.append(django_dir, ('Django project',))
self.__tree_store.append(django_dir, ('Django app',))
if __name__ == '__main__':
app = Gtk.Window(Gtk.WindowType.TOPLEVEL)
app.connect('destroy', lambda a: Gtk.main_quit())
dlg = WizardManager(app)
dlg.run()
dlg.destroy()
Gtk.main()
Here you have a hint!
#! /usr/bin/python
###########################################################
#
# Basic Gtk.TreeView Example with two sortable columns
#
###########################################################
# use the new PyGObject binding
from gi.repository import Gtk
import os
import getpass # this is only to automatically print your home folder.
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title='My Window Title')
self.connect('delete-event', Gtk.main_quit)
# Gtk.ListStore will hold data for the TreeView
# Only the first two columns will be displayed
# The third one is for sorting file sizes as numbers
store = Gtk.ListStore(str, str, long)
# Get the data - see below
self.populate_store(store)
treeview = Gtk.TreeView(model=store)
# The first TreeView column displays the data from
# the first ListStore column (text=0), which contains
# file names
renderer_1 = Gtk.CellRendererText()
column_1 = Gtk.TreeViewColumn('File Name', renderer_1, text=0)
# Calling set_sort_column_id makes the treeViewColumn sortable
# by clicking on its header. The column is sorted by
# the ListStore column index passed to it
# (in this case 0 - the first ListStore column)
column_1.set_sort_column_id(0)
treeview.append_column(column_1)
# xalign=1 right-aligns the file sizes in the second column
renderer_2 = Gtk.CellRendererText(xalign=1)
# text=1 pulls the data from the second ListStore column
# which contains filesizes in bytes formatted as strings
# with thousand separators
column_2 = Gtk.TreeViewColumn('Size in bytes', renderer_2, text=1)
# Mak the Treeview column sortable by the third ListStore column
# which contains the actual file sizes
column_2.set_sort_column_id(1)
treeview.append_column(column_2)
# Use ScrolledWindow to make the TreeView scrollable
# Otherwise the TreeView would expand to show all items
# Only allow vertical scrollbar
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_policy(
Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
scrolled_window.add(treeview)
scrolled_window.set_min_content_height(200)
self.add(scrolled_window)
self.show_all()
def populate_store(self, store):
directory = '/home/'+getpass.getuser()
for filename in os.listdir(directory):
size = os.path.getsize(os.path.join(directory, filename))
# the second element is displayed in the second TreeView column
# but that column is sorted by the third element
# so the file sizes are sorted as numbers, not as strings
store.append([filename, '{0:,}'.format(size), size])
# The main part:
win = MyWindow()
Gtk.main()
I'm new to PyQt programming and I'm designing a GUI using qt4 designer. In the GUI i have a table which gets populated once when i hit a button. The GUI appears like the one in the following picture:
Now in the table i want the strings in the "status" column to be coloured i.e "checked" string to "green" and "not_checked" string to "red"
How can i do this, can i do this using style sheets? Please help me with this
I suggest use QtGui.QItemDelegate to delegate show your data. Your also available in QTableWidget and QTableView. Implement in QItemDelegate.paint (self, QPainter painter, QStyleOptionViewItem option, QModelIndex index) method.
In that method, If column in your custom field (In this case column 2), Paint with your custom color it in paint. And implement your custom QtGui.QItemDelegate completed, Put in your QTableWidget or QTableView by use QAbstractItemView.setItemDelegate (self, QAbstractItemDelegate delegate).
Example;
import sys
from PyQt4 import QtGui, QtCore
class ENUM_STATUS:
CHECKED = QtCore.QString('checked')
NOT_CHECKED = QtCore.QString('not_checked')
class QCustomDelegate (QtGui.QItemDelegate):
def paint (self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 1:
textQString = indexQModelIndex.model().data(indexQModelIndex, QtCore.Qt.EditRole).toString()
if textQString == ENUM_STATUS.CHECKED:
currentQColor = QtCore.Qt.darkGreen
elif textQString == ENUM_STATUS.NOT_CHECKED:
currentQColor = QtCore.Qt.darkRed
else:
currentQColor = QtCore.Qt.darkGray
painterQPainter.setPen(currentQColor)
painterQPainter.drawText(optionQStyleOptionViewItem.rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, textQString)
else:
QtGui.QItemDelegate.paint(self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex)
class QCustomTableWidget (QtGui.QTableWidget):
def __init__ (self, parent = None):
super(QCustomTableWidget, self).__init__(parent)
### <! This is initiate your QTableWidget or QTableView, Your code >! ###
listsHorizontalHeaderItem = ['Name', 'Status']
self.setColumnCount(len(listsHorizontalHeaderItem))
for index in range(self.columnCount()):
self.setHorizontalHeaderItem(index, QtGui.QTableWidgetItem(listsHorizontalHeaderItem[index]))
listsData = [
['Mr. A', ENUM_STATUS.CHECKED],
['Mr. B', ENUM_STATUS.NOT_CHECKED],
['Mr. C', ENUM_STATUS.NOT_CHECKED],
['Mr. D', ENUM_STATUS.CHECKED],
['Mr. E', ENUM_STATUS.CHECKED]]
self.setRowCount(len(listsData))
for row in range(len(listsData)):
for column in range(len(listsData[row])):
self.setItem(row, column, QtGui.QTableWidgetItem(listsData[row][column]))
### <! End initiate >! ###
# After initiated, Your have to setup delegate to your QTableWidget or QTableView, Add line
self.myQCustomDelegate = QCustomDelegate()
self.setItemDelegate(self.myQCustomDelegate)
if __name__ == '__main__':
myQApplication = QtGui.QApplication(sys.argv)
myQCustomTableWidget = QCustomTableWidget()
myQCustomTableWidget.show()
sys.exit(myQApplication.exec_())
I need to store items in a Gtk TreeView and when interacting with this TreeView, the user will can select one or more items in the list.
Because I'm new to GTK, I managed to populate the treeview and display a checkbox as the code below shows. But when I try to select, nothing happens and I do not know how to make this possible.
This is my Code:
# the column is created
renderer_products = gtk.CellRendererText()
column_products = gtk.TreeViewColumn("Products", renderer_products, text=0)
# and it is appended to the treeview
view.append_column(column_products)
# the column checkbox is created
renderer_checkbox = gtk.CellRendererToggle()
column_checkbox = gtk.TreeViewColumn("Selected", renderer_checkbox, text=0)
# and it is appended to the treeview
view.append_column(column_checkbox)
If you want to select the whole row and something happen:
#double click or not double click use
Gtk.TreeView.set_activate_on_single_click (bool)
#connect the treeview
treeview.connect ("row-activated", on_row_activate)
#inside the callback
def on_row_activate (treeview, path, column):
model = treeview.get_model ()
iter = treeview.get_iter (path)
yourdata = model[iter][model_index]
#do whatever with yourdata
If you want when you click the toggle and something happen:
#connect the renderer
renderer_checkbox.connect ("toggled", on_selected_toggled)
#inside the callback
def on_selected_toggled (renderer, path):
#modify the model or get the value or whatever
I'm having some problem sorting the items in my QListView using values in a field I specified.
Basically what I'm trying to do is this:
Detect faces in a collection of photos and display them in a QListView
Cluster the faces (images)
Update the view by placing items in the list (which are face images) belonging to the same cluster in together. Concretely, if item 1, 3, 5 are in one cluster and items 2, 4, 6 are in another, then items 1, 3, 5 should be displayed (in whatever permutations) before any of items 2, 4, 6 are displayed or vice versa.
The way I went about doing this is to set one of the UserRole field for each QStandardItem in my list to the cluster label and then try to get the QStandardModel to sort according to this UserRole. This would then display items in the same cluster (i.e. with the same cluster label in the UserRole) next to each other.
I'm able to set the UserRole successfully for the items but calling the sort function on the QStandardModel did not sort the items even though when I set the sort role to be the default DisplayRole (i.e. sort according to the text label of each face) it worked as intended.
Can anyone tell me what is wrong with my code or offer an alternative method? I've googled sorting list and I found the following link on QSortFilterProxyModel but as I'm quite new to Qt, I'm not able to adapt it to my situation.
Thanks in advance to any replies.
Here is the relevant code:
import os
from PySide.QtGui import QListView, QStandardItemModel, QStandardItem, QIcon
from PySide.QtCore import Qt
class FacesView(QListView):
"""
View to display detected faces for user to see and label.
"""
UNCLUSTERED_LABEL = -1
CLUSTER_ROLE = Qt.UserRole + 1
def __init__(self, *args):
super(FacesView, self).__init__(*args)
self._dataModel = QStandardItemModel()
self.setModel(self._dataModel)
# Layout items in batches instead of waiting for all items to be
# loaded before user is allowed to interact with them.
self.setLayoutMode(QListView.Batched)
def updateFaceClusters(self, labels):
"""Update the cluster label for each face.
#param labels: [1 x N] array where each element is an integer
for the cluster the face belongs to."""
assert(len(labels) == self._dataModel.rowCount())
# Put the cluster label each item/face belong to in the
# CLUSTER_ROLE field.
for i in xrange(self._dataModel.rowCount()):
index = self._dataModel.index(i, 0)
self._dataModel.setData(index, labels[i], self.CLUSTER_ROLE)
# Use cluster label as sort role
self._dataModel.setSortRole(self.CLUSTER_ROLE)
# This does NOT seem to sort the items even though it works fine
# when sort role is the default Qt.DisplayRole.
self._dataModel.sort(0)
print("Finished updating face clusters")
def itemsInList(self):
"""Returns the label for a face and the path to its image.
#return: (label, path)"""
items = []
for i in xrange(self._dataModel.rowCount()):
label = self._dataModel.index(i, 0).data(Qt.DisplayRole)
imagePath = self._dataModel.index(i, 0).data(Qt.UserRole)
clusterLabel = self._dataModel.index(i, 0).data(self.CLUSTER_ROLE)
items.append((imagePath, label, clusterLabel))
return items
def addItem(self, label, imagePath):
"""Add an item to list view
#param label: The label associated with the item.
#param imagePath: Path to image for the icon."""
if os.path.exists(imagePath):
icon = QIcon(imagePath)
else:
icon = QIcon(':/res/Unknown-person.gif')
item = QStandardItem(icon, label)
item.setEditable(True)
# Add image path to the UserRole field.
item.setData(imagePath, Qt.UserRole)
# Add cluster label to image. CLUSTER_ROLE is where I intend
# to put the item's cluster label.
item.setData(self.UNCLUSTERED_LABEL, self.CLUSTER_ROLE)
# Prevent an item from dropping into another item.
item.setDropEnabled(False)
# Add item to list indirectly by adding it to the model.
self._dataModel.appendRow(item)
def clear(self):
self._dataModel.clear()
There's nothing wrong with the code you posted. So there must be something wrong with how you are using it. How are you generating the cluster labels?
Here's a test script using your FacesView class that sorts as you intended:
from random import randint
from PySide.QtGui import QWidget, QPushButton, QVBoxLayout, QApplication
from facesview import FacesView
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.list = FacesView(self)
self.button = QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.list)
layout.addWidget(self.button)
def handleButton(self):
labels = []
self.list.model().setRowCount(0)
for row in range(10):
labels.append(randint(0, 3))
text = 'Item(%d) - Cluster(%d)' % (row, labels[-1])
self.list.addItem(text, 'icon.png')
self.list.updateFaceClusters(labels)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())