PyQt4 QAbstractItemModel cuts of floats at 2 decimal places - python

I have an abstract item model. which is displayed as a tree.
The purpose is to store a set of preferences.
The code for the qt object is:
class QPreferenceModel(QAbstractItemModel):
'Convention states only items with column index 0 can have children'
#report_thread_error
def __init__(self, pref_struct, parent=None):
super(QPreferenceModel, self).__init__(parent)
self.rootPref = pref_struct
#report_thread_error
def index2Pref(self, index=QModelIndex()):
'''Internal helper method'''
if index.isValid():
item = index.internalPointer()
if item:
return item
return self.rootPref
#-----------
# Overloaded ItemModel Read Functions
#report_thread_error
def rowCount(self, parent=QModelIndex()):
parentPref = self.index2Pref(parent)
return parentPref.qt_row_count()
#report_thread_error
def columnCount(self, parent=QModelIndex()):
parentPref = self.index2Pref(parent)
return parentPref.qt_col_count()
#report_thread_error
def data(self, index, role=Qt.DisplayRule):
'''Returns the data stored under the given role
for the item referred to by the index.'''
if not index.isValid():
return QVariant()
if role != Qt.DisplayRole and role != Qt.EditRole:
return QVariant()
nodePref = self.index2Pref(index)
data = nodePref.qt_get_data(index.column())
var = QVariant(data)
print('---')
print('data = %r' % data)
print('type(data) = %r' % type(data))
#if isinstance(data, float):
#var = var.toDouble()[0]
print('var= %r' % var)
return var
#report_thread_error
def index(self, row, col, parent=QModelIndex()):
'''Returns the index of the item in the model specified
by the given row, column and parent index.'''
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parentPref = self.index2Pref(parent)
childPref = parentPref.qt_get_child(row)
if childPref:
return self.createIndex(row, col, childPref)
else:
return QModelIndex()
#report_thread_error
def parent(self, index=None):
'''Returns the parent of the model item with the given index.
If the item has no parent, an invalid QModelIndex is returned.'''
if index is None: # Overload with QObject.parent()
return QObject.parent(self)
if not index.isValid():
return QModelIndex()
nodePref = self.index2Pref(index)
parentPref = nodePref.qt_get_parent()
if parentPref == self.rootPref:
return QModelIndex()
return self.createIndex(parentPref.qt_parents_index_of_me(), 0, parentPref)
#-----------
# Overloaded ItemModel Write Functions
#report_thread_error
def flags(self, index):
'Returns the item flags for the given index.'
if index.column() == 0:
# The First Column is just a label and unchangable
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
if not index.isValid():
return Qt.ItemFlag(0)
childPref = self.index2Pref(index)
if childPref:
if childPref.qt_is_editable():
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
return Qt.ItemFlag(0)
#report_thread_error
def setData(self, index, data, role=Qt.EditRole):
'Sets the role data for the item at index to value.'
if role != Qt.EditRole:
return False
leafPref = self.index2Pref(index)
result = leafPref.qt_set_leaf_data(data)
if result is True:
self.dataChanged.emit(index, index)
return result
#report_thread_error
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if section == 0:
return QVariant('Config Key')
if section == 1:
return QVariant('Config Value')
return QVariant()
I assume the problem is probably in setData or data
def data(self, index, role=Qt.DisplayRule):
'''Returns the data stored under the given role
for the item referred to by the index.'''
if not index.isValid():
return QVariant()
if role != Qt.DisplayRole and role != Qt.EditRole:
return QVariant()
nodePref = self.index2Pref(index)
data = nodePref.qt_get_data(index.column())
var = QVariant(data)
print('---')
print('data = %r' % data)
print('type(data) = %r' % type(data))
#if isinstance(data, float):
#var = var.toDouble()[0]
print('var= %r' % var)
return var
def setData(self, index, data, role=Qt.EditRole):
'Sets the role data for the item at index to value.'
if role != Qt.EditRole:
return False
leafPref = self.index2Pref(index)
result = leafPref.qt_set_leaf_data(data)
if result is True:
self.dataChanged.emit(index, index)
return result
I was messing around with data to see if turning the QVariant into a double would do something. I also tried replacing Qt.DisplayRole and Qt.EditRole with Qt.UserRole.
I'm not actually sure what is controlling the display and edit precision of the float. The type is correct, but whenever I want to enter something like .0002 Qt prevents me from typing after .00

You could use a custom item delegate to adjust the precision of floats, but it might be overkill for this particular use-case.
A simpler solution would be to just return the values as strings - then you can format the displayed values in whatever way you like.

Related

pyqt5 comboBox - get the associate value for selected item

i want to set a comboBox and show my table's column1 data as items and associated value column2 is for selected item id and want to setText in a qLabel.
i am using a model to view the items in comboBox and working fine. i can get the currentText value but how to get the associated value for the items.
in my case :
my sql table like:
type id
------------
DIV 2
TRANS 33
FEE 41
EXP 89
now , i can set the column1(type) values into comboBox successfully. now, if user selected value 'FEE' , than qlable should be updated as its associate id : 41. how to do that!
df=loadData()
model=PandasModel(df)
self.comboBox.setModel(model)
self.comboBox.setModelColumn(0)
content=self.comboBox.currentText()
self.label.setText(content) # content should be ID instead of currentText
pandasMode:
class PandasModel(QtCore.QAbstractTableModel):
def __init__(self, df = pd.DataFrame(), parent=None):
QtCore.QAbstractTableModel.__init__(self, parent=parent)
self._df = df
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
if orientation == QtCore.Qt.Horizontal:
try:
return self._df.columns.tolist()[section]
except (IndexError, ):
return QtCore.QVariant()
elif orientation == QtCore.Qt.Vertical:
try:
return self._df.index.tolist()[section]+1
except (IndexError, ):
return QtCore.QVariant()
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
return str(self._df.iloc[index.row(), index.column()])
return None
def setData(self, index, value, role):
if not index.isValid():
return False
if role != QtCore.Qt.EditRole:
return False
row = index.row()
if row < 0 or row >= len(self._df.values):
return False
column = index.column()
if column < 0 or column >= self._df.columns.size:
return False
self._df.values[row][column] = value
self.dataChanged.emit(index, index)
return True
# def rowCount(self, parent=QtCore.QModelIndex()):
# return len(self._df.index)
def rowCount(self, parent=None):
return len(self._df.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self._df.columns)
def sort(self, column, order):
colname = self._df.columns.tolist()[column]
self.layoutAboutToBeChanged.emit()
self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True)
self._df.reset_index(inplace=True, drop=True)
self.layoutChanged.emit()
Assuming that the model stores the id in the second column then it has to obtain the associated index:
ID_COLUMN = 1
index = self.comboBox.model().index(
self.comboBox.currentIndex(), ID_COLUMN, self.comboBox.rootModelIndex()
)
id_ = index.data()
print(id_)
self.label.setText(id_)

Qt Model for both QTreeView and QTableView

I'm trying to create a model that can be used for both a QTableView and QTreeView. As an example, my data is something like:
ID
Location
Name
101
201
Apple
201
None
Kitchen
102
201
Banana
301
None
Cellar
302
301
Potatoes
202
302
Nail
So every entry has a location which is itself an entry in the model. For the QTableView, I'd like to simply display all entries under each other as shown above, while for the QTreeView I'd like something like
201: Kitchen
101: Apple
102: Banana
301: Cellar
302: Potatoes
202: Nail
My problem however is that I can't figure out how to implement QAbstractProxyModel.maptoSource() or mapfromSource() as I lose information about the parent in the QTableView. Reading https://www.qtcentre.org/threads/26163-Map-table-to-tree-through-model-view-possible it seems that perhaps this is not possible at all. However the QAbstractProxyModel explicitly says that's it's meant for showing data in both views. Can anyone point me in the right direction or knows whether it's possible to implement a model like this? Especially in Python, I can't find any examples unfortunately.
I really like the idea of just using an unindented TreeView as a sort of TableView. Unfortunately I'm still having trouble creating the model. Currently, only the top entries are being shown.
class MyModel(qtg.QStandardItemModel):
def __init__(
self,
engine
):
self.engine = engine
self.hierarchy_key = 'location_id'
self.column_names = ['id', 'location_id', 'name', 'quantity']
super().__init__(0, len(self.fields))
self.setHorizontalHeaderLabels(self.column_names)
self.root = self.invisibleRootItem()
self.build_model()
def build_model(self):
def add_children_to_tree(entries, parent_item):
for entry in entries:
items = []
for col in self.column_names:
text = getattr(entry, col)
item = qtg.QStandardItem(text)
items.append(qtg.QStandardItem(text))
parent_item.appendRow(items)
item = items[1] #the location_id item
parent_item.setChild(item.index().row(), item.index().column(), item)
with session_scope(self.engine) as session:
child_entries = (
session.query(self.entry_type)
.filter(
getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(
entry.id
)
)
.all()
)
if child_entries:
add_children_to_tree(child_entries, item)
self.removeRows(0, self.rowCount())
with session_scope(self.engine) as session:
root_entries = session.query(self.entry_type).filter(getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(None)).all()
if not isinstance(root_entries, list):
root_entries = [root_entries]
add_children_to_tree(root_entries, self.root)
The idea is that the session query results in a list of entries. Each entry is a record in the database with the attributes "id", "location_id", etc. Each attribute thus is an Item and the list of items creates a row in the model. I can't figure out how one makes the row of items a child of another row in the way it's shown here:
I assume the setChild() function needs to be called differently?
As there is a distinct lack of examples for python, I'll post my modified version of the simpletreemodel here, which is what ended up working for me. By then using a QTreeView instead of a QTableView as suggested, I got the table to behave as I wanted it too. Overall, this creates MyItem which is an item containing the entire row of information and I then use recursion to add children to parents if their value for the hierarchy_key (location_id) is equal to the id of the parent.
class MyItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return len(self.itemData)
def data(self, column=None):
try:
if column == None:
return [self.itemData[i] for i in range(self.columnCount())]
return self.itemData[column]
except IndexError:
return None
def parent(self):
return self.parentItem
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class MyModel(QtCore.QAbstractItemModel):
def __init__(self, entry_type, engine, hierarchy_key, description_key, parent=None):
super(ORMModel, self).__init__(parent)
self.entry_type = entry_type
if isinstance(self.entry_type, str):
self.entry_type = getattr(ds, self.entry_type)
self.engine = engine
self.hierarchy_key = hierarchy_key
self.column_names = ['id', 'location_id', 'name', 'quantity']
self.rootItem = MyItem(self.column_names)
self.setHeaderData(0, Qt.Horizontal, self.rootItem)
self.initiateModel()
def root(self):
return self.rootItem
def columnCount(self, parent):
if parent.isValid():
return parent.internalPointer().columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == Qt.DisplayRole:
return item.data(index.column())
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 self.rootItem.data(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.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
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 parentItem.childCount()
def initiateModel(self):
def add_children_to_tree(entries, parent_item):
for entry in entries:
row = []
for field in self.fields.keys():
val = getattr(entry, field)
if isinstance(val, list):
text = "; ".join(map(str, val))
else:
text = str(val)
row.append(text)
item = ORMItem(row, parent_item)
parent_item.appendChild(item)
with session_scope(self.engine) as session:
child_entries = (
session.query(self.entry_type)
.filter(
getattr(
getattr(self.entry_type, self.hierarchy_key), "is_"
)(entry.id)
)
.all()
)
if child_entries:
add_children_to_tree(child_entries, item)
with session_scope(self.engine) as session:
root_entries = (
session.query(self.entry_type)
.filter(
getattr(getattr(self.entry_type, self.hierarchy_key), "is_")(None)
)
.all()
)
if not isinstance(root_entries, list):
root_entries = [root_entries]
add_children_to_tree(root_entries, self.rootItem)

removeRow in QTableView/QAbstractTableModel by CheckBox

I've created a table view where I'm trying to drop rows by clicking the checkbox embedded in the table view. I get to the point where it indexes the correct row when the checkbox is deselected, but the model refuse to remove the row and instead returns 'False'. Any thoughts? Still new to tableviews after switching from tablewidgets
class pandasModel(QAbstractTableModel):
def __init__(self, data, check_col=[0, 0]):
QAbstractTableModel.__init__(self)
self._data = data
self._check = check_col
cs = 0
if check_col[1]: cs = 2
self._checked = [[cs for i in range(self.columnCount())] for j in range(self.rowCount())]
def rowCount(self, parent=QModelIndex):
return self._data.shape[0]
def columnCount(self, parent=QModelIndex):
return self._data.shape[1]
def data(self, index, role):
if index.isValid():
data = self._data.iloc[index.row(), index.column()]
if role == Qt.DisplayRole:
if isinstance(data, float):
return f'{data:,.2f}'
elif isinstance(data, (int, np.int64)):
return f'{data:,}'
else:
return str(data)
elif role == Qt.TextAlignmentRole:
if isinstance(data, (float, int, np.int64)):
return Qt.AlignVCenter + Qt.AlignRight
elif role == Qt.CheckStateRole:
if self._check[0] > 0:
if self._check[0] == index.column():
return self._checked[index.row()][index.column()]
return None
def flags(self, index):
if not index.isValid(): return
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsEditable
def setData(self, index, value, role):
if not index.isValid() or role != Qt.CheckStateRole: return False
self._checked[index.row()][index.column()] = value
if value == 0:
print('checked')
self.removeRow(index.row()) #THIS IS WHERE IT RETURNS FALSE
self.dataChanged.emit(index, index)
return True
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
return None

How can I check if the value passed in a PyQt TableModelView is an integer and not for example a letter/symbol?

I'm relatively new to Python and especially PyQt and model-view programming. I want to have it so that someone can only enter integers in my table and not letters/symbols etc. Here's the model I've made so far for my tableView widget:
class PixelTableModel(QtCore.QAbstractTableModel):
def __init__(self):
super(PixelTableModel, self).__init__()
self.pixel_coordinate = [[None, None, None, None]]
def rowCount(self, parent):
return 1
def columnCount(self, parent):
return 4
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
self.pixel_coordinate[row][column] = value
print(self.pixel_coordinate) #testing
return True
return False
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = self.pixel_coordinate[row][column]
return value
def headerData(self, section, orientation, role): # section = row column, orientation = vertical/horizontal
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
dict = {0: "Xstart", 1: "Ystart", 2: "Xmax", 3: "Ymax"}
for key in dict:
if section == key:
return dict[key]
else:
return "Pixel coordinate"
It seems to work except obviously for the part where it still can take letters/symbols as input in the tableView. I've tried a couple of things in the setData() method but can't seem to get it work, always get some type of error or it won't even change the box at all. Thanks to anyone that can help me with this. Also sorry for bad English.
For anyone still interested, after going through it again fixed it with simple try except block:
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
try:
row = index.row()
column = index.column()
string_to_int = int(value)
self.pixel_coordinate[row][column] = value
print(self.pixel_coordinate) #testing
return True
except ValueError:
print("Not a number")
return False
return False

How do I create a tree view (with checkbox) inside a combo box - PyQt

I'm using PYQT for developing an application. my requirement is to insert a tree view with checkbox inside a combobox items. I would like to know how to achieve this?
I have the following code but this does not work.
class CheckboxInsideListbox(QWidget):
def __init__(self, parent = None):
super(CheckboxInsideListbox, self).__init__(parent)
self.setGeometry(250,250,300,300)
self.MainUI()
def MainUI(self):
#stb_label = QLabel("Select STB\'s")
stb_combobox = QComboBox()
length = 10
cb_layout = QVBoxLayout(stb_combobox)
for i in range(length):
c = QCheckBox("STB %i" % i)
cb_layout.addWidget(c)
main_layout = QVBoxLayout()
main_layout.addWidget(stb_combobox)
main_layout.addLayout(cb_layout)
self.setLayout(main_layout)
Please let me know if I'm missing anything here.
You should create a model that support Qt.CheckStateRole in data and SetData methods and the flag Qt.ItemIsUserCheckable in the flags method.
I Paste you here an example i am using in a project, this is a QSortFilterProxyModel generic implementation to use in any model but you can use the same ideas in your model implementation, obviously i am using internal structures in this subclass you have not directly in PyQt and are attached to my internal implementation (self.booleanSet and self.readOnlySet).
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
if index.column() in self.booleanSet:
return Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
elif index.column() in self.readOnlySet:
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
else:
return QSortFilterProxyModel.flags(self, index)
def data(self, index, role):
if not index.isValid():
return QVariant()
if index.column() in self.booleanSet and role in (Qt.CheckStateRole, Qt.DisplayRole):
if role == Qt.CheckStateRole:
value = QVariant(Qt.Checked) if index.data(Qt.EditRole).toBool() else QVariant(Qt.Unchecked)
return value
else: #if role == Qt.DisplayRole:
return QVariant()
else:
return QSortFilterProxyModel.data(self, index, role)
def setData(self, index, data, role):
if not index.isValid():
return False
if index.column() in self.booleanSet and role == Qt.CheckStateRole:
value = QVariant(True) if data.toInt()[0] == Qt.Checked else QVariant(False)
return QSortFilterProxyModel.setData(self, index, value, Qt.EditRole)
else:
return QSortFilterProxyModel.setData(self, index, data, role)
If you really mean to apply a layout to a layout, try adding your widget to your cb_layout. Otherwise get rid of your sublayout.

Categories