In QMdiArea, we can select (point) to the activate subwindow. In my application, I want to select multiple subwindows (maybe using the "Ctrl" button) and set them as active windows (>=2 subwindows) and create a list pointer for them. I am trying to get pointers for more than one subwindow at the same time. Yes, activeSubWindow() gives only one window. But I wonder if I can use somthing like the "Ctrl" button in keyboard to select two subwindows and print the pointers to these subwindows. The idea is to get the widgets inside each subwindow (e.g TextEditor) at the same time to do afterward tasks, e.g., comparison
from PyQt5.QtWidgets import QApplication, QMainWindow, QMdiArea, QAction, QMdiSubWindow, QTextEdit
import sys
class MDIWindow(QMainWindow):
count = 0
def __init__(self):
super().__init__()
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.addAction("cascade")
file.addAction("Tiled")
file.addAction("selected_subwindows")
file.triggered[QAction].connect(self.WindowTrig)
self.setWindowTitle("MDI Application")
def WindowTrig(self, p):
if p.text() == "New":
MDIWindow.count = MDIWindow.count + 1
sub = QMdiSubWindow()
sub.setWidget(QTextEdit())
sub.setWindowTitle("Sub Window" + str(MDIWindow.count))
self.mdi.addSubWindow(sub)
sub.show()
if p.text() == "cascade":
self.mdi.cascadeSubWindows()
if p.text() == "Tiled":
self.mdi.tileSubWindows()
if p.text()=="selected_subwindows":
"""I want to select multiple subwindows and and set as activate
windows with the "Ctrl" button and return a points fot all active windows"""
print("active windows: ", self.mdi.activeSubWindow())
app = QApplication(sys.argv)
mdi =MDIWindow()
mdi.show()
app.exec_()
Just like with normal window handling, it's not possible to have multiple active sub windows even in an MDI area.
In order to achieve a "multiple selection" system, you need to track the activation state of the sub windows, which can be tricky.
Subwindows can be activated in different ways:
by clicking on their title bar (including any of its buttons);
by clicking on its contained widget;
by programmatically activating it with setActiveSubWindow() (which is similar to selecting a normal window from the task bar);
While Qt provides the aboutToActivate signal, it's not always reliable: it is always emitted even when the top level window gets focus, so there's no direct way to know the reason of the activation.
The same also goes for the windowStateChanged signal (which is emitted after the state has changed).
For your situation, the best approach is mainly based on the mousePressEvent implementation of the subwindow, but also considering the window state changes, because you need to keep track of the current active windows whenever the activation is changed in any other way (by clicking on the widget or by using setActiveSubWindow().
Since mouse events are handled after the window activation is changed, the proper solution is to create a signal for which the emission will be delayed (scheduled), in order to know if the activation was actually achieved by a mouse button press on the subwindow (not on the child widget) and finally check if the Ctrl key was pressed in the meantime.
Please note that the following code is very basic, and you might need to do some adjustments. For instance, it doesn't consider activations for minimized windows (unlike normal windows, a subwindow could be active even if it's minimized), nor considers activations when clicking on any of the window buttons.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class SubWindow(QMdiSubWindow):
activated = pyqtSignal(object, bool)
ctrlPressed = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAttribute(Qt.WA_DeleteOnClose)
self.windowStateChanged.connect(self.delayActivated)
self.activatedTimer = QTimer(
singleShot=True, interval=1, timeout=self.emitActivated)
def delayActivated(self, oldState, newState):
# Activation could also be triggered for a previously inactive top
# level window, but the Ctrl key might still be handled by the child
# widget, so we should always assume that the key was not pressed; if
# the activation is done through a mouse press event on the subwindow
# then the variable will be properly set there.
# Also, if the window becomes inactive due to programmatic calls but
# *after* a mouse press event, the variable has to be reset anyway.
self.ctrlPressed = False
if newState & Qt.WindowActive:
self.activatedTimer.start()
elif not newState and self.activatedTimer.isActive():
self.activatedTimer.stop()
def emitActivated(self):
self.activated.emit(self, self.ctrlPressed)
self.ctrlPressed = False
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.ctrlPressed = event.modifiers() & Qt.ControlModifier
self.activatedTimer.start()
super().mousePressEvent(event)
class MDIWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("MDI Application")
self.activeWindows = []
activeContainer = QWidget()
activeLayout = QVBoxLayout(activeContainer)
activeLayout.setContentsMargins(0, 0, 0, 0)
self.activeList = QListWidget()
# Note: the following "monkey patch" is only for educational purposes
# and done in order to keep the code short, you should *not* normally
# do this unless you really know what you're doing.
self.activeList.sizeHint = lambda: QSize(150, 256)
activeLayout.addWidget(self.activeList)
self.compareBtn = QPushButton('Compare', enabled=False)
activeLayout.addWidget(self.compareBtn)
self.activeDock = QDockWidget('Selected windows')
self.activeDock.setWidget(activeContainer)
self.addDockWidget(Qt.LeftDockWidgetArea, self.activeDock)
self.activeDock.setFeatures(self.activeDock.NoDockWidgetFeatures)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
fileMenu = bar.addMenu("File")
self.newAction = fileMenu.addAction("New")
self.cascadeAction = fileMenu.addAction("Cascade")
self.tileAction = fileMenu.addAction("Tiled")
self.compareAction = fileMenu.addAction("Compare subwindows")
fileMenu.triggered.connect(self.menuTrigger)
self.compareBtn.clicked.connect(self.compare)
def menuTrigger(self, action):
if action == self.newAction:
windowList = self.mdi.subWindowList()
if windowList:
count = windowList[-1].index + 1
else:
count = 1
sub = SubWindow()
sub.index = count
sub.setWidget(QTextEdit())
sub.setWindowTitle("Sub Window " + str(count))
self.mdi.addSubWindow(sub)
sub.show()
sub.activated.connect(self.windowActivated)
elif action == self.cascadeAction:
self.mdi.cascadeSubWindows()
elif action == self.tileAction:
self.mdi.tileSubWindows()
elif action == self.compareAction:
self.compare()
def windowActivated(self, win, ctrlPressed):
if not ctrlPressed:
self.activeWindows.clear()
if win in self.activeWindows:
self.activeWindows.remove(win)
self.activeWindows.append(win)
self.activeList.clear()
self.activeList.addItems([w.windowTitle() for w in self.activeWindows])
valid = len(self.activeWindows) >= 2
self.compareBtn.setEnabled(valid)
self.compareAction.setEnabled(valid)
def compare(self):
editors = [w.widget() for w in self.activeWindows]
if len(editors) < 2:
return
it = iter(editors)
oldEditor = next(it)
while True:
try:
editor = next(it)
except:
msg = 'Documents are equal!'
break
if oldEditor.toPlainText() != editor.toPlainText():
msg = 'Documents do not match!'
break
oldEditor = editor
QMessageBox.information(self, 'Comparison result', msg, QMessageBox.Ok)
app = QApplication(sys.argv)
mdi = MDIWindow()
mdi.show()
app.exec_()
Note that I had to make some further changes to your code:
action checking should never be done by string comparison: the style or localization could potentially add mnemonics or text variations to action texts, and you'll never get your action triggered: create proper instance attributes and verify the action by object comparison instead.
the count must be an instance attribute, not a class one: if, for any reason, you have to create multiple instances of the main window, you'll get an inconsistent count; you should also consider the currently existing windows;
you should not specify signal overloads if there are no overloads at all (which is the case of QMenu.triggered) nor create local variables if they are being used only once (and their names are not that long, like self.menuBar());
Related
Here is my code, How to show My Labels and activate respective QShortcut?. Want to show my labels(both instances) and assign respective shortcut keys to labels and activate it.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Create_workingDict(QWidget):
def __init__(self,lblname,lblscut):
super(). __init__()
self.lblname = lblname
self.lblscut = lblscut
lbl_1 = QLabel()
lbl_1.setText(self.lblname)
lbl_2 = QLabel()
lbl_2.setText(self.lblscut)
vbox = QVBoxLayout()
vbox.addWidget(lbl_1)
vbox.addWidget(lbl_2)
self.setLayout(vbox)
self.msgSc = QShortcut(QKeySequence(f'{self.lblscut}'), self)
self.msgSc.activated.connect(lambda: QMessageBox.information(self,'Message', 'Ctrl + M initiated'))
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
x = Create_workingDict("Accounts","Alt+A")
y = Create_workingDict("Inventory", "Ctrl+B")
def main():
app = QApplication(sys.argv)
ex = Mainclass()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Shortcuts work based on the context (and visibility) of the parent widget: as long as the parent is visible and the context is compatible, they will be triggered, otherwise they will not even be considered.
Be aware that the visibility is of the parent widgets (note the plural) is mandatory, no matter of the context: Qt has no API for a "global" shortcut, not only external to the application, but also within it: there is no shortcut that can automatically work anywhere in the app if (any of) its parent(s) is hidden. The context only ensures that the shortcut can only be activated if the active widget is part of the application (ApplicationShortcut), if the current active window is an ancestor of the shortcut parent (WindowShortcut), if any of the grand[...]parent widgets has focus (WidgetWithChildrenShortcut) or the current parent has it (WidgetShortcut).
Long story short: if the shortcut's parent is not visible (at any level), it will not be triggered.
Not only. In your code, both x and y are potentially garbage collected (they are not due to the fact that the lambda scope avoids destruction, but that's just "sheer dumb luck"), so that code would be actually prone to fail anyway if the activated signal would have been connected to an instance method.
If you want them to be available to the visible window, you must add their parent widgets to that window, even if they're not shown. Otherwise, just add the shortcuts to that window.
For instance:
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
x = Create_workingDict("Accounts","Alt+A")
y = Create_workingDict("Inventory", "Ctrl+B")
layout = QVBoxLayout(self)
layout.addWidget(x)
layout.addWidget(y)
The only and automatic way to access a global application shortcut from any window of the application is to create a QKeySequence that is checked within an event filter installed on the application.
This is a possible, but crude implementation, so, take it as it is and consider its consequences:
class ShortCutFilter(QObject):
triggered = pyqtSignal(QKeySequence)
def __init__(self, shortcuts=None):
super().__init__()
self.shortcuts = {}
def addShortcut(self, shortcut, slot=None):
if isinstance(shortcut, str):
shortcut = QKeySequence(shortcut)
slots = self.shortcuts.get(shortcut)
if not slots:
self.shortcuts[shortcut] = slots = []
if slot is not None:
slots.append(slot)
return shortcut
def eventFilter(self, obj, event):
if event.type() == event.KeyPress:
keyCode = event.key()
mods = event.modifiers()
if mods & Qt.ShiftModifier:
keyCode += Qt.SHIFT
if mods & Qt.MetaModifier:
keyCode += Qt.META
if mods & Qt.ControlModifier:
keyCode += Qt.CTRL
if mods & Qt.ALT:
keyCode += Qt.ALT
for sc, slots in self.shortcuts.items():
if sc == QKeySequence(keyCode):
self.triggered.emit(sc)
for slot in slots:
try:
slot()
except Exception as e:
print(type(e), e)
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
shortcutFilter = ShortCutFilter()
app.installEventFilter(shortcutFilter)
shortcutFilter.addShortcut('alt+b', lambda:
QMessageBox.information(None, 'Hello', 'How are you'))
shortcutFilter.triggered.connect(lambda sc:
print('triggered', sc.toString())
ex = Mainclass()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This, obviously, means that any key press event will pass through the known python bottleneck. A better solution would be to create global QActions and addAction() to any possible top level window that could accept it.
While this approach might seem more complex, it has its benefits; for instance, you have more control on the context of the shortcut: in the case above, you could trigger Alt+B from any window, including the one shown after previously triggering it, which is clearly not a good thing.
Add the layout in main widget.
Then pass the layout to the function where you are creating labels and add them to layout.
Below is the modified code.
Find the details as comments in below code.
Your main window class
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
#CREATE THE LAYOUT HERE IN MAIN WINDOW
vbox = QVBoxLayout()
x = Create_workingDict("Accounts","Alt+A",vbox) #PASS THE LAYOUT OBJECT (vbox) AS AN ARGUMENT
y = Create_workingDict("Inventory", "Ctrl+B",vbox) #PASS THE LAYOUT OBJECT (vbox) AS AN ARGUMENT
#SET THE LAYOUT TO MAIN WINDOW
self.setLayout(vbox)
Your function where labels created.
Observe that the functions vbox = QVBoxLayout() and self.setLayout(vbox) are moved out of this function to main window.
class Create_workingDict(QWidget):
def __init__(self,lblname,lblscut,vbox): #RECEIVE LAYOUT (vbox) ARGUMENT
super(). __init__()
self.lblname = lblname
self.lblscut = lblscut
lbl_1 = QLabel()
lbl_1.setText(self.lblname)
lbl_2 = QLabel()
lbl_2.setText(self.lblscut)
vbox.addWidget(lbl_1)
vbox.addWidget(lbl_2)
self.msgSc = QShortcut(QKeySequence(f'{self.lblscut}'), self)
self.msgSc.activated.connect(lambda: QMessageBox.information(self,'Message', 'Ctrl + M initiated'))
I am trying to make the user not switch to the next TAB where "Form 2" is located until they fill in Form 1.
I tried the "currentChange" event but it doesn't work the way I want as it shows the alert when it was already changed from TAB.
Is there a way to leave the current TAB fixed until the task is complete?
I attach the code and an image
import sys
from PyQt5.QtCore import Qt
from PyQt5 import QtWidgets
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.setGeometry(0, 0, 800, 500)
self.setLayout(QtWidgets.QVBoxLayout())
#flag to not show the alert when starting the program
self.flag = True
#changes to True when the form is completed
self.form_completed = False
#WIDGET TAB 1
self.widget_form1 = QtWidgets.QWidget()
self.widget_form1.setLayout(QtWidgets.QVBoxLayout())
self.widget_form1.layout().setAlignment(Qt.AlignHCenter)
label_form1 = QtWidgets.QLabel("FORM 1")
self.widget_form1.layout().addWidget(label_form1)
#WIDGET TAB 2
self.widget_form2 = QtWidgets.QWidget()
self.widget_form2.setLayout(QtWidgets.QVBoxLayout())
self.widget_form2.layout().setAlignment(Qt.AlignHCenter)
label_form2 = QtWidgets.QLabel("FORM 2")
self.widget_form2.layout().addWidget(label_form2)
#QTABWIDGET
self.tab_widget = QtWidgets.QTabWidget()
self.tab_widget.currentChanged.connect(self.changed)
self.tab_widget.addTab(self.widget_form1,"Form 1")
self.tab_widget.addTab(self.widget_form2, "Form 2")
self.layout().addWidget(self.tab_widget)
def changed(self,index):
if self.flag:
self.flag = False
return
if not self.form_completed:
QtWidgets.QMessageBox.about(self, "Warning", "You must complete the form")
return
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = MyWidget()
mw.show()
sys.exit(app.exec_())
The currentChanged signal is emitted when the index is already changed (the verb is in past tense: Changed), so if you want to prevent the change, you have to detect any user attempt to switch tab.
In order to do so, you must check both mouse and keyboard events:
left mouse clicks on the tab bar;
Ctrl+Tab and Ctrl+Shift+Tab on the tab widget;
Since you have to control that behavior from the main window, the only solution is to install an event filter on both the tab widget and its tabBar(), then if the action would change the index but the form is not completed, you must return True so that the event won't be handled by the widget.
Please consider that the following assumes that the tab that has to be kept active is the current (the first added tab, or the one set using setCurrentIndex()).
class MyWidget(QtWidgets.QWidget):
def __init__(self):
# ...
self.tab_widget.installEventFilter(self)
self.tab_widget.tabBar().installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == event.KeyPress and \
event.key() in (Qt.Key_Left, Qt.Key_Right):
return not self.form_completed
elif source == self.tab_widget.tabBar() and \
event.type() == event.MouseButtonPress and \
event.button() == Qt.LeftButton:
tab = self.tab_widget.tabBar().tabAt(event.pos())
if tab >= 0 and tab != self.tab_widget.currentIndex():
return self.isInvalid()
elif source == self.tab_widget and \
event.type() == event.KeyPress and \
event.key() in (Qt.Key_Tab, Qt.Key_Backtab) and \
event.modifiers() & Qt.ControlModifier:
return self.isInvalid()
return super().eventFilter(source, event)
def isInvalid(self):
if not self.form_completed:
QTimer.singleShot(0, lambda: QtWidgets.QMessageBox.about(
self, "Warning", "You must complete the form"))
return True
return False
Note that I showed the message box using a QTimer in order to properly return the event filter immediately.
Also consider that it's good practice to connect signals at the end of an object creation and configuration, and this is much more important for signals that notify property changes: you should not connect it before setting the property that could trigger it.
Since an empty QTabWidget has a -1 index, as soon as you add the first tab the index is changed to 0, thus triggering the signal. Just move the currentChanged signal connection after adding the tabs, and you can get rid of the self.flag check.
I am writing code for my collage project in pyqt5 where I need to make one help tab. I am planning to make help content-wise as most the software have as shown in the below image(the help of onlyoffice). Is there any way to write it easily?
The problem with that kind of interface, which shows multiple "tabs" embedded in the title bar, is that it's not easily doable with Qt, and you should implement the whole title bar by hand, which is not easy.
If you're looking for a simpler solution, I'd suggest to use a QTabWidget that doesn't show the tab bar if there's only one tab. If you're not already using a tabbed interface with closable tabs, you can set the tab widget to allow closable tabs and override the default methods in order to hide the close button if not really required.
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setDocumentMode(True)
self.setTabsClosable(True)
self.tabCloseRequested.connect(self.removeTab)
self.tabBar().hide()
def addTab(self, *args, **kwargs):
self.insertTab(-1, *args, **kwargs)
def insertTab(self, *args, **kwargs):
super().insertTab(*args)
closable = kwargs.get('closable', False)
if not closable:
index = args[0]
if index < 0:
index = self.count() - 1
for side in QtWidgets.QTabBar.LeftSide, QtWidgets.QTabBar.RightSide:
widget = self.tabBar().tabButton(index, side)
if isinstance(widget, QtWidgets.QAbstractButton):
self.tabBar().setTabButton(index, side, None)
break
self.tabBar().setVisible(self.count() > 1)
def removeTab(self, index):
super().removeTab(index)
self.tabBar().setVisible(self.count() > 1)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.tabWidget = TabWidget()
self.setCentralWidget(self.tabWidget)
self.main = QtWidgets.QWidget()
self.tabWidget.addTab(self.main, 'My program')
layout = QtWidgets.QGridLayout(self.main)
someButton = QtWidgets.QPushButton('Some button')
layout.addWidget(someButton, 0, 0)
layout.addWidget(QtWidgets.QLabel('Some label'), 0, 1)
helpButton = QtWidgets.QPushButton('Show help!')
layout.addWidget(helpButton, 0, 2)
textEdit = QtWidgets.QTextEdit()
layout.addWidget(textEdit, 1, 0, 1, 3)
self.helpTab = QtWidgets.QTextBrowser()
self.helpTab.setHtml('Hello, this is <b>help</b>!')
helpButton.clicked.connect(self.showHelp)
def showHelp(self):
for i in range(self.tabWidget.count()):
if self.tabWidget.widget(i) == self.helpTab:
break
else:
self.tabWidget.addTab(self.helpTab, 'Help!', closable=True)
self.tabWidget.setCurrentWidget(self.helpTab)
self.tabWidget.tabBar().show()
Now, since you also want some context-based help, you could hack your way through the whatsThis() feature. The "what's this" feature allows to show some context-based help in a small overlayed window when the window is in the "what's this mode" and the user clicks on a widget. We can use an event filter to detect when the user clicks on a widget and use the whatsThis() property as a context for showing the related help.
In the following example I'm using a simple dictionary that fills the QTextBrowser, but you can obviously use access to local documentation files or even the Qt Help framework.
Note that in order to use this approach, I had to install an event filter on all child widgets, and that's because Qt is able to react to "what's this" events if the widget actually has a whatsThis() property set. The trick is to set a whatsThis property for all child widgets when the window enters the what's this mode and install a specialized event filter on each of them, then uninstall the event filter as soon as the what's this mode is left.
NoWhatsThisText = '__NoWhatsThis'
NoWhatsThisValue = 'There is no help for this object'
HelpData = {
NoWhatsThisText: NoWhatsThisValue,
'someButton': 'Do something with the button',
'helpButton': 'Click the button to show this help',
'textEdit': 'Type <b>some text</b> to <i>read</i> it',
'mainWindow': 'A main window is cool!',
}
class WhatsThisWatcher(QtCore.QObject):
whatsThis = QtCore.pyqtSignal(str)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.WhatsThis:
whatsThis = source.whatsThis()
while whatsThis == NoWhatsThisText:
if not source.parent():
break
source = source.parent()
whatsThis = source.whatsThis()
self.whatsThis.emit(whatsThis)
event.accept()
return True
return super().eventFilter(source, event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
whatsThisAction = self.menuBar().addAction('What\'s this?')
whatsThisAction.triggered.connect(
QtWidgets.QWhatsThis.enterWhatsThisMode)
self.watchedWhatsThis = []
self.whatsThisWatcher = WhatsThisWatcher()
self.whatsThisWatcher.whatsThis.connect(self.showHelp)
self.installEventFilter(self.whatsThisWatcher)
someButton.setWhatsThis('someButton')
helpButton.setWhatsThis('helpButton')
textEdit.setWhatsThis('textEdit')
self.setWhatsThis('mainWindow')
def showHelp(self, context=''):
# ...
if context:
self.helpTab.setHtml(HelpData.get(context, NoWhatsThisValue))
if QtWidgets.QWhatsThis.inWhatsThisMode():
QtWidgets.QWhatsThis.leaveWhatsThisMode()
i have this function that deleates the last QLineEdit widget from the QGridLayout
it checks if the index of the widget is the last one and if the widget is a instance of QLineEdit
---> deleates the widget
def deleate_lastlineedit(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for index, widget in enumerate(widgets):
if index == (self.main_layout.count()-1) and isinstance(widget, (qtw.QLineEdit,qtw.QLabel)):
widget.deleteLater()
break
I have added a Qlabel widget to the same row and want that the function deleates the last Qlabel and Qlinedit widget at the same time after pushing a button, so far its deleates one at a time, need to click the button two times.
I tried to insert an counter so the iteration stops not at one iteration but at two iterrations so it gets the two widgets but didnt had an effekt.
also inserted two versions of the function
one that deleates the qline edit and the other that deleates the qlabel
and connected them to the same button but didnt work either
self.getlistof_button.clicked.connect(self.deleate_lastlineedit)
self.getlistof_button.clicked.connect(self.deleate_lastqlabel)
so how can I deleate the two widgets at the same time ?
fullcode
#!/usr/bin/env python
"""
Creates an linedit when button pushed
dleates last linedit
"""
import sys
import sqlite3
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import QtSql as qsql
from PyQt5 import sip
class AddWidget(qtw.QWidget):
'''
Interface
'''
# Attribut Signal
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# your code will go here
# interface
# position
qtRectangle = self.frameGeometry()
centerPoint = qtw.QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
# size
self.resize(700, 410)
# frame title
self.setWindowTitle("add Widget")
# heading
heading_label = qtw.QLabel('add Widget')
heading_label.setAlignment(qtc.Qt.AlignHCenter | qtc.Qt.AlignTop)
# add Button
self.addwidget_button = qtw.QPushButton("add Widget")
self.getlistof_button = qtw.QPushButton("deleate")
self.linedittext_button = qtw.QPushButton("linedit text")
self.main_layout = qtw.QGridLayout()
self.main_layout.addWidget(self.getlistof_button,0,0)
self.main_layout.addWidget(self.addwidget_button, 1, 0)
self.main_layout.addWidget(self.linedittext_button, 2, 0)
self.setLayout(self.main_layout)
self.show()
# functionality
self.addwidget_button.clicked.connect(self.add_widget)
self.getlistof_button.clicked.connect(self.deleate_lastlineedit)
self.getlistof_button.clicked.connect(self.deleate_lastqlabel)
self.linedittext_button.clicked.connect(self.count)
def count(self):
x = self.main_layout.rowCount()
print(self.main_layout.rowCount()+1)
print(type(x))
def add_widget(self):
my_lineedit = qtw.QLineEdit()
x1 = (self.main_layout.rowCount()+1)
my_dynmic_label = qtw.QLabel("Dynamic")
self.main_layout.addWidget(my_dynmic_label,x1,0)
self.main_layout.addWidget(my_lineedit,x1,1)
def deleate_lastqlabel(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for index, widget in enumerate(widgets):
if index == (self.main_layout.count()-1) and isinstance(widget, qtw.QLabel):
# print("yes")
widget.deleteLater()
break
def deleate_lastlineedit(self):
widgets = (self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count()))
for index, widget in enumerate(widgets):
if index == (self.main_layout.count()-1) and isinstance(widget, qtw.QLineEdit):
widget.deleteLater()
break
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = AddWidget()
sys.exit(app.exec_())
As the name suggests, deleteLater() deletes the object later.
Schedules this object for deletion.
The object will be deleted when control returns to the event loop.
If you add a print(self.main_layout.count()) after each deleteLater call in the cycle, you'll see that the count is still the same, and that's because the control is not yet returned to the event loop.
You should use layout.removeWidget() instead.
Besides that, it will not be enough anyway.
You are cycling through the whole list until you find the last element, but this means that the next-to-last will not be checked. A possible solution would be to do the for cycle twice, but doing it wouldn't be the smartest thing to do.
So, as I already suggested, you should use reversed().
Also, you need some form of control since you're going to remove two widgets, otherwise the cycle will break as soon as it finds the first match for isinstance.
def deleate_lastlineedit(self):
labelRemoved = editRemoved = False
widgets = [self.main_layout.itemAt(i).widget() for i in range(self.main_layout.count())]
for widget in reversed(widgets):
if isinstance(widget, qtw.QLineEdit):
editRemoved = True
elif isinstance(widget, qtw.QLabel):
labelRemoved = True
else:
continue
# in this case, removeWidget is not necessary, since we're not
# checking the count, but I'll leave it anyway for completeness;
self.main_layout.removeWidget(widget)
widget.deleteLater()
if editRemoved and labelRemoved:
break
Since you only need to remove the last widgets, creating a generator for the whole widgets is unnecessary. As long as you always insert only QLabels and QLineEdits at the end of the layout, you can just use a while loop.
def deleate_lastlineedit(self):
labelRemoved = editRemoved = False
while not (labelRemoved and editRemoved):
widget = self.main_layout.itemAt(self.main_layout.count() - 1).widget()
# now here removeWidget *IS* required, otherwise the while loop will
# never exit
self.main_layout.removeWidget(widget)
widget.deleteLater()
if isinstance(widget, qtw.QLineEdit):
editRemoved = True
elif isinstance(widget, qtw.QLabel):
labelRemoved = True
PS: I've already suggested you to better study Python's control flows, please follow my advice: this is your FOURTH question with almost the same issue, and I only answered because I wanted to clarify the deleteLater() problem, but you wouldn't even have needed to ask it, if you'd follow my previous suggestions and answers. Please, do study and practice, you can't expect to code a GUI if you don't even understand the most elementary basics of its language.
I have a handful of QPushButtons which play different .wav files either on mouse click or when a keyboard shortcut (single letter key) is pressed.
I would like to know if there is a way to recognise when the buttons are clicked in a particular sequence, and then play a different sound? The closest I have been able to get so far was using setShortcut to assign a sound to play when a particular key sequence is pressed, but this only works using keys which are not assigned as pushbutton shortcuts.
I am new to Python (and PySide) so I'm not sure whether this is even possible.
What you need is an event that is fired if and only if a sequence of other events has fired in between in the correct order. I don't know of any inbuilt framework in Qt that does it except for pressing sequences of keys. So you must build this for yourself. It's not that difficult, you can for example listen to each button then calling a method with a certain number (the position in the sequence of events you are interested in) then checking that you are having positions in this sequence in strictly increasing order (otherwise reseting) and if you arrive at a certain length firing your own event.
Example:
from functools import partial
from PySide import QtGui
class MyEvent():
def __init__(self):
self.last_level = 0
self.top_level = 3
def update(self, level):
if level == self.last_level + 1:
self.last_level += 1
if level == self.top_level:
print('beep')
self.last_level = 0
else:
if level == 1:
self.last_level = level
else:
self.last_level = 0
app = QtGui.QApplication([])
e = MyEvent()
w = QtGui.QWidget()
l = QtGui.QVBoxLayout(w)
b1 = QtGui.QPushButton('Button 1')
b1.clicked.connect(partial(e.update, 1))
l.addWidget(b1)
b2 = QtGui.QPushButton('Button 2')
b2.clicked.connect(partial(e.update, 3))
l.addWidget(b2)
b3 = QtGui.QPushButton('Button 3')
b3.clicked.connect(partial(e.update, 2))
l.addWidget(b3)
w.show()
app.exec_()
This prints "beep" if buttons 1, 3, 2 are pressed in this order.
Put all the buttons in a button-group, so that activating a button sends an identifier that can be recorded by a central signal handler. The identifiers can then be added together to form a sequence that is looked up in a dictionary of sound files.
Here's a simple demo:
from PySide import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QGridLayout(self)
self.buttonGroup = QtGui.QButtonGroup(self)
for column in range(3):
identifier = column + 1
button = QtGui.QPushButton('&%d' % identifier, self)
self.buttonGroup.addButton(button, identifier)
layout.addWidget(button, 0, column)
self.edit = QtGui.QLineEdit(self)
self.edit.setReadOnly(True)
layout.addWidget(self.edit, 1, 0, 1, 3)
self.buttonGroup.buttonClicked[int].connect(self.handleButtons)
self._sounds = {
'123': 'moo.wav', '132': 'bark.wav',
'213': 'meow.wav', '231': 'baa.wav',
'312': 'oink.wav', '321': 'quack.wav',
}
self._sequence = ''
def handleButtons(self, identifier):
self._sequence += str(identifier)
if len(self._sequence) == 3:
self.edit.setText(self._sounds.get(self._sequence, ''))
self._sequence = ''
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 100)
window.show()
sys.exit(app.exec_())