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())
Related
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)
I have a list of items that I need to filter based on some conditions. I'm wondering whether Dask could do this filtering in parallel, as the list is very long (a few dozen million records).
Basically, what I need to do is this:
items = [
{'type': 'dog', 'weight': 10},
{'type': 'dog', 'weight': 20},
{'type': 'cat', 'weight': 15},
{'type': 'dog', 'weight': 30},
]
def item_is_valid(item):
item_is_valid = True
if item['type']=='cat':
item_is_valid = False
elif item['weight']>20:
item_is_valid = False
# ...
# elif for n conditions
return item_is_valid
items_filtered = [item for item in items if item_is_valid(item)]
With Dask, what I have achieved to do is the following:
def item_is_valid_v2(item):
"""Return the whole item if valid."""
item_is_valid = True
if item['type']=='cat':
item_is_valid = False
elif item['weight']>20:
item_is_valid = False
# ...
# elif for n conditions
if item_is_valid:
return item
results = []
item = []
for item in items:
delayed = dask.delayed(item_is_valid)(item)
results.append(delayed)
results = dask.compute(*results)
However, the result I get contains a few None values, which then need to be filtered out somehow in a non-parallel way.
({'type': 'dog', 'weight': 10}, {'type': 'dog', 'weight': 20}, None, None)
Perhaps the bag API will work you, this is a rough pseudo-code:
import dask.bag as db
bag = db.from_sequence() # or better yet read it from disk
result = bag.filter(item_is_valid) # note this uses the first version (bool)
To inspect if this is working, inspect the outcome of result.take(5) and if that is satisfactory:
computed_result = result.compute()
Im trying to append additional info to an existing list but i received an error message instead.
Error: 4.Invalid embedded document instance provided to an
EmbeddedDocumentField: ['family']
class Family(db.EmbeddedDocument):
name = db.StringField()
# gender = db.StringField()
class House(db.Document):
house_id = db.IntField(required=True, unique=True)
housingType = db.StringField(required=True)
family = db.EmbeddedDocumentListField(Family)
def to_json(self):
return {
"house_id": self.house_id,
"housingType": self.housingType,
"family_members": self.family
}
#app.route('/api/add_family/<h_id>', methods=['POST'])
def add_family(h_id):
content = request.json
h = House.objects(house_id=h_id).get()
h.family.append(content['family'])
h.save()
return make_response("Added family member successfully", 201)
What im trying to achieve is as follows:
Current data:
{
'house_id': 1,
'family': [{'name': 'John', 'Gender': 'Male'}]
}
After appending, it should look like this:
{
'house_id': 1,
'family': [{'name': 'John, 'Gender': 'Male'}, {'name': 'Peter', 'Gender': 'Male'}]
}
Here is my solution. Hopefully it helps.
#app.route('/api/add_family/<h_id>', methods=['POST'])
def add_family(h_id):
'''
family member is added only if its not already in the database
'''
edited = False
content = request.json
h = House.objects.get(house_id=h_id).to_json()
h = json.loads(h)
family_arr = h['family']
if family_arr:
# family_arr not empty
count = family_arr[-1].get('id') + 1
else:
count = 1
for new_name in content['family']:
if not dup_name_check(family_arr, new_name['name']):
new_name.update({'id': count})
family_arr.append(new_name)
count += 1
edited = True
if edited:
House.objects.get(house_id=h_id).update(family=family_arr)
return make_response(f"Successfully added family member in House ID:{h_id}", 201)
else:
return make_response(f"Duplicated entries detected!", 400)
I have data which looks like this.
tempList2=[{'Date': '21-Aug-2019', 'Day': 'Sunday', 'Status': 'This is the message. It should be wrapped!!'}, {'Date': '22-Aug-2019', 'Day': 'Monday', 'Status': 'Message Delivered'}, {'Date': '23-Aug-2019', 'Day': 'Tuesday', 'Status': 'Invalid Data found!! Please retry'}]
Next I am creating a dataframe for that Data. After that, I am using tkinter module to view the data.
Here's the Sample Code:
import pandas as pd
import tkinter as tk
a =[]
tempList2=[{'Date': '21-Aug-2019', 'Day': 'Sunday', 'Status': 'This is the message. It should be wrapped!!'}, {'Date': '22-Aug-2019', 'Day': 'Monday', 'Status': 'Message Delivered'}, {'Date': '23-Aug-2019', 'Day': 'Tuesday', 'Status': 'Invalid Data found!! Please retry'}]
for i in tempList2:
print(i)
print(type(i))
b = list(i.values())
a.append(b)
print(a)
tempList = a
df = pd.DataFrame(tempList)
# --- functions ---
def change(event, row, col):
# get value from Entry
value = event.widget.get()
# set value in dataframe
df.iloc[row,col] = value
print(df)
# --- main --
root = tk.Tk()
# create entry for every element in dataframe
rows, cols = df.shape
for r in range(rows):
for c in range(cols):
e = tk.Entry(root)
e.insert(0, df.iloc[r,c])
e.grid(row=r, column=c)
# ENTER
e.bind('<Return>', lambda event, y=r, x=c: change(event,y,x))
# ENTER on keypad
e.bind('<KP_Enter>', lambda event, y=r, x=c: change(event,y,x))
# start program
root.mainloop()
Here's the output image I got:
In the Output, the data doesn't get wrapped up. The data gets breaked. I need the data to be wrapped up with in the certain row. I have lots of data to be viewed. So I need a Scrollbar to access it. Help me with some solutions to wrap up the data and a scrollbar attached to the entire window.
As #stovfl commended, you can use tk.Text, which supports word wrap via the WORD keyword argument.
tk.Text(root, height=col_height, width=col_width, wrap=tk.WORD)
Two issues to handle:
Adjusting the height of all columns in a row to be the same.
You can find the largest string in a row and determine the height of the column based on that.
On Enter key the Text field will move the cursor down, sometimes hiding the text.
Strip the text and update the column on Enter key.
Code
# ...
def change(event, row, col):
# get value from Entry
value = event.widget.get("1.0", tk.END)
# update column & set value in dataframe
event.widget.delete("1.0", tk.END)
event.widget.insert("1.0", value.strip())
df.iloc[row, col] = value.strip()
print(df)
root = tk.Tk()
rows, cols = df.shape
col_width = 30
for r in range(rows):
longest_string = max(df.loc[r].values, key=len)
# If you know last element will be longest,
# No need to calculate longest string
# col_height = len(df.loc[r].values[-1])//col_width +1
col_height = len(longest_string)//col_width + 1
# or int(len(col_text)/col_width)+1
for c in range(cols):
col_text = df.iloc[r, c]
e = tk.Text(root, height=col_height, width=col_width, wrap=tk.WORD)
e.insert("1.0", df.iloc[r, c])
e.grid(row=r, column=c)
e.bind('<Return>', lambda event, y=r, x=c: change(event, y, x))
e.bind('<KP_Enter>', lambda event, y=r, x=c: change(event, y, x))
# ...
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)