I need to create multi-window GUI, first I tried it with QWidgets, but finally I discover QStackWidget tool I need to use. So Im trying to, but Ive got some problems. Thanks for Your time.
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.mainWidget = MainWidget()
self.searchWidget = SearchWidget()
self.sWidget = QStackedWidget()
self.sWidget.addWidget(self.mainWidget)
self.sWidget.addWidget(self.searchWidget)
self.initUI()
and calling setCurrentWidget from the sub_widget class:
class MainWidget(QWidget):
def __init__(self, parent=MainWindow):
super(MainWidget,self).__init__()
self.initUI()
def initUI(self):
searchButton = QPushButton('searchButton',self)
optionButton = QPushButton('optionButton',self)
quitButton = QPushButton('quitButton',self)
listButton = QPushButton('listButton',self)
searchButton.clicked.connect(self.goSearch)
hbox = QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addWidget(searchButton)
vbox.addWidget(optionButton)
vbox.addLayout(hbox)
self.setLayout(vbox)
def goSearch(self):
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
Ive got this message from IDE:
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
AttributeError: 'PySide.QtGui.QStackedWidget' object has no attribute 'sWidget'
What is the thing Im doing wrong?
I'm going to comment on the code you posted here: http://pastebin.com/fBfS1X5m
An important thing to know is that you can put widgets within widgets and so on. For example:
class Widget(QWidget):
def __init__(self, parent=None):
layout = QVBoxLayout(self)
childWidget = QWidget(parent=self)
layout.addWidget(childWidget)
Just a quick note: You don't need setLayout if you pass self to the main layout constructor - via the docs.
Anyways, what I'm trying to illustrate here is that the QStackedWidget and the SearchWidget really shouldn't be a part of the MainWindow, but should live inside their own relevant widget that will handle switching between the QStackedWidget pages.
For example the MainWindow.__init__ would only look like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.mainWidget = MainWidget(self)
self.setCentralWidget(self.mainWidget)
self.initUI()
Your MainWidget would then look something like:
class MainWidget(QtGui.QWidget):
...
def initUI(self):
...
self.stack = QtGui.QStackedWidget(parent=self)
self.searchWidget = SearchWidget(parent=self)
self.searchWidget.searchButton.clicked.connect(self.goSearch)
self.backWidget = BackWidget(parent=self)
self.backWidget.backButton.clicked.connect(self.goBack)
...
def goSearch(self):
self.stack.setCurrentWidget(self.backWidget)
def goBack(self):
self.stack.setCurrentWidget(self.searchWidget)
I've renamed some of the class names to make a little more sense (To me at least). SearchWidget was your old MainWidget. BackWidget was your old SearchWidget. Following those changes SearchWidget would look the same as your old MainWidget with one exception - we save a reference to the search button so we can access it in the MainWidget class as seen above (when we connect their signals to our slots). We do the same for the button in BackWidget.
The two renamed "child" widgets:
class SearchWidget(QtGui.QWidget):
...
def initUI(self):
self.searchButton = QtGui.QPushButton('searchButton', parent=self)
optionButton = QtGui.QPushButton('optionButton', parent=self)
quitButton = QtGui.QPushButton('quitButton', parent=self)
listButton = QtGui.QPushButton('listButton', parent=self)
vbox = QtGui.QVBoxLayout(self)
vbox.addStretch(1)
vbox.addWidget(self.searchButton)
vbox.addWidget(optionButton)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox.addLayout(hbox)
class BackWidget(QtGui.QWidget):
...
def initUI(self):
self.backButton = QtGui.QPushButton('GoBack', parent=self)
So now we have something like:
MainWindow
|---MainWidget
|---QStackedWidget
|---SearchWidget
|---BackWidget
You can find the full working code here.
This line:
def __init__(self, parent=MainWindow):
sets the MainWindow class as a default argument, when you actually need an instance. But even if it was an instance, in the next line, you also fail to pass it on to the base-class:
super(MainWidget,self).__init__()
What you need to do instead is something like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
# pass an instance of MainWindow here
self.mainWidget = MainWidget(self)
...
class MainWidget(QWidget):
def __init__(self, parent):
# pass the parent to the base-class
super(MainWidget, self).__init__(parent)
...
UPDATE:
The stack-widget will re-parent any widgets added to it, so that it becomes the parent. There are ways of working around this, but I think the real problem with your code is that you have the structure backwards. The buttons that set the current widget should be controlled by the main-window, and the widgets in the stack should work completely independantly of that.
Related
My main window has a variable self.x setup in the INIT. Then I need to access this value from the popup password box that is created. This is just a test script to get my point accross. I'm assuming it's something with the inheritance stuff, all that is still a little foreign to me.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtWidgets
# Created by MyWindow
class LoginDlg(QtWidgets.QDialog):
def __init__(self):
super(LoginDlg, self).__init__()
self.password = QtWidgets.QLineEdit()
# THIS IS THE LINE I NEED IT TO PULL X FROM PARENT
self.password.setText(self.x)
self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout = QtWidgets.QFormLayout()
layout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
layout.addRow('Password', self.password)
layout.addWidget(self.button_box)
self.setLayout(layout)
self.setWindowTitle("Login")
self.setMinimumWidth(350)
# Main parent window
class MyWindow(QtWidgets.QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.edit = QtWidgets.QLineEdit()
button = QtWidgets.QPushButton("Get input from dialog")
button.clicked.connect(self.get_login)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.edit)
layout.addWidget(button)
self.setLayout(layout)
self.x = "PASS THIS STRING"
def get_login(self):
login = LoginDlg()
if login.exec_():
self.edit.setText(login.password.text())
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Normally I would just pass this data through the constructor, but let's say I have a lot of data that I don't want to pass back and forth, is there a way to access parent attributes another way?
Don't get too complicated and just pass the value of that variable through the constructor:
class LoginDlg(QtWidgets.QDialog):
def __init__(self, x):
super(LoginDlg, self).__init__()
self.password = QtWidgets.QLineEdit()
self.password.setText(x)
# ...
class MyWindow(QtWidgets.QWidget):
# ...
def get_login(self):
login = LoginDlg(self.x)
if login.exec_():
self.edit.setText(login.password.text())
Another similar option is to access "password" in get_login:
class LoginDlg(QtWidgets.QDialog):
def __init__(self):
super(LoginDlg, self).__init__()
self.password = QtWidgets.QLineEdit()
# self.password.setText(self.x)
self.button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
)
# ...
class MyWindow(QtWidgets.QWidget):
# ...
def get_login(self):
login = LoginDlg()
login.password.setText(self.x)
if login.exec_():
self.edit.setText(login.password.text())
Note: my answer does not try to implement what the OP asks is to "access a class from another" (there is no relationship so using parent-child is incorrect) since that complicates the modularization because if a object could affect the other, in general. I think the OP has an XY problem as it asks how to implement a possible solution instead of the underlying problem which is "how to share information between classes"
I have a checkbox and a run button. When the checkbox is checked, I want to run some functions by clicking the button. The problem is that the function is in another class outside the button's class. My example codes are as below.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Tab1Widget1(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.Tab1Widget1initUI()
def Tab1Widget1initUI(self):
self.setLayout(QGridLayout())
self.T1W1checkBox1 = QCheckBox('a', self)
self.layout().addWidget(self.T1W1checkBox1, 1, 0)
def run(self):
if self.T1W1checkBox1.isChecked() == True:
pass
class Tab1Layout(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setLayout(QGridLayout())
self.group1 = Tab1Widget1(self)
self.layout().addWidget(self.group1, 0, 0)
btn = QPushButton('Run', self)
self.layout().addWidget(btn, 1, 0)
btn.clicked.connect(Tab1Widget1().run()) ##the problem is in this line.
class Page1(QTabWidget):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.tab1 = Tab1Layout()
self.addTab(self.tab1, "Tab1")
self.tab2 = QWidget()
self.tab3 = QWidget()
self.addTab(self.tab2, "Tab2")
self.addTab(self.tab3, "Tab3")
self.tab2_initUI()
self.tab3_initUI()
def tab2_initUI(self):
grid = QGridLayout()
self.tab2.setLayout(grid)
def tab3_initUI(self):
grid = QGridLayout()
self.tab3.setLayout(grid)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(300, 200, 600, 370)
self.startPage1()
def startPage1(self):
x = Page1(self)
self.setWindowTitle("Auto Benchmark")
self.setCentralWidget(x)
self.show()
def main():
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As you can see, I want to run the "run" function in "Tab1Widget1" class. However, the button is in "Tab1Layout" class.
When I run the codes, it returns to me "TypeError: connect() slot argument should be a callable or a signal, not 'NoneType'"
If anyone knows how to solve this, pls let me know. Appreciated!
There is no problem in connecting any callable to a button click regardless of what object it is in. But your code has two specific problems. You write
btn.clicked.connect(Tab1Widget1().run())
The first problem here is that Tab1Widget1() is creating a new Tab1Widget1 but presumably you don't want that. You want to call run on the Tab1Widget1 you have already created and stored in self.group.
The second problem is that when you connect a signal you need to connect it to a callable: the method you want to call. Instead here you are calling the run method at connect time and trying to connect to the result of that call (which is None). So you are trying to connect the signal to None which will of course fail. You need to refer to the method without calling it: just remove the calling brackets.
Putting it together:
btn.clicked.connect(self.group1.run)
That seems to work.
I'm having issues getting items with custom widgets to show up in a list widget. The items show up blank in the example below...
from PySide2 import QtWidgets
class ItemWidget(QtWidgets.QWidget):
def __init__(self,parent = None):
super(ItemWidget, self).__init__(parent)
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
self.checkBox = QtWidgets.QCheckBox()
self.label = QtWidgets.QLabel('test')
layout.addWidget(self.checkBox)
layout.addWidget(self.label)
class ListWidget(QtWidgets.QListWidget):
def __init__(self,parent = None):
super(ListWidget,self).__init__(parent)
self.initUI()
def initUI(self):
for i in range(10):
item = QtWidgets.QListWidgetItem()
self.addItem(item)
widget = ItemWidget(self)
self.setItemWidget(item,widget)
self.show()
lister = ListWidget()
It looks like QlistWidget won't do what you want, so you'll need to approach it from a lower level.
PySide.QtGui.QListWidget.setItemWidget(item, widget)
This function should only be used to display static content in the place of a list widget item. If you want to display custom dynamic content or implement a custom editor widget, use PySide.QtGui.QListView and subclass PySide.QtGui.QItemDelegate instead.
I'm very new to GUIs and I have a quick question on inheritance of variables.
In my GUI i have two buttons, one selects an xlsx file, the other graphs it. The first class below sets the buttons and selects the file:
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
vBoxLayout = QtGui.QVBoxLayout(self)
file_btn = QtGui.QPushButton('Select File', self)
file_btn.clicked.connect(self.get_graph_file)
graphing_btn = QtGui.QPushButton('Plot Graph', self)
graphing_btn.clicked.connect(Plotting_Graph)
self.show()
def get_graph_file(self):
fname_graphfile = QtGui.QFileDialog.getOpenFileName(self, 'Open file', '/Users/.../', 'excel files (*.xlsx)')
... and the second should inherit fname_graphfile and graph it (I've only added in a bit of the graphing code)...
class Plotting_Graph(Example):
def __init__(self):
self.PlottingGraph()
def PlottingGraph(self):
xl = ef(fname_graphfile[0])......
When run, it gives an error global name 'fname_graphfile' is not defined.
How do I get the second class to remember something I've defined in the previous class?
fname_graphfile is a local variable in the get_graph_file so other methods don't have access to it what you should do is make it an instance attribute so it can be accessed from any method and then add an argument to Plotting_Graph.PlottingGraph that accepts the xlsx file as a parameter to the method and pass self.fname_graphfile to it from the button click
your final code should look like this
from PyQt4 import QtCore, QtGui
import sys
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.fname_graph_file = ''
file_btn = QtGui.QPushButton('Select File', self)
file_btn.setGeometry(100, 100, 150, 30)
file_btn.clicked.connect(self.get_graph_file)
graphing_btn = QtGui.QPushButton('Plot Graph', self)
plot = Plotting_Graph()
graphing_btn.clicked.connect(lambda: plot.PlottingGraph(self.fname_graph_file))
self.show()
def get_graph_file(self):
self.fname_graph_file = QtGui.QFileDialog.getOpenFileName(self, 'Open file', '/home', 'excel files (*.xlsx)')
class Plotting_Graph(Example):
def __init__(self):
pass
def PlottingGraph(self, fname_graphfile):
print(fname_graphfile)
# xl = ef(fname_graphfile[0])
app = QtGui.QApplication([])
a = Example()
app.exec_()
First, I started using PyQt few hours ago.
So far so good - im writing rss client to familiarize myself with PyQt
I got QApplication, QMainWindow and two custom widgets.
First custom widget is:
class RssItem(QWidget):
__pyqtSignals__ = ("articleViewed(bool)",
"articleOpened(bool)",
"articleMarkedGood(bool)")
def __init__(self, title, date, parent = None):
super(RssItem, self).__init__(parent)
self.initWidget(title, date)
def initWidget(self, title, date):
title = QLabel(title)
date = QLabel(date)
titleBox = QHBoxLayout()
titleBox.addWidget(title)
titleBox.addWidget(date)
self.setLayout(titleBox)
That displays (for now) title and date in single row
Second one accepts array of RssItem widgets and display them in vertical list:
class ItemsList(QWidget):
def __init__(self, items, parent=None):
super(ItemsList, self).__init__(parent)
self.initWidget(items)
def initWidget(self, items):
listBox = QVBoxLayout(self)
for item in items:
listBox.addWidget(item)
listBox.addStretch(1)
self.setLayout(listBox)
How do I make this list scrollable?
Keep in mid I'm planing to have multiple ItemList's in one window each should have it's own scrollbar.
Main app function as for now is only for testing these 2 widgets:
class MainApp(Qt.QApplication):
def __init__(self, args):
super(MainApp, self).__init__(args)
self.addWidgets()
self.exec_()
def addWidgets(self):
self.window = MainWindow()
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
self.statusBar().showMessage("ok")
self.resize(640, 480)
self.setWindowTitle("Smart Rss")
items=[]
for x in range(0, 200):
items.append(RssItem("Title no %s" % x, "2000-1-%s" %x))
self.setCentralWidget(ItemsList(items))
self.show()
EDIT:Getting closer, changed ItemList.initWidget to
def initWidget(self, items):
scroll= QScrollArea(self)
wrap = QWidget(self)
listBox = QVBoxLayout(self)
for item in items:
listBox.addWidget(item)
listBox.addStretch(1)
wrap.setLayout(listBox)
scroll.setWidget(wrap)
But now I cant figure out how to make QScrollArea fill all available space and auto resize when it's changed.
Try scroll.setWidgetResizable(True) like in here:
def initWidget(self, items):
listBox = QVBoxLayout(self)
self.setLayout(listBox)
scroll = QScrollArea(self)
listBox.addWidget(scroll)
scroll.setWidgetResizable(True)
scrollContent = QWidget(scroll)
scrollLayout = QVBoxLayout(scrollContent)
scrollContent.setLayout(scrollLayout)
for item in items:
scrollLayout.addWidget(item)
scroll.setWidget(scrollContent)