how to have auto-completer in a QTableWidget cell that newly-created in pyqt5? - pyqt5

I am trying to have auto-completer in my table cells and it is working but the problem comes when I add a new row to the table,
1. how can I add the same functionality to newly-created rows?
I use _addRow method to add a new row.
2. how can I add this functionality to the second or the third column of the table?
here I use self.locs for the first column and I need sth like self.tech to match the second column.
Here is the code:
class TableItemCompleter(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
completionlist = index.data(Qt.UserRole)
autoCompleter = QCompleter(completionlist,parent)
autoCompleter.setCaseSensitivity(Qt.CaseInsensitive)
autoCompleter.setFilterMode(Qt.MatchContains)
editor.setCompleter(autoCompleter)
return editor
class TableWidget(QTableWidget):
def __init__(self, df, action='Edit'):
super().__init__()
self.df = df
self.setStyleSheet('font-size:15px;')
# Set table dimensions
rows, cols = self.df.shape
if action == 'Edit':
pass
elif action == 'Append':
n = 4 # number of rows
self.setRowCount(n)
self.setColumnCount(cols)
self.locs = self.df['LOCATION'].unique().tolist()
#tech = self.df['TECHNOLOGY'].unique().tolist()
self.setHorizontalHeaderLabels(list(self.df.columns))
self.verticalHeader().setDefaultSectionSize(50)
self.horizontalHeader().setDefaultSectionSize(200)
self.setItemDelegateForColumn(0, TableItemCompleter())
#self.setItemDelegateForColumn(TableItemCompleter(), 2)# how can I have it for second col?
for row in range(n-1):
for col in range(self.columnCount()):
item = QTableWidgetItem('')#str(self.df.tail(n-1).iloc[row,col]))
item.setData(Qt.UserRole, self.locs)
self.setItem(row, col, item)
#self.setItem(row, col, QTableWidgetItem(str(self.df.tail(2).iloc[row,col])))
self.cellChanged[int, int].connect(self.update_df)
#self.setItemDelegate(TableItemCompleter())
else:
pass
def _addRow(self):
rowCount = self.rowCount()
self.insertRow(rowCount)
Thank you :)

Since the list of completer strings is the same for each column of the model, there's no use in setting that data on the index. A better solution is to initialize the delegate with a "matrix" of completions based on the columns.
CompletionKeys = {
0: 'LOCATION',
1: 'TECHNOLOGY',
2: ...
}
class TableItemCompleter(QStyledItemDelegate):
def __init__(self, completionMap, parent=None):
super().__init__(parent)
self.completers = {}
for column, completionList in completionMap.items():
completer = QCompleter(completionList, self)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setFilterMode(Qt.MatchContains)
self.completers[column] = completer
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
if index.column() in self.completers:
editor.setCompleter(self.completers[index.column()])
return editor
class TableWidget(QTableWidget):
def __init__(self, df, action='Edit'):
super().__init__()
self.df = df
self.setStyleSheet('font-size:15px;')
# Set table dimensions
rows, cols = self.df.shape
if action == 'Edit':
pass
elif action == 'Append':
n = 4 # number of rows
self.setRowCount(n)
self.setColumnCount(cols)
self.setHorizontalHeaderLabels(list(self.df.columns))
self.verticalHeader().setDefaultSectionSize(50)
self.horizontalHeader().setDefaultSectionSize(200)
for row in range(n-1):
for col in range(cols):
item = QTableWidgetItem('')
self.setItem(row, col, item)
completionMap = {}
for col in range(cols):
key = CompletionKeys.get(col)
if key:
completionMap[col] = self.df[key].unique().tolist()
self.setItemDelegate(TableItemCompleter(completionMap, self))
self.cellChanged[int, int].connect(self.update_df)

Related

How to display a button in each cell of a QTableWidget's column so that it removes its corresponding row when clicked?

I want to display a button in each cell of a QTableWidget's column. Each button, when clicked, must remove its corresponding row in the table.
To do so, I created a RemoveRowDelegate class with the button as editor and used the QAbstractItemView::openPersistentEditor method in a CustomTable class to display the button permanently.
class RemoveRowDelegate(QStyledItemDelegate):
def __init__(self, parent, cross_icon_path):
super().__init__(parent)
self.cross_icon_path = cross_icon_path
self.table = None
def createEditor(self, parent, option, index):
editor = QToolButton(parent)
editor.setStyleSheet("background-color: rgba(255, 255, 255, 0);") # Delete borders but maintain the click animation (as opposed to "border: none;")
pixmap = QPixmap(self.cross_icon_path)
button_icon = QIcon(pixmap)
editor.setIcon(button_icon)
editor.clicked.connect(self.remove_row)
return editor
# Delete the corresponding row
def remove_row(self):
sending_button = self.sender()
for i in range(self.table.rowCount()):
if self.table.cellWidget(i, 0) == sending_button:
self.table.removeRow(i)
break
class CustomTable(QTableWidget):
def __init__(self, parent=None, df=None):
super().__init__(parent)
self.columns = []
self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
if df is not None:
self.fill(df)
# Build the table from a pandas df
def fill(self, df):
self.columns = [''] + list(df.columns)
nb_rows, _ = df.shape
nb_columns = len(self.columns)
self.setRowCount(nb_rows)
self.setColumnCount(nb_columns)
self.setHorizontalHeaderLabels(self.columns)
for i in range(nb_rows):
self.openPersistentEditor(self.model().index(i, 0))
for j in range(1, nb_columns):
item = df.iloc[i, j-1]
table_item = QTableWidgetItem(item)
self.setItem(i, j, table_item)
def add_row(self):
nb_rows = self.rowCount()
self.insertRow(nb_rows)
self.openPersistentEditor(self.model().index(nb_rows, 0))
def setItemDelegateForColumn(self, column_index, delegate):
super().setItemDelegateForColumn(column_index, delegate)
delegate.table = self
I set the delegate for the first column of the table and build the latter from a pandas dataframe:
self.table = CustomTable() # Here, self is my user interface
remove_row_delegate = RemoveRowDelegate(self, self.cross_icon_path)
self.table.setItemDelegateForColumn(0, remove_row_delegate)
self.table.fill(df)
For now, this solution does the job but I think of several other possibilities:
Using the QTableWidget::setCellWidget method
Overriding the paint method and catching the left click event
But:
I believe the first alternative is not very clean as I must create the buttons in a for loop and each time a row is added (but after all, I also call openPersistentEditor the same way here).
I am wondering if the second alternative is worth the effort. And if it does, how to do it?
Also:
I believe my remove_row method can be optimized as I iterate over all rows (that is one of the reasons why I thought about the second alternative). Would you have a better suggestion ?
I had to override the setItemDelegateForColumn method so that I can access the table from the RemoveRowDelegate class. Can it be avoided ?
Any other remark that you think might be of interest would be greatly appreciated!
As suggested by #ekhumoro, I finally used a context menu:
class CustomTable(QTableWidget):
def __init__(self, parent=None, df=None, add_icon_path=None, remove_icon_path=None):
super().__init__(parent)
self.add_icon_path = add_icon_path
self.remove_icon_path = remove_icon_path
# Activation of customContextMenuRequested signal and connecting it to a method that displays a context menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(lambda pos: self.show_context_menu(pos))
def show_context_menu(self, pos):
idx = self.indexAt(pos)
if idx.isValid():
row_idx = idx.row()
# Creating context menu and personalized actions
context_menu = QMenu(parent=self)
if self.add_icon_path:
pixmap = QPixmap(self.add_icon_path)
add_icon = QIcon(pixmap)
add_row_action = QAction('Insert a line', icon=add_icon)
else:
add_row_action = QAction('Insert a line')
add_row_action.triggered.connect(lambda: self.insertRow(row_idx))
if self.remove_icon_path:
pixmap = QPixmap(self.remove_icon_path)
remove_icon = QIcon(pixmap)
remove_row_action = QAction('Delete the line', icon=remove_icon)
else:
remove_row_action = QAction('Delete the line')
remove_row_action.triggered.connect(lambda: self.removeRow(row_idx))
context_menu.addAction(add_row_action)
context_menu.addAction(remove_row_action)
# Displaying context menu
context_menu.exec_(self.mapToGlobal(pos))
Moreover, note that using QTableWidget::removeRow method is more optimized than my previous method. One just need to get the row index properly from the click position thanks to QTableWidget::indexAt method.

Apppend and Delete Rows to Grid with GridTableBase

I am having trouble appending and deleting rows. My table changes a lot and must be rebuilt often so this has been a little tricky. All of my information comes from an SQL database. I am loading the results into a pandas DataFrame and then using it to populate the GridTableBase class. I am now trying to Append and Delete rows, but am having trouble overriding the class. I have been able to somewhat get it to work, but it behaves weird. For some reason, self.table.AppendRows(row) doesn't work and throws an error. The original was self.table.AppendRow(row), but AppendRow isn't a method. So I had to use a different method. I have to change a value in order to get the GridTableMessage to realize there has been a change, which is what I am doing here data.iloc[data.shape[0]-1,0] = str(val)
Ideally, I would add/delete the row from the table itself, but I can't figure out how to do that. I have derived most of my code from here https://github.com/wxWidgets/Phoenix/blob/master/demo/Grid_MegaExample.py but a lot of that will not work properly for me.
As of now, I can append a row, but for some reason, it appends 2 even though only one has been added to the DataFrame and GetNumberRows is returning the correct count. I assume it has something to do with the way I am accessing the table class. Can anyone provide some clarity?
def rowPopup(self, row, evt):
"""(row, evt) -> display a popup menu when a row label is right clicked"""
appendID = wx.Window.NewControlId()#wx.NewId()
deleteID = wx.Window.NewControlId()#wx.NewId()
x = self.GetRowSize(row)/2
if not self.GetSelectedRows():
self.SelectRow(row)
menu = wx.Menu()
xo, yo = evt.GetPosition()
menu.Append(appendID, "Append Row")
menu.Append(deleteID, "Delete Row(s)")
def append(event, self=self, row=row):#event, self=self, row=row
global data
#print("Append")
#self.table.AppendRows(row)
dlg = wx.TextEntryDialog(self,'Enter a new Key ID to insert into the ' + str("'") + data.columns[0] + str("'") + ' column.', 'Insert New Record')
dlg.SetValue("")
if dlg.ShowModal() == wx.ID_OK:
#print('You entered: %s\n' % dlg.GetValue())
val = dlg.GetValue()
#data[~pd.isnull(data).all(1)].fillna('')
#data['tables_id'].apply('(g)'.format)
data.loc[data.iloc[-1].name + 1,:] = ""
data.iloc[data.shape[0]-1,0] = str(val)
self.Reset()
#print(data)
#data = data.append(pd.Series(dtype='object'), ignore_index=True)
#self.data = DataTable(data)
#data[~pd.isnull(data).all(1)].fillna('')
#self.data = DataTable(data)
def delete(event, self=self, row=row):#event, self=self, row=row
global data
rows = self.GetSelectedRows()
data.drop(data.index[rows],inplace=True)
print (data)
self.Reset()
#self.table.DeleteRow(row)
#print(row)
#print(rows)
#EVT_MENU(self, appendID, append)
#EVT_MENU(self, deleteID, delete)
self.Bind(wx.EVT_MENU, append, id=appendID)
self.Bind(wx.EVT_MENU, delete, id=deleteID)
self.PopupMenu(menu, wx.Point(round(x), round(yo)))
menu.Destroy()
class DataTable(gridlib.GridTableBase):
def __init__(self, data):
gridlib.GridTableBase.__init__(self)
self.headerRows = 1
if data is None:
data = pd.DataFrame()
self.data = data
print("Instance")
#Store the row and col length to see if table has changed in size
self._rows = self.GetNumberRows()
self._cols = self.GetNumberCols()
self.odd=gridlib.GridCellAttr()
self.odd.SetBackgroundColour((217,217,217))
self.even=gridlib.GridCellAttr()
self.even.SetBackgroundColour((255,255,255))
def GetAttr(self, row, col, kind):
attr = [self.even, self.odd][row % 2]
attr.IncRef()
return attr
def GetNumberRows(self):
#print("# Rows:",len(self.data))
return len(self.data)# - 1
def GetTypeName(self, row, col):
#print(wx.grid.GRID_VALUE_STRING)
return wx.grid.GRID_VALUE_STRING
def GetNumberCols(self):
#print("# Cols:",len(self.data.columns)+ 1)
return len(self.data.columns) + 1
#return len(self.data.columns) #+ 1
def IsEmptyCell(self, row, col):
return False
def GetValue(self, row, col):
if col == 0:
try:
return self.data.index[row]
except:
print("Row,Col(",row,col,")","OOB")
return ""
else:
try:
return str(self.data.iloc[row, col - 1])
except:
print("Row,Col(",row,col,")","OOB")
return ""
def GetColLabelValue(self, col):
if col == 0:
if self.data.index.name is None:
return 'Index'
else:
return self.data.index.name
return self.data.columns[col - 1]
def ResetView(self, grid):
"""
(wxGrid) -> Reset the grid view. Call this to
update the grid if rows and columns have been added or deleted
"""
print('Old::' , self._rows, self._cols)
print('New::' , self.GetNumberRows(),self.GetNumberCols())
print(data)
grid.BeginBatch()
for current, new, delmsg, addmsg in [
(self._rows, self.GetNumberRows(), gridlib.GRIDTABLE_NOTIFY_ROWS_DELETED, gridlib.GRIDTABLE_NOTIFY_ROWS_APPENDED),
(self._cols, self.GetNumberCols(), gridlib.GRIDTABLE_NOTIFY_COLS_DELETED, gridlib.GRIDTABLE_NOTIFY_COLS_APPENDED),
]:
if new < current:
msg = gridlib.GridTableMessage(self,delmsg,new,current-new)
#grid.ProcessTableMessage(msg)
self.GetView().ProcessTableMessage(msg)
print("OvN:",self._rows,self.GetNumberRows())
return True
if new > current:
msg = gridlib.GridTableMessage(self,addmsg,new-current)
self.GetView().ProcessTableMessage(msg)
grid.ProcessTableMessage(msg)
#self.UpdateValues(grid)
msg = gridlib.GridTableMessage(self, gridlib.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
grid.ProcessTableMessage(msg)
print("OvN:",self._rows,self.GetNumberRows())
grid.EndBatch()
self._rows = self.GetNumberRows()
self._cols = self.GetNumberCols()
# update the column rendering plugins
#self._updateColAttrs(grid)
# XXX
# Okay, this is really stupid, we need to "jiggle" the size
# to get the scrollbars to recalibrate when the underlying
# grid changes.
h,w = grid.GetSize()
grid.SetSize((h+1, w))
grid.SetSize((h, w))
grid.ForceRefresh()
def UpdateValues(self, grid):#self, grid
"""Update all displayed values"""
# This sends an event to the grid table to update all of the values
msg = gridlib.GridTableMessage(self, gridlib.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
grid.table.ProcessTableMessage(msg)
class DataGrid(gridlib.Grid):
def __init__(self, parent, data, lc, tc): # data
gridlib.Grid.__init__(self, parent, - 1) #,colnames,-1 # data
self.lc = lc
self.tc = tc
self.table = DataTable(data)
self.SetTable(self.table, True)
self.Bind(gridlib.EVT_GRID_LABEL_RIGHT_CLICK, self.OnLabelRightClicked)
self.Bind(gridlib.EVT_GRID_CELL_RIGHT_CLICK, self.OnCellRightClick)
self.Bind(gridlib.EVT_GRID_CELL_CHANGED, self.onCellChanged) #wx.grid
def Reset(self):
"""reset the view based on the data in the table. Call
this when rows are added or destroyed"""
self.table.ResetView(self)
def OnCellRightClick(self, event):
print ("OnCellRightClick: (%d,%d)\n" % (event.GetRow(), event.GetCol()))
def OnLabelRightClicked(self, evt):
row, col = evt.GetRow(), evt.GetCol()
if row == -1: print("col")#self.colPopup(col, evt)
elif col == -1: self.rowPopup(row, evt)
def rowPopup(self, row, evt):
"""(row, evt) -> display a popup menu when a row label is right clicked"""
appendID = wx.Window.NewControlId()#wx.NewId()
deleteID = wx.Window.NewControlId()#wx.NewId()
x = self.GetRowSize(row)/2
if not self.GetSelectedRows():
self.SelectRow(row)
menu = wx.Menu()
xo, yo = evt.GetPosition()
menu.Append(appendID, "Append Row")
menu.Append(deleteID, "Delete Row(s)")
def append(event, self=self, row=row):#event, self=self, row=row
global data
#print("Append")
#self.table.AppendRows(row)
dlg = wx.TextEntryDialog(self,'Enter a new Key ID to insert into the ' + str("'") + data.columns[0] + str("'") + ' column.', 'Insert New Record')
dlg.SetValue("")
if dlg.ShowModal() == wx.ID_OK:
val = dlg.GetValue()
#data[~pd.isnull(data).all(1)].fillna('')
#data['tables_id'].apply('(g)'.format)
data.loc[data.iloc[-1].name + 1,:] = ""
data.iloc[data.shape[0]-1,0] = str(val)
self.Reset()
#print(data)
#self.data = DataTable(data)
def delete(event, self=self, row=row):#event, self=self, row=row
global data
rows = self.GetSelectedRows()
data.drop(data.index[rows],inplace=True)
print (data)
self.Reset()
self.Bind(wx.EVT_MENU, append, id=appendID)
self.Bind(wx.EVT_MENU, delete, id=deleteID)
self.PopupMenu(menu, wx.Point(round(x), round(yo)))
menu.Destroy()
class MainFrame(wx.Frame):
def __init__(self, parent, data): # (self, parent, data):
wx.Frame.__init__(self, parent, -1, "Varkey Foundation") #, size=(640,480))
#Create a panel
self.p = wx.Panel(self)
self.Maximize(True)
#Create blank dataframe
data = pd.DataFrame() #pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD')
#data.reset_index(drop=True, inplace=True)
self.data = DataTable(data)
self.nb = wx.Notebook(self.p)
self.p.SetBackgroundColour( wx.Colour( 0, 0, 0 ) ) # 38,38,38
self.nb.SetBackgroundColour(wx.Colour(58, 56, 56) )
#self.SetBackgroundColour( wx.Colour( 255, 255, 56 ) )
#create the page windows as children of the notebook
self.page1 = PageOne(self.nb)
self.page2 = PageTwo(self.nb)
self.page3 = PageThree(self.nb)
# add the pages to the notebook with the label to show on the tab
self.nb.AddPage(self.page1, "Data")
self.nb.AddPage(self.page2, "Analyze")
self.nb.AddPage(self.page3, "Change Log")
#CreateFonts
self.b_font = wx.Font(14,wx.ROMAN,wx.NORMAL,wx.BOLD, True)
self.lbl_font = wx.Font(14,wx.ROMAN,wx.NORMAL,wx.NORMAL, True)
self.cb_font = wx.Font(11,wx.SCRIPT,wx.ITALIC,wx.NORMAL, True)
self.h_font = wx.Font(18,wx.DECORATIVE,wx.ITALIC,wx.BOLD, True)
#Create username textcontrol <<<<<<<<<<<< Passed to grid class
self.tc_user =wx.TextCtrl(self.p,value='cmccall95',size = (130,25))
self.tc_password =wx.TextCtrl(self.p,value='Achilles95', style=wx.TE_PASSWORD | wx.TE_PROCESS_ENTER,size = (130,25))
self.tc_password.Bind(wx.EVT_TEXT_ENTER,self.onLogin)
self.tc_user.SetFont(self.cb_font)
self.tc_password.SetFont(self.cb_font)
#Create Change log lstCtrl <<<<<<<<<<<< Passed to grid class
self.lc_change = wx.ListCtrl(self.p,-1,style = wx.TE_MULTILINE | wx.LC_REPORT | wx.LC_VRULES)
self.lc_change.InsertColumn(0,"User ID")
self.lc_change.InsertColumn(1,"Status")
self.lc_change.InsertColumn(2,"Description")
self.lc_change.InsertColumn(3,"Date/Time")
#Set column widths
self.lc_change.SetColumnWidth(0, 75)
self.lc_change.SetColumnWidth(1, 75)
self.lc_change.SetColumnWidth(2, 450)
self.lc_change.SetColumnWidth(3, 125)
#Create the grid and continue layout
self.grid = DataGrid(self.page1, data, self.lc_change, self.tc_user)
#More layout code...
def onLoadNewData(self, event): #This is how I'm replacing the data in my table class
global data
self.Freeze()
if self.combo_table.GetValue():
#Connect to db
self.connect_mysql()
#Determine db table
self.getTable()
#Get new data
sql_query = "SELECT * FROM " + tbl
self.cursor.execute(sql_query)
temp = pd.read_sql(sql_query, con=self.db_con)
temp.reset_index(drop=True, inplace=True)
data = temp[~pd.isnull(temp).all(1)].fillna('')
#Create title #if data:
if not data.empty:
self.title.SetLabel(str(self.combo_table.GetValue()))
print(str(self.combo_table.GetValue()))
self.grid.Destroy()
self.grid = DataGrid(self.page1, data, self.lc_change, self.tc_user)
#self.grid.HideCol(0)
self.grid.AutoSizeColumns()
#Insert grid into existing sizer
self.p1_sizer.Insert(1,self.grid,1,wx.RIGHT| wx.LEFT|wx.EXPAND, 20)
self.p1_sizer.Layout()
#RESIZE
else:
print("Error:Dataframe is empty")
self.close_connection()
else:
print('CANT BE BLANK')
self.Thaw()
if __name__ == '__main__':
import sys
app = wx.App()
frame = MainFrame(None, sys.stdout) # (None, sys.stdout)
frame.Show(True)
app.MainLoop()

How to map different indices in Pyomo?

I am a new Pyomo/Python user. Now I need to formulate one set of constraints with index 'n', where all of the 3 components are with different indices but correlate with index 'n'. I am just curious that how I can map the relationship between these sets.
In my case, I read csv files in which their indices are related to 'n' to generate my set. For example: a1.n1, a2.n3, a3.n5 /// b1.n2, b2.n4, b3.n6, b4.n7 /// c1.n1, c2.n2, c3.n4, c4.n6 ///. The constraint expression of index n1 and n2 is the follows for example:
for n1: P(a1.n1) + L(c1.n1) == D(n1)
for n2: - F(b1.n2) + L(c2.n2) == D(n2)
Now let's go the coding. The set creating codes are as follow, they are within a class:
import pyomo
import pandas
import pyomo.opt
import pyomo.environ as pe
class MyModel:
def __init__(self, Afile, Bfile, Cfile):
self.A_data = pandas.read_csv(Afile)
self.A_data.set_index(['a'], inplace = True)
self.A_data.sort_index(inplace = True)
self.A_set = self.A_data.index.unique()
... ...
Then I tried to map the relationship in the constraint construction like follows:
def createModel(self):
self.m = pe.ConcreteModel()
self.m.A_set = pe.Set( initialize = self.A_set )
def obj_rule(m):
return ...
self.m.OBJ = pe.Objective(rule = obj_rule, sense = pe.minimize)
def constr(m, n)
As = self.A_data.reset_index()
Amap = As[ As['n'] == n ]['a']
Bs = self.B_data.reset_index()
Bmap = Bs[ Bs['n'] == n ]['b']
Cs = self.C_data.reset_index()
Cmap = Cs[ Cs['n'] == n ]['c']
return sum(m.P[(p,n)] for p in Amap) - sum(m.F[(s,n)] for s in Bmap) + sum(m.L[(r,n)] for r in Cmap) == self.D_data.ix[n, 'D']
self.m.cons = pe.Constraint(self.m.D_set, rule = constr)
def solve(self):
... ...
Finally, the error raises when I run this:
KeyError: "Index '(1, 1)' is not valid for indexed component 'P'"
I know it is the wrong way, so I am wondering if there is a good way to map their relationships. Thanks in advance!
Gabriel
I just forgot to post my answer to my own question when I solved this one week ago. The key thing towards this problem is setting up a map index.
Let me just modify the code in the question. Firstly, we need to modify the dataframe to include the information of the mapped indices. Then, the set for the mapped index can be constructed, taking 2 mapped indices as example:
self.m.A_set = pe.Set( initialize = self.A_set, dimen = 2 )
The names of the two mapped indices are 'alpha' and 'beta' respectively. Then the constraint can be formulated, based on the variables declared at the beginning:
def constr(m, n)
Amap = self.A_data[ self.A_data['alpha'] == n ]['beta']
Bmap = self.B_data[ self.B_data['alpha'] == n ]['beta']
return sum(m.P[(i,n)] for i in Amap) + sum(m.L[(r,n)] for r in Bmap) == D.loc[n, 'D']
m.TravelingBal = pe.Constraint(m.A_set, rule = constr)
The summation groups all associated B to A with a mapped index set.

QSortFilterProxyModel does not apply Caseinsensitive

As I've subclassed QSortFilterModel to be able to search thru several coloumns in a QListView, the CaseInsensitive option no longer works. Ive tried to apply it as follows:
class CustomSortFilterProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super(CustomSortFilterProxyModel, self).__init__(parent)
self.filterString = ''
self.filterFunctions = {}
self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) #Applied here
def setFilterString(self, text):
self.filterString = str(text)
self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) #And applied here
self.invalidateFilter()
def filterAcceptsRow(self, row_num, parent):
self.filterColumns = [1,3]
model = self.sourceModel()
row = model.row(row_num)
tests = [self.filterString in row[col] for col in self.filterColumns]
return True in tests
How come my search string is case sensitive?
The sensitivity you set there only applies to the default filterAcceptsRow implementation. If you override it, you'll need to handle this yourself, by doing something like:
return any(self.filterString.casefold() in row[col].casefold() for col in self.filterColumns))
(see the str.casefold docs)

How to resize rows in a QTreeView and a QStandardItemModel?

I'd like to set my rows to a fixed height. I've found an example using QAbstractItemModel, but I'm using QStandardItemModel. When I run the app, the QTreeView is blank. Any thoughts on how I could get this working for a QStandardItemModel?
import sys
from PySide import QtCore, QtGui
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.data = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.rootItem = TreeItem(None)
for i, c in enumerate("abcdefg"):
child = TreeItem([i, c], self.rootItem)
self.rootItem.appendChild(child)
parent = self.rootItem.childItems[1]
child = TreeItem(["down", "down"], parent)
parent.appendChild(child)
def columnCount(self, parent):
return 2
def data(self, index, role):
if not index.isValid():
return None
if role == QtCore.Qt.DisplayRole:
item = index.internalPointer()
return item.data[index.column()]
elif role == QtCore.Qt.SizeHintRole:
print "giving size hint"
return QtCore.QSize(10, 10)
return None
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return ["A", "B"][section]
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.childItems[row]
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
parentItem = index.internalPointer().parentItem
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return len(parentItem.childItems)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
model = TreeModel()
view = QtGui.QTreeView()
view.setModel(model)
view.setWindowTitle("Simple Tree Model")
view.show()
sys.exit(app.exec_())
Also, I've been looking around for the meaning of the term delegate in Qt, but the concept still isn't clicking in my head yet. Any insight into that would be appreciated as well!
You generally do this using QItemDelegates (or QStyledItemDelegate if you want stylesheet styling to work) by overriding the sizeHint method and always returning a size of a fixed height.
class MyDelegate(QtGui.QStyledItemDelegate):
def sizeHint(self, option, index):
my_fixed_height = 30
size = super(MyDelegate, self).sizeHint(option, index)
size.setHeight(my_fixed_height)
return size
view = QtGui.QTreeView()
delegate = MyDelegate()
view.setItemDelegate(delegate)