I have created a form using PyQT5 using python, in the form I getting value from user through QLineEdit.
My problem is that user should not leave any of the field empty in the form.
How to avoid empty Lineedit?
A simple solution is to use a custom QValidator:
from PyQt5 import QtGui, QtWidgets
class NotEmptyValidator(QtGui.QValidator):
def validate(self, text, pos):
state = QtGui.QIntValidator.Acceptable if bool(text) else QtGui.QIntValidator.Invalid
return state, text, pos
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QLineEdit("Hello World")
validator = NotEmptyValidator(w)
w.setValidator(validator)
w.show()
sys.exit(app.exec_())
Extending on eyllanesc's answer:
If you want to make it possible that the user empties the field temporarily (e.g. to remove all text and write completely new content), you should not return an empty field as Invalid but rather as Intermediate. If there is one letter left but an empty QLineEdit would be Invalid, then the user may be blocked from removing the last letter and would instead have to do some kind of workaround. Also, the code below strips possible whitespace before checking for emptiness.
class NotEmptyValidator(QValidator):
def validate(self, text: str, pos):
if bool(text.strip()):
state = QValidator.Acceptable
else:
state = QValidator.Intermediate # so that field can be made empty temporarily
return state, text, pos
You can first convert it into text with
name = lineEdit.text()
use a if statement to detect empty value like this
if name == "":
print("Empty")
More details here
Related
It's my code. How to store the lastly clicked/checked button as default in the next opening of the programme? For example: If we run the programme and click the 3rd button and close the entire programme. Whenever I reopen/re-run the programme, the lastly clicked button is checked by default ( ie. third button is checked by default)
import sys
from PyQt5 import QtWidgets
class RadioButton(QtWidgets.QWidget):
def __init__(self):
super(). __init__()
self.setWindowTitle("Radio Button")
self.rbtn1 = QtWidgets.QRadioButton("Button1")
self.rbtn1.clicked.connect(self.getvalue)
self.rbtn2 = QtWidgets.QRadioButton("Button2")
self.rbtn2.clicked.connect(self.getvalue)
self.rbtn3 = QtWidgets.QRadioButton("Button3")
self.rbtn3.clicked.connect(self.getvalue)
self.rbtn4 = QtWidgets.QRadioButton("Button4")
self.rbtn4.clicked.connect(self.getvalue)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self.rbtn1)
vbox.addWidget(self.rbtn2)
vbox.addWidget(self.rbtn3)
vbox.addWidget(self.rbtn4)
self.setLayout(vbox)
def getvalue(self):
if self.rbtn1.isChecked():
self.rbtn1.setChecked(True)
print("Radio button 1 is checked")
elif self.rbtn2.isChecked():
self.rbtn2.setChecked(True)
print("Radio button 2 is checked")
elif self.rbtn3.isChecked():
self.rbtn3.setChecked(True)
print("Radio button 3 is checked")
elif self.rbtn4.isChecked():
self.rbtn4.setChecked(True)
print("Radio button 4 is checked")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
win = RadioButton()
win.show()
sys.exit(app.exec_())
You can use QSettings to save and restore data. Note that values are serialized as QVariants, so you cannot, for instance, save a custom python subclass instance.
In order to properly use QSettings you must set both the Qt application name and organization name (otherwise data won't be stored anywhere).
It is possible to save settings in other ways, but for general usage it's better to use the default behavior.
Note that, since values are stored as strings, Qt can only try to guess the actual type, so it's always better to specify the type when using value() (see the case below).
Then, while by default Qt is able to automatically group radio buttons and make them exclusive (see the autoExclusive property of QAbstractButton, which is set to True for QRadioButtons), for this kind of situations it's better to rely on a QButtonGroup, which not only allows better control on the buttons, but also allows identifying buttons member of the group by using unique IDs they were assigned to.
Finally, it's just a matter of connecting the appropriate signal of the button group to the function that saves the currently checked button.
from PyQt5 import QtWidgets, QtCore
class RadioButton(QtWidgets.QWidget):
def __init__(self):
super(). __init__()
self.setWindowTitle("Radio Button")
self.rbtn1 = QtWidgets.QRadioButton("Button1")
self.rbtn2 = QtWidgets.QRadioButton("Button2")
self.rbtn3 = QtWidgets.QRadioButton("Button3")
self.rbtn4 = QtWidgets.QRadioButton("Button4")
buttons = self.rbtn1, self.rbtn2, self.rbtn3, self.rbtn4
vbox = QtWidgets.QVBoxLayout(self)
self.optionGroup = QtWidgets.QButtonGroup()
for i, button in enumerate(buttons):
vbox.addWidget(button)
self.optionGroup.addButton(button, i)
# for Qt>=5.15:
# self.optionGroup.idToggled.connect(self.getvalue)
# otherwise:
self.optionGroup.buttonToggled[int, bool].connect(self.getvalue)
self.settings = QtCore.QSettings()
# read the value if it exists, otherwise use a default; note that
# the type MUST be specified, otherwise numbers would be read as
# strings, raising an exception and crashing the program in this case
default = self.settings.value('MyOption', 2, type=int)
self.optionGroup.button(default).setChecked(True)
def getvalue(self, id, checked):
if checked:
button = self.optionGroup.button(id)
print("{} is checked".format(button.text()))
self.settings.setValue('MyOption', id)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('MyOrganization')
app.setApplicationName('MyApplication')
win = RadioButton()
win.show()
sys.exit(app.exec_())
I strongly recommend you to carefully study the whole documentation about all the classes specified above.
I want to paste in a QTextEdit an text with an certain font size, ex. 14
I made an app that replace a paraghaph sign with a blank space, like in
PyQt QLineEdit and 'paste' event?
On def __init__(self) I code:
self.textEdit.textChanged.connect(self.valueChanged)
then
def valueChanged(self, text):
if QtGui.QApplication.clipboard().text() == text:
self.pasteEvent(text)
and then
def pasteEvent(self, text):
text.toUpper()
TypeError: valueChanged() takes exactly 2 arguments (1 given)
In the previous question that links you use a QLineEdit that has the void QLineEdit::textChanged(const QString &text) signal that carries the text, but in the case of QTextEdit there is a signal with the same name void textChanged() but it does not carry the text so that is the cause of the error. The solution for that case is to obtain the text using the object and not through the signal.
def valueChanged(self):
if QtGui.QApplication.clipboard().text() == self.textEdit.text():
self.pasteEvent(text)
Although if your goal is to change the size of the font then your previous logic does not work since you are detecting the event after the text is pasted, if you want to modify something during the paste then you must override the insertFromMimeData() method:
from PyQt4 import QtCore, QtGui
class TextEdit(QtGui.QTextEdit):
def insertFromMimeData(self, source):
last_font = self.currentFont()
new_font = QtGui.QFont(last_font)
new_font.setPointSize(14)
self.setCurrentFont(new_font)
super(TextEdit, self).insertFromMimeData(source)
self.setCurrentFont(last_font)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
w = TextEdit()
w.show()
sys.exit(app.exec_())
I try to validate if a editable QCombobox input is a directory or not before it gets added to the QCombobox.
from PySide import QtGui, QtCore
class DirValidator(QtGui.QValidator):
def __init__(self, cb_input):
super(DirValidator, self).__init__()
self._input = cb_input
def validate(self, _text, _pos):
_dir = QtCore.QDir(_text)
if self._input.hasFocus(): # ignore validation while editing not complete
return QtGui.QValidator.Acceptable
if QtCore.QDir.exists(_dir):
return QtGui.QValidator.Acceptable
return QtGui.QValidator.Invalid
dir_validator = DirValidator(self.cb_path.lineEdit())
self.cb_path.setValidator(dir_validator)
sadly it does not work properly because every input still gets added to the combobox when i hit enter.
Any suggestions?
EDIT: i also tried to use the validator on the QLineEdit like so:
dir_validator = DirValidator(self.cb_path.lineEdit())
self.cb_path.lineEdit().setValidator(dir_validator)
Does not work either.
EDIT: It kinda works...but when i press return "hasFocus" is still True so it is just accepting the input and then of course adding it to the combobox. if i get rid of "if self._input.hasFocus():" it does not accept any input if i type it in...just if a paste a complete directory path.
So what i need is a way to check if the edit is finished to then check if it is a directory.
And as far as i understand i can only check this in a combobox via QValidator...because it adds the input to the box right away...before i can intercept it in any way.
EDIT: i did find solution for my case. I just abandoned the whole validator approach. The purpose of that was to prevent the combobox from creating a new item if it was no valid directory...what i now did instead was to validate the input after it was finished by taking advantage of the QLineEdit().editingFinished() signal. After it created the new Item i just removed it again if the input was not valid and it also gave me the opportunity to add a error Popup telling the user that the input was no directory.
I do not see the need for hasFocus(), because if you are writing to the QLineEdit it obviously has the focus. If the path is incorrect then you must return a QValidator::Intermediate:
from PySide import QtGui, QtCore
class DirValidator(QtGui.QValidator):
def validate(self, _text, _pos):
_dir = QtCore.QDir(_text)
if _dir.exists():
return QtGui.QValidator.Acceptable
return QtGui.QValidator.Intermediate
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
combo = QtGui.QComboBox(editable=True)
dir_validator = DirValidator(combo.lineEdit())
combo.setValidator(dir_validator)
combo.show()
sys.exit(app.exec_())
In my application I have to replace all QLineEdit elements with customized QLineEdit. To do that there are different solutions:
Modify the generated py file from pyuic4 and replace there all QLineEdit objects with my one LineEdit. This solution is not really the best, because every time I run pyuic4 I lose the modification I did into the generated output file.
Write a new class that search recursively inside my window or dialog for QLineEdit widget types and replace them with my one LineEdit. This solution is much better. It allows to me to do the same also with other objects or extend it as I want without to care about the dialog or window.
So far, I could write a module (WidgetReplacer) which search recursively for QGridLayout items and search if there are QLineEdit children. If yes, then replace it with my one.
The problem is that when I try to access one of my LineEdit objects I get the following error:
RuntimeError: wrapped C/C++ object of type QLineEdit has been deleted
And if I look at my output I notice that the modified QLineEdit object has sitll the old id:
old qt_obj <PyQt4.QtGui.QLineEdit object at 0x0543C930>
Replaced txt_line_1 with LineEdit
new qt_obj <LineEdit.LineEdit object at 0x0545B4B0>
----------------------------------
...
----------------------------------
<PyQt4.QtGui.QLineEdit object at 0x0543C930>
Question
Why happen this? How can I replace QWidgets at runtime?
Update
Here some additional details regarding the ojects wrapping:
Inside the module WidgetReplace if I dump the QEditLine and LineEdit objects I get:
<PyQt4.QtGui.QLineEdit object at 0x05378618>
Reference count: 7
Address of wrapped object: 051FAC40
Created by: Python
To be destroyed by: C/C++
Parent wrapper: <PyQt4.QtGui.QGridLayout object at 0x05378588>
Next sibling wrapper: NULL
Previous sibling wrapper: <PyQt4.QtGui.QLabel object at 0x05378930>
First child wrapper: NULL
and
<LineEdit.LineEdit object at 0x05378978>
Reference count: 3
Address of wrapped object: 051FAC40
Created by: Python
To be destroyed by: C/C++
Parent wrapper: <__main__.Dialog object at 0x0535FBB8>
Next sibling wrapper: <PyQt4.QtGui.QGridLayout object at 0x05378780>
Previous sibling wrapper: NULL
First child wrapper: NULL
Instead if I dump my line edit from the main, I get follow, which represent the deleted QLineEdit object:
<PyQt4.QtGui.QLineEdit object at 0x05378618>
Reference count: 2
Address of wrapped object: 00000000
Created by: Python
To be destroyed by: C/C++
Parent wrapper: NULL
Next sibling wrapper: NULL
Previous sibling wrapper: NULL
First child wrapper: NULL
Here my code
Dialog
The ui and generate file can be dowloaded from DropBox dialog.ui and dialog.py
main
import sys
from PyQt4.QtGui import QDialog, QApplication
from dialog import Ui_Form
from WidgetReplacer import WidgetReplacer
class Dialog(QDialog, Ui_Form):
def __init__(self, parent = None):
super(Dialog, self).__init__(parent)
# Set up the user interface from Designer.
self.setupUi(self)
WidgetReplacer().replace_all_qlineedit(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Dialog()
window.show()
print window.txt_line_1
window.txt_line_1.setText("Hello Text 1")
sys.exit(app.exec_())
LineEdit
from PyQt4.QtGui import QLineEdit, QValidator, QPalette
from PyQt4 import QtCore
class LineEdit(QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
self.color_red = QPalette()
self.color_red.setColor(QPalette.Text, QtCore.Qt.red)
self.color_black = QPalette()
self.color_black.setColor(QPalette.Text, QtCore.Qt.red)
# Make connections
self.textChanged.connect(self.verify_text)
def verify_text(self, text):
validator = self.validator()
is_valid = QValidator.Acceptable
if validator is not None:
is_valid = validator.validate(text, 0)
if is_valid == QValidator.Acceptable:
self.setPalette(self.color_black)
elif is_valid in [QValidator.Invalid, QValidator.Intermediate]:
self.setPalette(self.color_red)
WidgetReplacer
import sip
from LineEdit import LineEdit
from PyQt4.QtCore import QRegExp
from PyQt4 import QtGui
class WidgetReplacer():
def __init__(self):
pass
def replace_all_qlineedit(self, qt_dlg):
children = self._get_all_gridlayout(qt_dlg)
items = []
for child in children:
new_items = self._find_all_obj_in_layout(child, QtGui.QLineEdit)
items.extend(new_items)
for item in items:
layout = item[0]
row = item[1]
column = item[2]
qt_obj = item[3]
self._replace_qlineedit_from_gridlayout(qt_dlg, qt_obj,
layout, row, column)
def _get_all_gridlayout(self, qt_dlg):
return qt_dlg.findChildren(QtGui.QGridLayout, QRegExp("gridLayout_[0-9]"))
def _find_all_obj_in_layout(self, layout, qt_type):
# Output list format:
# layout, row, col, qt_obj,
objects = []
if type(layout) == QtGui.QGridLayout:
layout_cols = layout.columnCount()
layout_rows = layout.rowCount()
for i in range(layout_rows):
for j in range(layout_cols):
item = layout.itemAtPosition(i, j)
# print "item(",i, j, "):", item
if type(item) == QtGui.QWidgetItem:
if type(item.widget()) == qt_type:
new_obj = [layout, i, j, item.widget()]
objects.append(new_obj)
elif type(layout) in [QtGui.QHBoxLayout, QtGui.QVBoxLayout]:
raise SyntaxError("ERROR: Find of Qt objects in QHBoxLayout or QVBoxLayout still not supported")
# for i in range(layout.count()):
# item = layout.itemAt(i)
return objects
def _replace_qlineedit_from_gridlayout(self, parent, qt_obj, layout, row, column):
print "old qt_obj", qt_obj
obj_name = qt_obj.objectName()
layout.removeWidget(qt_obj)
sip.delete(qt_obj)
qt_obj = LineEdit(parent)
qt_obj.setObjectName(obj_name)
layout.addWidget(qt_obj, row, column)
print "Replaced", obj_name, "with LineEdit"
print "new qt_obj", qt_obj
print "----------------------------------"
Don't replace the widgets at runtime: promote the widgets in Qt Designer so that the line-edits are automatically replaced by your custom class when the GUI module is generated.
Here's how to promote a widget to use a custom class:
In Qt Designer, select all the line-edits you want to replace, then right-click them and select "Promote to...". In the dialog, set "Promoted class name" to "LineEdit", and set "Header file" to the python import path for the module that contains this class (e.g. myapp.LineEdit). Then click "Add", and "Promote", and you will see the class change from "QLineEdit" to "LineEdit" in the Object Inspector pane.
Now when you re-generate your ui module with pyuic, you should see that it uses your custom LineEdit class and there will be an extra line at the bottom of the file like this:
from myapp.LineEdit import LineEdit
I didn't go through all of your code...
My guess though is, that even though you've replaced the widget in the layout, window.txt_line_1 still points to the deleted object.
So, in your replacement-procedure you'll also have to update this attribute.
So, adding
setattr(parent, obj_name, qt_obj);
to _replace_qlineedit_from_gridlayout might do the trick.
In reference to a previous question, I need some help with keeping references in my application.
First a snippet from my code.
from PyQt4 import QtGui
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#fill list view with items from equation collection
listview = QtGui.QListWidget()
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(equation.text())
listview.addItem(item)
layout.addWidget(listview)
layout.addWidget(de)
window.setWindowTitle("Equation {}".format(str(current))
window.setLayout(layout)
self._window = window
self._listview = listview
window.show()
return window
def clicked(self):
"""Method for handling the button events in the solver settings\n
signal = the button hit\n"""
return self.showAddEquation()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())
The application will (later) create a window that allows the manipulation of certain settings - in my code example represented by the QPushButton. These settings are later written to a txt-file, but until then I save them in form of there widget. I simply add the widget to a collection and recall them from there. That works well on the Python-level.
Now, I have a button that creates a new instance of the window from inside of the window itself. That works too. But only until the third instance. At that point I loose the reference to my QPushButton on the Qt-level. I get the
wrapped C/C++ object of type `QPushButton` has been deleted
error when trying to retrieve the buttons from my collection (equationEditor). In Python they are still there, but obviously the corresponding Qt-objects where destroyed because I somewhere mishandled the references.
Can someone point out a better solution or how I can keep the references?
Thanks...
Edit:
As there seem to be some confusions I will try to explain the functionality a bit more in detail.
The program starts and creates a window "Equation 1" with a QListViewand a QPushButton "1". In the list view all available QPushButton are listed (at start only 1 item). In my actual program the QPushButton is QWidget with some text fields and the QPushButton.
If the user clicks "1" then the button "1" should disappear and a new instance of QPushButton named "2" should appear at the position of "1". Additionally, the listview should now hold two items "1" and "2" and the window should have the title "Equation 2". If it is a new window or the same as before with new contents is not relevant. Both variants would be okay. The former is the way it is implemented at the moment. Visible should by only one window at a time.
All instances of QPushButton should by collected in a small list (called equationEditor) to keep them in the memory. In my actual program this is used for saving all changes made in the widgets without writing the changes to a temp file.
Later, if the user selects item "1" in the QListView then the current visible QPushButton should be replaced by the QPushButton "1" (from the collection equationEditor) or if he selects the second item the QPushButton "2" should be shown.
Why?
The widget that will be used later contains a lot of editable data. As the user could edit that at any time it is easier to keep the widgets in memory without showing them instead of repopulating all the data again. As soon as the user selects one in the QListView the corresponding widget should be shown in the window so that he can edited the data in the widget again.
It's quite hard to understand what exactly you're trying to do. Looking at your code I wonder why it is even working twice before failing.
Btw. I just saw, that there is a quite accurate description of why it's failing in the previous post, given by Schollii
Anyways, I think you should make a new class for the equation window. The main class can then keep track of all opened windows in the equationEditor list. It can also add the values of the other opened windows to a new one, once created.
Here is how it would look like
from PyQt4 import QtGui
import os, os.path
import sys
class ShowAddEquation(QtGui.QWidget):
"""Creates a new instance of the dynamic editor for adding an equation"""
def __init__(self,parent=None):
super(ShowAddEquation, self).__init__(parent=parent)
#create a horizontal split layout
layout = QtGui.QHBoxLayout()
self.current = 0
self.de = QtGui.QPushButton(str(self.current))
self.listview = QtGui.QListWidget()
layout.addWidget(self.listview)
layout.addWidget(self.de)
self.setWindowTitle("Equation Editor")
self.setLayout(layout)
self.show()
def setCurrent(self, current):
self.current=current
self.de.setText(str(self.current))
class mainWindowHandler():
equationEditor = []
def __init__(self):
return
def clicked(self):
se = ShowAddEquation()
self.equationEditor.append(se)
se.de.clicked.connect(self.clicked)
current = len(self.equationEditor) - 1
se.setCurrent(current)
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(str(equation.current))
se.listview.addItem(item)
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
ewh.clicked()
sys.exit(app.exec_())
So, after understanding the approach given in the first answer I have solved my problem. Here is the working code
# -*- coding: utf-8 -*-
"""
Created on Sat Sep 3 14:31:15 2016
"""
from PyQt4 import QtGui
from PyQt4 import QtCore
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
self._window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
self._listview = QtGui.QListWidget()
layout.addWidget(self._listview)
self._listview.clicked[QtCore.QModelIndex].connect(self.changed)
self._window.setLayout(layout)
#populate the right side of the layout with a button
self.clicked()
self._window.show()
return self._window
def clicked(self):
"""Make a new button instance and add it to the window and the collection"""
window = self._window
layout = window.layout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#close the currently shown button
item = layout.takeAt(1)
if item is not None:
item.widget().close()
layout.addWidget(de)
#fill list view with items from material collection
item = QtGui.QListWidgetItem()
item.setText(de.text())
self._listview.addItem(item)
self._window.setWindowTitle("Equation Editor {}".format(str(current)))
def changed(self, index):
"""hide the object on the right side of the layout and show the button at position index in the collection"""
layout = self._window.layout()
item = layout.takeAt(1)
item.widget().close()
# insert the selected button from the collection
de = self.equationEditor[index.row()]
layout.insertWidget(1, de)
self._window.setWindowTitle("Equation Editor {}".format(str(index.row() - 1)))
de.show()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())