How to set the central widget of existing MainWidnow in Python PyQt4? - python

I have a code which consists of 3 classes. The classes widget1 and widget2 inherit from QFrame class and consists of several lineedit and combobox for the user to enter information.
So, what I want is: when the program is launched, it will firstly bring up the QMainWindow class with widget1 set as the central widget.
The widget1 has a Check function which is connected to a button on it. The check button will check whether a condition is true based on the data entered by the user on the page.
If the condition is true. I want the widget2 to set it as central wiget of MainWindow to replace existing central widget, the widget1.
My question is, how do I set the widget2 as the central widget of existing MainWidnow class instance?
This is the format of my code:
class widget1(QtGui.QFrame):
def __init__(self,parent = None):
......
......
def Check(self):
if (condition):
#set widget2 as central widget on MainWindow
class widget2(QtGui.QFrame):
def __int__(self,parent = None):
.....
.....
class MainWindow(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
....
mywidgetone = widget1()
self.setCentralWidget(mywidgetone)
if __name__ == '__main__':
app = QtGui.QApplicaiton(sys.argv)
main = MainWindow()
main.show()
app.exec_()

I'd do the following for MainWindow:
class MainWindow(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
....
self.setMyCentral(widget1)
def setMyCentral(self, widgetClass):
mywidget = widgetClass(self)
self.setCentralWidget(mywidget)
and then, in widget1:
def Check(self):
if (condition):
self.parent().setMyCentral(widget2)
Now, please, follow the conventions: classes start in capital letters (Widget1, Widget2) and methods don't (check instead of Check)

Another option is a panel creates all 3 widgets but set widget2 and 3 visible = false. Then when time to change, set widget1 visible=false, and widget2 visible. Etc. You set the panel as central widget, this never changes. Just the panel decides which one of its three children to show.

Related

Why is an absolute positioned widget not shown?

Using Qt5 I am trying to make a widget work using absolute positioning. The code below is a minimum working example of something I am trying to do. A quick walk through of the code:
CentralWidget is the central widget of the main window and holds MyWidget using absolute positioning, e.g. without using any layouts.
MyWidget does not set its child widgets immediately but provides a SetData method which first removes all current child widgets and then adds the new child widgets to its layout.
SetData is triggered using a timer in the main window.
I commented out two lines of code. The first "enables" relative positioning using layouts by adding a layout to CentralWidget. This line shows what I am trying to achieve but with absolute positioning. The second comment enables some debug information:
MyWidget
layout.count: 3
size: PyQt5.QtCore.QSize(-1, -1)
sizeHint: PyQt5.QtCore.QSize(200, 100)
CentralWidget
size: PyQt5.QtCore.QSize(18, 18)
sizeHint: PyQt5.QtCore.QSize(18, 18)
What I am doing wrong in order for MyWidget to be visible using absolute positioning?
Code:
from PyQt5 import QtCore, QtWidgets
import sys
class MyWidget(QtWidgets.QWidget):
z = 0
def __init__(self, parent):
super().__init__(parent)
self._layout = QtWidgets.QVBoxLayout(self)
def SetData(self):
while self._layout.count() > 0:
widget = self._layout.takeAt(0).widget()
widget.hide()
widget.deleteLater()
for i in range(3):
self._layout.addWidget(QtWidgets.QLabel(str(MyWidget.z * 10 + i)))
MyWidget.z += 1
class CentralWidget(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__(parent)
self._myWidget = MyWidget(self)
# QtWidgets.QHBoxLayout(self).addWidget(self._myWidget)
def SetData(self):
self._myWidget.SetData()
# print("MyWidget\n layout.count: {}\n size: {}\n sizeHint: {}\n\nCentralWidget\n size: {}\n sizeHint: {}\n\n".format(self._myWidget.layout().count(), self.sizeHint(), self.size(), self._myWidget.sizeHint(), self._myWidget.size()))
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
centralWidget = CentralWidget(self)
self.setCentralWidget(centralWidget)
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(centralWidget.SetData)
self._timer.start(500)
def main():
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec_()
if __name__ == "__main__":
main()
The reason for this behavior is directly related to the fact that the widget is not added to a layout and its contents are added after being shown.
In fact, if you call centralWidget.SetData() upon initialization and before mainWindow.show(), it will work as expected.
A lot of things happen when you add a child widget to a layout, and this usually involves multiple calls to the children size hints, allowing the parent to adapt its own size hint, and, after that, adapt its size and that of its children.
If that "container widget" is itself contained in another layout, that widget will be automatically resized (based on its hint) in the next cycle of events, but this doesn't happen in your case, since yours is a "free" widget.
The function you are looking for is QWidget.adjustSize(), but, for the aforementioned reasons, you cannot call it immediately after adding the children widgets.
To overcome your issue, you can call QApplication.processEvents() before adjustSize(), or, eventually, use a 0-based single shot QTimer:
def SetData(self):
while self._layout.count() > 0:
widget = self._layout.takeAt(0).widget()
widget.hide()
widget.deleteLater()
for i in range(3):
self._layout.addWidget(QtWidgets.QLabel(str(MyWidget.z * 10 + i)))
MyWidget.z += 1
QtCore.QTimer.singleShot(0, self.adjustSize)

QWidgetAction checked state not changing when using setDefaultWidget to a QCheckBox

I want to create a menu with a checkable list. To prevent the menu from closing when the action is clicked, I'm setting the DefaultWidget to be a QCheckBox. The problem is when I'm trying to get isClicked from the action - it doesn't seem to be synced to the checkbox. How do I get the value of the action to change when the checkbox is clicked?
tool_button = QtWidgets.QToolButton()
menu = QtWidgets.QMenu()
check_box = QtWidgets.QCheckBox(menu)
check_box.setText("abc")
check_box.setChecked(True)
action_button = QtWidgets.QWidgetAction(menu)
action_button.setDefaultWidget(check_box)
menu.addAction(action_button)
tool_button.setMenu(menu)
print(check_box.text()) # returns abc
print(check_box.isChecked()) # returns True
print(action_button.isChecked()) # returns False - it's not picking up the values from check_box
Since QWidgetAction acts as some sort of container for any kind of widget, it has no way to know if its defaultWidget could even have any support for a bool state like "isChecked", so you have to provide it.
The simplest way is to subclass a specific QWidgetAction class for that action, and override its isChecked() method so that it returns the checked value based on its default widget.
from PyQt5 import QtWidgets
class CheckableWidgetAction(QtWidgets.QWidgetAction):
def setDefaultWidget(self, widget):
super().setDefaultWidget(widget)
try:
# if the widget has the toggled signal, connect that signal to the
# triggered signal
widget.toggled.connect(self.triggered)
except:
pass
def isChecked(self):
try:
return self.defaultWidget().isChecked()
except:
# failsafe, in case no default widget has been set or the provided
# widget doesn't have a "checked" property
return super().isChecked()
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
tool_button = QtWidgets.QToolButton()
layout.addWidget(tool_button)
menu = QtWidgets.QMenu()
check_box = QtWidgets.QCheckBox(menu)
check_box.setText("abc")
check_box.setChecked(True)
self.action_button = CheckableWidgetAction(menu)
self.action_button.setDefaultWidget(check_box)
self.action_button.triggered.connect(self.action_triggered)
menu.addAction(self.action_button)
tool_button.setMenu(menu)
controlButton = QtWidgets.QPushButton('is checked?')
layout.addWidget(controlButton)
controlButton.clicked.connect(self.is_checked)
def is_checked(self):
print('checked is {}'.format(self.action_button.isChecked()))
def action_triggered(self, state):
print('triggered {}'.format(state))
if __name__ == '__main__':
import sys
a = QtWidgets.QApplication(sys.argv)
test = Test()
test.show()
sys.exit(a.exec_())

Accessing passed parameters of PyQt5 QChartView inside of overrided class

I am trying to use PyQt5 QtChart module to plot multiple charts on two different tab widgets with custom mouse and key events to zoom in and out from the charts.
So I created another class to override the QChartView class events like follows:
class my_view(QChartView):
def keyPressEvent(self,event):
if event.key() == Qt.Key_Plus:
ex.my_chart.zoomIn() # ex.chart is accessed from outside
if event.key() == Qt.Key_Minus: # self.passed_parameter would be the solution
ex.my_chart.zoomOut()
def mousePressEvent(self, event):
...
def mouseMoveEvent(self, event):
...
def mouseReleaseEvent(self, event):
...
and in the class Window is the call to my_view:
self.my_chart = QChart()
self.my_chart_view = my_view(self.my_chart, self.parent)
There is the call of the Window class from which the chart can be accessed outside the window class:
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Window()
ex.show()
sys.exit(app.exec_())
The problem came out when I created two instances of my_view like this:
self.my_chart = QChart()
self.my_chart_view = my_view(self.my_chart, self.parent)
self.my_chart1 = QChart()
self.my_chart1_view = my_view(self.my_chart1, self.parent1)
and if I zoomed the second one, the first one was zoomed on the other tab...
So my question is, how can I access self.my_chart from inside the overrided class if I don't know how it is named inside the class?
As ekhumoro stated in his comment, the solution was
self.chart().zoomIn()

Accessing to QListWidgetItem from widget inside

I'm trying to figure out how I can get the QWidget that I insert into a QListWidget as a QListWidgetItem to be able to access the list it is a part of so that it can do the following:
Increase/decrease it's position in the list
Remove itself from the list
Pass information from it's own class to a function in the main class
My script layout is a main.py which is where the MainWindow class is. The MainWindow uses the class generated from the main ui file. I also have the custom widget which is it's own class.
Example of GUI:
Relevant code snippets:
main.py
from PyQt4.QtGui import QMainWindow, QApplication
from dungeonjournal import Ui_MainWindow
from creature_initiative_object import InitCreatureObject
from os import walk
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(QMainWindow, self).__init__(parent)
self.setupUi(self)
etc......
def AddToInitiative(self):
creature = self.comboBoxSelectCharacter.currentText()
if(creature):
creatureInfo = ''
with open("creatures/"+str(creature)+".creature", "r") as f:
creatureInfo = f.read()
creatureInfo = creatureInfo.split("|")
customWidget = InitCreatureObject()
customWidgetItem = QtGui.QListWidgetItem(self.initiativeList)
customWidgetItem.setSizeHint(QtCore.QSize(400,50))
self.initiativeList.addItem(customWidgetItem)
self.initiativeList.setItemWidget(customWidgetItem, customWidget)
customWidget.setName(creatureInfo[0])
return
creature_initiative_object.py
class Ui_InitCreatureObject(object):
def setupUi(self, InitCreatureObject):
etc...
class InitCreatureObject(QtGui.QWidget, Ui_InitCreatureObject):
def __init__(self, parent=None, f=QtCore.Qt.WindowFlags()):
QtGui.QWidget.__init__(self, parent, f)
self.setupUi(self)
Edit 1:
To clarify again, I need to be able to use the buttons in the widget to modify the position of itself in the list. The list is part of the main ui. The buttons for up arrow, down arrow, Select, and Remove are the one's I'm trying to get to interact with things outside of their class.
The function they call needs to be able to determine which listItem is being called, be able to modify the list.
For example, if I click remove, it then needs to know which item in the list to remove. So it needs to first know what the list is, then it needs to know what item it is. I'm not sure how to access the instance of the widget that is occupying that listitem. I also am not sure how to get that listitem based on a button press from inside that listitem's class.
Edit 2:
Per the first answer I tried to work that into my code.
main.py had the following function added
def RemoveItem(self):
cwidget = self.sender().parent()
item = self.initiativeList.itemAt(cwidget.pos())
row = self.initiativeList.row(item)
self.initiativeList.takeItem(row)
print(row)
creature_initiative_object.py had the following added to the InitCreatureObject class
class InitCreatureObject(QtGui.QWidget, Ui_InitCreatureObject):
def __init__(self, parent=None, f=QtCore.Qt.WindowFlags()):
QtGui.QWidget.__init__(self, parent, f)
self.setupUi(self)
self.mainwidget = main.MainWindow()
self.btnRemove.clicked.connect(self.mainwidget.RemoveItem)
Item is still not being passed. The parent object seems to be right but when I get the row it always says -1.
The strategy to get the QTableWidgetItem is to use the itemAt() method but for this you must know the position of some point within the QTableWidgetItem.
Since the main objective is to get the item when a signal is sent, then the connected slot is used, so I recommend connecting all the signals to that slot. Given the above the following steps are taken:
Get the object that emits the signal through sender().
Get the sender parent() since this will be the custom widget that was added to the QListWidget() along with the item.
Get the position of the custom widget through pos(), this is the position that should be used in the itemAt() method.
Then you get the text of the button or some parameter that tells me the task to know what action you want to do.
The above can be implemented as follows:
def someSlot(self):
p = self.sender().parent()
it = self.lw.itemAt(p.pos())
text = self.sender().text()
if text == "task1":
do task1
elif text == "task2":
do task2
From the above, the following example is proposed:
class CustomWidget(QWidget):
def __init__(self, text, parent=None):
QWidget.__init__(self, parent)
self.setLayout(QHBoxLayout())
self.buttons = []
vb = QVBoxLayout()
self.layout().addLayout(vb)
self.btnTask1 = QPushButton("task1")
self.btnTask2 = QPushButton("task2")
vb.addWidget(self.btnTask1)
vb.addWidget(self.btnTask2)
self.buttons.append(self.btnTask1)
self.buttons.append(self.btnTask2)
self.btnTask3 = QPushButton("task3")
self.btnTask4 = QPushButton("task4")
self.btnTask5 = QPushButton("task5")
self.btnTask6 = QPushButton("task6")
self.layout().addWidget(self.btnTask3)
self.layout().addWidget(self.btnTask4)
self.layout().addWidget(self.btnTask5)
self.layout().addWidget(self.btnTask6)
self.buttons.append(self.btnTask3)
self.buttons.append(self.btnTask4)
self.buttons.append(self.btnTask5)
self.buttons.append(self.btnTask6)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.lw = QListWidget(self)
self.setCentralWidget(self.lw)
for i in range(5):
cw = CustomWidget("{}".format(i))
for btn in cw.buttons:
btn.clicked.connect(self.onClicked)
item = QListWidgetItem(self.lw)
item.setSizeHint(QSize(400, 80))
self.lw.addItem(item)
self.lw.setItemWidget(item, cw)
def onClicked(self):
p = self.sender().parent()
it = self.lw.itemAt(p.pos())
row = self.lw.row(it)
text = self.sender().text()
print("item {}, row {}, btn: {}".format(it, row, text))
#if text == "task1":
# do task1
#elif text == "task2":
# do task2
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
In your Case:
class MainWindow(QMainWindow, Ui_MainWindow):
[...]
def AddToInitiative(self):
[...]
customWidget = InitCreatureObject()
customWidget.btnRemove.clicked.connect(self.RemoveItem)
# ^^^^^
[...]
def RemoveItem(self):
cwidget = self.sender().parent()
item = self.initiativeList.itemAt(cwidget.pos())
row = self.initiativeList.row(item)
self.initiativeList.takeItem(row)
print(row)

How to select parent QListWidgetItem on click of Child Widgets?

I have a GUI where a QListWidget is the central widget. The QListWidgetItems are composed of a custom widget that include a Graphics area and a QTable.
When I click on the graphics area, or on the table, the parent QListWidgetItem is not selected
See Picture of the GUI I'm working on
The red border around the top QListWidgetItem indicates that is the one that is currently selected.
From the Input on the table in the second QListWidgetItem, you can see I'm inputting text into the table widget there.
How can I make it so that if I click anywhere on that QListWidgetItem, including its child widgets, that the corresponding item is selected?
For reference, here is some of the relevant code:
class MainWindow(QMainWindow):
move_splitter = Signal(int, int)
def __init__(self):
super().__init__()
# stuff
self.panels = QListWidget()
self.panels.setAcceptDrops(True)
self.panels.setDragEnabled(True)
self.panels.setDragDropMode(QAbstractItemView.InternalMove)
self.panels.setStyleSheet("QListWidget::item:selected \
{ border: 1px solid red }")
self.panels.setSelectionMode(QAbstractItemView.SingleSelection)
self.setCentralWidget(self.panels)
# more stuff
def add_panel(self):
insert_item = QListWidgetItem()
# Panel inherits from QWidget
new_panel = Panel(parent=insert_item,
splitter_pos=self.initialSplitterPosition)
self.move_splitter.connect(new_panel.setSplitterPosition)
new_panel.slider_position.connect(self.slotSplitterPosition)
insert_item.setSizeHint(new_panel.sizeHint())
insert_item.setFlags(insert_item.flags() | Qt.ItemIsAutoTristate)
insert_item.setCheckState(Qt.PartiallyChecked)
self.panels.insertItem(self.panels.currentRow() + 1, insert_item)
self.panels.setItemWidget(insert_item, new_panel)
EDIT:
With the suggestion of implementing an event filter, I created the following subclass of my table
class ControlTable(QTableWidget):
focusReceived = Signal()
def __init__(self, parent=None):
super().__init__()
def focusInEvent(self, event):
if event.type() == QFocusEvent.gotFocus:
self.focusReceived.emit()
QTableWidget.focusInEvent(self, event)
In my panel function, I created a signal and a slot to capture that signal and emit a signal to the MainWindow with which QListWidgetItem should be selected
class Panel(QWidget):
slider_position = Signal(int, int)
select_me = Signal(QObject)
def __init__(self, parent=None, splitter_pos=[]):
# stuff
self.table = ControlTable()
self.table.focusReceived.connect(self.childWidgetFocus)
# more stuff
#Slot()
def childWidgetFocus(self):
parent = self.parent_item
self.select_me.emit(parent)
In my mainwindow class I also added
def add_panel(self):
insert_item = QListWidgetItem()
new_panel = Panel(parent=insert_item,
splitter_pos=self.initialSplitterPosition)
new_panel.parent_item = insert_item
new_panel.select_me.connect(self.changeSelection)
# more stuff
#Slot(QObject)
def changeSelection(self, item):
item.setSelected(True)
While the code runs, the change of selection is not happening, am I implementing the wrong event filter?
EDIT2:
Got it working, was using the wrong event filter type, I needed to use QEvent.FocusIn, but now that I look at my code, and looking at the function I'm overwriting, that if statement is unnecessary it would seem.
I also had to change the signal pass type from QObject to QListWidgetItem.
One possible but long way could be to set an event filter on each child of the item's widget (in your case its Panel) and select the the QListWidgetItem bound to the parent (Panel) from that event filter when focus is on the child element.
You can store the item reference in item attribute of Panel widget (you should create it) to make the item selection easier. So add before setItemWidget
new_panel.item = insert_item
In your event filter you can emit pyqtSignal passing self.parent().item as a parameter and further process it to select the corresponding item
some references which should help you to start:
How to change a parent widget's background when a child widget has focus?

Categories

Resources