I'm trying to run an example from the book "Rapid GUI Programming with Python and QT" and I'm getting an error message.
import sys
from math import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Form(QDialog):
def __init__(self,parent = None):
super(Form,self).__init__(parent)
self.browser = QTextBrowser()
self.lineedit = QLineEdit("Type an Expression and press enter")
self.lineedit.selectAll()
layout = QBoxLayout()
layout.addWidget(self.browser)
layout.addWidget(self.lineedit)
self.setLayout(layout)
self.lineedit.setFocus()
self.connect(self.lineedit, SIGNAL("returnPressed()"),self.UpdateGUI)
self.setWindowTitle("Ryans App")
def UpdateGUI(self):
try
text = self.lineedit.text()
self.browser.append("%s = <b>%s</b>" % (text,eval(text)))
except:
self.browser.append("<font color=red>%s is Invalid!</font>" % text )
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
The trace I'm getting is:
Traceback (most recent call last):
File "C:\Users\MyName\workspaces\LearningProject\src\LearningModule.py", line 33, in <module>
form = Form()
File "C:\Users\MyName\workspaces\LearningProject\src\LearningModule.py", line 16, in __init__
layout = QBoxLayout()
TypeError: QBoxLayout(QBoxLayout.Direction, QWidget parent=None): not enough arguments
I'm confused as to why it's requiring an argument to create the Form object as I'm just trying to inherit from QDialog... am I missing a subtlety in the syntax?
The version I have uses QVBoxLayout instead:
...
self.lineedit.selectAll()
layout = QVBoxLayout()
layout.addWidget(self.browser)
...
My understanding is that since it lines the widgets up vertically, the .LeftToRight and parent are not strictly necessary.
I'm using the most recent code archive for python 2.6 from the book website.
When creating a QBoxLayout, you need to specify a direction (e.g. QBoxLayout.LeftToRight) and optionally a parent (in this case, self should work as the parent).
These should be added on your layout = QBoxLayout() line.
Related
Consider this PyQT5 example, let's call it test.py (for me, behaves the same under both python2 and python3 on Ubuntu 18.04):
#!/usr/bin/env python
from __future__ import print_function
import sys, os
from PyQt5 import QtCore, QtWidgets, QtGui
class PhotoViewer(QtWidgets.QGraphicsView):
def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
self.parent = parent
#self.resetMatrix() # SO: 39101834, but "AttributeError: 'PhotoViewer' object has no attribute 'resetMatrix'"
self.scale(1.0, 1.0)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setWindowTitle("test.py")
self.setMinimumWidth(1000)
self.setMinimumHeight(600)
self.viewer = PhotoViewer(self)
wid = QtWidgets.QWidget(self)
self.setCentralWidget(wid)
VBlayout = QtWidgets.QVBoxLayout()
VBlayout.addWidget(self.viewer)
wid.setLayout(VBlayout)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
main = MainWindow()
main.show()
sys.exit(app.exec_())
If I run it as is, it runs fine, without a problem.
If I uncomment the commented self.resetMatrix() line, then the program fails with:
$ python test.py
Traceback (most recent call last):
File "test.py", line 29, in <module>
main = MainWindow()
File "test.py", line 20, in __init__
self.viewer = PhotoViewer(self)
File "test.py", line 11, in __init__
self.resetMatrix() # SO: 39101834, but "AttributeError: 'PhotoViewer' object has no attribute 'resetMatrix'"
AttributeError: 'PhotoViewer' object has no attribute 'resetMatrix'
But this I find rather bizarre, because PhotoViewer inherits from QGraphicsView, calling PhotoViewer.scale() which is a QGraphicsView method is clearly not a problem - and How to reset the scale in QGraphicsView? documents that calling QGraphicsView()->resetMatrix() should be possible, and also it is documented for both:
http://pyqt.sourceforge.net/Docs/PyQt4/qgraphicsview.html#resetMatrix
http://pyqt.sourceforge.net/Docs/PyQt5/api/QtWidgets/qgraphicsview.html -> "The C++ documentation can be found here." -> https://doc.qt.io/qt-5/qgraphicsview.html#resetMatrix
What is the mistake I'm making - why cannot I call resetMatrix in this case; and what should I do to be able to call this function?
It seems that it is a bug of PyQt5, I have tested it with PySide2 and it works correctly. But there is a workaround, if you check the source code you see that the the resetMatrix() method calls only resetTransform() so it uses that method.
class PhotoViewer(QtWidgets.QGraphicsView):
def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
self.parent = parent
self.resetTransform() # self.resetMatrix()
self.scale(1.0, 1.0)
I'm trying to make a GUI for a small program I wrote with the help of some people from here, anyway, I made the GUI in PyQt and it looks fine. I added a button called dirButton that says "Choose Directory"
self.dirButton = QtGui.QPushButton(self.buttonWidget)
self.dirButton.setGeometry(QtCore.QRect(0, 0, 91, 61))
self.dirButton.setObjectName(_fromUtf8("dirButton"))
self.dirButton.clicked.connect(self.browse)
and in the bottom line there I've made it call self.browse when I click it, which is:
def browse(self):
filename = QtGui.QFileDialog.getOpenFileName(self, 'Open File', '.')
fname = open(filename)
data = fname.read()
self.textEdit.setText(data)
fname.close()
However, this is the error I get:
Traceback (most recent call last):
File "C:\Users\Kevin\Desktop\python-tumblr-0.1\antearaGUI.py", line 88, in browse
filename = QtGui.QFileDialog.getOpenFileName(self, 'Open File', '.')
TypeError: QFileDialog.getOpenFileName(QWidget parent=None, QString caption=QString(), QString directory=QString(), QString filter=QString(), QString selectedFilter=None, QFileDialog.Options options=0): argument 1 has unexpected type 'Ui_mainWindow'
So, ui_mainWindow is the class that all of my GUI buttons and the GUI itself is stored in.
class Ui_mainWindow(object):
I don't understand why I'm getting an error, does anyone have any ideas?
Here is a pastebin link to the entire GUI: http://pastebin.com/BWCcXxUW
As I understand, you are using Ui_mainWindow generated from .ui file. As you can see Ui_mainWindow is just python class which contains widgets. getOpenFileName recieves QWidget instance as first parameter. So you need to subclass QWidget or QMainWindow and define methods in that class.
Code will look like this:
import sys
from PyQt4 import QtCore, QtGui
from file_with_ui import Ui_MainWindow
class Main(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
def browse(self):
filename = QtGui.QFileDialog.getOpenFileName(self, 'Open File', '.')
fname = open(filename)
data = fname.read()
self.textEdit.setText(data)
fname.close()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Main()
window.show()
sys.exit(app.exec_())
Alternatively you can store ui as instance attribute:
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui=Ui_MainWindow()
self.ui.setupUi(self)
And acces your controls through self.ui, e.g.: self.ui.textEdit.setText(data)
Consider reading tutorial about pyuic usage PyQt by Example (Session 1)
import the following:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtGui, QtCore
In class Ui_MainWindow(object), replace object with QWidget:
Ui_MainWindow(QWidget)
def browseFiles(self):
text, placeholder = QFileDialog.getOpenFileName(None, 'Select file')
The way it worked out for me is to use None, rather than self, as self inherits the type of the window above it, which is expected to be None, but is given QMainWindow as a type.
I also unpacked the tuple it returns, since this function returns two objects to work with.
I hope this helps, perhaps someone else could explain this better, as to why it works.
TLDR: The function expects None as the first parameter, but inherits a QmainWindow as type, due to self.
file = str(QFileDialog.getExistingDirectory(None, "Select Directory"))
i just started to learn PyQt and GUI programing and
i copied this code exactly from the book "Rapid GUI Programming with Python and Qt:The Definitive Guide to PyQt Programming" and it supposed to show a calculator that calculates an expression.
when i run the application main window shows up but does not do anything and since i copied the code form a well known pyqt book it's very strange.
i am using python 3.4.4 and pyqt4 .this is the code i copied from book:
import sys
from math import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Form(QDialog):
def __init__(self,parent=None):
super(Form, self).__init__(parent)
self.browser = QTextBrowser()
self.lineedit = QLineEdit("Type an expression and press Enter")
self.lineedit.selectAll()
layout = QVBoxLayout()
layout.addWidget(self.browser)
layout.addWidget(self.lineedit)
self.setLayout(layout)
self.lineedit.setFocus()
self.connect(self.lineedit, SIGNAL("retrunPressed()"),
self.updateUi)
self.setWindowTitle("Calculate")
def updateUi(self):
try:
text= unicode(self.lineedit.text())
self.browser.append("{0} = <b>{1}</b>".format(text,eval(text)))
except:
self.browser.append(
"<font color=red>%s is invalid!</font>" % text)
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
these are the errors i get:
Traceback (most recent call last):
File "calculator.pyw", line 25, in updateUi
text = unicode(self.lineedit.text())
NameError: name 'unicode' is not defined
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "calculator.pyw", line 29, in updateUi
"%s is invalid!" % text)
UnboundLocalError: local variable 'text' referenced before assignment
i know its not good idea to ask someone else to debug my code but i did all i could about it and nothing came up.
thanks
You have a typo in your signal name it should be
self.connect(self.lineedit, SIGNAL("returnPressed()"),
not
self.connect(self.lineedit, SIGNAL("retrunPressed()"),
so apparently you didn't copy it well, also it seems that book was written in python2 so you don't need the unicode function strings in python3 are already unicode by default
Why doesn't the following example work?
from PyQt4 import QtGui
import sys
class TestView(QtGui.QWidget):
def __init__(self):
super(TestView, self).__init__()
self.initUI()
def initUI(self):
self.btn = QtGui.QPushButton('Button', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
class TestViewController():
def __init__(self, view):
view.btn.clicked.connect(self.buttonClicked)
view.show()
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestView()
TestViewController(view)
app.exec_()
if __name__ == '__main__':
main()
The example is supposed to represent an MVC structure (like the one in Figure 4 -- without the Model) where the controller (TestViewController) receives a reference to the view (TestView) and connects the clicked signal from the view's button view.btn to its function self.buttonClicked.
I'm sure the line view.btn.clicked.connect(self.buttonClicked) is executed but, apparently, it has no effect. Does anyone knows how to solve that?
Update (awful solution):
In the example, if I replace the line
view.btn.clicked.connect(self.buttonClicked)
with
view.clicked = self.clicked
view.btn.clicked.connect(view.clicked)
it works. I'm still not happy with that.
The reason it is not working is because the controller class is being garbage collected before you can ever click anything for it.
When you set view.clicked = self.clicked, what you're actually doing is making one of the objects from the controller persist on the view object so it never gets cleaned up - which isn't really the solution.
If you store your controller to a variable, it will protect it from collection.
So if you change your code above to read:
ctrl = TestViewController(view)
You'll be all set.
That being said - what exactly you are trying to do here, I am not sure...it seems you're trying to setup an MVC system for Qt - but Qt already has a pretty good system for that using the Qt Designer to separate the interface components into UI (view/template) files from controller logic (QWidget subclasses). Again, I don't know what you are trying to do and this may be a dumb down version of it, but I'd recommend making it all one class like so:
from PyQt4 import QtGui
import sys
class TestView(QtGui.QWidget):
def __init__(self):
super(TestView, self).__init__()
self.initUI()
def initUI(self):
self.btn = QtGui.QPushButton('Button', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
self.btn.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestView()
view.show()
app.exec_()
if __name__ == '__main__':
main()
Edit: Clarifying the MVC of Qt
So this above example doesn't actually load the ui dynamically and create a controller/view separation. Its a bit hard to show on here. Best to work through some Qt/Designer based examples/tutorials - I have one here http://bitesofcode.blogspot.com/2011/10/introduction-to-designer.html but many can be found online.
The short answer is, your loadUi method can be replace with a PyQt4.uic dynamic load (and there are a number of different ways to set that up) such that your code ultimately reads something like this:
from PyQt4 import QtGui
import PyQt4.uic
import sys
class TestController(QtGui.QWidget):
def __init__(self):
super(TestController, self).__init__()
# load view
uifile = '/path/to/some/widget.ui'
PyQt4.uic.loadUi(uifile, self)
# create connections (assuming there is a widget called 'btn' that is loaded)
self.btn.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestController()
view.show()
app.exec_()
if __name__ == '__main__':
main()
Edit 2: Storing UI references
If it is easier to visualize this concept, you Can also store a reference to the generated UI object:
from PyQt4 import QtGui
import PyQt4.uic
import sys
class TestController(QtGui.QWidget):
def __init__(self):
super(TestController, self).__init__()
# load a view from an external template
uifile = '/path/to/some/widget.ui'
self.ui = PyQt4.uic.loadUi(uifile, self)
# create connections (assuming there is a widget called 'btn' that is loaded)
self.ui.btn.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestController()
view.show()
app.exec_()
if __name__ == '__main__':
main()
I have a small program that renders a typed equation on the fly using SymPy's 'pretty-printing' facility. This works fine but doesn't look very professional. As SymPy will produce latex or mml I was wondering whether these could be rendered graphically with a PySide widget? I would obviously need to change the 'QTextBrowser()', but to what I'm not sure. I know Nokia provides QtMmlWidget but I'm not sure if this could be used by PySide.
Many thanks and best wishes.
from __future__ import division
import sys
import sympy
from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtXml import *
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.browser = QTextBrowser()
self.browser.setCurrentFont(QFont("Courier New",10,QFont.Bold))
self.lineedit = QLineEdit("please type an expression")
self.lineedit.selectAll()
layout = QVBoxLayout()
layout.addWidget(self.browser)
layout.addWidget(self.lineedit)
self.setLayout(layout)
self.lineedit.setFocus()
self.connect(self.lineedit, SIGNAL("textChanged (const QString&)"),self.updateUi)
def updateUi(self):
text = unicode(self.lineedit.text())
for z in range(0,9):
text = text.replace('x'+str(z),'x^'+str(z))
text = text.replace(')'+str(z),')^'+str(z))
text = text.replace(str(z)+'x',str(z)+'*x')
text = text.replace(str(z)+'(',str(z)+'*(')
try:
self.browser.append(sympy.printing.pretty(sympy.sympify(text)))
self.browser.clear()
self.browser.append(sympy.printing.pretty(sympy.sympify(text)))
except Exception:
if text=='': self.browser.clear()
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
One hacky way would be to display it in a QWebiew using something like MathJax
Or, inspired by this question you could use the SVGMath module to convert form MathML to SVG, which can then be displayed in a QSvgWidget