PyQt won't display my answer as a float - python

I am trying to make a simple calculator that will take 2 numbers that a user enters and multiplies them together. However, when I set the ans_string as a float the code doesn't work but if I set it as a str it works.
I want to be able to input numbers with decimal places and only display 2 decimal places on the answer.
If I run the code with ans_str I get the following error "AtrributeError: 'QLabel' object has no attribute 'setT'
from PyQt5.QtWidgets import QMainWindow, QWidget, QApplication
from ui_multform import Ui_CalcWindow
class MainWindow(QMainWindow, Ui_CalcWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
self.ui = Ui_CalcWindow()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.mult)
def mult(self, value):
in_num1 = float(self.ui.lineEdit.text())
in_num2 = float(self.ui.lineEdit_2.text())
ans = in_num1 * in_num2
ans_string = float(ans) # << here ans_string is created
print (ans_string) # << Where u need to put it
self.ui.label_2.setText(ans_string)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv) # A new instance of QApplication
calculator = MainWindow() # We set the form to be our MainWindow (design)
calculator.show() # Show the form
sys.exit(app.exec_())

You just need to convert the float to a string, and apply the formatting that you want to use to display the correct number of decimals. You can use the python string formatting mini-language to convert the float to a string, and also only display two decimal places.
ans_string = '{0:0.2f}'.format(ans)
self.ui.label_2.setText(ans_string)

Related

Why can't I sort columns in my PyQt5 QTableWidget using UserRole data? [duplicate]

This question already has answers here:
How to customize sorting behaviour in QTableWidget
(2 answers)
Closed 9 days ago.
I am trying to sort my QTableWidget columns by the values stored in the user role of each QTableWidgetItem, but I am unable to do so. I have enabled sorting with self.setSortingEnabled(True), and I have set the data in each QTableWidgetItem with item.setData(Qt.DisplayRole, f'M - {r}') and item.setData(Qt.UserRole, r). However, when I try to sort the columns by the values stored in the user role, it sorts the columns by the values stored in the display role instead.
Here is a minimal working example of my code:
from random import randint
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget, QWidget, QGridLayout, \
QTableWidgetItem, QPushButton
class Table(QTableWidget):
def __init__(self):
super().__init__()
self.setSortingEnabled(True)
def populate(self):
self.clear()
self.setColumnCount(3)
self.setRowCount(200)
for row in range(500):
for column in range(3):
r = randint(0, 1000)
item = QTableWidgetItem()
item.setData(Qt.DisplayRole, f'M - {r}')
item.setData(Qt.UserRole, r)
self.setItem(row, column, item)
class MainApp(QMainWindow):
def __init__(self):
super().__init__()
self.table = Table()
self.button = QPushButton('Roll')
self.button.clicked.connect(self.table.populate)
layout = QWidget()
self.setCentralWidget(layout)
grid = QGridLayout()
layout.setLayout(grid)
grid.addWidget(self.button)
grid.addWidget(self.table)
if __name__ == '__main__':
app = QApplication([])
main_app = MainApp()
main_app.showMaximized()
app.exec()
Additionally, I tried using EditRole, but the values that appear in the table are not the values from DisplayRole. For example, in the code below, I set item.setData(Qt.DisplayRole, f'M - {r}'), but even though r is an integer, the display role value is a string ('M - {r}'). I was hoping that sorting by UserRole or EditRole would sort based on the integer value of r, but that doesn't seem to be the case.
item.setData(Qt.DisplayRole, f'M - {r}')
item.setData(Qt.EditRole, int(r))
Sorting is always based on Qt.DisplayRole.
Trying to use the EditRole is pointless, as the setData() documentation points out:
Note: The default implementation treats Qt::EditRole and Qt::DisplayRole as referring to the same data.
The Qt.UserRole is a custom role that could be used for anything (and containing any type) and by default is not used for anything in Qt. Setting a value with the UserRole doesn't change the sorting, because Qt knows nothing about its existence or how the value should be used.
Since you are using strings for the sorting, the result is that numbers are not sorted as you may think: for instance "120" is smaller than "13", because "12" comes before "13".
The only occurrence in which the DisplayRole properly sorts number values is when it is explicitly set with a number:
item.setData(Qt.DisplayRole, r)
Which will not work for you, as you want to show the "M - " prefix. Also, a common mistake is trying to use that in the constructor:
item = QTableWidgetItem(r)
And while the syntax is correct, its usage is wrong, as the integer constructor of QTableWidgetItem is used for other purposes.
If you want to support custom sorting, you must create a QTableWidgetItem subclass and implement the < operator, which, in Python, is the __lt__() magic method:
class SortUserRoleItem(QTableWidgetItem):
def __lt__(self, other):
return self.data(Qt.UserRole) < other.data(Qt.UserRole)
Then you have to create new items using that class. Note that:
you should always try to use existing items, instead of continuously creating new ones;
as explained in the setItem() documentation, you should always disable sorting before adding new items, especially when using a loop, otherwise the table will be constantly sorted at each insertion (thus making further insertion inconsistent);
you're using the a range (500) inconsistent with the row count (200);
you should also set an item prototype based on the subclass above;
class Table(QTableWidget):
def __init__(self):
super().__init__()
self.setSortingEnabled(True)
self.setItemPrototype(SortUserRoleItem())
def populate(self):
self.setSortingEnabled(False)
self.setColumnCount(3)
self.setRowCount(200)
for row in range(200):
for column in range(3):
r = randint(0, 1000)
item = self.item(row, column)
if not item:
item = SortUserRoleItem()
self.setItem(row, column, item)
item.setData(Qt.DisplayRole, 'M - {}'.format(r))
item.setData(Qt.UserRole, r)
self.setSortingEnabled(True)
Note that, as an alternative, you could use a custom delegate, then just set the value of the item as an integer (as shown above) and override the displayText():
class PrefixDelegate(QStyledItemDelegate):
def displayText(self, text, locale):
if isinstance(text, int):
text = f'M - {text}'
return text
class Table(QTableWidget):
def __init__(self):
super().__init__()
self.setItemDelegate(PrefixDelegate(self))
# ...
def populate(self):
# ...
item = self.item(row, column)
if not item:
item = QTableWidgetItem()
self.setItem(row, column, item)
item.setData(Qt.DisplayRole, r)
Use a QTableView instead. Widgets are meant for very basic use cases. It's important to invoke setSortRole on the model. Also, your setData arguments were in reverse order, correct is data, role.
from random import randint
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
class MainApp(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table_view = QtWidgets.QTableView()
self.table_view.setSortingEnabled(True)
self.model = QtGui.QStandardItemModel()
self.model.setSortRole(QtCore.Qt.UserRole)
self.table_view.setModel(self.model)
self.button = QtWidgets.QPushButton('Roll')
layout = QtWidgets.QWidget()
self.setCentralWidget(layout)
grid = QtWidgets.QGridLayout()
layout.setLayout(grid)
grid.addWidget(self.button)
grid.addWidget(self.table_view)
self.button.clicked.connect(
self.populate
)
def populate(self):
self.table_view.model().clear()
for _ in range(500):
r = randint(0, 1000)
item = QtGui.QStandardItem()
item.setData(f'M - {r}', QtCore.Qt.DisplayRole)
item.setData(r, QtCore.Qt.UserRole)
self.table_view.model().appendRow(item)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
main_app = MainApp()
main_app.showMaximized()
app.exec()

How can I change the text of the edit line and add and subtract it?

I have two edit lines that when a letter from a dictionary is typed in it, it returns a number, and this number is added to the next edit line number and printed on a label. Now I want the previous number to be reduced and the new number to be added when I change one of the edit lines.
import sys
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QLabel
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.uic import loadUiType
oroh, _ = loadUiType("oroh.ui")
class OrOh(QWidget, oroh):
def __init__(self):
QWidget.__init__(self)
self.setupUi(self)
self.lin_sh1.returnPressed.connect(self.shift1)
self.lin_sh2.returnPressed.connect(self.shift2)
def shift1(self):
shifts = {"m":7, "s":7, "v":7, "t":7, "e":3, "le":5, "ld":12,
"n":12, "ln":14, "mn":19, "en":17, "me":10, "f":24}
sh1 = self.lin_sh1.text()
if sh1 in shifts.keys():
a = shifts[sh1]
print(a)
else:
a = 0
print(a)
self.lbl_shifts.setText(f"{a}")
def shift2(self):
shifts = {"m":7, "s":7, "v":7, "t":7, "e":3, "le":5, "ld":12,
"n":12, "ln":14, "mn":19, "en":17, "me":10, "f":24}
a = self.lbl_shifts.text()
sh2 = self.lin_sh2.text()
if sh2 in shifts.keys():
b = shifts[sh2]
else:
b = 0
result = int(a) + int(b)
self.lbl_shifts.setText(f"{result}")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = OrOh()
window.show()
app.exec_()
Since the keys can use more than one character, a possible solution is to use the textChanged(text) signal and call the function to compute the shifts afterwards.
The fact, though, is that the two existing functions are actually flawed: they almost do the same thing, but don't consider the two fields as they should.
I suggest you to merge those functions and use the textChanged to "reset" the value only when a valid key is entered. Note that this implies a logic that is not immediate to understand (even for the user) and might also cause some confusion
class OrOh(QWidget, oroh):
def __init__(self):
QWidget.__init__(self)
self.setupUi(self)
self.shifts = {"m":7, "s":7, "v":7, "t":7, "e":3, "le":5, "ld":12,
"n":12, "ln":14, "mn":19, "en":17, "me":10, "f":24}
self.lin_sh1.returnPressed.connect(self.computeShift)
self.lin_sh2.returnPressed.connect(self.computeShift)
self.lin_sh1.textChanged.connect(self.checkText)
self.lin_sh2.textChanged.connect(self.checkText)
def checkText(self):
lin1 = self.lin_sh1.text()
lin2 = self.lin_sh2.text()
if self.lbl_shifts.text() and (not lin1 or not lin2):
self.computeShift()
def computeShift(self):
lin1 = self.lin_sh1.text()
lin2 = self.lin_sh2.text()
if ((lin1 in self.shifts and not lin2) or
(lin2 in self.shifts and not lin1) or
(lin1 in self.shifts and lin2 in self.shifts)):
shift = self.shifts.get(lin1, 0) + self.shifts.get(lin2, 0)
self.lbl_shifts.setText(str(shift))
I would suggest you to add some validation logic instead, or at least always use the textChanged signal. A simple solution would be to change the text color whenever the text entered is not in the dictionary, and eventually clear it when the focus changes:
class OrOh(QWidget, oroh):
def __init__(self):
QWidget.__init__(self)
self.setupUi(self)
self.shifts = {"m":7, "s":7, "v":7, "t":7, "e":3, "le":5, "ld":12,
"n":12, "ln":14, "mn":19, "en":17, "me":10, "f":24}
self.lin_sh1.textChanged.connect(self.computeShift)
self.lin_sh2.textChanged.connect(self.computeShift)
self.lin_sh1.editingFinished.connect(lambda: self.checkField(self.lin_sh1))
self.lin_sh2.editingFinished.connect(lambda: self.checkField(self.lin_sh2))
def checkField(self, field):
if not field.text() in self.shifts:
field.setText('')
def computeShift(self):
shift = 0
for field in (self.lin_sh1, self.lin_sh2):
value = self.shifts.get(field.text(), 0)
if field.text() and not value:
field.setStyleSheet('color: red;')
else:
field.setStyleSheet('')
shift += self.shifts.get(field.text(), 0)
self.lbl_shifts.setText(str(shift))
Another possibility would be to use an editable QComboBox (with the insert policy set to NoInsert) and check the shift only when the inserted text is in the list.

Is it possible to restrict QListWidget so users can only select 3 items max?

I know that you can change the selection mode to select more than one item from a list. However, changing to multiselection means users can choose to select all items in a list if they wanted to. I was wondering if it was possible to allow users to select multiple items but set a max number of items (ie users can select 1-3 items from a list of 20 items).
I've looked through the documentation and various questions, but can't see any methods that would do this.
import sys
from PyQt5.QtWidgets import QAbstractItemView, QApplication, QListWidget, QListWidgetItem, QVBoxLayout, QWidget
class Example(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(50,50,320,200)
layout = QVBoxLayout(self)
combo = QListWidget(self)
combo.setSelectionMode(QAbstractItemView.MultiSelection)
counter = 1
while (counter < 21):
combo.addItem(str(counter))
counter = counter + 1
layout.addWidget(combo)
self.setWindowTitle("QListWidget")
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
My example code displays a list of 20 items. It has multiselection set so users can select multiple but no current restrictions.
One way is to subclass QListWidget and override selectionCommand, e.g.
from PyQt5.QtCore import QItemSelectionModel
class MyListWidget(QListWidget):
def __init__(self, parent=None, max_selected = 3):
super().__init__(parent)
self.max_selected = max_selected
def selectionCommand(self, index, event):
if len(self.selectedItems()) >= self.max_selected:
return QItemSelectionModel.Deselect
else:
return super().selectionCommand(index, event)
Okay an excerpt from the documentation found here:
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QAbstractItemView.html
States the following:
Note that the range is not updated until the widget is shown.
Several other functions are concerned with selection control; for
example setSelectionMode() , and setSelectionBehavior() . This class
provides a default selection model to work with ( selectionModel() ),
but this can be replaced by using setSelectionModel() with an instance
of QItemSelectionModel
So yes it is possible to do this (as it is with all coding endeavors -- anything is possible) and the above states the how you just need to figure out how you are going to implement it -- probably going to need to use Behavior or maybe make your own Model
This may work for similiar cases.
list is a QListWidget defined in example.ui. change 3 in len(selected_items) > 3 to any value you desire.
ui_filename = "example.ui"
baseUIClass, baseUIWidget = uic.loadUiType(ui_filename)
class Example(baseUIWidget, baseUIClass):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.setupUi(self)
self.list.itemSelectionChanged.connect(self.Enforce_Selection_Size)
def Enforce_Selection_Size(self):
selected_items = self.list.selectedItems()
if len(selected_items) > 3:
selected_items[3].setSelected(False)

QTextEditor has no attribute related to text

I'm new to working with python and even more new to working with PyQt4. I created this GUI using QT designer
I converted the file from .ui to a .py file already. The problem I'm running into though is that in my code it is not grabbing the data that the user inputs in the first texteditor box instead I'm getting this error message.
I also looked up the .plainText and tried to use that as well, but I could not get that working either.
import sys
from Calculator import *
class Calc(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.calc_payment, QtCore.SIGNAL('clicked()'), self.calculate)
def calculate(self):
if len(self.ui.Mortgage_value.text())!=0:
q = int(self.ui.Mortgage_value.text())
else:
q = 0
if len(self.ui.tax_rate.text())!=0:
r = int(self.ui.tax_rate.text())
else:
r = 0
if len(self.ui.years.text())!=0:
y = int(self.ui.years.text())
else:
y = 0
taxrate = r / 12
time = y * 12
total = q / ((1 - ((1 + r)**-y))/ r)
#if (calc_payment == 'clicked()'):#test
self.ui.results_window.setText("Monthly Payment: " +int(total))#was str
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = Calc()
myapp.show()
sys.exit(app.exec_())
I appreciate all input that is given.
Thank you.
QTextEdit does not have a text () method, as it is an editor that can render html, it has 2 methods:
toPlainText()
toHTML()
The first returns the plain text, the second in HTML format. in your case, use the first method:
if self.ui.Mortgage_value.toPlainText() != "":
q = int(self.ui.Mortgage_value.toPlainText())
Note: Although I would recommend using QLineEdit instead of QTextEdit since in the case of QTextEdit it has several lines and how you want to convert it to a number can cause problems, besides QLineEdit supports validators like QIntValidator that helps you not to have problems with numbers.

Editing a dictionary with a QTreeView

I have a reduced code sample in which a window is created with just one Button.
Pressing it will pop a Qt dialog TestDialog which takes as parameter a Python dictionary. This dictionary is displayed in an editable QTreeView inside the dialog.
After changing some values you can click on Ok or Cancel to either accept or discard the changes. Once the dialog is closed my intention is to retrieve from the main window the modified dictionary calling dialog.get_data() which, right now only returns the original unmodified dictionary. After clicking the Ok button, the retrieved dictionary is printed to stdout.
My question is, how can I modify the dictionary when the tree view is modified? Is there a way to automatically attach a function to be executed on each item modification? So that when editing, for example, a float in the tree view, then the corresponding value will be updated as float in the dictionary?
The dictionary does not have a fixed size and the types on it may change. The list of types is limited and known though and, for this example, could be reduced to {int, str, float, Other}. It can be assumed as well that the parents are not supposed to be editable and the children are only editable in the second column, just as it is shown in the example bellow.
Here is the code I have:
import sys
from PyQt4 import QtGui, QtCore, uic
from copy import deepcopy
class TestDialog(QtGui.QDialog):
def __init__(self, data):
super(TestDialog, self).__init__()
self.data = deepcopy(data)
# Layout
btOk = QtGui.QPushButton("OK")
btCancel = QtGui.QPushButton("Cancel")
self.tree = QtGui.QTreeView()
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(btOk)
hbox.addWidget(btCancel)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.addWidget(self.tree)
self.setLayout(vbox)
self.setGeometry(300, 300, 600, 400)
# Button signals
btCancel.clicked.connect(self.reject)
btOk.clicked.connect(self.accept)
# Tree view
self.tree.setModel(QtGui.QStandardItemModel())
self.tree.setAlternatingRowColors(True)
self.tree.setSortingEnabled(True)
self.tree.setHeaderHidden(False)
self.tree.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.tree.model().setHorizontalHeaderLabels(['Parameter', 'Value'])
for x in self.data:
if not self.data[x]:
continue
parent = QtGui.QStandardItem(x)
parent.setFlags(QtCore.Qt.NoItemFlags)
for y in self.data[x]:
value = self.data[x][y]
child0 = QtGui.QStandardItem(y)
child0.setFlags(QtCore.Qt.NoItemFlags |
QtCore.Qt.ItemIsEnabled)
child1 = QtGui.QStandardItem(str(value))
child1.setFlags(QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable |
~ QtCore.Qt.ItemIsSelectable)
parent.appendRow([child0, child1])
self.tree.model().appendRow(parent)
self.tree.expandAll()
def get_data(self):
return self.data
class Other(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return '(%s, %s)' % (self.x, self.y)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
btn = QtGui.QPushButton('Button', self)
btn.resize(btn.sizeHint())
btn.clicked.connect(self.show_dialog)
self.data = {}
# This example will be hidden (has no parameter-value pair)
self.data['example0'] = {}
# A set example with an integer and a string parameters
self.data['example1'] = {}
self.data['example1']['int'] = 14
self.data['example1']['str'] = 'asdf'
# A set example with a float and other non-conventional type
self.data['example2'] = {}
self.data['example2']['float'] = 1.2
self.data['example2']['other'] = Other(4, 8)
def show_dialog(self):
dialog = TestDialog(self.data)
accepted = dialog.exec_()
if not accepted:
return
self.data = deepcopy(dialog.get_data())
print self.data
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
You can connect to the model's itemChanged signal:
self.tree.model().itemChanged.connect(self.handleItemChanged)
The handler would look something like this:
def handleItemChanged(self, item):
parent = self.data[item.parent().text()]
key = item.parent().child(item.row(), 0).text()
parent[key] = type(parent[key])(item.text())
Note that the conversion of values using type won't necessarily work for custom classes like Other. So you will have to either ensure that the constructor for such classes can convert a string representation, or parse the string into the appropriate arguments before passing them to the constructor.
Also note that I haven't bothered to deal with QString values in the above example code. If you use Python 3, this is not an issue, because they are automatically converted to/from Python strings by default. But for Python 2, you can switch this behaviour on by doing the following:
import sip
sip.setapi('QString', 2)
from PyQt4 import QtGui, QtCore, uic
For more details on this, see Selecting Incompatible APIs in the PyQt4 Docs.

Categories

Resources