dynamically generating unique combo box and filling them with two different sources - python

I want to dynamically generate Label on left & combos on the right in a grid
{'PHOTOSHOP': '6.5', 'NUKE': '7.0v9', 'MAYA': '2014', 'TESTING': '1.28', 'KATANA': '1.7', 'MARI': '4.0'}
{'PHOTOSHOP': '10.5', 'NUKE': '6.3v6', 'MAYA': '2012', 'TESTING': '1.28', 'KATANA': '1.0', 'MARI': '1.0'}
my first problem is creating combos box in a way i can access them later outside the method based on name created at the time of for loop
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QWidget):
"""docstring for MainWindow"""
def __init__(self, dataIn1, dataIn2):
super(MainWindow, self).__init__()
self._dataIn1 = dataIn1
self._dataIn2 = dataIn2
self.buildUI()
def main(self):
self.show()
def buildUI(self):
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.setSpacing(10)
self.combo = QtGui.QComboBox('combo')
for index, item in enumerate(self._dataIn1.iteritems()):
# Below line doesn't work which i want to make work
# objective is to assign unique name so i can access-
# them later outside this method
#self.item[0]+"_Combo" = QtGui.QComboBox()
self.gridLayout.addWidget(QtGui.QLabel(item[0]), index, 0)
# once uique combo is created I want to populate them from dataIn1 & dataIn2 lists
self.gridLayout.addWidget(self.combo.addItems([item[-1]]), index, 1)
self.setLayout(self.gridLayout)
self.setWindowTitle('Experiment')
def main():
app = QtGui.QApplication(sys.argv)
smObj = MainWindow(dataIn1, dataIn2)
smObj.main()
app.exec_()
if __name__ == '__main__':
main()
secondly I want to those combo box to be filled in by each Keys value from both dataIn1 and dataIn2 sources..

To dynamically create attributes, you can use setattr:
setattr(self, 'combo%d' % index, combo)
However, it's probably much more flexible to keep the combos in a list (then you can easily iterate over them afterwards).
Your loop should end up looking something like this:
data1 = {
'PHOTOSHOP': '6.5', 'NUKE': '7.0v9', 'MAYA': '2014',
'TESTING': '1.28', 'KATANA': '1.7', 'MARI': '4.0',
}
data2 = {
'PHOTOSHOP': '10.5', 'NUKE': '6.3v6', 'MAYA': '2012',
'TESTING': '1.28', 'KATANA': '1.0', 'MARI': '1.0',
}
self.combos = []
for index, (key, value) in enumerate(data1.items()):
label = QtGui.QLabel(key, self)
combo = QtGui.QComboBox(self)
combo.addItem(value)
combo.addItem(data2[key])
self.combos.append(combo)
# or setattr(self, 'combo%d' % index, combo)
layout.addWidget(label, index, 0)
layout.addWidget(combo, index, 1)

Related

How to make custom QStandardItem for QTreeView?

In my project there is a window for editing files. Each file has some properties that the user can change with several checkboxes. Also each file has an icon.
Now it is implemented through QScrollArea in which widgets are added. It looks like this:
Now I want to add folder support for files. I would like to support drag n drop and so on.
I thought it would be a good idea in my case to use QTreeView.
The widget for the files has already been drawn and it suits me completely:
I haven't worked with QT's model/view framework before. after a couple of days of trying to draw something acceptable, I broke my brain. That's what I did:
import sys
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
from scanner.models import *
from gui.styles.settings import *
from gui.utils import setup_fonts
class BaseFileItem(QStandardItem):
def __init__(self, parent=None):
super(BaseFileItem, self).__init__(parent)
self.setEditable(False)
class TitleItem(QStandardItem):
file_type = 'title'
class FolderItem(QStandardItem):
file_type = 'folder'
class PreviewItem(BaseFileItem):
file_type = 'preview'
class FileItem(BaseFileItem):
file_type = 'file'
class Model(QStandardItemModel):
def supportedDropActions(self):
return Qt.MoveAction
class Delegate(QStyledItemDelegate):
checkbox_width = 50
checkboxes = [
'main',
'preview',
'use',
]
def sizeHint(self, option, index):
match index.data(1002):
case 'title':
height = 32
case 'folder':
height = 24
case _:
height = 48
return QSize(height, height)
def paint(self, painter, option, index):
super(Delegate, self).paint(painter, option, index)
match index.data(1002):
case 'title':
item_type = 'label'
case 'folder':
return
case _:
item_type = 'checkbox'
for column, name in enumerate(self.checkboxes):
self.add_checkbox_item(painter, option, column, name, item_type)
def add_checkbox_item(self, painter, option, column, text, item_type):
rect = option.rect
if item_type == 'checkbox':
item = QStyleOptionButton()
ce = QStyle.CE_CheckBox
else:
item = QStyleOptionHeader()
item.text = text
ce = QStyle.CE_HeaderLabel
item.item_type = item_type
x, y = rect.left() + rect.width() - (column + 1) * self.checkbox_width, rect.top()
item.rect = QRect(x, y, self.checkbox_width, rect.height())
QApplication.style().drawControl(ce, item, painter)
class AssetEditFilesView(QWidget):
rows = ['Asset files', 'Previews', 'Candidates']
def __init__(self, files, parent=None):
self.files = files
super(AssetEditFilesView, self).__init__(parent)
self.setContentsMargins(24, 24, 24, 24)
self.model = Model()
self.tree = QTreeView(self)
self.tree.setContentsMargins(4, 4, 4, 4)
self.tree.setItemDelegate(Delegate(self.tree))
self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.tree.setSortingEnabled(True)
self.tree.setDragEnabled(True)
self.tree.setAcceptDrops(True)
self.tree.setDropIndicatorShown(True)
self.tree.header().hide()
self.tree.setModel(self.model)
self.import_data(files)
self.tree.expandAll()
layout = QVBoxLayout(self)
layout.addWidget(self.tree)
def import_data(self, data):
self.model.setRowCount(0)
for group, files in zip(self.rows, self.prepare_files(data)):
if files:
self.add_files_to_group(group, files)
def add_files_to_group(self, group, files):
files = sorted(files, key=lambda x: str(x.data().asset_path))
data = [f.data().asset_path.parts for f in files]
max_len = max([len(x) for x in data])
prepared = [x for x in zip(*[[y[i] if i < len(y) else None for i in range(max_len)] for y in data])]
structure = self.create_structure(prepared, files)
root = self.add_file_row(self.model.invisibleRootItem(), group, TitleItem)
for folder, items in structure.items():
file_model = PreviewItem if group == 'Previews' else FileItem
self.add_items(root, folder, items, file_model=file_model)
def add_items(self, root, folder, items, file_model=FileItem):
if folder == '.':
for item in items:
self.add_file_row(root, item, file_model)
else:
new_root = self.add_file_row(root, folder, FolderItem)
for _folder, _items in items.items():
self.add_items(new_root, _folder, _items, file_model=file_model)
def add_file_row(self, root, item, file_model):
item_name = str(item.filename.name) if hasattr(item, 'filename') else item
item_model = file_model(item_name)
item_model.setData(item, 1001)
item_model.setData(file_model.file_type, 1002)
print(root)
root.appendRow(item_model)
return item_model
def create_structure(self, prepared, files):
structure = {'.': []}
for *parts, file in zip(*prepared, [f.data() for f in files]):
parts = list(filter(lambda x: x is not None, parts))
parent = structure
for part in parts:
if part not in parent:
parent[part] = {'.': []}
parent = parent[part]
parent['.'].append(file)
return structure
def prepare_files(self, data):
groups = [[], [], []]
for file in data:
if type(file) in [PreviewImage, PreviewVideo]:
group = 1
elif isinstance(file, BaseFileModel):
group = 0
else:
group = 2
item = QStandardItem(str(file.filename.name))
item.setData(file)
groups[group].append(item)
return groups
style = f'''
AssetEditFilesView QTreeView {{
{Fonts.normal}
border: 2px;
border-radius: 8px;
background-color: {Colors.asset_edit_bg};
}}
AssetEditFilesView {{
background-color: {Colors.popup_bg};
}}
AssetEditFilesView {{
background-color: {Colors.popup_bg};
'''
if __name__ == '__main__':
files = [
BaseFileModel(filename=r'c:\temp0036.png'),
BaseFileModel(filename=r'c:\temp0036.png'),
BaseFileModel(filename=r'c:\temp0036.png', asset_path=Path('./huita/huyatina')),
BaseFileModel(filename=r'c:\temp0048.png', asset_path=Path('./huita')),
BaseFileModel(filename=r'c:\temp0127.png', asset_path=Path('./ne_huita')),
BaseFileModel(filename=r'c:\temp0229.png', asset_path=Path('./ne_huita/huiyatina')),
PreviewVideo(filename=r'c:\references.gif', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp321.png', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp0267.png', asset_path=Path('./previews/generated')),
PreviewImage(filename=r'c:\temp3.png', asset_path=Path('./previews/generated/1/2')),
PreviewImage(filename=r'c:\temp.bin', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp.bin.png', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/2/1')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/1/2')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/1')),
]
app = QApplication(sys.argv)
setup_fonts(app)
app.setStyleSheet(style)
view = AssetEditFilesView(files)
view.setGeometry(800, 500, 800, 640)
view.setWindowTitle('QTreeview Example')
view.show()
sys.exit(app.exec())
I have 3 entities:
file (should look like on the 1st screenshot (3 checkboxes and an icon))
folder (must contain a name)
title (in fact the folder only the font should be larger)
how do i implement all entities using 1 QStyledItemDelegate?
Can I use a ready-made custom QWidget as a file entity?
the only thing I managed to do was to implement a more or less working sizeHint, but I have no idea how to add a file widget.
I will be grateful for any help, as now I am close to implementing all this through QScrollArea instead of QTreeView
i need some thing like this:
I also can't figure out how to apply styles to a QStandardItem, QStyledItemDelegate,

Update treemodel in real time PySide

How can I make it so when I click the Randomize button, for the selected treeview items, the treeview updates to show the changes to data, while maintaining the expanding items states and the users selection? Is this accomplished by subclasses the StandardItemModel or ProxyModel class? Help is much appreciated as I'm not sure how to resolve this issue.
It's a very simple example demonstrating the issue. When clicking Randmoize, all it's doing is randomly assigning a new string (name) to each coaches position on the selected Team.
import os
import sys
import random
from PySide2 import QtGui, QtWidgets, QtCore
class Team(object):
def __init__(self, name='', nameA='', nameB='', nameC='', nameD=''):
super(Team, self).__init__()
self.name = name
self.headCoach = nameA
self.assistantCoach = nameB
self.offensiveCoach = nameC
self.defensiveCoach = nameD
def randomize(self):
names = ['doug', 'adam', 'seth', 'emily', 'kevin', 'mike', 'sarah', 'cassy', 'courtney', 'henry']
cnt = len(names)-1
self.headCoach = names[random.randint(0, cnt)]
self.assistantCoach = names[random.randint(0, cnt)]
self.offensiveCoach = names[random.randint(0, cnt)]
self.defensiveCoach = names[random.randint(0, cnt)]
print('TRADED PLAYERS')
TEAMS = [
Team('Cowboys', 'doug', 'adam', 'seth', 'emily'),
Team('Packers'),
Team('Lakers', 'kevin', 'mike', 'sarah', 'cassy'),
Team('Yankees', 'courtney', 'henry'),
Team('Gators'),
]
class MainDialog(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.resize(600,400)
self.button = QtWidgets.QPushButton('Randomize')
self.itemModel = QtGui.QStandardItemModel()
self.proxyModel = QtCore.QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.itemModel)
self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setDynamicSortFilter(True)
self.proxyModel.setFilterKeyColumn(0)
self.treeView = QtWidgets.QTreeView()
self.treeView.setModel(self.proxyModel)
self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.treeView.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.treeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.treeView.setAlternatingRowColors(True)
self.treeView.setSortingEnabled(True)
self.treeView.setUniformRowHeights(False)
self.treeView.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
self.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.selectionModel = self.treeView.selectionModel()
# layout
self.mainLayout = QtWidgets.QVBoxLayout()
self.mainLayout.addWidget(self.treeView)
self.mainLayout.addWidget(self.button)
self.mainWidget = QtWidgets.QWidget()
self.mainWidget.setLayout(self.mainLayout)
self.setCentralWidget(self.mainWidget)
# connections
self.selectionModel.selectionChanged.connect(self.updateControls)
self.button.clicked.connect(self.randomizeTeams)
# begin
self.populateModel()
self.updateControls()
def randomizeTeams(self):
for proxyIndex in self.selectionModel.selectedRows():
sourceIndex = self.proxyModel.mapToSource(proxyIndex)
item = self.itemModel.itemFromIndex(sourceIndex)
team = item.data(QtCore.Qt.UserRole)
team.randomize()
# UPDATE UI...
def updateControls(self):
self.button.setEnabled(self.selectionModel.hasSelection())
def populateModel(self):
self.itemModel.clear()
self.itemModel.setHorizontalHeaderLabels(['Position', 'Name'])
# add teams
for ts in TEAMS:
col1 = QtGui.QStandardItem(ts.name)
col1.setData(ts, QtCore.Qt.UserRole)
# add coaches
childCol1 = QtGui.QStandardItem('Head Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.headCoach)
col1.appendRow([childCol1, childCol2])
childCol1 = QtGui.QStandardItem('Head Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.assistantCoach)
col1.appendRow([childCol1, childCol2])
childCol1 = QtGui.QStandardItem('Offensive Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.offensiveCoach)
col1.appendRow([childCol1, childCol2])
childCol1 = QtGui.QStandardItem('Defensive Coach')
childCol1.setData(ts, QtCore.Qt.UserRole)
childCol2 = QtGui.QStandardItem(ts.defensiveCoach)
col1.appendRow([childCol1, childCol2])
self.itemModel.appendRow([col1])
self.itemModel.setSortRole(QtCore.Qt.DisplayRole)
self.itemModel.sort(0, QtCore.Qt.AscendingOrder)
self.proxyModel.sort(0, QtCore.Qt.AscendingOrder)
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainDialog()
window.show()
app.exec_()
if __name__ == '__main__':
pass
main()
Your Team class should be a subclass of QStandardItem, which will be the top-level parent in the model. This class should create its own child items (as you are currently doing in the for-loop of populateModel), and its randomize method should directly reset the item-data of those children. This will ensure the changes are immediately reflected in the model.
So - it's really just a matter of taking the code you already have and refactoring it accordingly. For example, something like this should work:
TEAMS = {
'Cowboys': ('doug', 'adam', 'seth', 'emily'),
'Packers': (),
'Lakers': ('kevin', 'mike', 'sarah', 'cassy'),
'Yankees': ('courtney', 'henry'),
'Gators': (),
}
class Team(QtGui.QStandardItem):
def __init__(self, name):
super(Team, self).__init__(name)
for coach in ('Head', 'Assistant', 'Offensive', 'Defensive'):
childCol1 = QtGui.QStandardItem(f'{coach} Coach')
childCol2 = QtGui.QStandardItem()
self.appendRow([childCol1, childCol2])
def populate(self, head='', assistant='', offensive='', defensive=''):
self.child(0, 1).setText(head)
self.child(1, 1).setText(assistant)
self.child(2, 1).setText(offensive)
self.child(3, 1).setText(defensive)
def randomize(self, names):
self.populate(*random.sample(names, 4))
class MainDialog(QtWidgets.QMainWindow):
...
def randomizeTeams(self):
for proxyIndex in self.selectionModel.selectedRows():
sourceIndex = self.proxyModel.mapToSource(proxyIndex)
item = self.itemModel.itemFromIndex(sourceIndex)
if not isinstance(item, Team):
item = item.parent()
item.randomize(self._coaches)
def populateModel(self):
self.itemModel.clear()
self.itemModel.setHorizontalHeaderLabels(['Position', 'Name'])
self._coaches = []
# add teams
for name, coaches in TEAMS.items():
team = Team(name)
team.populate(*coaches)
self._coaches.extend(coaches)
self.itemModel.appendRow([team])
self.itemModel.setSortRole(QtCore.Qt.DisplayRole)
self.itemModel.sort(0, QtCore.Qt.AscendingOrder)
self.proxyModel.sort(0, QtCore.Qt.AscendingOrder)

Displaying an multiple excel tables into different tabs (one table per tab)

I'm trying to make a widget with multiple tabs that are occupied with different excel tables per tab and can't figure why I cant populate each tab separately.
About the tables the number of columns remain the same for all Excel tables only the number of rows change.
import sys
from PyQt5 import QtCore , QtWidgets ,QtGui
import pandas as pd
import os
class MyApp(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.sizeHint().width(), self.sizeHint().height()
self.resize(self.width(), self.height())
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self.table = QtWidgets.QTableWidget()
self.tabs = QtWidgets.QTabWidget()
self.table.setSortingEnabled(True)
self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.selectionModel().selectionChanged.connect(self.selected)
# self.add_flight_tab()
layout.addWidget(self.tabs)
tab_list = self.add_flight_tab(path_xl)
for tables , names in zip(range(tab_list.__sizeof__()), tab_list):
self.tabs.setCurrentIndex(tables)
self.load_data(f"{path_xl}/{names}")
self.setLayout(layout)
def load_data(self, path_to_xl):
df = pd.read_excel(path_to_xl,sheet_name=0, usecols=["AWB", "Destn.", "SCC", "Agent Name",
"Stated Pcs/Wgt/Vol (kg/CBM)","Booked Flight"])
if df.size == 0:
return
df.fillna('', inplace=True)
self.table.setRowCount(df.shape[0])
self.table.setColumnCount(df.shape[1])
self.table.setHorizontalHeaderLabels(df.columns)
self.table.insertColumn(self.table.columnCount())
self.table.setHorizontalHeaderItem(6 , QtWidgets.QTableWidgetItem("Offload"))
self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
for row in df.iterrows():
values = row[1]
for i in range(df.shape[0]):
check_box = QtWidgets.QTableWidgetItem()
check_box.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
check_box.setCheckState(QtCore.Qt.CheckState())
self.table.setItem(i, 6, check_box)
for col_index, value in enumerate(values):
if isinstance(value, (float, int)):
value = '{0:0,.0f}'.format(value)
tableItem = QtWidgets.QTableWidgetItem(str(value))
self.table.setItem(row[0], col_index, tableItem)
def add_flight_tab(self, path_files):
files = os.listdir(path_files)
new_ls = []
for xl, tab_ind in zip(files, range(files.__sizeof__())):
if xl.endswith(".xlsx") or xl.endswith(".XLSX"):
new_ls.append(xl)
self.tabs.insertTab(tab_ind,QtWidgets.QTableWidget(), xl)
self.tabs.setCurrentIndex(tab_ind)
return new_ls
if __name__ == '__main__':
path_xl = "path to excel"
app = QtWidgets.QApplication(sys.argv)
myApp = MyApp()
myApp.show()
# print(myApp.add_flight_tab())
try:
sys.exit(app.exec_())
except SystemExit:
print("Closing window...")
Everything I have tried gives two results(there must be something I'm missing out in here) :
The Widget is populated but the are no other tabs only thing changed is the following lines:
layout.addWidget(self.tabs)
tab_list = self.add_flight_tab(path_xl)
for tables , names in zip(range(tab_list.__sizeof__()), tab_list):
self.tabs.setCurrentIndex(tables)
self.load_data(f"{path_xl}/{names}")
layout.addChildWidget(self.table)
For now the names of the tabs is irrelevant , I only need to have number of tabs according to number of Excel files and each tab is filled with table accordingly.

How to properly order columns in a QTreeView based on a list as input

Basically I am displaying a QTreeView with 20 columns. I want the user to re-arrange the columns and push a save button to store the ordering as a list in an ini-file. On starting up the program, I want to re-order the columns based on the settings from the ini-file.
I am storing the original colum orders as list in "list_origin".
The desired order is in "list_custom".
E.g.
list_origin=['From', 'Subject', 'Date']
list_custom=['Date', 'Subject', 'From']
Now the problem is, when I move columns with the model headers moveSection() command, the original indexes are sometimes not correct anymore, because the columns might get inserted in between and thus lose their origin position index.
See example below: pushing the button "Rearrange cols to Date/Subject/From" will create an undesired order of the columns. How to arrange the colums in the desired order, based on the list_custom?
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# file: treeView_FindCol.py
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
def find_col_by_name(tree_obj, col_name: str) -> int:
""" Returns found column position as integer, else -1 """
pos_found: int = -1
model = tree_obj.model()
if model:
for col in range(model.columnCount()):
header = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
if str(header) == col_name:
pos_found = col
return pos_found
def find_col_by_index(tree_obj, col_index: int) -> int:
""" Returns found column position as integer, else -1 """
pos_found: int = -1
model = tree_obj.model()
header = tree_obj.header()
pos_found = header.visualIndex(col_index)
header_txt = model.headerData(pos_found, Qt.Horizontal, Qt.DisplayRole)
return pos_found
class App(QWidget):
FROM, SUBJECT, DATE = range(3)
def __init__(self):
super().__init__()
self.title = 'PyQt5 Treeview Example - pythonspot.com'
self.left = 800
self.top = 200
self.width = 640
self.height = 240
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
pushButton = QPushButton("Rearrange cols to Date/Subject/From")
groupBox = QGroupBox("Inbox")
treeView = QTreeView()
treeView.setRootIsDecorated(False)
treeView.setAlternatingRowColors(True)
pushButton.clicked.connect(lambda: self.rearrange_column_layout(treeView))
dataLayout = QHBoxLayout()
dataLayout.addWidget(treeView)
dataLayout.addWidget(pushButton)
groupBox.setLayout(dataLayout)
model = self.createMailModel(self)
treeView.setModel(model)
self.addMail(model, 'service#github.com', 'Your Github Donation','03/25/2017 02:05 PM')
self.addMail(model, 'support#github.com', 'Github Projects','02/02/2017 03:05 PM')
self.addMail(model, 'service#phone.com', 'Your Phone Bill','01/01/2017 04:05 PM')
self.addMail(model, 'service#abc.com', 'aaaYour Github Donation','03/25/2017 02:05 PM')
self.addMail(model, 'support#def.com', 'bbbGithub Projects','02/02/2017 03:05 PM')
self.addMail(model, 'service#xyz.com', 'cccYour Phone Bill','01/01/2017 04:05 PM')
mainLayout = QVBoxLayout()
mainLayout.addWidget(groupBox)
self.setLayout(mainLayout)
self.show()
def createMailModel(self,parent):
model = QStandardItemModel(0, 3, parent)
model.setHeaderData(self.FROM, Qt.Horizontal, "From")
model.setHeaderData(self.SUBJECT, Qt.Horizontal, "Subject")
model.setHeaderData(self.DATE, Qt.Horizontal, "Date")
return model
def addMail(self,model, mailFrom, subject, date):
model.insertRow(0)
model.setData(model.index(0, self.FROM), mailFrom)
model.setData(model.index(0, self.SUBJECT), subject)
model.setData(model.index(0, self.DATE), date)
def rearrange_column_layout(self, treeView):
print("restore_column_layout() called.")
list_custom: list = ['Date', 'Subject', 'From']
list_origin: list = []
model = treeView.model()
header = treeView.header()
col_count = model.columnCount()
for col_search_index in range(col_count):
col_found = header.visualIndex(col_search_index)
header_txt = model.headerData(col_search_index, Qt.Horizontal, Qt.DisplayRole)
list_origin.append(header_txt)
print(f"{list_origin=}")
print(f"{list_custom=}")
pos_custom: int = 0
pos_origin_last: int = 0
for item_custom in list_custom:
pos_origin: int = 0
for item_origin in list_origin:
if item_custom == item_origin:
msg_txt = f"moving col '{item_origin}' from {pos_origin} to {pos_custom}."
print(msg_txt)
QMessageBox.information(self, f"{item_origin}", msg_txt)
header.moveSection(pos_origin, pos_custom)
pos_origin_last = pos_origin
pos_origin += 1
pos_custom += 1
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I want to set the column ordering based on an input list
"list_origin" contains the header names in original order, e.g.
list_origin=['From', 'Subject', 'Date']
"list_custom" is the desired order of the columns in my QTreeView, e.g.
list_custom=['Date', 'Subject', 'From']
Now I am iterating over the custom list, and want to put the columns based on this positions with header moveSection().
So the algorithm basically is:
step1: start with index 0 of the custom list, it's "Date"
step2: get the position of "Date" from origin list, which is 2.
step3: call moveSection(2,0)
Trace output:
moving col 'Date' from 2 to 0.
moving col 'Subject' from 1 to 1.
moving col 'From' from 0 to 2.
But anyway the result is "From"/"Subject"/"Date" (!) and not as desired "Date"/"Subject"/"From".
The logic is to obtain the index of each element at the time of the iteration since the visual order is changing within the loop:
def rearrange_column_layout(self, treeView):
to_list = ["Date", "Subject", "From"]
header = treeView.header()
model = treeView.model()
for i, c in enumerate(to_list[:-1]):
from_list = [
model.headerData(header.logicalIndex(visual_index), Qt.Horizontal)
for visual_index in range(header.count())
]
j = from_list.index(c)
header.moveSection(j, i)

QTableview Select Item from filtered model

I have a QTableView which is using a QStandardItemModel. How can I select a specific row in the table with/without a search applied.
For example I typed 'y' in the search bar to filter the list to only display rows that contain the letter 'y'. When I click the button 'Select Emily' how can i make it select the correct row in the tableview, considering users can change the sort order?
import sys
from PySide import QtCore, QtGui
class Example(QtGui.QWidget):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.resize(400,400)
# controls
model = QtGui.QStandardItemModel(5, 3)
model.setHorizontalHeaderLabels(['NAME', 'AGE', 'CAREER'])
people = [
{'name': 'Kevin', 'age': 5, 'career': 'athlete'},
{'name': 'Maggie', 'age': 13, 'career': 'banker'},
{'name': 'Leslie', 'age': 32, 'career': 'banker'},
{'name': 'Abby', 'age': 32, 'career': 'marketing'},
{'name': 'Emily', 'age': 45, 'career': 'athlete'},
{'name': 'David', 'age': 27, 'career': 'banker'},
{'name': 'Johnny', 'age': 27, 'career': 'soccer'},
{'name': 'Marie', 'age': 63, 'career': 'secretary'}
]
for row, obj in enumerate(people):
item = QtGui.QStandardItem(obj['name'])
model.setItem(row, 0, item)
item = QtGui.QStandardItem(str(obj['age']))
model.setItem(row, 1, item)
item = QtGui.QStandardItem(obj['career'])
model.setItem(row, 2, item)
proxy_model = QtGui.QSortFilterProxyModel()
proxy_model.setSourceModel(model)
# controls
self.ui_table = QtGui.QTableView()
self.ui_table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.ui_table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.ui_table.setModel(proxy_model)
self.ui_table.setSortingEnabled(False)
self.ui_table.setSortingEnabled(True)
self.ui_table.sortByColumn(0, self.ui_table.horizontalHeader().sortIndicatorOrder())
self.ui_search = QtGui.QLineEdit()
self.ui_selected = QtGui.QPushButton('Select Emily')
# lay main
lay_main = QtGui.QVBoxLayout()
lay_main.addWidget(self.ui_search)
lay_main.addWidget(self.ui_table)
lay_main.addWidget(self.ui_selected)
self.setLayout(lay_main)
# connections
self.ui_selected.clicked.connect(self.clicked_selected_emily)
self.ui_search.textChanged.connect(self.filter_items)
def clicked_selected_emily(self):
print 'select emily'
self.ui_table.selectRow(2)
def filter_items(self, text):
rows = self.ui_table.model().rowCount()
for row in range(rows):
self.filter_row(row, text)
def filter_row(self, row, pattern):
if not pattern:
self.ui_table.setRowHidden(row, False)
return
model = self.ui_table.model()
columns = model.columnCount()
stringlist = []
# collect text from all columns into single string for searching
for c in range(columns):
mdx = model.index(row, c)
if mdx.isValid():
val = str(mdx.data(role=QtCore.Qt.DisplayRole)).lower()
stringlist.append(val)
# search for string
patterns = filter(None, [x.lower() for x in pattern.split(' ')])
results = all(any(x in y for y in stringlist) for x in patterns)
if results:
self.ui_table.setRowHidden(row, False)
else:
self.ui_table.setRowHidden(row, True)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
You have to use the match() method of the model:
def clicked_selected_emily(self):
print("select emily")
self.ui_table.clearSelection()
indexes = self.ui_table.model().match(
self.ui_table.model().index(0, 0),
QtCore.Qt.DisplayRole, # role of the search, the text uses the role Qt::DisplayRole
"Emily", # value that is being searched in the model.
-1, # maximum number of values ​​are being searched, if it is -1 it will search for all the matches
QtCore.Qt.MatchExactly # type of search
)
for ix in indexes:
self.ui_table.selectRow(ix.row())
Update:
By default, the search is the column that is passed to self.ui_table.model().index(0, col) in the previous example, if you want to search through all the columns you should only iterate over them, to observe the effect, multiselection is enabled:
self.ui_table.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)
...
def clicked_selected_emily(self):
print("select banker")
self.ui_table.clearSelection()
word = "banker"
for i in range(self.ui_table.model().columnCount()):
indexes = self.ui_table.model().match(
self.ui_table.model().index(0, i),
QtCore.Qt.DisplayRole, # role of the search, the text uses the role Qt::DisplayRole
"banker", # value that is being searched in the model.
-1, # maximum number of values ​​are being searched, if it is -1 it will search for all the matches
QtCore.Qt.MatchExactly # type of search
)
for ix in indexes:
self.ui_table.selectRow(ix.row())

Categories