PyQt5 QTabWidget showing currentWidget() "NoneType" - python

I'm building a simple browser with python, PyQt5 QWebEnjineView.
I want to make the reload button to be hidden when the page is loading and "stop loading" button visible,
When loading is finished, then reload button will be visible again and "stop loading" button will be hidden.
My QWebEnjineView is in a method called add_new_tab and I've defined QTabWidget as self.tabs in the init method.
import os
import sys
from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import QWebEngineView
class mainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(mainWindow, self).__init__()
# create tabs
self.tabs = QTabWidget()
self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick)
self.tabs.setTabsClosable(True)
self.tabs.tabCloseRequested.connect(self.close_current_tab)
self.tabs.currentWidget().loadProgress.connect(self.loadProgressHandler)
self.tabs.currentWidget().loadFinished.connect(self.loadFinishedHandler)
self.setCentralWidget(self.tabs)
# self.setCentralWidget(self.browser)
self.showMaximized()
# nav bar
self.navbar = QToolBar()
self.navbar.setMovable(False)
self.addToolBar(self.navbar)
# Refresh button
self.reload_butn = QPushButton(self, text="Reload")
self.reload_butn.clicked.connect(self.reload_tab)
# Set reload button visible
self.reload_butn.setHidden(False)
# Stop button
self.stop_btn = QPushButton(self, text="Stop")
self.stop_btn.clicked.connect(self.stop_loading_tab)
self.stop_btn.setHidden(True)
# Set stop_butn hidden initially
self.stop_btn.setHidden(True)
# Add Refresh and Stop button
self.navbar.addWidget(self.stop_btn)
self.navbar.addWidget(self.reload_butn)
# Add Address bar
self.url_bar = QLineEdit()
self.url_bar.returnPressed.connect(self.navigate_to_url)
self.navbar.addWidget(self.url_bar)
# on startup
self.add_new_tab(QUrl("https://www.google.com/"), "Homepage")
self.show()
#QtCore.pyqtSlot(int)
def loadProgressHandler(self, prog):
self.stop_btn.setHidden(False) # When any page is loading, then stop_butn will visible
self.reload_butn.setHidden(True) # When any page is loading, then reload_butn will hidde
#QtCore.pyqtSlot()
def loadFinishedHandler(self):
self.reload_butn.setHidden(False) # When loading is finished, then reload_butn will be visible again for the user
self.stop_btn.setHidden(True) # When load finished, stop button will be hidden
# reload tab
def reload_tab(self):
self.tabs.currentWidget().reload()
def stop_loading_tab(self):
self.tabs.currentWidget().stop()
def close_current_tab(self, i):
if self.tabs.count() < 2 :
return
self.tabs.removeTab(i)
# stop load current tab
def stop_loading_tab(self):
self.tabs.currentWidget().stop()
# doubleclick on empty space for new tab
def tab_open_doubleclick(self, i):
if i == -1: # No tab under the click
self.add_new_tab(QUrl("http://www.google.com/"), label="New tab")
# function to add new tab
def add_new_tab(self, qurl=None, label="Blank"):
if qurl is None:
qurl = QUrl('https://www.google.com/')
browser = QWebEngineView()
browser.setUrl(qurl)
i = self.tabs.addTab(browser, label)
self.tabs.setCurrentIndex(i)
def navigate_to_url(self):
self.tabs.currentWidget().setUrl(QUrl(self.url_bar.text()))
app = QApplication(sys.argv)
app.setApplicationName("browser")
window = mainWindow()
app.exec_()
I have some button for reload, back, home etc. where I called self.tabs.currentWidget().reload() for example in the reload method,
But when I'm adding self.tabs.currentWidget().loadProgress.connect(self.loadProgressHandler)
for the operation, then It's giving me a error
self.tabs.currentWidget().loadProgress.connect(self.loadProgressHandler)
AttributeError: 'NoneType' object has no attribute 'loadProgress'
Can anyone tell me why is it showing that the self.tabs.currentWidget() is NoneType?
Is there a way to fix it? Ask me if you need more details
Thank you!

The cause of the error is:
self.tabs.currentWidget().loadProgress.connect(self.loadProgressHandler)
self.tabs.currentWidget().loadFinished.connect(self.loadFinishedHandler)
Where the OP is assuming that the connection will occur with all the pages, and that is incorrect since it will only occur with the current widget which in that case is None causing the error.
In this case the solution is to connect each QWebEngineView created and check in the slots if the sender() matches the currentWidget().
remove
self.tabs.currentWidget().loadProgress.connect(self.loadProgressHandler)
self.tabs.currentWidget().loadFinished.connect(self.loadFinishedHandler)
add connection:
def add_new_tab(self, qurl=None, label="Blank"):
if qurl is None:
qurl = QUrl('https://www.google.com/')
browser = QWebEngineView()
browser.loadProgress.connect(self.loadProgressHandler)
browser.loadFinished.connect(self.loadFinishedHandler)
i = self.tabs.addTab(browser, label)
self.tabs.setCurrentIndex(i)
browser.load(qurl)
Validate:
#QtCore.pyqtSlot(int)
def loadProgressHandler(self, prog):
if self.tabs.currentWidget() is not self.sender():
return
self.stop_btn.show()
self.reload_butn.hide()
#QtCore.pyqtSlot()
def loadFinishedHandler(self):
if self.tabs.currentWidget() is not self.sender():
return
self.reload_butn.show()
self.stop_btn.hide()
Update:
There are the following errors:
The visibility of the widgets added to the QToolBar are managed using the associated QActions.
Instead of managing 2 slots associated with the progress and completion of loading, only one of them should be used since, for example, the associated slot is also called when it is loaded at 100% so it could be hidden since it can be invoked together with finished.
It is better to verify that the variables that can be None to avoid exceptions.
Considering the above, the solution is:
import sys
from PyQt5.QtCore import pyqtSlot, QUrl
from PyQt5.QtWidgets import (
QApplication,
QLineEdit,
QMainWindow,
QPushButton,
QTabWidget,
QToolBar,
)
from PyQt5.QtWebEngineWidgets import QWebEngineView
class mainWindow(QMainWindow):
def __init__(self, parent=None):
super(mainWindow, self).__init__(parent)
self.tabs = QTabWidget(tabsClosable=True)
self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick)
self.tabs.tabCloseRequested.connect(self.close_current_tab)
self.navbar = QToolBar(movable=True)
self.addToolBar(self.navbar)
self.reload_butn = QPushButton(self, text="Reload")
self.reload_butn.clicked.connect(self.reload_tab)
self.stop_btn = QPushButton(self, text="Stop")
self.stop_btn.clicked.connect(self.stop_loading_tab)
self.url_bar = QLineEdit()
self.url_bar.returnPressed.connect(self.navigate_to_url)
self.stop_action = self.navbar.addWidget(self.stop_btn)
self.reload_action = self.navbar.addWidget(self.reload_butn)
self.navbar.addWidget(self.url_bar)
self.stop_action.setVisible(False)
self.add_new_tab(QUrl("https://www.google.com/"), "Homepage")
self.setCentralWidget(self.tabs)
self.showMaximized()
#pyqtSlot(int)
def loadProgressHandler(self, prog):
if self.tabs.currentWidget() is not self.sender():
return
loading = prog < 100
self.stop_action.setVisible(loading)
self.reload_action.setVisible(not loading)
def reload_tab(self):
self.tabs.currentWidget().reload()
def stop_loading_tab(self):
self.tabs.currentWidget().stop()
def close_current_tab(self, i):
if self.tabs.count() < 2:
return
self.tabs.removeTab(i)
def stop_loading_tab(self):
if self.tabs.currentWidget() is None:
return
self.tabs.currentWidget().stop()
def tab_open_doubleclick(self, i):
if i == -1:
self.add_new_tab(QUrl("http://www.google.com/"), label="New tab")
def add_new_tab(self, qurl=None, label="Blank"):
if qurl is None:
qurl = QUrl("https://www.google.com/")
browser = QWebEngineView()
browser.loadProgress.connect(self.loadProgressHandler)
i = self.tabs.addTab(browser, label)
self.tabs.setCurrentIndex(i)
browser.load(qurl)
def navigate_to_url(self):
if self.tabs.currentWidget() is None:
return
self.tabs.currentWidget().load(QUrl.fromString(self.url_bar.text()))
def main():
app = QApplication(sys.argv)
app.setApplicationName("browser")
window = mainWindow()
app.exec_()
if __name__ == "__main__":
main()

Related

Open popup notification on the same monitor where the mainwindow is in python an pyqt5

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 want to change the sound that plays every time a button is pressed depending on a checked actiion in a QMenu

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.

My PyQt5 script is skipping a line inside a function

I want to make a simple browser GUI for learning purposes with PyQt5. A function that I want is to have in the status bar a text "Online". If the user clicks somewhere else from the browser and the application loses focus, a message will appear in the status bar indicating that, and after a few seconds the browser will change the url to google.
If I run the following code everything works fine as expected, when app loses focus it navigates to google. However, the message "Application lost focus ..." doesn't appear in the statusbar. It simply skips that line. If I remove the seturl and time.sleep line, the script will change the text as expected.
Why is it skipping that line? (Line 55)
import sys
import time
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox, QFrame ,QMainWindow, QLabel
from PyQt5.QtWebEngineWidgets import *
class VLine(QFrame):
# a simple VLine, like the one you get from designer
def __init__(self):
super(VLine, self).__init__()
self.setFrameShape(self.VLine|self.Sunken)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setFocus()
app.focusChanged.connect(self.on_focusChanged)
self.browser = QWebEngineView()
self.browser.setContextMenuPolicy(Qt.PreventContextMenu)
self.browser.setUrl(QUrl('http://stackoverflow.com'))
self.setCentralWidget(self.browser)
self.showMaximized()
self.date = QDate.currentDate()
font = QFont('Arial', 16, QFont.Bold)
self.statusBar().setFont(font)
timer = QTimer(self)
timer.timeout.connect(self.showTime)
timer.start(1000)
self.lbl1 = QLabel(self)
self.lbl1.setStyleSheet('border: 0; color: red;')
self.lbl1.setFont(font)
self.statusBar().reformat()
self.statusBar().setStyleSheet('border: 0; background-color: #FFF8DC;')
self.statusBar().setStyleSheet("QStatusBar::item {border: none;}")
self.statusBar().addPermanentWidget(VLine()) # <---
self.statusBar().addPermanentWidget(self.lbl1)
self.statusBar().addPermanentWidget(VLine())
def showTime(self):
current_time = QTime.currentTime()
label_time = current_time.toString('hh:mm:ss')
self.statusBar().showMessage('Time: ' + label_time + ' || Date: ' + self.date.toString('dd.MM.yyyy'))
def on_focusChanged(self):
if self.isActiveWindow() == False:
print(f"\nwindow is the active window: {self.isActiveWindow()}")
self.lbl1.setText('Application lost focus. Returning to Google in 5 seconds')
time.sleep(5)
self.browser.setUrl(QUrl('http://google.com'))
self.lbl1.setText('Online')
else:
print(f"window is the active window: {self.isActiveWindow()}")
self.lbl1.setText('Online')
app = QApplication(sys.argv)
QApplication.setApplicationName('Browser')
window = MainWindow()
app.exec_()
You are confusing how the focusChanged signal works: it emits a signal regarding the focus changes within the program.
What you need is to override the changeEvent of the window and intercept an ActivationChange event type.
class MainWindow(QMainWindow):
# ...
def changeEvent(self, event):
if event.type() == event.ActivationChange:
# ...
That said, NEVER put a blocking function within the main thread.
Remove the time.sleep and never think about using it again for this kind of things.
Add a function for that redirect, and create a QTimer that you can start when losing focus (and stop if regaining it again before the timeout).
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.focusLostTimer = QTimer(
interval=5000, singleShot=True, timeout=self.focusRedirect)
def focusRedirect(self):
self.browser.setUrl(QUrl('http://google.com'))
def changeEvent(self, event):
if event.type() == event.ActivationChange:
if not self.isActiveWindow():
self.focusLostTimer.start()
else:
self.focusLostTimer.stop()

PyQt5: Slot in separate file not being called

I currently have a basic GUI right now with each page in its own file. I can navigate to and from each page with no problem, but I'm having difficulty simply passing a search query to another Widget. Here's where I setup the connections in the main file:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
import search
import watching
import helpinfo
import results
class MainWindow(QMainWindow):
def __init__(self, parent=None):
'''
Constructor
'''
QMainWindow.__init__(self, parent)
self.centralWidget = QStackedWidget()
self.setCentralWidget(self.centralWidget)
self.startScreen = Start(self)
self.searchScreen = search.Search(self)
self.watchingScreen = watching.Watching(self)
self.helpInfoScreen = helpinfo.HelpInfo(self)
self.resultsScreen = results.Results(self)
self.centralWidget.addWidget(self.startScreen)
self.centralWidget.addWidget(self.searchScreen)
self.centralWidget.addWidget(self.watchingScreen)
self.centralWidget.addWidget(self.helpInfoScreen)
self.centralWidget.addWidget(self.resultsScreen)
self.centralWidget.setCurrentWidget(self.startScreen)
self.startScreen.searchClicked.connect(lambda: self.centralWidget.setCurrentWidget(self.searchScreen))
self.startScreen.watchingClicked.connect(lambda: self.centralWidget.setCurrentWidget(self.watchingScreen))
self.startScreen.helpInfoClicked.connect(lambda: self.centralWidget.setCurrentWidget(self.helpInfoScreen))
self.searchScreen.searchSubmitted.connect(lambda: self.centralWidget.setCurrentWidget(self.resultsScreen))
self.searchScreen.passQuery.connect(lambda: self.resultsScreen.grabSearch) #This is the problem line
self.searchScreen.clicked.connect(lambda: self.centralWidget.setCurrentWidget(self.startScreen))
self.watchingScreen.clicked.connect(lambda: self.centralWidget.setCurrentWidget(self.startScreen))
self.helpInfoScreen.clicked.connect(lambda: self.centralWidget.setCurrentWidget(self.startScreen))
self.resultsScreen.clicked.connect(lambda: self.centralWidget.setCurrentWidget(self.startScreen))
Here's the search file:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Search(QWidget):
clicked = pyqtSignal()
searchSubmitted = pyqtSignal()
passQuery = pyqtSignal(str)
def __init__(self, parent=None):
super(Search, self).__init__(parent)
logo = QLabel(self)
pixmap = QPixmap('res/logo.png')
logo.setPixmap(pixmap)
logo.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
logo.setAlignment(Qt.AlignCenter)
self.textbox = QLineEdit(self)
label = QLabel(text="This is the search page.")
label.setAlignment(Qt.AlignCenter)
button = QPushButton(text='Submit')
button.clicked.connect(lambda: self.submitSearch())
button2 = QPushButton(text='Go back.')
button2.clicked.connect(self.clicked.emit)
layout = QVBoxLayout()
layout.addWidget(logo)
layout.addWidget(label)
layout.addWidget(self.textbox)
layout.addWidget(button)
layout.addWidget(button2)
layout.setAlignment(Qt.AlignTop)
self.setLayout(layout)
def submitSearch(self):
self.searchSubmitted.emit()
self.passQuery.emit(self.textbox.text())
And here is the results file:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Results(QWidget):
clicked = pyqtSignal()
def __init__(self, parent=None):
super(Results, self).__init__(parent)
# Create Logo
logo = QLabel(self)
pixmap = QPixmap('res/logo.png')
logo.setPixmap(pixmap)
logo.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
logo.setAlignment(Qt.AlignCenter)
# Create page contents
label = QLabel(text="This is the results page. If you see this, it's still broken.")
label.setAlignment(Qt.AlignCenter)
button = QPushButton(text='Add to watching.')
button2 = QPushButton(text='Go back.')
button2.clicked.connect(self.clicked.emit)
# Set up layout
layout = QVBoxLayout()
layout.addWidget(logo)
layout.addWidget(label)
layout.addWidget(button)
layout.addWidget(button2)
layout.setAlignment(Qt.AlignTop)
self.setLayout(layout)
#pyqtSlot(str)
def grabSearch(self, str):
print(str)
self.label.setText(str)
The way I understand it, what I have right now should be working. When the user submits some text on the search page, it calls the submitSearch() function. That function emits two signals: the first, searchSubmitted, changes the screen to the results screen (this works as intended). The second, passQuery, should be passing the contents of the textbox to the connected function grabSearch() in the results file. However, the passQuery never seems to be caught by the results page despite being connected. I've verified with print statements that it is being emitted, but that's it.
What am I missing here?
Your code has the following errors:
If you are going to use a lambda to make the connection you must invoke the function with the arguments.
self.searchScreen.passQuery.connect(lambda text: self.resultsScreen.grabSearch(text))
But it is better to use the direct connection since the signatures are the same:
self.searchScreen.passQuery.connect(self.resultsScreen.grabSearch)
Another error is that the results.py label must be a member of the class:
self.label = QLabel(text="This is the results page. If you see this, it's still broken.") # <--
self.label.setAlignment(Qt.AlignCenter) # <--
# ..
# Set up layout
layout = QVBoxLayout()
layout.addWidget(logo)
layout.addWidget(self.label) # <--
And finally do not use reserved words like str, change to:
#pyqtSlot(str)
def grabSearch(self, text):
self.label.setText(text)

Update PyQt menu

I am trying to dynamically update a menu with new items when I add new items via the form into Qsettings. For example, if you open my code and click the button and then click "new" it will open a QLineEdit with a button. When the button is clicked the list data gets stored via Qsettings.
I'd like to be able to update the menu somehow to show the items without restarting. I tried some things like calling repaint and update in a few areas with no luck.
Here is my code, I slimmed it down the best that I could for the example.
import functools
import sys
from PyQt5 import QtCore
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, \
QVBoxLayout, QLineEdit,QApplication, QWidgetAction, QTextBrowser, QAction, QMenu
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QHBoxLayout()
self.menu_action = QAction(QIcon("icon.png"),"New", self)
self.menu_btn = QPushButton()
self.menu = MyMenu("Menu", self.menu_btn)
self.add_menu = self.menu.addMenu(QIcon("icon.png"), "Menu")
self.add_menu.addAction(self.menu_action)
self.menu_btn.setMenu(self.menu)
self.textBox = QTextBrowser(self)
action = QWidgetAction(self.menu_btn)
action.setDefaultWidget(self.textBox)
self.menu_btn.menu().addAction(action)
settings = QtCore.QSettings('test_org', 'my_app')
self.new_items = settings.value('new_item', [])
print('%s' % self.new_items)
for item in self.new_items:
self.create_action = QAction(QIcon("icon.png"), item[0], self)
self.create_action.setData(item)
self.add_menu.addAction(self.create_action)
self.create_action.triggered.connect(functools.partial(self.menu_clicked, self.create_action))
self.layout.addWidget(self.menu_btn)
self.setLayout(self.layout)
self.menu_action.triggered.connect(self.open_window)
def open_window(self):
self.create_menu_item = Create_Menu_Item()
self.create_menu_item.show()
def menu_clicked(self, item):
itmData = item.data()
print(itmData)
class Create_Menu_Item(QWidget):
def __init__(self, parent=None):
super(Create_Menu_Item, self).__init__(parent)
self.resize(200, 195)
self.layout = QVBoxLayout()
self.form = QLineEdit()
self.btn = QPushButton()
self.layout.addWidget(self.form)
self.layout.addWidget(self.btn)
self.btn.clicked.connect(self.save_new_item)
self.setLayout(self.layout)
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
class MyMenu(QMenu):
def event(self,event):
if event.type() == QtCore.QEvent.Show:
self.move(self.parent().mapToGlobal(QtCore.QPoint(0,0))-QtCore.QPoint(0,self.height()))
return super(MyMenu,self).event(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
Anyone have any ideas? Thanks.
You can create a for loop to access the list.
Bare in mind it is a list of lists so a nested for loop is neccessary
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
# Add new menu items..
for x in self.new_item:
for y in x:
print(y)
The above will keep adding the WHOLE list of items every time you add a new item..
To just add the newest item, this is all you need (the last item added to the list)
w.add_menu.addAction(self.new_item[0][-1])
so
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
#ADD last item in the list
w.add_menu.addAction(self.new_item[0][-1])

Categories