How to create combo filter for Qtables in pyqt - pyqt4

I have below code snippet from the help of stackoverflow followers.
I am able to filter the table now. but when i try to filter it sorts first as i enabled sort for the view.
I want to create the QTableview such a way that if i click on header it should sort. and should have a dropdown box (may be combox box style) at the right of every header. i am uploading a snap of how i want (which i made it in .NET)
Code Snippet
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtCore, QtGui
class myWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.centralwidget = QtGui.QWidget(self)
self.view = QtGui.QTableView(self.centralwidget)
self.view.setSortingEnabled(True)
self.gridLayout = QtGui.QGridLayout(self.centralwidget)
self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
self.setCentralWidget(self.centralwidget)
self.model = QtGui.QStandardItemModel(self)
for rowName in range(3) * 5:
self.model.invisibleRootItem().appendRow(
[ QtGui.QStandardItem("row {0} col {1}".format(rowName, column))
for column in range(3)
]
)
self.proxy = QtGui.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(self.horizontalHeader_Clicked)
#QtCore.pyqtSlot(int)
def horizontalHeader_Clicked(self, logicalIndex):
self.logicalIndex = logicalIndex
# local variable, and no parent
menuValues = QtGui.QMenu()
# delete the previous one
try:
self.signalMapper.deleteLater()
except:
pass
self.signalMapper = QtCore.QSignalMapper(self)
valuesUnique = [
self.proxy.index(row, self.logicalIndex).data().toString()
for row in xrange(self.proxy.rowCount())
]
print 'printing col %d values' % self.logicalIndex
for row in range(self.proxy.rowCount()):
print 'row %d Item %s' % (row,self.model.item(row, self.logicalIndex).text())
actionAll = QtGui.QAction("All", self)
actionAll.triggered.connect(self.actionAll)
menuValues.addAction(actionAll)
menuValues.addSeparator()
for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):
action = QtGui.QAction(actionName, self)
self.signalMapper.setMapping(action, actionNumber)
action.triggered.connect(self.signalMapper.map)
menuValues.addAction(action)
self.signalMapper.mapped.connect(self.signalMapper_mapped)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)
menuValues.exec_(QtCore.QPoint(posX, posY))
#QtCore.pyqtSlot()
def actionAll(self):
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( "",
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(int)
def signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( stringAction,
QtCore.Qt.CaseSensitive,
QtCore.QRegExp.FixedString
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
main = myWindow()
main.show()
main.resize(400, 600)
sys.exit(app.exec_())
This i how i am trying to get (Sort and filter)
If possible I need the ability to set the filter for selected columns only like in above image.

There is a discussion location here about the same topic: Quick way for QWidget in QHeaderView's columns?
They suggest that you would need to ditch the stock QHeaderView in your view, and provide your own custom widget for the header functionality, in order to place custom widgets into the header sections.

Related

Fading out multiple objects in pyQT

I'm trying to fade out an entire row of a GridLayout when a user clicks a button. So far I've been able to loop each of the widgets on the specific row which is working but I'm struggling with creating a parallel animation for them all...
idx = self.parent().findChild(QGridLayout).indexOf(self)
location = self.parent().findChild(QGridLayout).getItemPosition(idx)
row, col = location[:2]
self.parent_grid = self.parent().findChild(QGridLayout)
col_count = self.parent().findChild(QGridLayout).columnCount()
print(col_count)
self.effects = []
self.animations = []
anim_group = QParallelAnimationGroup()
for i in range(col_count-1):
effect = QGraphicsOpacityEffect(self)
self.effects.append(effect)
self.parent_grid.itemAtPosition(row,i).widget().setGraphicsEffect(effect)
new_anim = QPropertyAnimation(effect, b"opacity")
new_anim.setDuration(1000)
new_anim.setStartValue(1)
new_anim.setEndValue(0)
new_anim.finished.connect(lambda: self.hide_widget(col_count, row))
self.animations.append(new_anim)
anim_group.addAnimation(new_anim)
anim_group.start()
Since the count of widgets might be high, it's not a good idea to create an animation for each one of them.
Instead, you can use a "controller" that has its own unique animation, and updates the opacity of each widgets in its row.
While using a QPropertyAnimation can work, it would require adding a custom property for that, and it's not really needed for this purpose. Instead, use a basic QVariantAnimation and connect its valueChanged to a function that updates the opacity of each graphics effect.
Here is a possible implementation:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class RowFadeController(QObject):
def __init__(self, parent, buttons):
super().__init__()
self.buttons = buttons
self.effects = []
for button in buttons:
effect = QGraphicsOpacityEffect(button, opacity=1.0)
button.setGraphicsEffect(effect)
self.effects.append(effect)
self.animation = QVariantAnimation(self)
self.animation.setStartValue(1.)
self.animation.setEndValue(0.)
self.animation.valueChanged.connect(self.setOpacity)
def toggle(self, hide):
self.animation.setDirection(
self.animation.Forward if hide else self.animation.Backward
)
self.animation.start()
def setOpacity(self, opacity):
for effect in self.effects:
effect.setOpacity(opacity)
class Window(QWidget):
def __init__(self):
super().__init__()
layout = QGridLayout(self)
self.fadeControllers = []
for row in range(10):
rowButtons = []
for col in range(10):
button = QPushButton(str(col + 1))
layout.addWidget(button, row, col)
rowButtons.append(button)
toggleButton = QPushButton('toggle', checkable=True)
layout.addWidget(toggleButton, row, layout.columnCount() - 1)
fadeController = RowFadeController(self, rowButtons)
self.fadeControllers.append(fadeController)
toggleButton.clicked.connect(fadeController.toggle)
app = QApplication([])
test = Window()
test.show()
app.exec()
Tweaked for PyQT6 and added a function to get rid of the row once it's faded out.
from PyQt6.QtCore import *
from PyQt6.QtWidgets import *
class RowFadeController(QObject):
def __init__(self, parent, buttons):
super().__init__(parent)
self.buttons = buttons
self.effects = []
for button in buttons:
effect = QGraphicsOpacityEffect(button, opacity=1.0)
button.setGraphicsEffect(effect)
self.effects.append(effect)
self.animation = QVariantAnimation(self)
self.animation.setStartValue(1.)
self.animation.setEndValue(0.)
self.animation.valueChanged.connect(self.setOpacity)
self.animation.finished.connect(self.hideRow)
def toggle(self, hide):
self.animation.setDirection(
self.animation.Direction.Forward if hide else self.animation.Direction.Backward
)
self.animation.start()
def setOpacity(self, opacity):
for effect in self.effects:
effect.setOpacity(opacity)
def hideRow(self):
for button in self.buttons:
button.hide()
button.setParent(None)
class Window(QWidget):
def __init__(self):
super().__init__()
layout = QGridLayout(self)
self.fadeControllers = []
for row in range(10):
rowButtons = []
for col in range(10):
button = QPushButton(str(col + 1))
layout.addWidget(button, row, col)
rowButtons.append(button)
toggleButton = QPushButton('toggle', checkable=True)
rowButtons.append(toggleButton)
layout.addWidget(toggleButton, row, layout.columnCount() - 1)
fadeController = RowFadeController(self, rowButtons)
self.fadeControllers.append(fadeController)
toggleButton.clicked.connect(fadeController.toggle)
app = QApplication([])
test = Window()
test.show()
app.exec()

QHeaderView going out of viewport

I'm having couple of issues while trying to make a QHeaderView for my QTableView.
I want QHeaderView to be resizable by the user (Qt.ResizeMode.Interactive) while being able to stretch its sections proportionately when the window or QTableView is being resized. I found this problem online, and managed to mostly solve it but there is still some stuttering when the resizing begins and I think there should be a better solution than mine. Currently it's done by using QTimer to stop sections from going out of the viewport. Timer is being updated every millisecond. If the update interval is bigger, sections would go out of viewport and magically teleport back when the timer is updated, so once per millisecond in my case. There's still some stuttering visible if the user is dragging sections out of the viewport by dragging their mouse faster, not so visible when the mouse is slower, but visible none the less.
Every section should be resizable and movable, besides the first two. The first two sections should be immovable and fixed. I managed to make them fixed and they don't seem to have an effect on resizing of the sections, but I have no idea how to make them immovable while all the other sections are movable.
Sections should have text eliding, which I managed to make an item delegate for, but setting it on QHeaderView seems to do absolutely nothing (paint() method doesn't even get called). It's probably because item delegate isn't affecting sections, if so, how can I make a delegate that does affect them?
Here's my current code (it's a bit of a mess, but hopefully you'll get the idea):
import sys
import weakref
from typing import Any, Optional
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtCore import pyqtSlot, Qt
from PyQt6.QtGui import QFontMetrics
from PyQt6.QtWidgets import QHeaderView, QStyledItemDelegate, QStyleOptionViewItem
class MyItemDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter: QtGui.QPainter, option: QStyleOptionViewItem, index: QtCore.QModelIndex) -> None:
text = index.data(Qt.ItemDataRole.DisplayRole)
# print(text)
if text:
elided_text = QFontMetrics(option.font).elidedText(str(text), Qt.TextElideMode.ElideRight, option.rect.width())
painter.drawText(option.rect, Qt.AlignmentFlag.AlignLeft, elided_text)
class HeaderView(QtWidgets.QHeaderView):
def __init__(self,
orientation: QtCore.Qt.Orientation = Qt.Orientation.Horizontal,
parent: Optional[QtWidgets.QWidget] = None):
super(HeaderView, self).__init__(orientation, parent)
item_delegate = MyItemDelegate(self)
self.setItemDelegate(item_delegate)
self.setMinimumSectionSize(5)
self.setStretchLastSection(True)
self.setCascadingSectionResizes(True)
self.setSectionsMovable(True)
self.fixed_section_indexes = (0, 1)
timer = QtCore.QTimer(self)
timer.setSingleShot(True)
timer.setTimerType(Qt.TimerType.PreciseTimer)
timer.timeout.connect(self._update_sizes)
resize_mode_timer = QtCore.QTimer(self)
resize_mode_timer.setTimerType(Qt.TimerType.PreciseTimer)
resize_mode_timer.setSingleShot(True)
resize_mode_timer.timeout.connect(lambda: self.setSectionResizeMode(QHeaderView.ResizeMode.Interactive))
self._resize_mode_timer = weakref.proxy(resize_mode_timer)
self._timer = weakref.proxy(timer)
self.sectionResized.connect(self._handle_resize)
self.setTextElideMode(Qt.TextElideMode.ElideLeft)
self.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft)
self.proportions = []
self.mouse_pressed = False
def mouseReleaseEvent(self, e: QtGui.QMouseEvent) -> None:
self.mouse_pressed = False
super().mouseReleaseEvent(e)
self.proportions = [self.sectionSize(i) / self.width() for i in range(self.count())]
# print(self.mouse_pressed)
def init_sizes(self):
each = self.width() // self.count()
for i in range(self.count()):
self.resizeSection(self.logicalIndex(i), each)
#pyqtSlot(int, int, int)
def _handle_resize(self, logicalIndex: int, oldSize: int, newSize: int):
self._timer.start(1)
def resizeEvent(self, event: QtGui.QResizeEvent):
super().resizeEvent(event)
width = self.width()
# sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
for i in range(self.count()):
if not self.proportions:
break
if i not in self.fixed_section_indexes:
self.resizeSection(i, int(self.proportions[i] * width_without_fixed))
self._timer.start(1)
#pyqtSlot()
def _update_sizes(self):
width = self.width()
sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
# width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
index = len(sizes) - 1
i = 0
while index >= 0 and sum(sizes) > width:
i += 1
if i > 100:
break
if sizes[index] > 5 and index not in self.fixed_section_indexes: # minimum width (5)
new_width = width - (sum(sizes) - sizes[index])
if new_width < 5:
new_width = 5
sizes[index] = new_width
index -= 1
for j, value in enumerate(sizes):
self.resizeSection(self.logicalIndex(j), value)
if not self.proportions:
self.proportions = [self.sectionSize(i) / width for i in range(self.count())]
class Model(QtCore.QAbstractTableModel):
def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super(Model, self).__init__(parent)
self.__headers = ["Column A", "Column B", "Column C", "Column D", "Column E", "Column F", "Column G"]
self.__data = []
for i in range(10):
row = [0, 1, 2, 3, 42222222222, 5, 6, 74444444]
self.__data.append(row)
def rowCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__data)
def columnCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__headers)
def headerData(self, section: int, orientation: QtCore.Qt.Orientation,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role == Qt.ItemDataRole.DisplayRole:
if orientation == Qt.Orientation.Horizontal:
return self.__headers[section]
return f"{section}"
return None
def data(self, index: QtCore.QModelIndex,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role in [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole]:
return self.__data[index.row()][index.column()]
return None
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
view = QtWidgets.QTableView()
view.resize(600, 600)
header = HeaderView()
view.setHorizontalHeader(header)
model = Model()
view.setModel(model)
header.init_sizes()
view.horizontalHeader().resizeSection(0, 30)
view.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
view.horizontalHeader().resizeSection(1, 30)
view.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
view.show()
app.exec()

Is there a way to write to a qTableWidget more than once?

I am trying to create a qTableWidget in PyQt5 that will allow me to make a selection with a checkbox that filters a Pandas DataFrame and then show that data in the GUI.
I can get it to run the code the first time, and it presents the data just as I would expect.
When you try and refresh it however it won't load the new data into the qTableWidget.
There isn't any error messages, and if you print the DataFrame it is loading the new data as I would expect, it just isn't showing up in the GUI.
I've searched for similar issues on here, but can't find anyone having the same problems I am getting.
I need it to refresh the table in the same GUI as in my real data, the DataFrame is generated from an API call which when selecting a new group will pull the most recent data and could be ran an indefinite number of times.
Here is what I've got up to, I've tried to add comments to explain as much as I can do
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import pandas as pd
class TableWidget(QtWidgets.QTableWidget):
def __init__(self, tab2_df, parent=None):
QtWidgets.QTableWidget.__init__(self,parent)
tab2_df = tab2_df
nRows = len(tab2_df.index)
nColumns = len(tab2_df.columns)
self.setRowCount(nRows)
self.setColumnCount(nColumns)
for i in range(self.rowCount()):
for j in range(self.columnCount()):
x = "{}".format(tab2_df.iloc[i, j])
self.setItem(i, j, QtWidgets.QTableWidgetItem(x))
print(tab2_df) #validating that the new df is received
class Window(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__()
self.title = "Reporting Test"
self.left = 400
self.top = 50
self.width = 1300
self.height = 700
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
#initialize window with tabs
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.show()
class MyTableWidget(QtWidgets.QWidget):
def __init__(self, parent):
super(QtWidgets.QWidget, self).__init__(parent)
self.layout = QtWidgets.QVBoxLayout(self)
self.tabs = QtWidgets.QTabWidget()
self.tab1 = QtWidgets.QWidget() #Group select Tab
self.tab2 = QtWidgets.QWidget() #DataFrame Tab
# Name and add first Tab to Layout
self.tabs.addTab(self.tab1,"Group Select")
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
# Sample DataFrame for demonstation
self.tab1.df = pd.DataFrame({'Name':['Tom', 'Jack', 'Steve', 'Ricky'],'Age':[28,34,29,42]})
# Filter dataframe into a list to show as checkboxes
self.tab1.group = self.tab1.df[["Name"]]
self.tab1.groupList = self.tab1.group['Name'].tolist()
self.tab1.listLabel = ["",] * len(self.tab1.groupList)
self.tab1.grid = QtWidgets.QGridLayout()
self.tab1.setLayout(self.tab1.grid)
# Populate the checkboxes with the list
for i, v in enumerate(self.tab1.groupList):
self.tab1.groupList[i] = QtWidgets.QCheckBox(v)
self.tab1.listLabel[i] = QtWidgets.QLabel()
self.tab1.grid.addWidget(self.tab1.groupList[i], i, 0)
# Add the checkboxes into the tab
self.tab1.button = QtWidgets.QPushButton("Select Group")
self.tab1.button.clicked.connect(self.checkboxChanged)
self.tab1.labelResult = QtWidgets.QLabel()
self.tab1.grid.addWidget(self.tab1.button, i+1, 0, 1,2)
self.tab1.setLayout(self.tab1.grid)
def checkboxChanged(self):
# Clear the previous tab
self.tabs.clear()
# Add a new tab for the loaded data
self.tabs.addTab(self.tab2,"Loaded Data")
# Match the ticked checkbox to the DataFrame and filter to a new DataFrame
self.tab1.labelResult.setText("")
for i, v in enumerate(self.tab1.groupList):
self.tab1.listLabel[i].setText("True" if v.checkState() else "False")
self.tab1.labelResult.setText("{}, {}".format(self.tab1.labelResult.text(),
self.tab1.listLabel[i].text()))
self.tab1.groupList2 = self.tab1.group['Name'].tolist()
checked2 = str(self.tab1.labelResult.text()).split(',')
result = list(filter(None, checked2))
checked_list = {"Name":self.tab1.groupList2, "checked":result}
checked_list_df = pd.DataFrame(checked_list)
checked_list_filtered_df = checked_list_df[checked_list_df.checked.str.contains("true", case=False)]
self.tab1.filteredGroup_df = checked_list_filtered_df
group_select_df = pd.merge(self.tab1.group, self.tab1.filteredGroup_df, on="Name", how="inner")
group_select_list = group_select_df["Name"].tolist()
tab2_df = self.tab1.df[self.tab1.df["Name"].isin(group_select_list)]
# Populate the filtered DataFrame onto a TableWidget and populate this into the tab
self.tab2.tableWidget = TableWidget(tab2_df, self)
# Set headings and style for TableWidget
self.tab2.tableWidget.setHorizontalHeaderLabels(("Name", "Age"))
stylesheet3 = "::section{font: bold 18px}"
self.tab2.tableWidget.horizontalHeader().setStyleSheet(stylesheet3)
# Add button to refresh the data with a new group filter
self.tab2.button = QtWidgets.QPushButton("Refresh", self.tab2)
self.tab2.button.clicked.connect(self.refresh_button)
self.tab2.layout = QtWidgets.QVBoxLayout()
self.tab2.layout.addWidget(self.tab2.tableWidget)
self.tab2.layout.addWidget(self.tab2.button)
self.tab2.setLayout(self.tab2.layout)
# Refresh button that will clear the previous TableWidget and load the Group Select Tab to generate new data
def refresh_button(self):
# Clear the previous tab
self.tabs.clear()
# Clear the TableWidget
self.tab2.tableWidget.setRowCount(0)
# Load the Group Select Tab to select the new group
self.tabs.addTab(self.tab1,"Group Select")
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I'm sure a lot of this could be done much more elegantly and I'm trying to learn as I go, but I just can't figure this bit out.

How to get items from a database into a listbox

I'm trying to get all the data from my database into a listbox, but at the moment it is only getting the very first row, but when I print the same data, it prints every row. This is what I am attempting to build by populating the listbox. Imagine a listbox that collects data from columns in a table or database, this is how my solution implements
from tkinter import*
import sqlite3
connection = sqlite3.connect("TCCAnimalComplaints.db")
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
listData = cursor.execute("SELECT * FROM animalcomplaints")
customers = cursor.fetchall()
for name in customers:
#print('{0}'.format(name[0]))
customer = '{0}'.format(name[0])
itemsforlistbox = [customer]
customerSelect=Tk()
sizex = 600
sizey = 400
posx = 40
posy = 20
customerSelect.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
#itemsforlistbox=['one','two','three','four','five','six','seven']
def CurSelect(evt):
value=str((mylistbox.get(mylistbox.curselection())))
print (value)
mylistbox=Listbox(customerSelect,width=60,height=10,font=('times',13))
mylistbox.bind('<<ListboxSelect>>',CurSelect)
mylistbox.place(x=32,y=90)
for items in itemsforlistbox:
mylistbox.insert(END,items)
mainloop()
I want the listbox to list every row in the database
instead it only lists the very first row
No errors in python linting.
Expected Outcome: None of the rest of the row data entries in different columns are listed. It is something I am trying to implement.
in this example you can see all the power of the listbox widget.
self.rs is a recordset, a tuple of tuple, where every record have three fields.
The first filed is the pk,
The second field is the name of a fruit
The third can be 0 or 1.
We load every record in the listbox but even in a dictionary that is coupling with listbox as listbox index = pk of the record.
We do so to retrive all record data when we click on a listbox row.
after launch the script click on Load button and after try to click or double click on some rows of the listbox and see what happend.
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.init_ui()
def init_ui(self):
self.pack(fill=tk.BOTH, expand=1)
f = ttk.Frame()
ttk.Label(f, text = "Listbox").pack()
sb = tk.Scrollbar(f,orient=tk.VERTICAL)
self.lstItems = tk.Listbox(f,
relief=tk.GROOVE,
selectmode=tk.BROWSE,
exportselection=0,
height=20,
width=50,
background = 'white',
font='TkFixedFont',
yscrollcommand=sb.set,)
self.lstItems.bind("<<ListboxSelect>>", self.on_item_selected)
self.lstItems.bind("<Double-Button-1>", self.on_item_activated)
sb.config(command=self.lstItems.yview)
self.lstItems.pack(side=tk.LEFT,fill=tk.BOTH, expand =1)
sb.pack(fill=tk.Y, expand=1)
w = ttk.Frame()
ttk.Button(w, text="Load", command=self.on_load).pack()
ttk.Button(w, text="Close", command=self.on_close).pack()
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1)
def on_load(self,):
self.rs = ((1, 'Apple', 1), (2, 'Pear', 1), (3, 'Banana', 1),
(4, 'Orange',1), (5, 'Grapes', 0), (6, 'Watermelon', 1),
(7, 'Plum',1), (8, 'Strawberries', 0),)
index = 0
self.dict_items={}
if self.rs:
self.lstItems.delete(0, tk.END)
for i in self.rs:
self.lstItems.insert(tk.END, i[1])
if i[2] != 1:
self.lstItems.itemconfig(index, {'bg':'light gray'})
self.dict_items[index] = i[0]
index += 1
def on_item_activated(self, evt=None):
if self.lstItems.curselection():
index = self.lstItems.curselection()[0]
print("Double-Button-1 self.lstItems.curselection()[0]: {0}".format(index))
else:
messagebox.showwarning(self.parent.title(), "No item selected", parent=self)
def on_item_selected(self, evt):
if self.lstItems.curselection():
index = self.lstItems.curselection()[0]
pk = self.dict_items.get(index)
print("ListboxSelect self.dict_items.get(index) = {0}".format(pk))
print("ListboxSelect self.rs[index]: {0}".format(self.rs[index]))
def on_close(self):
self.parent.on_exit()
class App(tk.Tk):
"""Start here"""
def __init__(self):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_title()
self.set_style()
frame = Main(self,)
frame.pack(fill=tk.BOTH, expand=1)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel("Simple App", "Do you want to quit?", parent=self):
self.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
To populate the listbox with all the data from from your db you have to iterate over the records by doing this
for items in customers:
mylistbox.insert(END,items)
then the records will be inserted in the listbox. Adding below function to it is not needed.
for name in customers:
#print('{0}'.format(name[0]))
customer = '{0}'.format(name[0])
itemsforlistbox = [customer]
Have also organised your for it to be readable.
FULL CODE
from tkinter import*
import sqlite3
def CurSelect(evt):
value=str((mylistbox.get(mylistbox.curselection())))
print (value)
connection = sqlite3.connect("TCCAnimalComplaints.db")
cursor = connection.cursor()
listData = cursor.execute("SELECT * FROM animalcomplaints")
customers = cursor.fetchall()
customerSelect=Tk()
sizex = 600
sizey = 400
posx = 40
posy = 20
customerSelect.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
#itemsforlistbox=['one','two','three','four','five','six','seven']
mylistbox=Listbox(customerSelect,width=60,height=10,font=('times',13))
mylistbox.bind('<<ListboxSelect>>',CurSelect)
mylistbox.place(x=32,y=90)
for items in customers:
mylistbox.insert(END,items)
mainloop()

How can I align a right-click context menu properly in PyQt?

With the sample code below (heavily influenced from here) the right-click context menu is not really aligned properly.
As can be seen in the screenshot, the resulting menu is above the mouse cursor quite a bit. I would expect the menu's top left corner to be exactly aligned with the mouse pointer.
Is there any way to adjust for this?
import re
import operator
import os
import sys
import sqlite3
import cookies
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.tabledata = [('apple', 'red', 'small'),
('apple', 'red', 'medium'),
('apple', 'green', 'small'),
('banana', 'yellow', 'large')]
self.header = ['fruit', 'color', 'size']
# create table
self.createTable()
# layout
layout = QVBoxLayout()
layout.addWidget(self.tv)
self.setLayout(layout)
def popup(self, pos):
for i in self.tv.selectionModel().selection().indexes():
print i.row(), i.column()
menu = QMenu()
quitAction = menu.addAction("Quit")
action = menu.exec_(self.mapToGlobal(pos))
if action == quitAction:
qApp.quit()
def createTable(self):
# create the view
self.tv = QTableView()
self.tv.setStyleSheet("gridline-color: rgb(191, 191, 191)")
self.tv.setContextMenuPolicy(Qt.CustomContextMenu)
self.tv.customContextMenuRequested.connect(self.popup)
# set the table model
tm = MyTableModel(self.tabledata, self.header, self)
self.tv.setModel(tm)
# set the minimum size
self.tv.setMinimumSize(400, 300)
# hide grid
self.tv.setShowGrid(True)
# set the font
font = QFont("Calibri (Body)", 12)
self.tv.setFont(font)
# hide vertical header
vh = self.tv.verticalHeader()
vh.setVisible(False)
# set horizontal header properties
hh = self.tv.horizontalHeader()
hh.setStretchLastSection(True)
# set column width to fit contents
self.tv.resizeColumnsToContents()
# set row height
nrows = len(self.tabledata)
for row in xrange(nrows):
self.tv.setRowHeight(row, 18)
# enable sorting
self.tv.setSortingEnabled(True)
return self.tv
class MyTableModel(QAbstractTableModel):
def __init__(self, datain, headerdata, parent=None, *args):
""" datain: a list of lists
headerdata: a list of strings
"""
QAbstractTableModel.__init__(self, parent, *args)
self.arraydata = datain
self.headerdata = headerdata
def rowCount(self, parent):
return len(self.arraydata)
def columnCount(self, parent):
return len(self.arraydata[0])
def data(self, index, role):
if not index.isValid():
return QVariant()
elif role != Qt.DisplayRole:
return QVariant()
return QVariant(self.arraydata[index.row()][index.column()])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(self.headerdata[col])
return QVariant()
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol))
if order == Qt.DescendingOrder:
self.arraydata.reverse()
self.emit(SIGNAL("layoutChanged()"))
if __name__ == "__main__":
main()
the position is in viewport coordinate, so if you are using
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
so you don't have event passed to popup, you can do the following
action = menu.exec_(self.tableView.viewport().mapToGlobal(pos))
instead.
This was a bit tricky, but following the subclassing example in this wiki example and replacing
15 action = menu.exec_(self.mapToGlobal(event.pos()))
with
15 action = menu.exec_(event.globalPos())
will make the popup menu's top left corner match the mouse click exactly.
This will work for maximized/minified windows.
Menu will be generated at right-bottom position of mouse.
menu.exec_(self.mapToGlobal(self.mapFromGlobal(QtGui.QCursor.pos())))

Resources