Message with rounded corners with Qt - python

I'm trying to create a widget like the message box at the bottom in this picture:
It should overlay the main widget. I have 2 ways to do this:
With a QFrame and rounded corners
With a mask
But there's a problem with each of them:
The QFrame approach seems like a better idea but the background isn't transparent. This means that even though the borders have a radius, the background is still making it a rectangle. It's somewhat noticeable in the picture. Unfortunately, this doesn't seem to work. Same for self.setStyleSheet("background:transparent").
The mask looks very pixelated, and it's not the expected behavior because the mask used can only be a simple QRegion. These don't have squircles, which would be ideal.
Here's what the code looks like:
class Message(QFrame):
"""
A temporary message to show information in the GUI.
"""
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
# Main layout
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
# The label
label = QLabel(msg)
label.setFont(Fonts.text)
label.setStyleSheet(f"color: {Colors.fg};"
"padding: 20px;")
self.layout.addWidget(label)
self.setStyleSheet(f"background-color: {Colors.bg};"
"border-radius: 30px;")
# region = QRegion(self.x(), self.y(), self.sizeHint().width(),
# self.sizeHint().height(), QRegion.Ellipse)
# self.setMask(region)
self.adjustSize()
Edit for S. Nick: Your solution only works if the Message widget is the only widget in the application. The intended usage is this:
class MainWindow(QWidget):
def __init__(self, player: QWidget):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(player)
msg = Message("I can't make this work", self)
where player is the main widget, and the message overlays it when it appears. Imagine it being an image, which is overlayed by the message. Sorry for not explaining myself correctly.

Try it:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Message(QWidget): #(QFrame): #
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) # <---
self.setAttribute(Qt.WA_TranslucentBackground) # <---
Colors_fg = "#fa0"
Colors_bg = "rgba( 155, 155, 155, 150)" # <---
# Main layout
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
# The label
label = QLabel(msg, alignment=Qt.AlignCenter)
label.setFont(QFont("Times", 17, QFont.Bold, italic=True)) #(Fonts.text)
label.setStyleSheet(f"color: {Colors_fg};"
"padding: 0px;")
self.layout.addWidget(label)
# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
self.setStyleSheet(f"background-color: {Colors_bg};"
"min-height: 70px;"
"max-height: 70px;"
"width: 200px;"
"border-radius: 30px;"
)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
self.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Message("Hello \nWorld")
w.resize(400, 200)
w.show()
sys.exit(app.exec_())
Update
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Message(QDialog): #(QFrame):
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
self.setWindowFlags(self.windowFlags() |
Qt.FramelessWindowHint |
Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.widget = QWidget(self)
self.widget.setObjectName('Custom_Widget')
layout = QVBoxLayout(self)
layout.addWidget(self.widget)
self.layout = QGridLayout(self.widget)
self.layout.addItem(QSpacerItem(
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 0)
self.layout.addWidget(QPushButton('r', self,
clicked=self.accept,
objectName='closeButton'), 0, 1)
# The label
label = QLabel(msg)
label.setFont(QFont("Times", 17, QFont.Bold, italic=True)) #((Fonts.text)
self.layout.addWidget(label, 2, 0, 5, 2, alignment=Qt.AlignCenter)
self.adjustSize()
def mousePressEvent(self, event):
self.old_Pos = event.globalPos()
self.old_width = self.width()
self.old_height = self.height()
def mouseMoveEvent(self, event):
if (event.buttons() == Qt.LeftButton):
delta = QPoint (event.globalPos() - self.old_Pos)
if (self.old_Pos.x() > self.x() + self.old_width - 20) or \
(self.old_Pos.y() > self.y() + self.old_height - 20):
w = self.old_width+delta.x() if self.old_width+delta.x() > 500 else 500
h = self.old_height+delta.y() if self.old_height+delta.y() > 400 else 400
self.setFixedSize(w, h)
else:
self.move(self.x() + delta.x(), self.y() + delta.y())
self.old_Pos = event.globalPos()
class MainWindow(QWidget):
def __init__(self, player: QWidget):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(player)
self.msg = Message("I can't make this work")#, self)
self.msg.show()
Stylesheet = """
#Custom_Widget {
background: rgba( 155, 155, 155, 150);
border-radius: 20px;
border: 2px solid #ff2025;
}
#closeButton {
min-width: 36px;
min-height: 36px;
font-family: "Webdings";
qproperty-text: "r";
border-radius: 10px;
}
#closeButton:hover {
color: #ccc;
background: red;
}
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(Stylesheet)
w = MainWindow(QPushButton("player"))
w.resize(400, 200)
w.show()
sys.exit(app.exec_())
Update 2
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Message(QFrame): # QDialog
def __init__(self, msg: str, *args, destroy_time: int = None):
super().__init__(*args)
# self.setWindowFlags(self.windowFlags() |
# Qt.FramelessWindowHint #|Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.widget = QWidget(self)
self.widget.setObjectName('Custom_Widget')
layout = QVBoxLayout(self)
layout.addWidget(self.widget)
self.layout = QGridLayout(self.widget)
self.layout.addItem(QSpacerItem(
40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 0)
self.layout.addWidget(QPushButton('r', self,
# clicked=self.accept,
clicked=self.close,
objectName='closeButton'), 0, 1)
# The label
label = QLabel(msg)
label.setFont(QFont("Times", 15, QFont.Bold, italic=True)) #((Fonts.text)
self.layout.addWidget(label, 2, 0, 5, 2, alignment=Qt.AlignCenter)
self.adjustSize()
class MainWindow(QWidget):
def __init__(self, player: QWidget):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(player)
player.clicked.connect(self._msg)
def _msg(self):
self.msg = Message("I can't make this work\nself.msg.setGeometry(10, 10, 480, 150)", self)
self.msg.setGeometry(10, 10, 480, 150)
self.msg.show()
Stylesheet = """
#Custom_Widget {
background: rgba( 155, 155, 155, 150);
border-radius: 20px;
border: 2px solid #ff2025;
}
#closeButton {
min-width: 36px;
min-height: 36px;
font-family: "Webdings";
qproperty-text: "r";
border-radius: 10px;
}
#closeButton:hover {
color: #ccc;
background: red;
}
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(Stylesheet)
w = MainWindow(QPushButton("player"))
w.resize(500, 400)
w.show()
sys.exit(app.exec_())

Related

Background disappearing when changing stylesheet after qthread

After making a few interfaces using the PySide6 library I keep having this issue where the custom background that I initialize turns black when calling a setStyleSheet statement after the program has gone through a qthread process.
This is how I initialize the background:
background = QPixmap("Data\\background.png")
palette = QPalette()
palette.setBrush(QPalette.Window, background)
self.show()
self.setPalette(palette)
And this is how I make the qthread:
class Signaller(QObject):
progress = Signal(int)
finished = Signal()
class Generate(QThread):
def __init__(self):
QThread.__init__(self)
self.signaller = Signaller()
def run(self):
self.signaller.progress.emit(0)
#do stuff
self.signaller.progress.emit(1)
self.signaller.finished.emit()
class Main(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setEnabled(False)
QApplication.processEvents()
self.progressBar = QProgressDialog("Generating...", None, 0, 1, self)
self.progressBar.setWindowTitle("Status")
self.progressBar.setWindowModality(Qt.WindowModal)
self.worker = Generate()
self.worker.signaller.progress.connect(self.set_progress)
self.worker.signaller.finished.connect(self.patch_finished)
self.worker.start()
def set_progress(self, progress):
self.progressBar.setValue(progress)
def patch_finished(self):
box = QMessageBox(self)
box.setWindowTitle("Done")
box.setText("Mod generated !")
box.exec()
self.setEnabled(True)
Yet calling this statement after the program's gone through the qthread at least once removes the background:
self.seed_field.setStyleSheet("color: #ffffff")
And it doesn't happen if the qthread hasn't triggered beforehand
Here is a fully working example:
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
import sys
class Signaller(QObject):
progress = Signal(int)
finished = Signal()
class Generate(QThread):
def __init__(self):
QThread.__init__(self)
self.signaller = Signaller()
def run(self):
self.signaller.progress.emit(0)
#do stuff
self.signaller.progress.emit(1)
self.signaller.finished.emit()
class Main(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setStyleSheet("QWidget{background:transparent; color: #ffffff; font-family: Cambria; font-size: 18px}"
+ "QMessageBox{background-color: #1d1d1d}"
+ "QDialog{background-color: #1d1d1d}"
+ "QProgressDialog{background-color: #1d1d1d}"
+ "QPushButton{background-color: #1d1d1d}"
+ "QSpinBox{background-color: #1d1d1d}"
+ "QLineEdit{background-color: #1d1d1d}"
+ "QLineEdit[text=\"\"]{color: #666666}"
+ "QMenu{background-color: #1d1d1d}"
+ "QToolTip{border: 0px; background-color: #1d1d1d; color: #ffffff; font-family: Cambria; font-size: 18px}")
grid = QGridLayout()
grid.setSpacing(10)
box_1_grid = QGridLayout()
self.box_1 = QGroupBox("Test")
self.box_1.setLayout(box_1_grid)
grid.addWidget(self.box_1, 0, 0, 1, 1)
button_1 = QPushButton("QThread")
button_1.clicked.connect(self.button_1_pressed)
box_1_grid.addWidget(button_1, 0, 0)
button_2 = QPushButton("StyleSheet")
button_2.clicked.connect(self.button_2_pressed)
box_1_grid.addWidget(button_2, 0, 1)
self.setLayout(grid)
self.setFixedSize(1280, 720)
background = QPixmap("background.png")
palette = QPalette()
palette.setBrush(QPalette.Window, background)
self.show()
self.setPalette(palette)
center = QScreen.availableGeometry(QApplication.primaryScreen()).center()
geo = self.frameGeometry()
geo.moveCenter(center)
self.move(geo.topLeft())
QApplication.processEvents()
def button_1_pressed(self):
self.setEnabled(False)
QApplication.processEvents()
self.progressBar = QProgressDialog("Generating...", None, 0, 1, self)
self.progressBar.setWindowTitle("Status")
self.progressBar.setWindowModality(Qt.WindowModal)
self.worker = Generate()
self.worker.signaller.progress.connect(self.set_progress)
self.worker.signaller.finished.connect(self.patch_finished)
self.worker.start()
def button_2_pressed(self):
self.box_1.setStyleSheet("color: #ffffff")
def set_progress(self, progress):
self.progressBar.setValue(progress)
def patch_finished(self):
box = QMessageBox(self)
box.setWindowTitle("Done")
box.setText("QThread done")
box.exec()
self.setEnabled(True)
def main():
app = QApplication(sys.argv)
main = Main()
sys.exit(app.exec())
if __name__ == '__main__':
main()
I had a similar problem, I solved it by setting the background not with a pixel, but with some_object.setStyleSheet(background: url("some/url")

Display splash-screen in PyQt5 during function execution

I have a program where I display the GUI with PyQt5. From my MainWindow I open a QWidget from which a function is started. While this function is running a SplashScreen should appear. As soon as the function is finished, the SplashScreen should close again. I have tried to start the SplashScreen from within the function. However, the SplashScreen does not appear.
I have used the following code:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QDialog, QProgressBar, QLabel, QFrame, QMainWindow, QVBoxLayout, QPushButton
from PyQt5.QtCore import Qt
import time
class Function:
def loading(self):
self.screen = SplashScreen()
self.screen.show()
time.sleep(10)
self.screen.close()
class SplashScreen(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('Spash Screen Example')
self.setFixedSize(1100, 500)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowFlag(Qt.WindowStaysOnTopHint)
self.initUI()
def initUI(self):
layout = QVBoxLayout()
self.setLayout(layout)
self.frame = QFrame()
layout.addWidget(self.frame)
self.labelTitle = QLabel(self.frame)
self.labelTitle.setObjectName('LabelTitle')
# center labels
self.labelTitle.resize(self.width() - 10, 150)
self.labelTitle.move(0, 40) # x, y
self.labelTitle.setText('Splash Screen')
self.labelTitle.setAlignment(Qt.AlignCenter)
self.labelDescription = QLabel(self.frame)
self.labelDescription.resize(self.width() - 10, 50)
self.labelDescription.move(0, self.labelTitle.height())
self.labelDescription.setObjectName('LabelDesc')
self.labelDescription.setText('<strong>Working on Task #1</strong>')
self.labelDescription.setAlignment(Qt.AlignCenter)
self.progressBar = QProgressBar(self.frame)
self.progressBar.resize(self.width() - 200 - 10, 50)
self.progressBar.move(100, self.labelDescription.y() + 130)
self.progressBar.setAlignment(Qt.AlignCenter)
self.progressBar.setFormat('%p%')
self.progressBar.setTextVisible(True)
self.progressBar.setRange(0, 100)
self.progressBar.setValue(20)
self.labelLoading = QLabel(self.frame)
self.labelLoading.resize(self.width() - 10, 50)
self.labelLoading.move(0, self.progressBar.y() + 70)
self.labelLoading.setObjectName('LabelLoading')
self.labelLoading.setAlignment(Qt.AlignCenter)
self.labelLoading.setText('loading...')
self.setStyleSheet('''
#LabelTitle {
font-size: 60px;
color: #93deed;
}
#LabelDesc {
font-size: 30px;
color: #c2ced1;
}
#LabelLoading {
font-size: 30px;
color: #e8e8eb;
}
QFrame {
background-color: #2F4454;
color: rgb(220, 220, 220);
}
QProgressBar {
background-color: #DA7B93;
color: rgb(200, 200, 200);
border-style: none;
border-radius: 10px;
text-align: center;
font-size: 30px;
}
QProgressBar::chunk {
border-radius: 10px;
background-color: qlineargradient(spread:pad x1:0, x2:1, y1:0.511364, y2:0.523, stop:0 #1C3334, stop:1 #376E6F);
}
''')
class Window(QWidget):
def __init__(self):
super().__init__()
layout_window = QVBoxLayout()
self.setLayout(layout_window)
self.button = QPushButton("Open", self)
layout_window.addWidget(self.button)
self.button.clicked.connect(self.open)
def open(self):
self.function = Function()
self.function.loading()
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.window_width, self.window_height = 1200, 800
self.setMinimumSize(self.window_width, self.window_height)
layout = QVBoxLayout()
self.setLayout(layout)
self.button = QPushButton("Open", self)
layout.addWidget(self.button)
self.button.clicked.connect(self.open)
def open(self):
self.window = Window()
self.window.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
myapp = MyApp()
myapp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
I have represented the function (in the original program some time consuming data processing takes place) with time.sleep. When I leave out the close-function, the 'SplashScreen' opens after the time.sleep is finished.
The time.sleep will block the event-loop, so any GUI-related operations will be suspended until it finishes. You therefore need to explicitly force processing of any pending events, using processEvents:
class Function:
def loading(self):
self.screen = SplashScreen()
self.screen.show()
for i in range(100):
self.screen.progressBar.setValue(i)
QApplication.processEvents()
time.sleep(0.1)
self.screen.close()
Or move the time-consuming task to a worker thread, and then use signals to update the GUI:
from PyQt5.QtCore import pyqtSignal, QThread
class Thread(QThread):
progressChanged = pyqtSignal(int)
def run(self):
for i in range(100):
QThread.msleep(100)
self.progressChanged.emit(i)
class Function:
def loading(self):
self.screen = SplashScreen()
self.screen.show()
self.thread = Thread()
self.thread.progressChanged.connect(
self.screen.progressBar.setValue)
self.thread.finished.connect(self.screen.close)
self.thread.start()
The latter approach is usually recommended, but if the task can't be broken up into discrete steps, you may need to use multiprocessing instead.

CEFPython unable to embed inside pyqt5 application

I am new to Cefpython and PyQt5 both. I have tried to follow the tutorial in the cefpython repository.
I was trying to embed cefpython inside a pyqt application and haven't achieved any success, what's wrong here?
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QWindow
from PyQt5.QtWidgets import *
from cefpython3 import cefpython as cef
from navbar import NavigationBar
class ChromiumApplication(QApplication):
def __init__(self):
super().__init__([])
self.timer = self.create_timer()
def create_timer(self):
timer = QTimer()
timer.timeout.connect(self.on_timeout)
timer.start(10)
return timer
def on_timeout(self):
cef.MessageLoopWork()
class ChromiumBrowserWindow(QMainWindow):
DEFAULT_TITLE = "Chromium Browser"
DEFAULT_WIDTH = 800
DEFAULT_HEIGHT = 600
def __init__(self):
super().__init__()
self.chrome = None
self.web_view = None
self.setWindowTitle(self.DEFAULT_TITLE)
self.init_window()
self.show()
def init_window(self):
self.resize(self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
self.web_view = WebViewWidget(parent=self)
self.chrome = NavigationBar(parent=self, browser=self.web_view.browser)
layout = QGridLayout()
layout.addWidget(self.chrome, 0, 0)
layout.setColumnStretch(0, 1)
layout.addWidget(self.web_view, 1, 0)
layout.setRowStretch(1, 2)
layout.setContentsMargins(0, 0, 0, 0)
frame = QFrame()
frame.setLayout(layout)
self.setCentralWidget(frame)
self.web_view.init_browser()
def closeEvent(self, event):
if self.web_view.browser is not None:
self.web_view.browser.CloseBrowser(True) # force=True
self.web_view.browser = None # required to close cleanly
class WebViewWidget(QWidget):
DEFAULT_URL = "https://www.google.com"
HANDLERS = []
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
self.browser = None
self.browser_window = None
self.timer = None
def init_browser(self):
self.browser_window = QWindow()
window_config = cef.WindowInfo()
rect_pos_and_size = [0, 0, self.width(), self.height()]
window_config.SetAsChild(self.get_window_handle(), rect_pos_and_size)
self.browser = cef.CreateBrowserSync(window_config, url=self.DEFAULT_URL)
self.set_handlers()
def get_window_handle(self):
return int(self.browser_window.winId())
def set_handlers(self):
for handler in self.HANDLERS:
self.browser.SetClientHanlder(handler(self))
if __name__ == "__main__":
sys.excepthook = cef.ExceptHook
cef.Initialize()
app = ChromiumApplication()
window = ChromiumBrowserWindow()
app.exec()
app.timer.stop()
cef.Shutdown()
I know the issue is with my code because the example provided by cefpython works perfectly on my machine. I have no clue what I did wrong here, any suggestions will help!
EDIT: (code for navigation bar)
from PyQt5.QtWidgets import (
QApplication,
QFrame,
QHBoxLayout,
QPushButton,
QLineEdit,
)
class NavigationBar(QFrame):
def __init__(self, parent=None, browser=None):
super().__init__()
self.parent = parent
self.browser = browser
self.back_btn = self.create_button("Back")
self.back_btn.clicked.connect(self.on_back)
self.forward_btn = self.create_button("Forward")
self.forward_btn.clicked.connect(self.on_forward)
self.refresh_btn = self.create_button("Refresh")
self.refresh_btn.clicked.connect(self.on_refresh)
self.url_bar = self.create_url_bar()
self.url_bar.returnPressed.connect(self.on_search)
self.frame_layout = QHBoxLayout()
self.init_layout()
def create_button(self, name):
button_icon_path = f"./{name}.svg"
button = QPushButton()
button.setStyleSheet(f"""
QPushButton {{
background-image: url("{button_icon_path}");
background-repeat: no-repeat;
background-position: center;
background-color: rgba(0, 0, 0, 0.1);
border: 10px;
border-radius: 8px;
padding: 10px;
}}
QPushButton:hover {{
background-color: rgba(0, 0, 0, 0.5);
}}
QPushButton:pressed {{
background-color: none;
}}
""")
return button
def create_url_bar(self):
search = QLineEdit()
search.setStyleSheet("""QLineEdit {
min-width: 300px;
padding: 10px;
margin-left: 50px;
margin-right: 30px;
border-width: 10px;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.2);
color: white;
}
QLineEdit:hover {
background-color: #454549;
}
""")
return search
def init_layout(self):
self.setStyleSheet("""
background: #2A292E;
max-height: 40px;
""")
self.frame_layout.addWidget(self.back_btn, 0)
self.frame_layout.addWidget(self.forward_btn, 0)
self.frame_layout.addWidget(self.refresh_btn, 0)
self.frame_layout.addWidget(self.url_bar, 1)
self.setLayout(self.frame_layout)
def on_back(self):
if self.browser is not None:
self.browser.GoBack()
def on_forward(self):
if self.browser is not None:
self.browser.GoForward()
def on_refresh(self):
if self.browser is not None:
self.browser.Reload()
def on_search(self):
if self.browser is not None:
url = self.url_bar.text()
self.browser.LoadUrl(url)
if __name__ == "__main__":
app = QApplication([])
nav = NavigationBar()
nav.show()
app.exec()
The problem is that the QWindow used to render the browser is hidden. The solution is to create a QWidget using QWidget::createWindowContainer() and add it using a layout.
import sys
from cefpython3 import cefpython as cef
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QWindow
from PyQt5.QtWidgets import QApplication, QFrame, QGridLayout, QMainWindow, QVBoxLayout, QWidget
from navbar import NavigationBar
class ChromiumApplication(QApplication):
def __init__(self):
super().__init__([])
self.timer = self.create_timer()
def create_timer(self):
timer = QTimer()
timer.timeout.connect(self.on_timeout)
timer.start(10)
return timer
def on_timeout(self):
cef.MessageLoopWork()
class ChromiumBrowserWindow(QMainWindow):
DEFAULT_TITLE = "Chromium Browser"
DEFAULT_WIDTH = 800
DEFAULT_HEIGHT = 600
def __init__(self):
super().__init__()
self.chrome = None
self.web_view = None
self.setWindowTitle(self.DEFAULT_TITLE)
self.init_window()
self.show()
def init_window(self):
self.resize(self.DEFAULT_WIDTH, self.DEFAULT_HEIGHT)
self.web_view = WebViewWidget()
self.chrome = NavigationBar(parent=self, browser=self.web_view.browser)
frame = QFrame()
self.setCentralWidget(frame)
layout = QGridLayout(frame)
layout.addWidget(self.chrome, 0, 0)
layout.setColumnStretch(0, 1)
layout.addWidget(self.web_view, 1, 0)
layout.setRowStretch(1, 2)
layout.setContentsMargins(0, 0, 0, 0)
def closeEvent(self, event):
if self.web_view.browser is not None:
self.web_view.browser.CloseBrowser(True)
del self.web_view.browser
class WebViewWidget(QWidget):
DEFAULT_URL = "https://www.google.com"
HANDLERS = []
def __init__(self, parent=None):
super().__init__(parent)
self._browser = None
self._browser_widget = None
lay = QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
self.init_browser()
#property
def browser(self):
return self._browser
#browser.deleter
def browser(self):
self._browser = None
def init_browser(self):
browser_window = QWindow()
window_config = cef.WindowInfo()
window_config.SetAsChild(
int(browser_window.winId()), list(self.rect().getRect())
)
self._browser = cef.CreateBrowserSync(window_config, url=self.DEFAULT_URL)
self._browser_widget = QWidget.createWindowContainer(browser_window)
self.layout().addWidget(self._browser_widget)
self.set_handlers()
def set_handlers(self):
for handler in self.HANDLERS:
self.browser.SetClientHanlder(handler(self))
def resizeEvent(self, event):
if self.browser and self._browser_widget:
self.browser.SetBounds(*self._browser_widget.geometry().getRect())
self.browser.NotifyMoveOrResizeStarted()
if __name__ == "__main__":
sys.excepthook = cef.ExceptHook
cef.Initialize()
app = ChromiumApplication()
window = ChromiumBrowserWindow()
app.exec()
app.timer.stop()
cef.Shutdown()

QRadioButton ignores QLabel geometry

my code
import sys
from PyQt5.QtWidgets import (QRadioButton, QHBoxLayout, QButtonGroup,
QApplication, QWidget, QLabel)
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import QSize, Qt
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
class Label(QLabel):
def __init__(self, parent=None):
super(Label, self).__init__(parent)
self.parent = parent
self._animation = QtCore.QVariantAnimation(
startValue=QtGui.QColor("blue"),
endValue=QtGui.QColor("green"),
valueChanged=self._on_value_changed,
duration=400,
)
self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
def _on_value_changed(self, color):
foreground = (
QtGui.QColor("black")
if self._animation.direction() == QtCore.QAbstractAnimation.Forward
else QtGui.QColor("yellow")
)
self._update_stylesheet(color, foreground)
def _update_stylesheet(self, background, foreground):
self.setStyleSheet(
"""
QLabel{
padding:10;
margin10;
background: %s;
color: %s;
}
"""
% (background.name(), foreground.name())
)
def enterEvent(self, event):
self._animation.setDirection(QtCore.QAbstractAnimation.Backward)
self._animation.start()
super().enterEvent(event)
def leaveEvent(self, event):
self._animation.setDirection(QtCore.QAbstractAnimation.Forward)
self._animation.start()
super().leaveEvent(event)
def mousePressEvent(self, event):
self.parent.click()
class Radio(QRadioButton):
def __init__(self, parent=None):
super(Radio, self).__init__(parent)
lay = QtWidgets.QGridLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(0, 0, 0, 0)
self.setText('0')
self.label = Label(self)
self.label.setText('test0098908uhjhjk9')
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
self.setStyleSheet('QRadioButton{background:red} QRadioButton::indicator{ text:rgba(0, 0, 0, 0); background:rgba(0, 0, 0, 0)}')
self.label.setSizePolicy(sizePolicy)
self.label.setStyleSheet('padding:10;margin10;background:green')
self.label.setAlignment(Qt.AlignCenter)
lay.addWidget(self.label, 0, 0, 1, 1)
print('radio-2 h - {}'.format(self.height()))
print('radio-2 w - {}'.format(self.width()))
print('label h -{}'.format(self.label.height()))
print('label w -{}'.format(self.label.width()))
self.setMinimumSize(QSize(140, 34))
self.toggled.connect(self.on_off)
def on_off(self):
if self.isChecked():
self.label.setText('<div>&#xe3434</div>')
else:
self.label.setText('<div>&#xe3456</div>')
class Window(QWidget):
def __init__(self):
super().__init__()
self._dictRB = {
'0': False,
'rb1': False,
'rb2': False,
'rb3': False,
}
self.main_layout = QHBoxLayout(self)
self.buttonGroup = QButtonGroup()
self.attr_layout = QHBoxLayout()
self.main_layout.addLayout(self.attr_layout)
self.rb0 = Radio() #QRadioButton() # 'rb0'
sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.rb0.sizePolicy().hasHeightForWidth())
self.rb0.setSizePolicy(sizePolicy)
self.attr_layout.addWidget(self.rb0)
self.buttonGroup.addButton(self.rb0)
self.rb1 = QRadioButton('rb1')
self.attr_layout.addWidget(self.rb1)
self.buttonGroup.addButton(self.rb1)
self.rb2 = QRadioButton('rb2')
self.attr_layout.addWidget(self.rb2)
self.buttonGroup.addButton(self.rb2)
self.rb3 = QRadioButton('rb3')
self.buttonGroup.addButton(self.rb3)
self.buttonGroup.buttonClicked.connect(self.check_button)
def check_button(self, radioButton):
if self._dictRB[radioButton.text()]:
self._dictRB[radioButton.text()] = False
self._dictRB['rb3'] = True
self.rb3.setChecked(True)
else:
for b in self._dictRB:
self._dictRB[b] = False
self._dictRB[radioButton.text()] = True
print("Button -> `{} - {}`".format(radioButton.text(), radioButton.isChecked()))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
#w.setMinimumSize(QSize(0, 400))
w.show()
sys.exit(app.exec_())
I need Radio() not to be less than QLabel. And it behaved almost as if QLabel was connected to the Layout while inside the QWidget with the saved margin and padding.
I tried to use setMinimumSize() but QLabel is always 30X100 until you manually specify the size. But constantly calculating margin, padding, font-size, border, and other qss properties is too inconvenient.
It should work something like this
But without constant writing self.setMinimumSize(QSize (140, 34)) manual

How to make a nestable expandable widget in Qt

Similar to How to make an expandable/collapsable section widget in Qt I want expandable sections in my Qt app. So I've translated the example to PySide2(Qt5).
Everything looks fine until I nest expanders:
As far as my current understanding goes it seems that the maximumHeight is only calculated once (when setContentLayout is called).
I guess I would need to update parents maximumHeight when expanding via the inner animation, but I have no Idea where to start.
Nested reproduction example
from PySide2 import QtCore, QtGui, QtWidgets
class Expander(QtWidgets.QWidget):
def __init__(self, parent=None, title='', animationDuration=300):
"""
References:
# Adapted from PyQt4 version
https://stackoverflow.com/a/37927256/386398
# Adapted from c++ version
https://stackoverflow.com/a/37119983/386398
"""
super(Expander, self).__init__(parent=parent)
self.animationDuration = animationDuration
self.toggleAnimation = QtCore.QParallelAnimationGroup()
self.contentArea = QtWidgets.QScrollArea()
self.headerLine = QtWidgets.QFrame()
self.toggleButton = QtWidgets.QToolButton()
self.mainLayout = QtWidgets.QGridLayout()
toggleButton = self.toggleButton
toggleButton.setStyleSheet("QToolButton { border: none; }")
toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
toggleButton.setArrowType(QtCore.Qt.RightArrow)
toggleButton.setText(str(title))
toggleButton.setCheckable(True)
toggleButton.setChecked(False)
headerLine = self.headerLine
headerLine.setFrameShape(QtWidgets.QFrame.HLine)
headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
headerLine.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
self.contentArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
# start out collapsed
self.contentArea.setMaximumHeight(0)
self.contentArea.setMinimumHeight(0)
# let the entire widget grow and shrink with its content
toggleAnimation = self.toggleAnimation
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"minimumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"maximumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self.contentArea, b"maximumHeight"))
# don't waste space
mainLayout = self.mainLayout
mainLayout.setVerticalSpacing(0)
mainLayout.setContentsMargins(0, 0, 0, 0)
row = 0
mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, QtCore.Qt.AlignLeft)
mainLayout.addWidget(self.headerLine, row, 2, 1, 1)
row += 1
mainLayout.addWidget(self.contentArea, row, 0, 1, 3)
self.setLayout(self.mainLayout)
def start_animation(checked):
arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow
direction = QtCore.QAbstractAnimation.Forward if checked else QtCore.QAbstractAnimation.Backward
toggleButton.setArrowType(arrow_type)
self.toggleAnimation.setDirection(direction)
self.toggleAnimation.start()
self.toggleButton.clicked.connect(start_animation)
def setContentLayout(self, contentLayout):
# Not sure if this is equivalent to self.contentArea.destroy()
self.contentArea.destroy()
self.contentArea.setLayout(contentLayout)
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
contentHeight = contentLayout.sizeHint().height()
for i in range(self.toggleAnimation.animationCount()-1):
expandAnimation = self.toggleAnimation.animationAt(i)
expandAnimation.setDuration(self.animationDuration)
expandAnimation.setStartValue(collapsedHeight)
expandAnimation.setEndValue(collapsedHeight + contentHeight)
contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
contentAnimation.setDuration(self.animationDuration)
contentAnimation.setStartValue(0)
contentAnimation.setEndValue(contentHeight)
from PySide2.QtWidgets import *
app = QApplication()
window = QWidget()
layout = QVBoxLayout()
window.setLayout(layout)
layout.addWidget(QLabel('above'))
inner = QVBoxLayout()
inner.addWidget(QLabel('innertop'))
inex = Expander(title='inner')
innest = QVBoxLayout()
innest.addWidget(QLabel('content'))
inex.setContentLayout(innest)
inner.addWidget(inex)
inner.addWidget(QLabel('innerbottom'))
ex = Expander(title='outer')
ex.setContentLayout(inner)
layout.addWidget(ex)
layout.addWidget(QLabel('below'))
window.show()
app.exec_()
I ended up consuming the containers resizeEvent to update the min/max constraints.
Here's the working example
from PySide2 import QtCore, QtGui, QtWidgets
class ScrollArea(QtWidgets.QScrollArea):
resized = QtCore.Signal()
def resizeEvent(self, e):
self.resized.emit()
return super(ScrollArea, self).resizeEvent(e)
class Expander(QtWidgets.QWidget):
def __init__(self, parent=None, title='', animationDuration=300):
"""
References:
# Adapted from PyQt4 version
https://stackoverflow.com/a/37927256/386398
# Adapted from c++ version
https://stackoverflow.com/a/37119983/386398
"""
super(Expander, self).__init__(parent=parent)
self.animationDuration = animationDuration
self.toggleAnimation = QtCore.QParallelAnimationGroup()
self.contentArea = ScrollArea()
self.headerLine = QtWidgets.QFrame()
self.toggleButton = QtWidgets.QToolButton()
self.mainLayout = QtWidgets.QGridLayout()
toggleButton = self.toggleButton
toggleButton.setStyleSheet("QToolButton { border: none; }")
toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
toggleButton.setArrowType(QtCore.Qt.RightArrow)
toggleButton.setText(str(title))
toggleButton.setCheckable(True)
toggleButton.setChecked(False)
headerLine = self.headerLine
headerLine.setFrameShape(QtWidgets.QFrame.HLine)
headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
headerLine.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
self.contentArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
# start out collapsed
self.contentArea.setMaximumHeight(0)
self.contentArea.setMinimumHeight(0)
self.contentArea.resized.connect(self.updateContentLayout)
# let the entire widget grow and shrink with its content
toggleAnimation = self.toggleAnimation
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"minimumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self, b"maximumHeight"))
toggleAnimation.addAnimation(QtCore.QPropertyAnimation(self.contentArea, b"maximumHeight"))
mainLayout = self.mainLayout
mainLayout.setVerticalSpacing(0)
mainLayout.setContentsMargins(0, 0, 0, 0)
row = 0
mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, QtCore.Qt.AlignLeft)
mainLayout.addWidget(self.headerLine, row, 2, 1, 1)
row += 1
mainLayout.addWidget(self.contentArea, row, 0, 1, 3)
self.setLayout(self.mainLayout)
def start_animation(checked):
arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow
direction = QtCore.QAbstractAnimation.Forward if checked else QtCore.QAbstractAnimation.Backward
toggleButton.setArrowType(arrow_type)
self.toggleAnimation.setDirection(direction)
self.toggleAnimation.start()
self.toggleButton.clicked.connect(start_animation)
def updateContentLayout(self):
if self.toggleButton.isChecked() and self.toggleAnimation.state() != self.toggleAnimation.Running:
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
contentHeight = self.contentArea.layout().sizeHint().height()
self.setMinimumHeight(collapsedHeight + contentHeight)
self.setMaximumHeight(collapsedHeight + contentHeight)
self.contentArea.setMaximumHeight(contentHeight)
self.updateGeometry()
p = self.parent()
if isinstance(p, ScrollArea):
p.resized.emit()
def setContentLayout(self, contentLayout):
# Not sure if this is equivalent to self.contentArea.destroy()
self.contentArea.destroy()
self.contentArea.setLayout(contentLayout)
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
contentHeight = contentLayout.sizeHint().height()
for i in range(self.toggleAnimation.animationCount()-1):
expandAnimation = self.toggleAnimation.animationAt(i)
expandAnimation.setDuration(self.animationDuration)
expandAnimation.setStartValue(collapsedHeight)
expandAnimation.setEndValue(collapsedHeight + contentHeight)
contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
contentAnimation.setDuration(self.animationDuration)
contentAnimation.setStartValue(0)
contentAnimation.setEndValue(contentHeight)
from PySide2.QtWidgets import *
app = QApplication()
window = QWidget()
layout = QFormLayout()
window.setLayout(layout)
layout.addRow(QLabel('above'))
inner = QFormLayout()
inner.addRow(QLabel('innertop'))
inex = Expander(title='inner')
innest = QFormLayout()
innest.addRow(QLabel('content'))
inex.setContentLayout(innest)
inner.addRow(inex)
inner.addRow(QLabel('innerbottom'))
ex = Expander(title='outer')
ex.setContentLayout(inner)
layout.addRow(ex)
layout.addRow(QLabel('below'))
window.show()
app.exec_()

Categories

Resources