Consider this mcve:
import sys
from datetime import datetime
from PyQt5.Qt import * # noqa
class Foo(QPlainTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
def keyPressEvent(self, event):
if event.key() == Qt.Key_P:
self.appendPlainText(f"New line")
super().keyPressEvent(event)
class FooWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
m = self.menuBar().addMenu("&Debug")
action = QAction("Baz", self)
action.triggered.connect(self.bar)
action.setShortcut("Ctrl+N")
m.addAction(action)
self.obj = Foo()
self.obj.setLineWrapMode(QPlainTextEdit.NoWrap)
for i in range(20):
self.obj.appendPlainText(f"Line_{i}" * 10)
self.obj_dock = QDockWidget('Console', self)
self.obj_dock.setWidget(self.obj)
self.addDockWidget(Qt.BottomDockWidgetArea, self.obj_dock)
def bar(self):
self.obj.appendPlainText(f"New line from menu action")
def showEvent(self, event):
bar = self.obj.verticalScrollBar()
bar.setValue(bar.maximum())
print(f"2) showEvent {now()}")
def now():
return datetime.utcnow().strftime('%H:%M:%S.%f')[:-3]
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = FooWindow()
print(f"1) before show {now()}")
ex.show()
print(f"3) after show {now()}")
# ex.obj.ensureCursorVisible()
bar = ex.obj.horizontalScrollBar()
bar.setValue(bar.minimum())
bar = ex.obj.verticalScrollBar()
bar.setValue(bar.maximum())
print(f"4) manual bar.setValue {now()}")
# timer = QTimer()
# timer.setInterval(10)
# timer.setSingleShot(True)
# def set_scrollbar_at_maximum():
# bar = ex.obj.verticalScrollBar()
# bar.setValue(ex.obj.verticalScrollBar().maximum())
# print(f"5) bar.setValue from timer {now()}")
# timer.timeout.connect(set_scrollbar_at_maximum)
# timer.start()
sys.exit(app.exec_())
I'd like to view the last lines added without having to scroll the vertical bar down manually. Calling bar.setValue(ex.obj.verticalScrollBar().maximum()) after either ex.show() or def showEvent(self, event): don't work, ie:
Thing is, unless I scroll the vertical bar down manually or I set the value using a single-shot timer the last added lines won't be visible.
I'd like to know why my above snippet isn't working properly and I'd like to know a best alternative to these single-shot timers with random little intervals... Said otherwise, what's the proper place that will guarantee the child widgets will be painted properly after using functions such as setValue. This stuff is pretty confusing, one would expect the showEvent to be a proper place to do so... but it's not :/
As an alternative to use single-shot timers to guarantee the widgets will be painted properly, it seems overriding QMainWindow::event is a good choice, here's a possible solution to the problem:
import sys
from datetime import datetime
from PyQt5.Qt import * # noqa
class Foo(QPlainTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
def keyPressEvent(self, event):
if event.key() == Qt.Key_P:
self.appendPlainText(f"New line")
super().keyPressEvent(event)
class FooWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
m = self.menuBar().addMenu("&Debug")
action = QAction("Baz", self)
action.triggered.connect(self.bar)
action.setShortcut("Ctrl+N")
m.addAction(action)
self.obj = Foo()
self.obj.setLineWrapMode(QPlainTextEdit.NoWrap)
for i in range(20):
self.obj.appendPlainText(f"Line_{i}" * 10)
self.obj_dock = QDockWidget('Console', self)
self.obj_dock.setWidget(self.obj)
self.addDockWidget(Qt.BottomDockWidgetArea, self.obj_dock)
self.window_shown = False
def bar(self):
self.obj.appendPlainText(f"New line from menu action")
def update_widgets(self):
if self.window_shown:
return
bar = ex.obj.horizontalScrollBar()
bar.setValue(bar.minimum())
bar = self.obj.verticalScrollBar()
bar.setValue(bar.maximum())
self.window_shown = True
def event(self, ev):
ret_value = super().event(ev)
if ev.type() == QEvent.Paint:
self.update_widgets()
return ret_value
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = FooWindow()
ex.show()
sys.exit(app.exec_())
Related
I'm trying to update a QLineEdit when I do a wheel move on the mouse and if I press a number on the keyboard at the same time. Unfortunatly, I don't see a mouse wheel event for QLineEdit widget so I don't understand how I can set a signal to each QLineEdit. The other issue I have is as the QLineEdit are already in a scroll bar environment, I cannot detect all wheel event because the scroll is done first.
For instance with this code:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.centralTabs= QTabWidget()
self.setCentralWidget(self.centralTabs)
self.setFixedWidth(200)
self.setFixedHeight(200)
#tab Model selection
self.tab_ModelSelect = QWidget()
self.centralTabs.addTab(self.tab_ModelSelect,"Label")
self.groupscrolllayouttest = QHBoxLayout() ####
self.groupscrollbartest = QGroupBox() ####
self.mainHBOX_param_scene = QVBoxLayout()
for i in range(10):
LineEdit = QLineEdit(str(i))
LineEdit.setFixedWidth(200)
self.mainHBOX_param_scene.addWidget(LineEdit)
self.installEventFilter(self)
scroll = QScrollArea()
widget = QWidget(self)
widget.setLayout(QVBoxLayout())
widget.layout().addWidget(self.groupscrollbartest)
scroll.setWidget(widget)
scroll.setWidgetResizable(True)
self.groupscrollbartest.setLayout(self.mainHBOX_param_scene)
self.groupscrolllayouttest.addWidget(scroll)
self.tab_ModelSelect.setLayout(self.groupscrolllayouttest)
def eventFilter(self, widget, event):
if (event.type() == QEvent.Wheel) :
# get which key is pressed
# if the key is a number, put the number in 'num' variable
# get on which QLineEdit the wheel was make
# set the text of that QLineEdit to previous value +- num
event.ignore()
return QWidget.eventFilter(self, widget, event)
def main():
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
# ex.showMaximized()
ex.show()
sys.exit(app.exec_( ))
if __name__ == '__main__':
main()
I tried to something with wheelevent but as mention, I don't enter in this function because the scroll bar is activated first. I tried to test if the wheel event happend on a QLineEdit but I even struggle to do that...
What I'm seeking to do is when I do a wheelevent with the mouse on a QlineEdit with the '5' key pressed, I want increase or decrease the QLineEdit text by 5 according to the direction of the mouse wheel.
I red some post with eventfilter (as How to get Click Event of QLineEdit in Qt?) but I don't understand how does this work.
In my eventFilter function, I need to do several step:
# get which key is pressed
# if the key is a number, put the number in 'num' variable
# get on which QLineEdit the wheel was make
# set the text of that QLineEdit to previous value +- num
but I don't know how to make them. In particular getting the pressed key and knowing on which QlineEdit the wheel was made.
If you wish that all QLineEdit have that behavior then it is appropriate that you create your own class and overwrite the necessary events, to avoid that when you move the scroll when the pointer is inside the QLineEdit and pressing an established key, we accept the event so that it does not propage.
Another recommendation is not to use the 5 key since this is a text that QLineEdit can handle causing problems, for this you can use the SHIFT key:
class LineEdit(QLineEdit):
KEY = Qt.Key_Shift
def __init__(self, *args, **kwargs):
QLineEdit.__init__(self, *args, **kwargs)
self.isKeyPressed = False
def keyPressEvent(self, event):
if event.key() == LineEdit.KEY:
self.isKeyPressed = True
QLineEdit.keyPressEvent(self, event)
def keyReleaseEvent(self, event):
if event.key() == LineEdit.KEY:
self.isKeyPressed = False
QLineEdit.keyReleaseEvent(self, event)
def wheelEvent(self, event):
if self.isKeyPressed:
delta = 1 if event.angleDelta().y() > 0 else -1
fn = self.font()
fn.setPointSize(fn.pointSize() + delta)
self.setFont(fn)
event.accept()
The complete example:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class LineEdit(QLineEdit):
KEY = Qt.Key_Shift
def __init__(self, *args, **kwargs):
QLineEdit.__init__(self, *args, **kwargs)
self.isKeyPressed = False
def keyPressEvent(self, event):
if event.key() == LineEdit.KEY:
self.isKeyPressed = True
QLineEdit.keyPressEvent(self, event)
def keyReleaseEvent(self, event):
if event.key() == LineEdit.KEY:
self.isKeyPressed = False
QLineEdit.keyReleaseEvent(self, event)
def wheelEvent(self, event):
if self.isKeyPressed:
delta = 1 if event.angleDelta().y() > 0 else -1
fn = self.font()
fn.setPointSize(fn.pointSize() + delta)
self.setFont(fn)
event.accept()
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.centralTabs= QTabWidget()
self.setCentralWidget(self.centralTabs)
self.setFixedWidth(200)
self.setFixedHeight(200)
#tab Model selection
self.tab_ModelSelect = QWidget()
self.centralTabs.addTab(self.tab_ModelSelect,"Label")
self.groupscrolllayouttest = QHBoxLayout() ####
self.groupscrollbartest = QGroupBox() ####
self.mainHBOX_param_scene = QVBoxLayout()
for i in range(10):
le = LineEdit(str(i))
le.setFixedWidth(200)
self.mainHBOX_param_scene.addWidget(le)
self.installEventFilter(self)
scroll = QScrollArea()
widget = QWidget(self)
widget.setLayout(QVBoxLayout())
widget.layout().addWidget(self.groupscrollbartest)
scroll.setWidget(widget)
scroll.setWidgetResizable(True)
self.groupscrollbartest.setLayout(self.mainHBOX_param_scene)
self.groupscrolllayouttest.addWidget(scroll)
self.tab_ModelSelect.setLayout(self.groupscrolllayouttest)
def main():
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
# ex.showMaximized()
ex.show()
sys.exit(app.exec_( ))
if __name__ == '__main__':
main()
Why does my application crash when i run the function setup_controls() twice.
Am I missing a 'parent/self' somewhere that is critical in the design?
import sys
from PySide import QtGui, QtCore
class QCategoryButton(QtGui.QPushButton):
def __init__(self, Text, treeitem, parent=None):
super(QCategoryButton, self).__init__(Text, parent)
self.treeitem = treeitem
def mousePressEvent(self, event):
mouse_button = event.button()
if mouse_button == QtCore.Qt.LeftButton:
self.treeitem.setExpanded(not self.treeitem.isExpanded())
class Example(QtGui.QWidget):
def __init__(self,):
super(Example, self).__init__()
self.initUI()
def initUI(self):
# formatting
self.resize(300, 300)
self.setWindowTitle("Example")
# widgets
self.ui_treeWidget = QtGui.QTreeWidget()
self.ui_treeWidget.setRootIsDecorated(False)
self.ui_treeWidget.setHeaderHidden(True)
self.ui_treeWidget.setIndentation(0)
self.setup_controls()
# self.setup_controls()
# layout
self.mainLayout = QtGui.QGridLayout(self)
self.mainLayout.addWidget(self.ui_treeWidget)
self.show()
def setup_controls(self):
# Add Category
pCategory = QtGui.QTreeWidgetItem()
self.ui_treeWidget.addTopLevelItem(pCategory)
self.ui_toggler = QCategoryButton('Settings', pCategory)
self.ui_treeWidget.setItemWidget(pCategory, 0, self.ui_toggler)
pFrame = QtGui.QFrame(self.ui_treeWidget)
pLayout = QtGui.QVBoxLayout(pFrame)
self.ui_ctrl = QtGui.QPushButton('Great')
self.ui_ctrlb = QtGui.QPushButton('Cool')
pLayout.addWidget(self.ui_ctrl)
pLayout.addWidget(self.ui_ctrlb)
pContainer = QtGui.QTreeWidgetItem()
pContainer.setDisabled(False)
pCategory.addChild(pContainer)
self.ui_treeWidget.setItemWidget(pContainer, 0, pFrame)
# Main
# ------------------------------------------------------------------------------
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
The setItemWidget method takes ownership of the widget that is passed to it. If you don't keep a reference it, it could get garbage-collected by Python. But of course Qt knows nothing about Python, so when it subsequently tries to access the widget that is no longer there ... boom!
This is the problematic line:
self.ui_toggler = QCategoryButton('Settings', pCategory)
On the second call, the previous widget stored in self.ui_toggler will get deleted, because there is no other reference held for it (on the Python side). So instead you should do this:
ui_toggler = QCategoryButton('Settings', pCategory, self)
self.ui_treeWidget.setItemWidget(pCategory, 0, ui_toggler)
I'm doing a program with graphical interface using PyQt5 . I want to do is that when the user presses certain button, this change widget and show other options.
For this I decided to use QStackedWidget, and all my interface build it from the QT5 designer.
However, in my code, wanting to determine that my name button "btfr" show me "page_2" of my stackedWidget when pressed, using the QStackedWidget.setCurrentIndex method, this does nothing or make any error.
the code is as follows:
import sys
from PyQt5 import uic
from PyQt5.QtCore import QTimeLine
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
infz = uic.loadUiType("main.ui")[0]
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.pixmap_opacity = 1.0
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
painter.end()
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.repaint()
class StackedWidget(QStackedWidget):
def __init__(self, parent=None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
self.stack = MyWindowClass()
self.a = self.stack.stackedWidget.currentWidget()
self.b = self.stack.stackedWidget.widget(index)
self.fader_widget = FaderWidget(self.a, self.b)
QStackedWidget.setCurrentIndex(self, index)
print(self, index)
def setPage1(self):
self.setCurrentIndex(0)
def setPage2(self):
self.setCurrentIndex(1)
class MyWindowClass(QStackedWidget, infz):
def __init__(self, parent=None):
global pos, c, f
self.pos = 0
self.c = []
self.f = False
QStackedWidget.__init__(self, parent)
self.setupUi(self)
self.setWindowTitle('SkR')
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWindowClass()
window.resize(788, 518)
stack = StackedWidget()
window.btfr.clicked.connect(stack.setPage2)
window.btnpx.clicked.connect(stack.setPage1)
window.show()
sys.exit(app.exec_())
What I intend with this code is that the change of widget does so with an effect: "fade out".
If I print the "self " and the "index " receiving QStackedWidget.setCurrentIndex shows the following:
<__main__.StackedWidget object at 0x7fc2eb6b5c18> 0
The number zero is index, and the other element is self
Thank you for your attention, I hope someone can help.
Your question isn't completely clear, but don't you just want:
def setIndex(self, index):
self.setCurrentIndex(index)
However, this is a little redundant as you should able to link the button directly to the setCurrentIndex method and use lambda to pass the index value:
btfr.clicked.connect(lambda: self.setCurrentIndex(2))
I am new to pyqt.I am trying to invoke a child GUI when a button is clicked in the parent GUI. In this process, parent GUI has to wait for the child GUI to be closed by the user after selecting some inputs. But this is not happening, Parent GUI does execute the next lines after which the child GUI has been invoked. Below is the code where I am passing an argument to child GUI from parent GUI. The child GUI will return value based on OK/Cancel button click
Code:
import sys
from PyQt4 import QtGui,QtCore,Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Child(QtGui.QWidget):
def __init__(self,switches=None):
super(Child,self).__init__()
self.swwidget = QtGui.QWidget()
self.swlayout = QtGui.QGridLayout()
switches = ['abc1','def1']
switches.sort()
self.switches = switches
def switchesUI(self):
self.swwidget.setWindowModality(QtCore.Qt.ApplicationModal)
self.swl = len(self.switches)
self.sw = {}
self.addsw = []
print ("I am in switchesUI")
#Add the switches to layout dynamically
for i in range(self.swl):
self.sw[i] = QtGui.QCheckBox(self.switches[i])
self.swlayout.addWidget(self.sw[i],i,0)
self.swbuttonbox = QtGui.QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel);
self.swbuttonbox.setOrientation(QtCore.Qt.Horizontal)
self.swlayout.addWidget(self.swbuttonbox)
self.swwidget.setWindowTitle('Switches')
self.swwidget.setLayout(self.swlayout)
self.swwidget.show()
self.connect(self.swbuttonbox,QtCore.SIGNAL("accepted()"),self.swaccept)
self.connect(self.swbuttonbox,QtCore.SIGNAL("rejected()"),self.swreject)
def swaccept(self):
for i in range(self.swl):
if self.sw[i].isChecked():
self.addsw.append(self.switches[i])
self.swwidget.close()
return self.addsw
def swreject(self):
self.swwidget.close()
return None
class Parent(QtGui.QWidget):
def __init__(self):
super(Parent,self).__init__()
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.assw = ['Test1','Test2']
self.CH = Child(self.assw)
self.connect(self.button,SIGNAL("clicked()"),self.popup)
print ("Child GUI closed")
def popup(self):
self.CH.switchesUI()
def main():
app = QtGui.QApplication(sys.argv)
form = Parent()
form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
After the button "Test" is clicked, a child GUI will pop-up. I don't want the statement "Child GUI Closed" to be printed till the child GUI is closed.
Can someone suggest me how to achieve this functionality ?
You have to handle closeEvent to perform operations when a window wants to close, also since your Child class inherits from QWidget which means it's a QWidget itself you dont need to create another one with self.swwidget
import sys
from PyQt4 import QtGui,QtCore,Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Child(QtGui.QWidget):
def __init__(self,switches=None):
super(Child,self).__init__()
# self.swwidget = QtGui.QWidget() # you don't need to do this you can add all the properties to self
self.swlayout = QtGui.QGridLayout()
switches = ['abc1','def1']
switches.sort()
self.switches = switches
def switchesUI(self):
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.swl = len(self.switches)
self.sw = {}
self.addsw = []
print ("I am in switchesUI")
#Add the switches to layout dynamically
for i in range(self.swl):
self.sw[i] = QtGui.QCheckBox(self.switches[i])
self.swlayout.addWidget(self.sw[i],i,0)
self.swbuttonbox = QtGui.QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel);
self.swbuttonbox.setOrientation(QtCore.Qt.Horizontal)
self.swlayout.addWidget(self.swbuttonbox)
self.setWindowTitle('Switches')
self.setLayout(self.swlayout)
self.show()
self.connect(self.swbuttonbox,QtCore.SIGNAL("accepted()"),self.swaccept)
self.connect(self.swbuttonbox,QtCore.SIGNAL("rejected()"),self.swreject)
def swaccept(self):
for i in range(self.swl):
if self.sw[i].isChecked():
self.addsw.append(self.switches[i])
self.close()
return self.addsw
def swreject(self):
self.close()
return None
def closeEvent(self, event):
print ("Child GUI closed")
class Parent(QtGui.QWidget):
def __init__(self):
super(Parent,self).__init__()
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.assw = ['Test1','Test2']
self.CH = Child(self.assw)
self.connect(self.button,SIGNAL("clicked()"),self.popup)
def popup(self):
self.CH.switchesUI()
def main():
app = QtGui.QApplication(sys.argv)
form = Parent()
form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
My PyQt application starts with Login screen. If password OK, a module-screen (with icons) appears. When user click some button, a QMainWindow will appears. But I can't do this because of qmainwindow object has no attribute '_exec' error. This is my code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
...
...
class Login(QDialog):
def __init__(self, parent=None):
super(Login, self).__init__(parent)
...
...
uyg=QApplication(sys.argv)
class icons(QDialog):
def __init__(self, parent=None):
super(icons, self).__init__(parent)
...
self.buton = QPushButton()
self.buton.pressed.connect(self.open)
...
def open(self):
dialogmain = Main()
dialogmain._exec() #or dialogmain.show() ???
self.accept()
self.close()
uyg.exec_()
if Login().exec_() == QDialog.Accepted:
dialog = icons()
dialog.exec_()
else:
uyg.quit()
What am I doing wrong? Thank you.
Lately i have done the similar work:I have a loging window and a main window ,and I used something like a FSM to switch between the loging and main window.
Let's say we have 3 state:loging,main,quit.
STATE_LOGING = 0
STATE_MAIN = 1
STATE_QUIT = 2
STATE_DESTROY = 3 #this is a flag
class CState():
sigSwitchState = pyqtSignal(int)
def __init__(self):
super(CState,self).__init__()
def start(self):
pass
def sendQuit(self,nextstate):
self.sigSwitch.emit(nextstate)
class CLoginState(CState):
def __init__(self):
super(CLoginState,self).__init__()
def start(self):
w = Loging()
w.show()
def whenPasswdOk(self):
self.sendQuit(STATE_MAIN)
class CMainState(CState):
def __init__(self):
super(CMainState,self).__init__()
def start(self):
w = MainWindow()
w.show()
def whenMainWindowQuit(self):
self.sendQuit(STATE_QUIT)
class CQuitState(CState):
def __init__(self):
super(CQuitState,self).__init__()
def start(self):
#do some clean stuff...
pass
def whenCleanDone(self):
self.sendQuit(STATE_DESTROY)
class CMainApp():
def __init__(self):
self.app = QApplication(sys.argv)
def __CreateState(state):
if state == STATE_LOGING:
s = CLoginState()
if state == STATE_MAIN:
s = CMainState()
#... same as other state
s.sigSwitchState.connect(self.procNextState)
def procNextState(self,state):
if state == STATE_DESTROY:
QApplication().exit()
s = self.__CreateState(state)
s.start()
def run(self):
self.procNextState(STATE_LOGING)
sys.exit(self.app.exec_())
if __name__ == "__main__":
app = CMainApp()
app.run()
Apart from the application object and QDrag, please pretend that exec() doesn't exist. It is an utterly confusing method that essentially never has to be used. Especially not by anyone new to Qt.
If you want to display any widget, simply show() it. If you want to be notified when a dialog was accepted, connect some code to its accepted() signal. That's all.