Maybe I'm just having a bad day but I just can't seem to get this to work.
I'm trying to set the position of a widget floating inside another but it always seems to be offset.
My layout looks like this and I'm trying to make a "floating" widget inside canvas in the top right.
I re-implemented the show method (have also tried the showEvent) with this logic:
def show(self):
pos = self.parent().mapToGlobal(self.parent().pos())
topright = self.parent().rect().topRight()
self.resize(QSize(self.geometry().width(), self.parent().geometry().size().height()))
newpos = (pos + topright) - QPoint(self.geometry().width(), 0)
self.move(newpos)
super(InfoDock, self).show()
This is the result:
The two toolbars are added into canvas_page using:
self.canvas_page.layout().insertWidget(2, self.toolbar2)
self.canvas_page.layout().insertWidget(3, self.toolbar)
If I remove these calls it moves the widget higher but it still seems to be offset the size of settignsLabel_2 and line_2
To set an absolute (floating) position for a widget, reimplement the resizeEvent of its parent, and move() the widget relative to that parent:
def resizeEvent(self, event):
# move to top-right corner
self.widget.move(self.width() - self.widget.width() - 1, 1)
super(Canvas, self).resizeEvent(event)
UPDATE:
Working demo script:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
toolbar = self.addToolBar('Toolbar')
toolbar.addAction('Action')
widget = QtGui.QWidget(self)
layout = QtGui.QVBoxLayout(widget)
self.canvas = Canvas(widget)
layout.addWidget(self.canvas)
self.setCentralWidget(widget)
class Canvas(QtGui.QGraphicsView):
def __init__(self, parent):
super(Canvas, self).__init__(parent)
self.widget = QtGui.QComboBox(self)
def resizeEvent(self, event):
self.widget.move(self.width() - self.widget.width() - 2, 2)
super(Canvas, self).resizeEvent(event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
window.setGeometry(500, 300, 200, 200)
sys.exit(app.exec_())
Related
I've been recently learning pyqt5 as my first gui framework. So far I have been experimenting with QtStackedLayout. I currently have two window screens, one created inside the UI class and another in another separate class. I have two concerns:
Everything was working until I started experimenting on adding a background image for Window 1. There is no image displayed but the code runs ok.
There is this small fraction of time in the beginning where window one will get displayed first until it gets loaded to the mainwindow, I tried passing self during instantiation of the object to sert as some kind of parent to prevent this but I think I'm not doing it right.
See below code (I have bad import statements, I will sort it out)
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Ui(QWidget):
def setupUi(self, Main, width, height):
self.stack = QStackedLayout()
self.window_1 = WindowOne(width, height)
self.window_2 = QWidget(self)
self.window_2_UI()
self.stack.addWidget(self.window_1)
self.stack.addWidget(self.window_2)
# Only one button
self.btn = QPushButton("Change window", self)
# Create the central widget of your Main Window
self.main_widget = QWidget(self)
layout = QVBoxLayout(self.main_widget)
layout.addLayout(self.stack)
layout.addWidget(self.btn)
self.setCentralWidget(self.main_widget)
self.btn.clicked.connect(self.change_window)
def change_window(self):
if self.stack.currentIndex() == 0:
self.stack.setCurrentIndex(1)
else:
self.stack.setCurrentIndex(0)
def window_2_UI(self):
label = QLabel("In Window 2", self.window_2)
class WindowOne(QWidget):
def __init__(self, width, height):
super().__init__()
self.set_bg(width, height)
self.set_label()
# self.setStyleSheet("background-image: url(:resource/images/blue_bg.jpg)")
def set_label(self):
label = QLabel("In Window 1", self)
def set_bg(self, w, h):
oImage = QImage("resource/images/blue_bg.jpg")
sImage = oImage.scaled(QSize(w, h))
palette = QPalette()
palette.setBrush(10, QBrush(sImage))
self.setPalette(palette)
class Main(QMainWindow, Ui):
def __init__(self):
super().__init__()
self.w_width = 480
self.w_height = 720
self.resize(self.w_width, self.w_height)
self.init_ui()
self.setupUi(self, self.w_width, self.w_height)
def init_ui(self):
self.center()
self.setWindowTitle('Main Window')
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
if __name__ == "__main__":
app = QApplication(sys.argv)
M = Main()
M.show()
sys.exit(app.exec())
By default only the window (which is different to a widget) will use the background color of QPalette, if you want a widget to use the background color of QPalette you must enable the autoFillBackground property.
# ...
self.set_label(width, height)
self.setAutoFillBackground(True)
# ...
Although your code is a little messy, for example you establish that certain methods receive certain parameters but you never use them. Finally I think you want the background of the image to be re-scale using the size of the window so I have override the resizeEvent() method so that scaling takes the size of the window.
Considering all the above, I have improved your code:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.m_stacked_layout = QtWidgets.QStackedLayout()
self.widget_1 = WidgetOne()
self.widget_2 = QtWidgets.QWidget()
self.widget_2_UI()
self.m_stacked_layout.addWidget(self.widget_1)
self.m_stacked_layout.addWidget(self.widget_2)
button = QtWidgets.QPushButton(
"Change window", clicked=self.change_window
)
lay = QtWidgets.QVBoxLayout(self)
lay.addLayout(self.m_stacked_layout)
lay.addWidget(button)
#QtCore.pyqtSlot()
def change_window(self):
ix = self.m_stacked_layout.currentIndex()
self.m_stacked_layout.setCurrentIndex(1 if ix == 0 else 0)
def widget_2_UI(self):
label = QtWidgets.QLabel("In Window 2", self.widget_2)
class WidgetOne(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAutoFillBackground(True)
self.set_label()
self.m_image = QtGui.QImage("resource/images/blue_bg.jpg")
def set_label(self):
label = QtWidgets.QLabel("In Window 1", self)
def resizeEvent(self, event):
palette = self.palette()
sImage = self.m_image.scaled(event.size())
palette.setBrush(10, QtGui.QBrush(sImage))
self.setPalette(palette)
super(WidgetOne, self).resizeEvent(event)
class Main(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
widget = Widget()
self.setCentralWidget(widget)
self.resize(480, 720)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Main()
w.show()
sys.exit(app.exec())
Baseline
How I want to create
Hi
I have a simple PyQT5 app. The main window is a QMainWindow which houses a QWidget. The Layout of the QWidget is as follows:
Class Canvas(QWidget):
def __init__(self):
super().__init__()
self.ListOfPlots = []
self.outFile = "temp.prb"
self.initUI()
def initUI(self):
self.headLabel = QLabel("List of Plots:")
self.label = QLabel("",self)
self.setAcceptDrops(True)
self.createPushButtons()
hbox = QHBoxLayout() #Horizontal Layout
#hbox.addStretch(1)
hbox.addWidget(self.combineButton)
hbox.addWidget(self.openButton)
hbox.addWidget(self.resetButton)
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.headLabel)
self.vbox.addWidget(self.label)
self.vbox.addLayout(hbox) ## The horizontal box is placed into vertical layout
self.setLayout(self.vbox)
I want to create a translucent drop area as shown in the second picture with a label indicating drop files here. What would be the most suitable way to do it?
The entire widget is ok to allow drops. I just want a box indicating it is ok to drop here (like an indicator).
You can use dynamic properties to trigger an indicator when it's okay to drop. If you need the background to be semi-transparent, use rgba for your widget's stylesheet background property. background:rgba(255,255,255,90)
from PySide2 import QtWidgets
import sys
from PySide2.QtWidgets import QWidget, QGridLayout, QFrame
class DropZone(QFrame):
def __init__(self, parent=None):
QFrame.__init__(self)
self.setFixedSize(200, 200)
self.setAcceptDrops(True)
self.setObjectName('DropZone')
self.setStyleSheet(
'QFrame#DropZone[Dropindicator=true]{border:3px solid green;background:darkorange;}\nQFrame#DropZone{background:orange;}')
def dragEnterEvent(self, event):
if event.mimeData().hasFormat('text/plain'):
self.setProperty('Dropindicator',True)
print(event.mimeData().text())
self.setStyle(self.style())
...
event.accept()
else:
event.ignore()
def dropEvent(self, event):
event.accept()
if event.isAccepted():
self.setProperty('Dropindicator',False)
self.setStyle(self.style())
class Widget( QWidget):
def __init__(self,parent=None):
QWidget.__init__(self)
gl = QGridLayout()
self.setLayout(gl)
self.dz = DropZone()
self.dz.setParent(self)
gl.addWidget(self.dz)
self.setLayout(gl)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I'm trying to detect mouse clicks for anywhere inside an area with several widgets. For this I'm using the following code:
custom_widget = CustomWidget()
custom_widget.mouse_pressed_signal.connect(self.on_custom_label_mouse_pressed)
main_layout_vbox.addWidget(custom_widget)
hbox = QtWidgets.QHBoxLayout()
custom_widget.setLayout(hbox)
# Adding several widgets to hbox_l6
class CustomWidget(QtWidgets.QWidget):
mouse_pressed_signal = QtCore.pyqtSignal(QtGui.QMouseEvent)
def __init__(self):
super().__init__()
def mousePressEvent(self, i_qmouseevent):
super(CustomWidget, self).mousePressEvent(i_qmouseevent)
logging.debug("======== CustomWidget - mousePressEvent ========")
self.mouse_pressed_signal.emit(i_qmouseevent)
Problem
This works when clicking in any of the child widgets, but there's a problem: If I click between widgets (so in the area of the hbox layout that is not covered by a widget) the mousePressEvent is not captured
Question
How can I solve this problem? (Or is there another approach that you can recommend?) The important thing is that I am able to capture mouse clicks anywhere inside of custom_widget / hbox (see code above)
If you want to listen to other widget's mousePressEvent you can use an eventFilter as I show below:
from PyQt5 import QtCore, QtGui, QtWidgets
import random
class Widget(QtWidgets.QWidget):
mouse_clicked_signal = QtCore.pyqtSignal(QtGui.QMouseEvent, QtWidgets.QWidget)
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout(self)
for cls in (QtWidgets.QLabel, QtWidgets.QPushButton, QtWidgets.QFrame, QtWidgets.QWidget):
widget = cls()
color = QtGui.QColor(*random.sample(range(255), 3))
widget.setStyleSheet("background-color: {}".format(color.name()))
hlay.addWidget(widget)
for w in self.findChildren(QtWidgets.QWidget) + [self]:
w.installEventFilter(self)
self.resize(640, 480)
def eventFilter(self, watched, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
self.mouse_clicked_signal.emit(event, watched)
return super(Widget, self).eventFilter(watched, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.mouse_clicked_signal.connect(print)
w.show()
sys.exit(app.exec_())
I have a pushbutton to add a tab in a QTabWidget. But when I change the button's size, it's not in the corner anymore. So how can I bring it to the corner like in the first picture?
Not change size:
Change size:
Here is my code:
from PyQt4 import QtGui, QtCore
class PlaylistTable(QtGui.QWidget):
def __init__(self):
super(PlaylistTable, self).__init__()
self.playlistTable = QtGui.QTableWidget(self)
self.playlistTable.setFrameShape(QtGui.QFrame.NoFrame)
self.playlistTable.setFrameShadow(QtGui.QFrame.Sunken)
self.playlistTable.setLineWidth(0)
self.playlistTable.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.playlistTable.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.playlistTable.setHorizontalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.playlistTable.setShowGrid(True)
self.playlistTable.setGridStyle(QtCore.Qt.SolidLine)
self.playlistTable.setWordWrap(True)
self.playlistTable.setCornerButtonEnabled(True)
self.playlistTable.verticalHeader().setVisible(False)
class CustomTabWidget(QtGui.QTabWidget):
"""Tab Widget that that can have new tabs easily added to it."""
def __init__(self, parent=None):
super(CustomTabWidget, self).__init__(parent)
# QtGui.QTabWidget.__init__(self, parent)
# Tab Bar
self.tab = QtGui.QTabBar()
self.setTabBar(self.tab)
# Properties
self.setMovable(True)
self.setTabsClosable(True)
self.plusButton = QtGui.QPushButton("+")
self.plusButton.setFixedSize(QtCore.QSize(22, 22))
self.setCornerWidget(self.plusButton)
# Signals
self.connect(self.plusButton, QtCore.SIGNAL('clicked()'), self.addTab)
# self.tab.plusClicked.connect(self.addTab)
self.tab.tabMoved.connect(self.tab.moveTab)
self.tabCloseRequested.connect(self.removeTab)
def addTab(self):
string = QtCore.QString.fromUtf8("Playlist")
tab = PlaylistTable()
super(CustomTabWidget, self).addTab(tab, string)
class AppDemo(QtGui.QMainWindow):
def __init__(self):
super(AppDemo, self).__init__()
self.centralwidget = QtGui.QWidget(self)
self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setContentsMargins(0, -1, 0, -1)
self.playlist_manager = CustomTabWidget(self.centralwidget)
self.horizontalLayout.addWidget(self.playlist_manager)
self.playlist_manager.addTab()
self.setCentralWidget(self.centralwidget)
self.show()
# end class AppDemo
def main():
import sys
app = QtGui.QApplication(sys.argv)
w = AppDemo()
w.setWindowTitle('AppDemo')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I'm guessing the reason is this, from QTabWidget Documentation:
The geometry of the widget is determined
based on the widget's sizeHint() and the style().
If you print the sizeHint() of the QPushButton you will see that width never goes below a certain value.
I've found an alternative is to use a QToolButton which can do everything (even more) a QPushButton does.
self.plusButton = QtGui.QToolButton(self)
self.plusButton.setText("+")
Very new to pyside so maybe a stupid question. I want to create a pyside UI which has a variable number of items in it and also has the possibility to add items while it is running and to make it even more complex it also needs a scroll bar to fit it all on screen!
This is what I've got right now:
import sys
from PySide import QtGui
from PySide import QtCore
class example(QtGui.QWidget):
def __init__(self, parent= None):
super(example, self).__init__()
grid = QtGui.QGridLayout()
grid.setSpacing(10)
self.widget = QtGui.QWidget()
self.layout = QtGui.QGridLayout(self)
for i in range(5):
btn = QtGui.QPushButton("test"+str(i))
self.layout.addWidget(btn,i,0)
btn.clicked.connect(self.buttonClicked)
self.count = i
self.widget.setLayout(self.layout)
self.scroll = QtGui.QScrollArea()
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.scroll.setWidget(self.widget)
grid.addWidget(self.scroll,3,0)
self.setLayout(grid)
def buttonClicked(self):
title = QtGui.QLabel('Title'+str(self.count))
self.layout.addWidget(title,self.count + 1,0)
self.count += 1
self.widget.addLayout(self.layout,0)
self.scroll.addWidget(self.widget,0)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog = example()
dialog.show()
sys.exit(app.exec_())
But somehow the layout gets messed up when adding items through one of the buttons.
Does anybody have an idea how to fix this?
Thanx!
You're not far off. The key piece you're missing is QScrollArea.setWidgetResizable, which will ensure the scrollarea automatically resizes its viewport to fit the contents.
I've made some other adjustments to your example and added comments where appropriate:
class example(QtGui.QWidget):
def __init__(self, parent= None):
super(example, self).__init__()
grid = QtGui.QGridLayout()
grid.setSpacing(10)
self.widget = QtGui.QWidget()
# set the widget as parent of its own layout
self.layout = QtGui.QGridLayout(self.widget)
for i in range(5):
btn = QtGui.QPushButton("test"+str(i))
self.layout.addWidget(btn,i,0)
btn.clicked.connect(self.buttonClicked)
# following lines are redundant
# self.count = i
# self.widget.setLayout(self.layout)
self.scroll = QtGui.QScrollArea()
# need this so that scrollarea handles resizing
self.scroll.setWidgetResizable(True)
# these two lines may not be needed now
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.scroll.setWidget(self.widget)
grid.addWidget(self.scroll, 3, 0)
self.setLayout(grid)
def buttonClicked(self):
title = QtGui.QLabel('Title' + str(self.layout.count()))
self.layout.addWidget(title)
# following lines are redundant
# self.layout.addWidget(title, self.count + 1, 0)
# self.count += 1
# self.widget.addLayout(self.layout,0)
# self.scroll.addWidget(self.widget,0)