I just started using Python PyQt5 and I am trying to make a GUI. It all works, but I have a problem with the textbox. Every time the textbox gets filled and I try to add more, it stays the same size and just adds a small scrollbar IN the textbox. What I want is for the textbox to readjust depending on the size of the text so that you can always see the text. How can I do this.
Here is my current code:
#Import Module
from PyQt5.QtGui import QFont
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
import sys
#Create Main Class
class AddToDeckWindow(QtWidgets.QMainWindow):
def __init__(self):
super(AddToDeckWindow, self).__init__()
#Set The UI
self.initUI()
#Set The GUI Position And Size
self.setGeometry(200, 200, 900, 710)
#Set The GUI Title
self.setWindowTitle("Add")
#Set The GUI Icon
self.setWindowIcon(QtGui.QIcon('give_way.png'))
#Set The Translator
_translate = QtCore.QCoreApplication.translate
def initUI(self):
widAddToDeckWindow = QtWidgets.QWidget(self)
#Create The Text Box
self.setCentralWidget(widAddToDeckWindow)
textBox = QtWidgets.QTextEdit(widAddToDeckWindow)
textBox.setGeometry(200, 200, 500, 200)
#Set The Font
font = QFont()
font.setPointSize(14)
textBox.setFont(font)
#Create A Windows
def window():
app = QtWidgets.QApplication(sys.argv)
win = AddToDeckWindow()
#Centers The Window On The Screen
qtRectangle = win.frameGeometry()
centerPoint = QtWidgets.QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
win.move(qtRectangle.topLeft())
win.show()
sys.exit(app.exec_())
window()
First of all, consider that you're not using a layout manager, and using fixed geometries like you did, is considered bad practice and also a very bad idea.
For instance, in your case, if I try to resize the window to a size smaller than the bottom right corner of the text edit, it will become partially (or completely) invisible.
I strongly advise you against this pattern (actually, almost everybody would), as if you want to show such big margins you only need to correctly use the layout properties instead.
That said, if you want to resize a textedit according to its contents, you must implement a function that constantly checks its document().
Then, you should choose if set the minimumHeight, or, better the sizeHint, which is used by the layout to limit its size if no more space is available. I strongly suggest you to use the latter.
class ResizingTextEdit(QtWidgets.QTextEdit):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.textChanged.connect(self.computeHint)
self._sizeHint = super().sizeHint()
def computeHint(self):
hint = super().sizeHint()
height = self.document().size().height()
height += self.frameWidth() * 2
self._sizeHint.setHeight(max(height, hint.height()))
self.updateGeometry()
self.adjustSize()
def sizeHint(self):
return self._sizeHint
class AddToDeckWindow(QtWidgets.QMainWindow):
# ...
def initUI(self):
widAddToDeckWindow = QtWidgets.QWidget(self)
#Create The Text Box
self.setCentralWidget(widAddToDeckWindow)
textBox = ResizingTextEdit(widAddToDeckWindow)
textBox.setMinimumSize(500, 200)
# this is no more necessary, as the layout will completely override it
# textBox.setGeometry(200, 200, 500, 200)
layout = QtWidgets.QGridLayout(widAddToDeckWindow)
layout.addWidget(textBox, 1, 1)
layout.setColumnStretch(0, 1)
layout.setColumnStretch(2, 1)
layout.setRowStretch(0, 1)
layout.setRowStretch(2, 1)
#Set The Font
font = QFont()
font.setPointSize(14)
textBox.setFont(font)
In case of the minimumHeight, use the following:
class ResizingTextEdit(QtWidgets.QTextEdit):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.textChanged.connect(self.computeHint)
def computeHint(self):
height = self.document().size().height()
height += self.frameWidth() * 2
if height > super().sizeHint().height():
self.setMinimumHeight(height)
else:
self.setMinimumHeight(0)
Related
I'm trying to create a compound widget similiar to the following:
A Rectangle overlayed with a button that is partially outside the rectangle's bounds.
Here is the code corresponding to that image:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QPoint
from PyQt5.QtGui import QResizeEvent
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layout = QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
self.lbl = QLabel()
self.lbl.setStyleSheet('background: #EE6622')
self.lbl.setFixedSize(125, 150)
self.layout.addWidget(self.lbl)
self.btn = QPushButton(parent=self)
self.btn.setStyleSheet('background: #ABCDEF')
self.btn.setFixedSize(25, 25)
def resizeEvent(self, event: QResizeEvent) -> None:
super().resizeEvent(event)
self.update_btn_pos()
def update_btn_pos(self):
pos = (
self.lbl.pos() +
QPoint(
self.lbl.rect().width() - int(self.btn.width() / 2),
-int(self.btn.height() / 2))
)
self.btn.move(pos)
if __name__ == "__main__":
a = QApplication(sys.argv)
window = MyWidget()
window.show()
a.exec()
My problem is that the widget's behaviour when resizing suggests that the button is not really "part of" that widget - it is cut-off as if it weren't there:
I tried to overwrite the sizeHint()-method to include the button, but that only solves the problem on startup, I can still resize the window manually to cut the button off again.
What must be changed in order to make this work?
I think I might have found a solution myself by adding the following to the __init__ - method:
self.layout.setContentsMargins(
0,
int(self.btn.height() / 2),
int(self.btn.width() / 2),
0
)
By setting the contentsMargin, the size of the big rectangle doesn't change because it is fixed and the parent widget still covers the space under the button:
I'm not sure if this is the *right way* to do it though ...
Alright, thanks to #musicamante this is the final code:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QResizeEvent
class MyWidget(QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.lbl = QLabel()
self.lbl.setStyleSheet('background: #EE6622')
self.lbl.setFixedSize(125, 150)
self.layout.addWidget(self.lbl)
self.btn = QPushButton(parent=self)
self.btn.setStyleSheet('background: #ABCDEF')
self.btn.setFixedSize(25, 25)
# set contents margin of layout to half the button's size
self.layout.setContentsMargins(
*([int(self.btn.height() / 2), int(self.btn.width() / 2)]*2)
)
def resizeEvent(self, event: QResizeEvent) -> None:
super().resizeEvent(event)
self.update_btn_pos()
def update_btn_pos(self):
rect = self.btn.rect()
rect.moveCenter(self.lbl.geometry().topRight())
self.btn.move(rect.topLeft())
if __name__ == "__main__":
a = QApplication(sys.argv)
window = MyWidget()
window.show()
a.exec()
Result:
On paint event widget paints itself and all of his children clipped to his bounds. You can try to set button parent to MyWidget's parent, but you'll still have problem of button blocking part of some other widget or clipping on window's client area.
On the other hand there is no much difference between hovering button thats inside parent's widget and hovering button that sticks out, messing with other widgets.
In reference to this answer on adding a new tab button to QTabWidget,
I am unsure where the QPushButton is added to the QTabBar.
I assume the setParent method on the pushButton adds it to the tab bar.
But when I try to implement it, the pushButton doesnt seem to appear anywhere on the tab bar even if I add hard values to the move operation.
Here is a minimum reproducible example,
from PyQt5 import QtGui, QtCore, QtWidgets
class TabBarPlus(QtWidgets.QTabBar):
"""Tab bar that has a plus button floating to the right of the tabs."""
plusClicked = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
# Plus Button
self.plusButton = QtWidgets.QPushButton("+")
self.plusButton.setParent(self)
self.plusButton.setFixedSize(20, 20) # Small Fixed size
self.plusButton.clicked.connect(self.plusClicked.emit)
self.movePlusButton() # Move to the correct location
# end Constructor
def sizeHint(self):
"""Return the size of the TabBar with increased width for the plus button."""
sizeHint = QtWidgets.QTabBar.sizeHint(self)
width = sizeHint.width()
height = sizeHint.height()
return QtCore.QSize(width+25, height)
# end tabSizeHint
def resizeEvent(self, event):
"""Resize the widget and make sure the plus button is in the correct location."""
super().resizeEvent(event)
self.movePlusButton()
# end resizeEvent
def tabLayoutChange(self):
"""This virtual handler is called whenever the tab layout changes.
If anything changes make sure the plus button is in the correct location.
"""
super().tabLayoutChange()
self.movePlusButton()
# end tabLayoutChange
def movePlusButton(self):
"""Move the plus button to the correct location."""
# Find the width of all of the tabs
size = sum([self.tabRect(i).width() for i in range(self.count())])
# size = 0
# for i in range(self.count()):
# size += self.tabRect(i).width()
# Set the plus button location in a visible area
h = self.geometry().top()
w = self.width()
if size > w: # Show just to the left of the scroll buttons
self.plusButton.move(w-54, h)
else:
self.plusButton.move(size, h)
# end movePlusButton
# end class MyClass
class CustomTabWidget(QtWidgets.QTabWidget):
"""Tab Widget that that can have new tabs easily added to it."""
def __init__(self, parent=None):
super().__init__(parent)
# Tab Bar
self.tab = TabBarPlus()
self.setTabBar(self.tab)
# Properties
self.setMovable(True)
self.setTabsClosable(True)
# Signals
self.tab.plusClicked.connect(self.addTab)
# self.tab.tabMoved.connect(self.moveTab)
# self.tabCloseRequested.connect(self.removeTab)
# end Constructor
# end class CustomTabWidget
class AppDemo(QtWidgets.QMainWindow):
def __init__(self):
super(AppDemo, self).__init__()
self.centralwidget = QtWidgets.QWidget(self)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setContentsMargins(0, -1, 0, -1)
self.playlist_manager = CustomTabWidget(self.centralwidget)
self.horizontalLayout.addWidget(self.playlist_manager)
blankWidget = QtWidgets.QWidget(self.playlist_manager)
self.playlist_manager.addTab(blankWidget, "New")
self.setCentralWidget(self.centralwidget)
self.show()
# end class AppDemo
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = AppDemo()
w.setWindowTitle('AppDemo')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Expected behvaiour is that a "+" button appears at the right of all the tabs,
but no such button appears.
Okay so after some brainstorming, I figured the issue out. Unlike PyQt4. The QTabBar width does not span the entire width of the QTabWidget, and as such the PlusButton when moved to the right of all the tabs will exceed the width of the parent widget and disappear.
The Solution to this is to add the QPushButton in the QTabWidget itself and emit layoutchange from the QTabBar.
Here is a working example, I have modified values to fit my use case
class tabBarPlus(QTabBar):
layoutChanged = pyqtSignal()
def resizeEvent(self, event):
super().resizeEvent(event)
self.layoutChanged.emit()
def tabLayoutChange(self):
super().tabLayoutChange()
self.layoutChanged.emit()
class customTabWidget(QTabWidget):
plusClicked = pyqtSignal()
def __init__(self, parent=None):
super(customTabWidget, self).__init__(parent)
self.tab = tabBarPlus()
self.setTabBar(self.tab)
self.plusButton = QPushButton('+', self)
self.plusButton.setFixedSize(35, 25)
self.plusButton.clicked.connect(self.plusClicked.emit)
self.setMovable(True)
self.setTabsClosable(True)
self.tab.layoutChanged.connect(self.movePlusButton)
def movePlusButton(self):
size = sum([self.tab.tabRect(i).width() for i in range(self.tab.count())])
h = max(self.tab.geometry().bottom() - 24, 0)
w = self.tab.width()
print(size, w, h)
if size > w:
self.plusButton.move(w-self.plusButton.width(), h)
else:
self.plusButton.move(size-2, h)
I have a QScrollArea containing a widget with a QVBoxLayout. Inside this layout are several other widgets. I want the user to be able to drag the lower borders of those widgets to resize them in the vertical direction. When they are resized, I don't want them to "steal" size from the other widgets in the scrolling area; instead I want the entire scrolled "page" to change its size. So if you enlarge one of the widgets, it should push the other widgets down (out of the viewport of the scroll area); if you shrink it, it should pull the other widgets up. Dragging the border of one widget should not change the size of any of the other widgets in the vertical scroll; it should just move them.
I began by using a QSplitter. If I use that, I can drag to change the size of a widget, but there doesn't seem to be a way to get it to "push/pull" the others as I described above, rather than growing/shrinking them. But I can't find any other way to give a widget a draggable handle that will allow me to change its size. How can I accomplish this?
Here is a simple example of what I'm doing. (In this example I've commented out the splitter, but if you uncomment it you can see what happens with that version.)
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.Qsci import QsciScintilla, QsciLexerPython
class SimplePythonEditor(QsciScintilla):
def __init__(self, parent=None):
super(SimplePythonEditor, self).__init__(parent)
self.setMinimumHeight(50)
class Chunk(QFrame):
def __init__(self, parent=None):
super(Chunk, self).__init__(parent)
layout = QVBoxLayout(self)
sash = QSplitter(self)
layout.addWidget(sash)
sash.setOrientation(Qt.Vertical)
editor = self.editor = SimplePythonEditor()
output = self.output = SimplePythonEditor()
output.setReadOnly(True)
sash.addWidget(editor)
sash.addWidget(output)
self.setLayout(layout)
print(self.sizePolicy())
class Widget(QWidget):
def __init__(self, parent= None):
global inout
super(Widget, self).__init__()
#Container Widget
widget = QWidget()
#Layout of Container Widget
layout = QVBoxLayout(self)
#sash = QSplitter(self)
#layout.addWidget(sash)
#sash.setOrientation(Qt.Vertical)
for num in range(5):
editor = SimplePythonEditor()
editor.setText("Some stuff {}".format(num))
layout.addWidget(editor)
#sash.addWidget(editor)
widget.setLayout(layout)
#Scroll Area Properties
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(True)
scroll.setWidget(widget)
scroll.setMaximumHeight(500)
#Scroll Area Layer add
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
self.setLayout(vLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = Widget()
dialog.show()
app.exec_()
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowTitle("MainWindow")
MainWindow.resize(500, 500)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
QMetaObject.connectSlotsByName(MainWindow)
class Ewindow(QMainWindow,QApplication):
"""docstring for App"""
resized = pyqtSignal()
def __init__(self,parent):
super(Ewindow,self).__init__(parent=parent)
self.setGeometry(500, 500, 800,800)
self.setWindowTitle('Mocker')
self.setWindowIcon(QIcon('icon.png'))
self.setAttribute(Qt.WA_DeleteOnClose)
ui2 = Ui_MainWindow()
ui2.setupUi(self)
self.resized.connect(self.readjust)
def resizeEvent(self, event):
self.resized.emit()
return super(Ewindow, self).resizeEvent(event)
def readjust(self):
self.examForm.move(self.width()-self.examForm.width(),0)
self.btn_skip.move(self.width()-self.btn_skip.width(),self.height()-100)
self.btn_next.move(self.btn_showAnswers.x()+self.btn_showAnswers.width(),self.height()-100)
self.btn_prev.move(0,self.height()-100)
self.btn_showAnswers.move(self.btn_prev.x()+self.btn_prev.width(),self.height()-100)
self.btn_home.move(self.width()-200,self.height()-150)
self.lbscreen1.resize(self.width()-self.examForm.width(),self.height()-200)
self.examForm.resize(200,self.height()-150)
self.btn_skip.resize(self.examForm.width(),100)
self.btn_next.resize(self.btn_prev.width(),100)
self.btn_prev.resize(self.width()*0.25,100)
self.btn_showAnswers.resize(self.btn_prev.width(),100)
self.btn_home.resize(200,50)
here is an example code of a resizable window it moves and stretches widgets as you resize the window. The idea is to keep widget coordinates and sizes relative to each other.
so i had to make a class Ui_MainWindow and set it for my window class ui2.setupUi(self) and also declare the resized = pyqtSignal() which i'd be using to run the readjust function which resets size and coordinates of the widgets like so self.resized.connect(self.readjust).
i hope this helps!
I want to display welcome label in middle of frame, how can I do that? It seems like layout problem as I googled but I haven't got final solution.
Here is the code:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
palette = QPalette()
palette.setBrush(QPalette.Background, QBrush(QPixmap("Login page.jpg")))
self.setPalette(palette)
self.setWindowTitle("Login Frame")
self.setWindowIcon(QIcon('logo.png'))
self.setGeometry(50, 50, 500, 300)
self.setFixedSize(500, 300)
self.addWidgets()
def addWidgets(self):
self.lblWelcome = QLabel("Welcome to Railway e-Ticketing System", self)
self.lblWelcome.move(100,30)
wcFont = QFont("Open Sans", 25)
self.lblWelcome.setFont(wcFont)
self.lblUid = QLabel("User ID:", self)
self.lblUid.move(100,80)
font = QFont("Open Sans", 10)
self.lneUid = QLineEdit(self)
self.lneUid.setFont(font)
self.lneUid.setFixedHeight(25)
self.lneUid.setFixedWidth(200)
self.lneUid.move(225, 80)
self.lblPass = QLabel("Password:", self)
self.lblPass.move(100, 130)
self.lnePass = QLineEdit(self)
self.lnePass.setEchoMode(QLineEdit.Password)
self.lnePass.setFixedHeight(25)
self.lnePass.setFixedWidth(200)
self.lnePass.move(225, 130)
self.lblInvalid = QLabel("",self)
self.lblInvalid.move(100, 180)
self.btnLogin = QPushButton("Login",self)
#btnLogin.resize(btnLogin.sizeHint())
self.btnLogin.move(175, 230)
self.btnLogin.clicked.connect(self.authenticate)
#self.authenticate()
self.btnReg = QPushButton("Register", self)
self.btnReg.move(300, 230)
#btnReg.clicked.connect(register)
self.show()
def authenticate(self):
uid = self.lneUid.text()
upass = self.lnePass.text()
if(len(uid.strip()) == 0 or len(upass.strip()) == 0):
palette = QPalette()
palette.setColor(QPalette.Foreground, Qt.darkRed)
self.lblInvalid.setPalette(palette)
self.lblInvalid.setText("*Invalid credentials .")
else:
self.lblInvalid.setText("")
def main():
app = QApplication(sys.argv)
LoginWin = Window()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
And here is the output:
You are using a QMainWindow which already has a layout with a central widget, a toolbar, a menu bar etc. The right way to use it is to define a central Widget, and put all your label and buttons in it. You didn't, so your label is not displayed properly.
But for your login frame, you clearly don't need all of this. You just need a QWidget:
import sys
from PyQt4 import QtCore,QtGui
class LoginFrame(QtGui.QWidget):
def __init__(self):
super(LoginFrame, self).__init__()
...
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
win = LoginFrame()
win.show()
sys.exit(app.exec_())
Your code should work with a QWidget, but I would still advise reading about box layout. Right now, you're using absolute positioning, which means you have to manually place your widget at a precise position, and you can't resize your window.
A box layout would be more flexible, and practical. For example you can use QFormLayout for the userID and password.
More about layouts on the ZetCode tutorial
I have a small Python script that makes a transparent window for displaying a graphic on screen and I'd like to animate that graphic, but am entirely unsure how or where to even start. Here's what I do have at least:
import sys
from PyQt4 import QtGui, Qt, QtCore
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
self.setAutoFillBackground(True)
pixmap = QtGui.QPixmap("test1.gif")
pixmap2 = QtGui.QPixmap("test2.gif")
width = pixmap.width()
height = pixmap.height()
self.setWindowTitle("Status")
self.resize(width, height)
self.label = QtGui.QLabel(self)
def animateEvent():
imgnumber = 0
try:
if imgnumber == 1:
self.label.setPixmap(QtGui.QPixmap("test1.gif"))
self.setMask(pixmap.mask())
imgnumber = 0
else:
self.label.setPixmap(QtGui.QPixmap("test2.gif"))
self.setMask(pixmap2.mask())
imgnumber = 1
finally:
QtCore.QTimer.singleShot(1000, animateEvent)
animateEvent()
def paintEvent(self,event):
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
x = Transparent()
x.show()
app.exec_()
This feels like it has the right ingredients, but the pixmap doesn't update.
I tried QMovie, but then the area of the window that is supposed to be transparent is filled with black instead.
check out this code from www.daniweb.com and see if you can modify it to your needs:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MoviePlayer(QWidget):
def __init__(self, gif, parent=None):
super(MoviePlayer, self).__init__(parent)
self.setGeometry(200, 200, 400, 400)
self.setWindowTitle("QMovie to show animated gif")
self.movie_screen = QLabel()
self.movie_screen.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.movie_screen.setAlignment(Qt.AlignCenter)
btn_start = QPushButton("Start Animation")
btn_start.clicked.connect(self.start)
btn_stop = QPushButton("Stop Animation")
btn_stop.clicked.connect(self.stop)
main_layout = QVBoxLayout()
main_layout.addWidget(self.movie_screen)
main_layout.addWidget(btn_start)
main_layout.addWidget(btn_stop)
self.setLayout(main_layout)
self.movie = QMovie(gif, QByteArray(), self)
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.setSpeed(100)
self.movie_screen.setMovie(self.movie)
def start(self):
"""
Start animation
"""
self.movie.start()
def stop(self):
"""
Stop the animation
"""
self.movie.stop()
app = QApplication(sys.argv)
player = MoviePlayer("/path/to/image.gif")
player.show()
sys.exit(app.exec_())
This ended up being a simple correction of an oversight in the end.
imgnumber needed to be outside of the def as self.imgnumber and needed to be named self.imgnumber each time it was changed.
First, just make sure your animated gif really does have a proper transparent background. The following code works for me, using this fire image as a source:
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
filename = "test.gif"
size = QtGui.QImage(filename).size()
self.setWindowTitle("Status")
layout = QtGui.QVBoxLayout(self)
layout.setMargin(0)
self.movie = QtGui.QMovie(filename)
self.label = QtGui.QLabel(self)
self.label.setMovie(self.movie)
layout.addWidget(self.label)
self.resize(size)
self.movie.start()
This will create a completely transparent and frameless window, with the animated gif playing in a QMovie. There is no black being drawn behind the image. It should fully see through to what ever is underneath.
It is not so far off from your original code. You shouldn't need to set the mask, or do a paint event.