The below code creates a simple window with a date-picker, and a button which activates the rest of the code (not the most elegant yet, and I'm using global variables here temporarily so the "processing" script can access them, these will be removed soon)
The problem is, when I create an object of the MyWindow class, I can't seem to access it at all in the functions called by start_processing().
import calendar
import os
from datetime import date
from glob import glob
from time import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QComboBox, QPushButton, QLabel, QErrorMessage
def start_processing():
start = time()
files_found: tuple = file_search(month, year, r'O:\test\1 Originals')
for i in files_found:
process_file(i)
duration = time() - start
window1.set_text(f'Generation of files took {duration:.1f} seconds!')
class MyWindow(object):
def __init__(self):
self.app = QApplication([])
self.progress = QLabel('Foo')
self.title = 'Bar'
self.left = 10
self.top = 10
self.width = 300
self.height = 300
self.error_dialog = QErrorMessage()
self.setup_ui()
def setup_ui(self):
self.app.setStyle('Fusion')
window = QWidget()
layout = QVBoxLayout()
combobox_month = QComboBox()
combobox_year = QComboBox()
layout.addWidget(self.progress)
layout.addWidget(combobox_month)
layout.addWidget(combobox_year)
combobox_month.addItems(calendar.month_name[1:13])
combobox_year.addItems(['2017', '2018', '2019', '2020'])
combobox_year.setToolTip('Select the year')
combobox_month.setToolTip('Select the month')
combobox_month.setCurrentText('January')
combobox_year.setCurrentText('2019')
process_button = QPushButton('Process')
layout.addWidget(process_button)
window.setLayout(layout)
combobox_month.currentTextChanged.connect(self.on_combobox_month_changed)
combobox_year.currentTextChanged.connect(self.on_combobox_year_changed)
process_button.clicked.connect(self.on_button_clicked)
window.setGeometry(self.left, self.top, self.width, self.height)
window.setWindowTitle(self.title)
window.show()
self.app.exec_()
#staticmethod
def on_button_clicked():
start_processing()
#staticmethod
def on_combobox_month_changed(text):
global month
month_lookup = {v: k for k, v in enumerate(calendar.month_name)}
month = month_lookup[text]
#staticmethod
def on_combobox_year_changed(text):
global year
year = int(text)
def set_text(self, text):
self.progress.setText(text)
self.app.processEvents()
def error_message(self, text):
self.error_dialog.showMessage(text)
if __name__ == '__main__':
window1 = MyWindow()
If I then try window1.set_text('Blah'), within the function start_processing() I get a NameError: name 'window1' is not defined.
start_processing is defined in the main body of the script, as are all functions except the ones included in this class.
Not sure how much of the entire script I need to post, but happy to add anything else.
The statement window1 = MyWindow() will not actually assign a value to the variable window1 until after MyWindow() has completely finished executing. Before MyWindow() can finish executing, __init__() must finish executing. But first setup_ui must finish executing. But first exec_ must finish executing. But exec_ blocks indefinitely as long as the window remains open. So when the user clicks on a button and triggers on_button_clicked, then window1 will not yet have a value.
One possible solution is to move the exec_ call so it is not called directly or indirectly by __init__(). For instance, you could call it at the file-level scope, right after creating MyWindow().
if __name__ == '__main__':
window1 = MyWindow()
window1.app.exec_()
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'))
In my QMainWindow i have a button which opens a new QDialog in the bottom right monitorcorner with a successmessage when i click it.
Now, if i move the QMainWindow to another monitor (i have 3 monitor) and click the button the successmessage popup appears in the monitor where the QMainWindow was opened. What i want is that the popup message appears in the monitor where my QMainWindow actually is. So if i move the QMainWindow to Monitor 1 and click the button, the successpopup should opens in monitor 1. If the QMainWindow is in monitor 2, the successpopup should open in monitor 2 an same for monitor 3.
with
screenNumber = QDesktopWidget().screenNumber(self)
i can read the screennumber where the mainwindow is. and this works fine. Evertime i click the button i read out the screennumber. But i don't found a way, to set the screennumber to my notification.
Any ideas?
Edit:
maybe it helps if i show my notify class
notes.py
from UIs.UI_notify import Ui_Notification
from PyQt5.QtWidgets import QDialog, QApplication, QDesktopWidget
from PyQt5 import QtCore
from PyQt5.QtCore import QRect, QPropertyAnimation, QTimer
import sys
class icon():
checked = "check-circle"
alert = "times-circle"
question = "question-circle"
class notify(QDialog, Ui_Notification):
def __init__(self, parent=None):
super(notify,self).__init__(parent)
self.setupUi(self)
self.setWindowFlag(QtCore.Qt.WindowType.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground)
## Some helping stuff
############################################################
parent_sSize = QDesktopWidget().screenGeometry(parent)
parent_screenNumber = QDesktopWidget().screenNumber(parent)
sSize = QDesktopWidget().screenGeometry()
screenNumber = QDesktopWidget().screenNumber()
print("notification ScreenNumber = " + str(screenNumber))
print(sSize.width())
print(sSize.height())
print("Parents ScreenNumber = " + str(parent_screenNumber))
print(parent_sSize.width())
print(parent_sSize.height())
self.Note_Exit.clicked.connect(self.close)
## ScreenSize from parent
############################################################
self.hidedPos = QRect(parent_sSize.width()-self.width()-10,
parent_sSize.height()-self.height()+200,
self.frameGeometry().width(),
self.frameGeometry().height())
self.showPos = QRect(parent_sSize.width()-self.width()-10,
parent_sSize.height()-self.height()-50,
self.frameGeometry().width(),
self.frameGeometry().height())
def setNote(self, icon=icon.checked, headline="Headline", text="Text"):
self.icon = icon
self.headline = headline
self.text = text
self.noty_Label_Icon.setText(self.icon)
self.noty_Label_Headline.setText(self.headline)
self.noty_Label_Text.setText(self.text)
self.setGeometry(self.hidedPos)
self.anim = QPropertyAnimation(self,b"geometry")
self.anim.setDuration(700)
self.anim.setEasingCurve(QtCore.QEasingCurve.OutBack)
self.anim.setEndValue(self.showPos)
self.anim.start()
self.notyTimer = QTimer()
self.notyTimer.singleShot(4000,self.hideNote)
def hideNote(self):
self.anim = QPropertyAnimation(self,b"geometry")
self.anim.setDuration(700)
self.anim.setEasingCurve(QtCore.QEasingCurve.InOutBack)
self.anim.setEndValue(self.hidedPos)
self.anim.start()
self.anim.finished.connect(self.close)
if __name__ == "__main__":
notes = QApplication(sys.argv)
dialog = notify()
dialog.show()
sys.exit(notes.exec())
You cannot use the size of the widget during its construction, as at that moment it has a default size (640x480 for top level widgets, 100x30 for widgets created with a parent, including dialogs): the only reliable option is to use the sizeHint() or ensure that the layout has been properly activated with adjustSize().
Then, you don't need the screen to get the target position, as the parent geometry will suffice, but you do need it for the start position, otherwise the dialog will "pop up" at an arbitrary point below the window. Note that QDesktopWidget is considered obsolete, and you should use QScreen instead.
Finally, since you might want to reuse the notification, the start position should be set when the popup is actually being shown, not before. The same goes for the position when hiding (in case the notification could be moved).
class Notify(QDialog, Ui_Notification):
def __init__(self, parent):
super().__init__(parent)
self.setupUi(self)
self.setWindowFlag(QtCore.Qt.WindowType.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.showAnim = QPropertyAnimation(self, b'geometry')
self.showAnim.setDuration(700)
self.showAnim.setEasingCurve(QtCore.QEasingCurve.OutBack)
self.hideAnim = QPropertyAnimation(self, b'geometry')
self.hideAnim.setDuration(700)
self.hideAnim.setEasingCurve(QtCore.QEasingCurve.InOutBack)
self.hideTimer = QTimer(self, singleShot=True)
self.hideTimer.setInterval(4000)
self.hideTimer.timeout.connect(self.hideNote)
self.showAnim.finished.connect(self.hideTimer.start)
self.hideAnim.finished.connect(self.close)
def setNote(self, icon=icon.checked, headline="Headline", text="Text"):
self.icon = icon
self.headline = headline
self.text = text
self.noty_Label_Icon.setText(self.icon)
self.noty_Label_Headline.setText(self.headline)
self.noty_Label_Text.setText(self.text)
self.adjustSize() # important!
endRect = self.rect()
center = self.parent().geometry().center()
endRect.moveCenter(center)
screen = QApplication.screenAt(center)
startRect = QRect(endRect)
startRect.moveTop(screen.geometry().bottom())
self.setGeometry(startRect)
self.showAnim.setStartValue(startRect)
self.showAnim.setEndValue(endRect)
self.showAnim.start()
self.show()
def hideNote(self):
rect = self.geometry()
self.hideAnim.setStartValue(rect)
screen = QApplication.screenAt(rect.center())
rect.moveTop(screen.geometry().bottom())
self.hideAnim.setEndValue(rect)
self.hideAnim.start()
if __name__ == "__main__":
notes = QApplication(sys.argv)
notes.setStyle('fusion')
w = QMainWindow()
b = QPushButton('click!')
w.setCentralWidget(b)
w.show()
notify = Notify(w)
b.clicked.connect(lambda: notify.setNote())
sys.exit(notes.exec())
Be aware that if you're going to create the popup every time, you should also set the WA_DeleteOnClose attribute in order to destroy it when closed, otherwise it will be kept in memory.
Note: QTimer.singleShot() is a static function, creating an instance of QTimer to use it is pointless, as that instance won't be used and a new QTimer would be created anyway.
Thank you.
In the meantime i found a solution which works for me.
Look a little dirty but works.
could you tell me, whats the difference between my code an yours?
Why should i use your code, except the fact that you are a better programmer ;-)
I set the WA_DeleteOnClose Attribute.
Good to know that. Thanks
notes.py
from UIs.UI_notify import Ui_Notification
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5 import QtCore
from PyQt5.QtCore import QRect, QPropertyAnimation, QTimer
import sys
class icon():
checked = "check-circle"
alert = "times-circle"
question = "question-circle"
clock = "clock"
class notify(QDialog, Ui_Notification):
def __init__(self, parent=None):
super(notify,self).__init__(parent)
self.setupUi(self)
self.setWindowFlag(QtCore.Qt.WindowType.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.setWindowModality(QtCore.Qt.NonModal)
self.Note_Exit.clicked.connect(self.close)
self.parent_h = self.parent().geometry().height()
self.parent_w = self.parent().geometry().width()
self.parent_x = self.parent().geometry().x()
self.parent_y = self.parent().geometry().y()
self.dialog_w = self.width()
self.dialog_h = self.height()
self.setGeometry(self.parent_x+self.parent_w-self.dialog_w-10, self.parent_y+self.parent_h-self.dialog_h+120, self.dialog_w, self.dialog_h)
## ScreenSize from parent
############################################################
def setNote(self, icon=icon.checked, headline="Headline", text="Text"):
self.icon = icon
self.headline = headline
self.text = text
self.noty_Label_Icon.setText(self.icon)
self.noty_Label_Headline.setText(self.headline)
self.noty_Label_Text.setText(self.text)
self.anim = QPropertyAnimation(self,b"geometry")
self.anim.setDuration(700)
self.anim.setEasingCurve(QtCore.QEasingCurve.OutBack)
self.anim.setEndValue(QRect(self.parent_x+self.parent_w-self.dialog_w-10, self.parent_y+self.parent_h-self.dialog_h-20, self.dialog_w, self.dialog_h))
self.anim.start()
self.notyTimer = QTimer()
self.notyTimer.singleShot(4000,self.hideNote)
def hideNote(self):
self.anim = QPropertyAnimation(self,b"geometry")
self.anim.setDuration(700)
self.anim.setEasingCurve(QtCore.QEasingCurve.InOutBack)
self.anim.setEndValue(QRect(self.parent_x+self.parent_w-self.dialog_w-10, self.parent_y+self.parent_h-self.dialog_h+120, self.dialog_w, self.dialog_h))
self.anim.start()
self.anim.finished.connect(self.close)
if __name__ == "__main__":
notes = QApplication(sys.argv)
dialog = notify()
dialog.show()
sys.exit(notes.exec())
I have a QMainWindow, inside there is a QMenu, QLineEdit, and one QPushButton.
Every time I click the button, it plays a sound and then adds a text to the QLineEdit.
In my QMenu the user must be able to choose which sound plays by checking it.
I tried to achieve this by changing a variable self.s inside the MainWindow class every time a QAction is checked, meanwhile, the other QAction's are unchecked. So in my playsound() I just put the self.view.s as the argument.
But it seems that it's only reading the original self.view.s, which is the first sound. My signals to change self.view.s does not work. Also, the other QActions aren't unchecked as I wanted them to.
Below is my code:
import sys
from functools import partial
from playsound import playsound
from threading import Thread
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.buttons = {}
self.setWindowTitle("Try")
central_widget = QWidget()
self.setCentralWidget(central_widget)
self.lay = QVBoxLayout(central_widget)
self.lineedit()
button = {"HEY! ": (0, 0, 0, 0)}
page = QWidget()
layout = QGridLayout(page)
for btnText, pos in button.items():
self.buttons[btnText] = QPushButton(btnText)
layout.addWidget(self.buttons[btnText], *pos)
self.lay.addWidget(page)
self.music()
def music(self):
self.s = 'sound1.mp3'
self.x = 'sound1.mp3'
self.y = 'sound2.mp3'
self.z = 'disable.mp3'
def lineedit(self):
self.le = QLineEdit()
self.le.setFixedHeight(35)
self.lay.addWidget(self.le)
def set_lineedit(self, text):
self.le.setText(text)
self.le.setFocus()
def line(self):
return self.le.text()
class Menu:
def __init__(self, MainWindow):
super().__init__()
self.view = MainWindow
self.menuBar()
#self.actionSignals()
def menuBar(self):
self.menuBar = QMenuBar()
self.view.setMenuBar(self.menuBar)
self.menu = QMenu(self.menuBar)
self.menu.setTitle('Menu')
self.sounds = QMenu(self.menu)
self.sounds.setTitle('Select Sound')
self.sound1 = QAction(self.menuBar)
self.sound2 = QAction(self.menuBar)
self.disable = QAction(self.menuBar)
self.mute = QAction(self.menuBar)
self.mute.setText('Mute Background')
self.mute.setCheckable(True)
self.mute.setChecked(False)
self.sound1.setText('Sound 1')
self.sound1.setCheckable(True)
self.sound1.setChecked(True)
self.sound2.setText('Sound 2')
self.sound2.setCheckable(True)
self.sound2.setChecked(False)
self.disable.setText('Disable Sound')
self.disable.setCheckable(True)
self.disable.setChecked(False)
self.sounds.addAction(self.sound1)
self.sounds.addAction(self.sound2)
self.sounds.addAction(self.disable)
self.menuBar.addAction(self.menu.menuAction())
self.menu.addAction(self.mute)
self.menu.addAction(self.sounds.menuAction())
def menu_signals(self):
self.sound1.triggered.connect(self.sound_1)
self.sound2.triggered.connect(self.sound_2)
self.disable.triggered.connect(self.disabled)
def sound_1(self, checked):
if checked:
self.sound2.setChecked(False)
self.disable.setChecked(False)
self.view.s = self.view.x
else:
self.sound1.setChecked(True)
def sound_2(self, checked):
if checked:
self.sound1.setChecked(False)
self.disable.setChecked(False)
self.view.s = self.view.y
else:
self.sound2.setChecked(True)
def disabled(self, checked):
if checked:
self.sound2.setChecked(False)
self.sound1.setChecked(False)
self.view.s = self.view.z
else:
self.sound1.setChecked(True)
class Controller:
def __init__(self, MainWindow):
self.view = MainWindow
self.connectSignals()
def background(self):
while True:
playsound('background.mp3')
def playsound(self):
playsound(self.view.s, False)
def buildExpression(self, sub_exp):
expression = self.view.line() + sub_exp
self.view.set_lineedit(expression)
def connectSignals(self):
for btnText, btn in self.view.buttons.items():
self.view.buttons[btnText].clicked.connect(self.playsound)
self.view.buttons[btnText].clicked.connect(partial(self.buildExpression, btnText))
app = QApplication(sys.argv)
w = MainWindow()
x = Controller(w)
Thread(target = x.background, daemon = True).start()
m = Menu(w)
w.show()
app.exec()
I want to be able to change the value within playsound() depending on which QAction is checked in the Menu Bar. While one QAction is checked, the other QAction's should be unchecked.
This is where an action group comes into play. QActionGroup allows for mutually exclusive actions. It also provides convenient access to the selected action through the checkedAction method.
Create a QActionGroup object (e.g. self.soundGroup = QActionGroup(self))
Create your actions with the group as parent (e.g. self.sound1 = QAction(self.soundGroup))
For each of your actions, set their corresponding sound as their data, e.g. self.sound1.setData('sound1.mp3')
Ensure the action group is exclusive (I believe it's the default, but you may use self.soundGroup.setExclusive(True))
Use self.soundGroup.checkedAction() to get the checked action (selected sound) instead of self.view.s: playsound(self.soundGroup.checkedAction().data(), False)
You do not need any of your wiring between the actions and updates to self.view.s anymore. Just remove all of that.
I am trying to use a for loop to assign methods to the valueChanged signals of QScrollbars to make my code cleaner. However, this does not seem to work correctly. What am I doing wrong?
import sys
from PyQt5.QtWidgets import QScrollBar, QDialog, QVBoxLayout, QApplication
class MainWindow(QDialog):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout(self)
self.scrollbar1 = QScrollBar(self)
self.scrollbar2 = QScrollBar(self)
self.scrollbars = [self.scrollbar1, self.scrollbar2]
self.names = ['scrollbar 1', 'scrollbar 2']
self.layout.addWidget(self.scrollbar1)
self.layout.addWidget(self.scrollbar2)
for scrollbar, name in zip(self.scrollbars, self.names):
print(scrollbar, name)
scrollbar.valueChanged.connect(lambda: self.test(name))
# self.scrollbar1.valueChanged.connect(
# lambda: self.test(self.names[0])
# )
# self.scrollbar2.valueChanged.connect(
# lambda: self.test(self.names[1])
# )
self.show()
def test(self, scrollbar):
print(scrollbar)
if __name__ == '__main__':
app = QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
Assigning the methods "manually" works as expected, i.e. different names are passed on. When using the for loop however, for both scrollbars the same name is printed on a value change.
EDIT: Here is my snap_slider method
old:
def snap_slider(scrollbar):
x = np.modf(scrollbar.value() / scrollbar.singleStep())
if x[0] < 0.5:
scrollbar.setSliderPosition(
int(x[1] * scrollbar.singleStep()))
else:
scrollbar.setSliderPosition(
int((x[1]+1) * scrollbar.singleStep()))
new:
def snap_slider(self):
x = np.modf(self.sender().value() / self.sender().singleStep())
if x[0] < 0.5:
self.sender().setSliderPosition(
int(x[1] * self.sender().singleStep()))
else:
self.sender().setSliderPosition(
int((x[1] + 1) * self.sender().singleStep()))
A few things here, since you are trying to make your code cleaner:
You should prefer the use of the sender() method to a lambda function
It is a good practice to isolate the UI setup in a separate function that you could call elsewhere
Same thing about the show() method: avoid to put it in the __init__ method, since you may need to instanciate a MainWindow without showing it (eg: for testing purposes)
Which would give something like:
import sys
from PyQt5.QtWidgets import QScrollBar, QDialog, QVBoxLayout, QApplication
class MainWindow(QDialog):
def __init__(self):
super().__init__()
self.createWidgets()
def createWidgets(self):
self.layout = QVBoxLayout(self)
self.scrollbar1 = QScrollBar(self)
self.scrollbar2 = QScrollBar(self)
for widget in [self.scrollbar1, self.scrollbar2]:
widget.valueChanged.connect(self.test)
self.layout.addWidget(widget)
def test(self, event):
print(self.sender())
if __name__ == '__main__':
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
It seems I've got dangerously close to being able to link all the sub-processes initiated by subprocessing's Pool() to PyQt Progress Bar widgets. Here is the example code. Apparently there are few flows that stop my dream from happening.
The concept:
externalFunc() performs all the main tasks. From time to time it sends its 'progress values' to MainWindow() instance by storing its VALUE into poolDict variable declared with:
poolDict=manager.dict()
Meanwhile myEvenListener() method is running at the background awaiting for VALUE to be 'posted'. As soon as VALUE "received" myEvenListener() updates a ProgressBar to a received VALUE and resets it back to zero (to avoid the duplicate progressBar updates).
myEvenListener(self) before trying to update ProgressBar verifies the ProgressBar didn't yet reach its 100 maximum value before proceeding by using:
if pb.value()>=100: continue
Unfortunately even while externalFunc() supplies with more than enough values (160) the ProgressBar never reaches 100. Aside from it there is no way to exit MainWindow() while loop.
Please be carefull running this code before you read it since it may generate multi-python-processes on your machine that will have to be terminated.
Question:
Why does ProgressBar never reach its maximum 100
How to make sure myEvenListener() method is not running when it is not needed.
How to make sure all the sub-processes die after:
a. all the progressBars are at their 100%
b. user closes dialog box or terminates main process.
import sys, time
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
from multiprocessing import Pool
manager = mp.Manager()
poolDict=manager.dict()
class PbWidget(QtGui.QProgressBar):
def __init__(self, parent=None, total=20):
super(PbWidget, self).__init__()
self.setMinimum(1)
self.setMaximum(total)
self._active = False
def update_bar(self, to_add_number):
while True:
time.sleep(0.01)
value = self.value() + to_add_number
self.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or value >= self.maximum()):
break
self._active = False
def closeEvent(self, event):
self._active = False
def externalFunc(each):
for i in range(16):
print i
poolDict[each]=10+i
time.sleep(0.5)
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
# self.myList=[.1, .3, .5, 1.0, 1.5, 2.9, 3.1]
self.myList=[1]
self.main_layout = QtGui.QVBoxLayout()
self.pBars={}
self.state=True
for each in self.myList:
pb=PbWidget(total=101)
self.main_layout.addWidget(pb)
self.pBars[each]={'pb':pb, 'value':0, 'total_value':0}
ok_button = QtGui.QPushButton("OK")
ok_button.clicked.connect(self.OK)
self.main_layout.addWidget(ok_button)
central_widget = QtGui.QWidget()
central_widget.setLayout(self.main_layout)
self.setCentralWidget(central_widget)
def myEvenListener(self):
"""This function runs at the background as an infinite while loop. It constantly reads
a value stored in poolDict variable to which externalFunc() writes a new value every second.
The value represents a 'Progress' and needs to be passes to Progress Bar widget to which only MainWindow()
class has an access. After a Value was read it is used to update a Progress Bar.
After a progress bar was updated the Value is reset to zero.
The Problem: 'if pb.value()>=100' statement is used to make sure the function doesn't loop if the ProgressBar
is already reached 100. By some reason the bar never reaches its maximum 100 even while externalFunc() calls
enough times to reach this number. An ussue # 2: There is no meachanism to exit this while loop without adding
three more variables... Can it be achieved with what we already have?
"""
while self.state:
for each in self.pBars:
pb = self.pBars[each]['pb']
print "\n Current Bar value =", pb.value()
if pb.value()>=100:
print 'skipping'
continue
value=None
if each in poolDict.keys():
# read delivered value
delivered_value=poolDict[each]
# reset delivered value
poolDict[each]=0
# update bar with delivered value
if ( 101-pb.value() ) < delivered_value:
print "\n\t UPDATING WITH "
pb.update_bar( 101-pb.value() )
print "\n\t AFTER ", pb.value()
else:
pb.update_bar( delivered_value )
# print '\n\t\t\t Updating bar using this value', delivered_value
def OK(self):
pool = Pool(processes=3)
pool.map_async( externalFunc, self.myList)
self.myEvenListener()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(480, 320)
window.show()
sys.exit(app.exec_())
Here is a revised code. It seems to be working well and it is quite stable.
import sys, time
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
from multiprocessing import Pool
manager = mp.Manager()
poolDict=manager.dict()
class PbWidget(QtGui.QProgressBar):
def __init__(self, parent=None, total=20):
super(PbWidget, self).__init__()
self.setMinimum(1)
self.setMaximum(total)
self._active = False
def update_bar(self, to_add_number):
while True:
time.sleep(0.01)
value = self.value() + to_add_number
self.setValue(value)
QtGui.qApp.processEvents()
if (not self._active or value >= self.maximum()):
break
self._active = False
def closeEvent(self, event):
self._active = False
def externalFunc(each):
for i in range(16):
value =10+i
poolDict[each]=value
time.sleep(each)
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.myList=[.5, .8, 1.2, 1.8, 2.2, .3, .1]
self.main_layout = QtGui.QVBoxLayout()
self.pBars={}
self.state=True
self.name=None
for each in self.myList:
pb=PbWidget(total=101)
self.main_layout.addWidget(pb)
self.pBars[each]={'pb':pb, 'name':each}
ok_button = QtGui.QPushButton("Distribute")
ok_button.clicked.connect(self.OK)
self.main_layout.addWidget(ok_button)
central_widget = QtGui.QWidget()
central_widget.setLayout(self.main_layout)
self.setCentralWidget(central_widget)
def myEvenListener(self):
"""This function runs at the background as an infinite loop. It is constantly reading
a value stored in poolDict variable to which externalFunc() writes a new value.
The value represents a 'Progress' and needs to be passed to Progress Bar widget to which MainWindow()
class has an access. After a Value was read and used to update a Progress Bar it is reset to zero.
"""
entities = self.pBars.keys()
while self.state:
for each in entities:
if each not in self.pBars.keys(): continue
pb = self.pBars[each]['pb']
if pb.value()>=100:
self.pBars.pop(each, None)
value=None
if each in poolDict.keys():
# read delivered value
delivered_value=poolDict[each]
# reset delivered value
poolDict[each]=0
# update bar with delivered value
if ( 101-pb.value() ) < delivered_value:
pb.update_bar( 101-pb.value() )
elif delivered_value>0:
pb.update_bar( delivered_value )
if len(self.pBars.keys())==0:
self.state=False
def OK(self):
pool = Pool(processes=3)
pool.map_async( externalFunc, self.myList)
self.myEvenListener()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(480, 320)
window.show()
sys.exit(app.exec_())