Adding QTextEdit to QScrollArea - python

I have a block of text in my QTextEdit area but it is too big for the area. I have tried adding the textArea to a QScrollArea to enable me to scroll through the text. I am wondering where I am going wrong with this as it does not seem to do anything. I am wondering what is the correct thing to do. Here is my code with the outputted stack trace.
# Import Statements
from PyQt5 import QtCore, QtGui, QtWidgets
from MainGuiWindow import Ui_MainWindow
# Main Class that holds User Interface Objects
class Ui_informationWindow(object):
# Function for Opening Main GUI window from login window by clicking login button
def openMainWindow(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_MainWindow()
self.ui.setupUi(self.window)
loginWindow.hide()
self.window.show()
def setupUi(self, informationWindow):
informationWindow.setObjectName("User Information Window")
informationWindow.setFixedSize(393, 300)
self.centralwidget = QtWidgets.QWidget(informationWindow)
self.centralwidget.setObjectName("centralwidget")
informationWindow.setStyleSheet("background-color: Cornflowerblue")
# Proceed button
self.proceedButton = QtWidgets.QPushButton(self.centralwidget)
self.proceedButton.setGeometry(QtCore.QRect(280, 250, 101, 27))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(12)
font.setBold(True)
font.setWeight(75)
self.proceedButton.setFont(font)
self.proceedButton.setStyleSheet("background-color: Silver")
self.proceedButton.setObjectName("proceedButton")
# passwordTF
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(20, 10, 350, 230))
self.textEdit.setObjectName("informationTF")
informationWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(loginWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 393, 21))
self.menubar.setObjectName("menubar")
informationWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(informationWindow)
self.statusbar.setObjectName("statusbar")
self.textEdit.setStyleSheet("background-color: White")
# Set Scroll Area
self.textEdit.append("This is an information manual to guide users through the use of the software.")
self.textEdit.append("\n")
self.textEdit.append("To use this software, the user must first parse the data from each evidence item which will store the data in Microsoft Excel table format in an external directory")
self.textEdit.append("In turn, the corresponding report generation options will become available one by one to enable report generation for that piece of evidence.")
self.textEdit.append("\n")
self.textEdit.append("Once each report has been generated, the user can then view a single report for one piece of evidence or view a full report containing all parsed evidence.")
self.textEdit.append("\n")
self.textEdit.append("These reports, much like the parsed forensic data are stored in a Forensic Reports directory.")
self.textEdit.append("\n")
self.textEdit.append("Please press the Proceed button to begin using the software.")
self.textEdit.setFont(font)
self.textEdit.setEnabled(False)
# This is where I am trying to put the textEdit text into a scrollArea to make it scrollable
self.scrollArea = QtWidgets.QScrollArea()
self.textEdit.setVerticalScrollBar(self.textEdit)
informationWindow.setStatusBar(self.statusbar)
self.retranslateUi(informationWindow)
QtCore.QMetaObject.connectSlotsByName(informationWindow)
# Function that sets the text on all the UI Buttons
def retranslateUi(self, loginWindow):
_translate = QtCore.QCoreApplication.translate
loginWindow.setWindowTitle(_translate("informationWindow", "User Manual"))
self.proceedButton.setText(_translate("informationWindow", "Proceed"))
# Event Handling Code Section
# Event Handling to open Main GUI Window
self.proceedButton.clicked.connect(self.openMainWindow)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
loginWindow = QtWidgets.QMainWindow()
ui = Ui_informationWindow()
ui.setupUi(loginWindow)
loginWindow.show()
sys.exit(app.exec_())
Stack Trace
self.textEdit.setVerticalScrollBar(self.textEdit)
TypeError: QAbstractScrollArea.setVerticalScrollBar(QScrollBar): argument 1 has unexpected type 'QTextEdit'

QTextEdit already has a QScrollBar so you should not add any, if you want the text not to be editable you should use setReadOnly(True) instead of setEnabled(False).
# passwordTF
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
[...]
self.textEdit.setFont(font)
self.textEdit.setReadOnly(True)

Related

PyQt5: Textbox's Not working in windows 11

I recently switched to windows 11 and decided to start a QyQt5 Project, I started to add buttons, which worked. But when I try to type in the textbox it will not display text and not type anything. The only thing it showed was that I had highlighted the textbox. Sadly there is no error or logs showing why it is behaving like this.
This is what happens:
And this is the code that I have used:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(332, 121)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.Download = QtWidgets.QPushButton(self.centralwidget)
self.Download.setGeometry(QtCore.QRect(240, 70, 75, 23))
self.Download.setObjectName("Download")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(20, 70, 75, 23))
self.pushButton.setObjectName("pushButton")
self.linkbox = QtWidgets.QPlainTextEdit(self.centralwidget)
self.linkbox.setGeometry(QtCore.QRect(23, 30, 291, 21))
self.linkbox.setToolTipDuration(-5)
self.linkbox.setLayoutDirection(QtCore.Qt.LeftToRight)
self.linkbox.setInputMethodHints(QtCore.Qt.ImhMultiLine|QtCore.Qt.ImhNoEditMenu)
self.linkbox.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.linkbox.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.linkbox.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.linkbox.setTabChangesFocus(True)
self.linkbox.setPlainText("")
self.linkbox.setObjectName("linkbox")
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "ChaosCapture"))
self.Download.setText(_translate("MainWindow", "Download"))
self.pushButton.setText(_translate("MainWindow", "Options"))
self.linkbox.setPlaceholderText(_translate("MainWindow", "Enter the download link here"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
You're using a very small height for the QPlainTextEdit, and it's completely covered by the horizontal scroll bar (which you've set as always on).
You are also not using layout managers, which is highly discouraged. For instance, the user can resize the window and make it smaller, so the widgets can become partially visible or completely hidden. Layout managers should be always preferred against predetermined geometries.
There are three things that should be done:
set a layout in Designer, in your case a grid layout would be fine: right click on an empty area of the window, open the "Lay out" menu and select "Lay Out in a Grid";
set an arbitrary minimum height for the plain text edit (so that you can resize the window in designer);
in the program script, set a minimum height for the widget based on the font metrics;
Note that the program script must be a separate file, not the one you're showing us (the pyuic file), which must never be manually edited for any reason (read more about using Designer).
The following code assumes that the pyuic file is named ui_mainwindow.py.
from PyQt5 import QtWidgets
from ui_mainwindow import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.linkbox.setMinimumHeight(
self.fontMetrics().height() +
self.linkbox.frameWidth() * 2 +
self.linkbox.document().documentMargin() * 2 +
self.linkbox.horizontalScrollBar().sizeHint().height()
)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
Note: hiding the vertical scroll bar is not a very good idea. You either use a QLineEdit, or you should at least make the QPlainTextEdit a bit higher (self.fontMetrics().height() * 2 + ...), otherwise it would be very confusing if the user presses the Enter key by mistake or pastes text with new lines.

How to link Qt Designer button to a function in a separate file [duplicate]

This question already has answers here:
QtDesigner changes will be lost after redesign User Interface
(2 answers)
Closed 2 years ago.
I'm new to Python and I've searched for an answer but couldn't find it (or rather couldn't properly implement it).
I've generated a window with a few buttons in QtDesigner's file named "arch.ui", converted to arch.py.
As I'll be updating GUI occasionally, I don't want to create functions in arch.py, so I've created a main.py file for that.
I've a problem with linking button click to a function => I try to link "btnSource" (from arch.py) to function "printMe" (in main.py).
Obviously it doesn't work. Any help welcome.
Here is generated Designer file:
# Form implementation generated from reading ui file 'arch.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(460, 233)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.btnSource = QtWidgets.QPushButton(self.centralwidget)
self.btnSource.setGeometry(QtCore.QRect(80, 60, 75, 23))
self.btnSource.setObjectName("btnSource")
self.lblSource = QtWidgets.QLabel(self.centralwidget)
self.lblSource.setGeometry(QtCore.QRect(180, 60, 511, 21))
self.lblSource.setObjectName("lblSource")
self.lblTarget = QtWidgets.QLabel(self.centralwidget)
self.lblTarget.setGeometry(QtCore.QRect(180, 120, 481, 16))
self.lblTarget.setObjectName("lblTarget")
self.btnTarget = QtWidgets.QPushButton(self.centralwidget)
self.btnTarget.setGeometry(QtCore.QRect(80, 120, 75, 23))
self.btnTarget.setObjectName("btnTarget")
self.btnGo = QtWidgets.QPushButton(self.centralwidget)
self.btnGo.setGeometry(QtCore.QRect(280, 120, 75, 23))
self.btnGo.setObjectName("btnGo")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 460, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.btnSource.setText(_translate("MainWindow", "Source"))
self.lblSource.setText(_translate("MainWindow", "TextLabel"))
self.lblTarget.setText(_translate("MainWindow", "TextLabel"))
self.btnTarget.setText(_translate("MainWindow", "Target"))
self.btnGo.setText(_translate("MainWindow", "Go"))
And here is my main.py file:
from PyQt5 import QtCore, QtGui, QtWidgets
from arch import Ui_MainWindow
import sys
app = QtWidgets.QApplication(sys.argv)
class myWindow(Ui_MainWindow):
def __init__(self):
super(myWindow, self).__init__()
self.btnSource.clicked.connect(self.btnSource.printMe)#
def printMe(self):
print('blah blah blah')
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
tl;dr
Subclass from both QMainWindow and Ui_MainWindow, and call setupUi from there; then create an instance of myWindow:
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setupUi(self)
self.btnSource.clicked.connect(self.printMe)
def printMe(self):
print('blah blah blah')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
mainWindow = MyWindow()
mainWindow.show()
sys.exit(app.exec_())
Explanation
Your code doesn't work for many reasons; while the main problem might be that you actually never created an instance of myWindow (about that, you should always use capitalized names for classes), making it completely useless, it wouldn't have worked anyway.
That's because you should not subclass from the ui class object, but from the QWidget descendant (QMainWindow, in your case) you're going to use.
The ui_* objects created from pyuic are only intended as a high level (and unmodified) interface to create the UI on top of a QWidget subclass.
Calling setupUi(something) actually creates all child widgets for the widget something, sets the layout and, possibly, automatically connects to slots with a compatible name, but that's all: in fact, if you closely look at the code from the ui file, it actually does nothing besides setupUi and retranslateUi (nor it should!): there's not even an __init__!
If you need to add interaction and create connections from signals to slot/functions, you should use the single/multiple inheritance approaches as explained in the official guide about using Designer with PyQt; the only other possibility is to use loadUi (while still subclassing from the base class) with the source .ui file:
from PyQt5 import QtWidgets, uic
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi('path/to/gui.ui', self)
self.someWidget.someSignal.connect(self.someSlot)
# ...
def someSlot(self, signalArguments, [...]):
# do something...
PS: for various reasons, it's usually better to run a QApplication only if the script is the one that's been run (hence the if __name__ ...), mostly because there should be just only one QApplication instance for every running program; in any case, it shouldn't be created before the class declarations (unless, you really know what you're doing); it's not a big deal in your case, but, as usual, better safe than sorry.

PyQt5 move window when dragging frame

hope you're all doing well during these difficult times.
I've added a frame at the top of my window, and the aim is to make it act like the bar you find at the top of most applications (Forgive me, I don't know my jargon just yet), where you're able to click on it and move the screen by moving your mouse around. So far I've only got the frame at the top and have done research into how I could achieve this result, I've looked at examples such this one, but it is only for when you click anywhere on the window, while I am looking for a method which only moves the screen when you are clicking the top frame.
My code is as follows
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowModality(QtCore.Qt.NonModal)
MainWindow.resize(909, 544)
MainWindow.setAnimated(True)
MainWindow.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.centralwidget = QtWidgets.QWidget(MainWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
self.centralwidget.setSizePolicy(sizePolicy)
self.centralwidget.setObjectName("centralwidget")
self.backgroundFrame = QtWidgets.QFrame(self.centralwidget)
self.backgroundFrame.setEnabled(True)
self.backgroundFrame.setGeometry(QtCore.QRect(-1, -1, 941, 551))
self.backgroundFrame.setStyleSheet("QLabel {\n"
"font-family: \"Microsoft Sans Serif\", sans-serif;\n"
"color: white;\n"
"}\n"
"\n"
"#backgroundFrame\n"
"{\n"
"background-color: #060321;\n"
"}\n"
"\n"
"#infoBar\n"
"{\n"
"background-color: #0F0334;\n"
"}")
self.backgroundFrame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.backgroundFrame.setFrameShadow(QtWidgets.QFrame.Raised)
self.backgroundFrame.setObjectName("backgroundFrame")
self.infoBar = QtWidgets.QFrame(self.backgroundFrame)
self.infoBar.setGeometry(QtCore.QRect(-10, -10, 921, 71))
self.infoBar.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.infoBar.setFrameShadow(QtWidgets.QFrame.Raised)
self.infoBar.setObjectName("infoBar")
self.exitButton = QtWidgets.QPushButton(self.infoBar)
self.exitButton.setGeometry(QtCore.QRect(874, 26, 31, 31))
self.exitButton.setText("")
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("Resources/Exit.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.exitButton.setIcon(icon)
self.exitButton.setIconSize(QtCore.QSize(32, 32))
self.exitButton.setObjectName("exitButton")
self.pushButton = QtWidgets.QPushButton(self.infoBar)
self.pushButton.setGeometry(QtCore.QRect(827, 30, 31, 31))
self.pushButton.setText("")
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap("Resources/Minimize.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.pushButton.setIcon(icon1)
self.pushButton.setIconSize(QtCore.QSize(32, 32))
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(self.infoBar)
self.pushButton_2.setGeometry(QtCore.QRect(26, 26, 34, 31))
self.pushButton_2.setText("")
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap("Resources/Settings.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.pushButton_2.setIcon(icon2)
self.pushButton_2.setIconSize(QtCore.QSize(32, 32))
self.pushButton_2.setObjectName("pushButton_2")
self.label = QtWidgets.QLabel(self.infoBar)
self.label.setGeometry(QtCore.QRect(390, 23, 145, 40))
self.label.setText("")
self.label.setPixmap(QtGui.QPixmap("Resources/Titan Window.png"))
self.label.setObjectName("label")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Titan"))
def mousePressEvent(self, event):
self.offset = event.pos()
def mouseMoveEvent(self, event):
x=event.globalX()
y=event.globalY()
x_w = self.offset.x()
y_w = self.offset.y()
self.move(x-x_w, y-y_w)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Thanks for the help and hope you and your friends and family stay safe during this time <3
The source of your problem is that you're probably editing the code generated by pyuic (or you're trying to mimick its behavior), which is something that should never be done. Those files must only be imported as modules, and should not be modified for no reason at all. To know how to correctly use ui files created with Designer, read more on using Designer.
The result of following this kind of approach, in the way you did it, is that the mousePressEvent and mouseMoveEvent will never be called.
The reason for this is that, in your code, those methods belong to the Ui_MainWindow class instance (the ui created near the end), which is nothing else than a basic Python object subclass: it does almost nothing, except build the UI on the QMainWindow instance in its setupUi(); that QMainWindow instance is the one that should receive those events instead, and those methods should be implemented there.
Or, at least, theoretically. That's because the mousePressEvent and mouseMoveEvent you'll want to catch should actually be received from the infoBar object, not the main window.
A common solution is to subclass the widget that will receive those events and implement those methods there, but since you're probably using an ui created in Designer that's not as immediate as it seems (more about this later).
The most direct approach, is to install an event filter on the watched widget, so that we can filter those events and react to them if we want to.
In the following example I'm assuming that you've recreated the file with pyuic, naming it ui_mainwindow.py:
from PyQt5 import QtCore, QtGui, QtWidgets
from ui_mainwindow import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.offset = None
# install the event filter on the infoBar widget
self.infoBar.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.infoBar:
if event.type() == QtCore.QEvent.MouseButtonPress:
self.offset = event.pos()
elif event.type() == QtCore.QEvent.MouseMove and self.offset is not None:
# no need for complex computations: just use the offset to compute
# "delta" position, and add that to the current one
self.move(self.pos() - self.offset + event.pos())
# return True to tell Qt that the event has been accepted and
# should not be processed any further
return True
elif event.type() == QtCore.QEvent.MouseButtonRelease:
self.offset = None
# let Qt process any other event
return super().eventFilter(source, event)
About the usage of subclasses in Designer mentioned above: it is possible to create custom subclasses and "use" them in Designer. You have to create a subclass in your code, and then add to your ui its most close ancestor (in your case, a QFrame), which will work as a sort of placeholder, then promote it. Do some research about "promoted widgets" to know more.
An important suggestion: using fixed sizes and positions is not a good choice, and layout managers should always be preferred instead. The reason for this is that what you see on your computer will almost always be shown very differently in other's (amongst the most common reasons: different screen sizes, default settings, DPI, fonts, etc), and that might result in your GUI becoming unusable since widgets can become invisible, overlapped or unreadable. There are very, very rare situations for which fixed geometries could be used (but when programmers realizes that they need those, it's possibly due to bad code/ui design), and that's certainly not your case.
For instance, if I try to resize your window to a smaller size than the original, the top right buttons become inaccessible. Also, the button on the right near the "exit" one, is not corretly aligned. If you use a layout manager (like a horizontal layout on the infoBar widget), it would solve all those problems automatically.

pyqt5 with .ui file tab menu padding and window size in designer

my running app
here how i created my app step by step:
i am created a tab through designer tool with 3 pages:
a) Account
b) Security c) Performance and save as tab.ui
then i generate tab.py file from tab.ui by using pyuic5
now i added some classes manually in tab.py file >> TabBar, TabWidget and ProxyStyle classes, then change self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) to self.tabWidget = TabWidget(self.centralwidget), and add àpp.setStyle(ProxyStyle()) after app = QtWidgets.QApplication(sys.argv)
my code is working as i shown in pic, but my tab menu padding is not looking good and window size is not full (fit with window if i maximized). someone please look in to it.
Now my question is if we add some other elements in tab.ui and if i generate tab.py file again my previous tab.py code is overlapped which classes i manually added. this is not fine.
i know i am wrong .but tell me the procedure and give me a proper structure than i can start to create my tool in right way.
here is my tab.py code:
from PyQt5 import QtCore, QtGui, QtWidgets
class TabBar(QtWidgets.QTabBar):
def tabSizeHint(self, index):
s = QtWidgets.QTabBar.tabSizeHint(self, index)
s.transpose()
return s
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
opt = QtWidgets.QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt)
painter.save()
s = opt.rect.size()
s.transpose()
r = QtCore.QRect(QtCore.QPoint(), s)
r.moveCenter(opt.rect.center())
opt.rect = r
c = self.tabRect(i).center()
painter.translate(c)
painter.rotate(90)
painter.translate(-c)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt);
painter.restore()
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QTabWidget.__init__(self, *args, **kwargs)
self.setTabBar(TabBar(self))
self.setTabPosition(QtWidgets.QTabWidget.West)
class ProxyStyle(QtWidgets.QProxyStyle):
def drawControl(self, element, opt, painter, widget):
if element == QtWidgets.QStyle.CE_TabBarTabLabel:
ic = self.pixelMetric(QtWidgets.QStyle.PM_TabBarIconSize)
r = QtCore.QRect(opt.rect)
w = 0 if opt.icon.isNull() else opt.rect.width() + self.pixelMetric(QtWidgets.QStyle.PM_TabBarIconSize)
r.setHeight(opt.fontMetrics.width(opt.text) + w)
r.moveBottom(opt.rect.bottom())
opt.rect = r
QtWidgets.QProxyStyle.drawControl(self, element, opt, painter, widget)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
#self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.tabWidget = TabWidget(self.centralwidget)
self.tabWidget.setGeometry(QtCore.QRect(0, 40, 800, 541))
self.tabWidget.setStyleSheet("\n"
" QTabBar::tab { height: 100px; width: 50px; }\n"
" QTabBar::tab {background-color: rgb(34, 137, 163);}\n"
" QTabBar::tab:selected {background-color: rgb(48, 199, 184);}\n"
" QTabWidget>QWidget>QWidget{background: WHITE;}\n"
" ")
self.tabWidget.setTabPosition(QtWidgets.QTabWidget.West)
self.tabWidget.setObjectName("tabWidget")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.groupBox_3 = QtWidgets.QGroupBox(self.tab)
self.groupBox_3.setGeometry(QtCore.QRect(20, 10, 681, 80))
self.groupBox_3.setObjectName("groupBox_3")
self.groupBox_4 = QtWidgets.QGroupBox(self.tab)
self.groupBox_4.setGeometry(QtCore.QRect(20, 100, 681, 80))
self.groupBox_4.setObjectName("groupBox_4")
self.tabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.groupBox = QtWidgets.QGroupBox(self.tab_2)
self.groupBox.setGeometry(QtCore.QRect(30, 20, 251, 191))
self.groupBox.setObjectName("groupBox")
self.groupBox_2 = QtWidgets.QGroupBox(self.tab_2)
self.groupBox_2.setGeometry(QtCore.QRect(290, 20, 271, 191))
self.groupBox_2.setObjectName("groupBox_2")
self.tabWidget.addTab(self.tab_2, "")
self.tab_3 = QtWidgets.QWidget()
self.tab_3.setObjectName("tab_3")
self.tabWidget.addTab(self.tab_3, "")
self.frame = QtWidgets.QFrame(self.centralwidget)
self.frame.setGeometry(QtCore.QRect(-1, 0, 801, 41))
self.frame.setStyleSheet("background-color: rgb(59, 118, 150);")
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.comboBox = QtWidgets.QComboBox(self.frame)
self.comboBox.setGeometry(QtCore.QRect(50, 10, 141, 22))
self.comboBox.setObjectName("comboBox")
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(2)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.groupBox_3.setTitle(_translate("MainWindow", "GroupBox"))
self.groupBox_4.setTitle(_translate("MainWindow", "GroupBox"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Account"))
self.groupBox.setTitle(_translate("MainWindow", "GroupBox"))
self.groupBox_2.setTitle(_translate("MainWindow", "GroupBox"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Security"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Performance"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setStyle(ProxyStyle())
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
There are two main and common issues with your approach.
*NEVER* edit the output of pyuic
This happens very often: you get a well formatted python file, and are led to think that you can use that file to write your program.
You may have noticed the warning in that file too:
# WARNING! All changes made in this file will be lost!
As you've already found out, as soon as you need to modify the UI, you'll be caught in the mess of merging the new modifications with the code you already wrote.
The bottom line is that you've to think about those files as resource files (not unlike an image, a database or a configuration file), and they have to be used as such: since they are python files, they can be imported as a module, and their classes have to be used to build the interface of your actual widgets and windows.
There are three main ways to do that, all of them explained in the using Designer documentation.
I strongly suggest you to use the third method (the multiple inheritance approach), as it allows you to have references to UI objects as direct attributes of the instance (self.tabWidget, etc.).
Alternatively, you can completely avoid the pyuic approach at all, and directly import the .ui files using the loadUI function from the uic module.
from PyQt5 import QtWidgets
from PyQt5 import uic
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUI('mywindow.ui', self)
This allows you to save some time, since you don't have to rebuild the file each time you make modifications; also, sometimes one may forget to do that step, which might create some confusion.
Just remember that that path is always relative to the file that contains the class that will load it (and absolute paths should never be used).
Always use a layout manager
Using fixed widget sizes and positions is usually discouraged, as what you see on your computer will probably be very, if not drastically, different on another's.
That can depend on a series of aspects, but most importantly:
operating system (and its version);
screen settings (resolution, standard or High DPI - such as retina screens);
user customization (default font sizes, some "themes" that use different margins and spaces between objects);
All this potentially makes a fixed layout unusable, as widgets could overlap or become invisible because hidden by others or because the available screen size is not enough to show the interface as you designed it.
Using layout managers simplifies all this, because they will automatically resize the interface ensuring that all widgets will at least use as much space as they need, leaving space for those that might take advantage in using more size.
While this could seem a bit more difficult to manage (especially for complex interfaces), it's just a matter of habit.
Most of the times you'll end up with nested layouts, but there's nothing wrong with them.
In your case, you'll probably use something like the following structure:
a vertical layout as the main layout;
a horizontal layout for the top;
a horizontal spacer, with a fixed width;
the combobox;
another horitonzal spacer for the right margin;
the tabwidget;
a vertical layout for the first tab;
the two vertically aligned group boxes
a horizontal layout for the second tab;
the two horizontally aligned group boxes;
...
As a final note, I don't think you need to use the proxystyle to adjust the size of the rectangle: as you can see, the text is cropped, and that's due to the way you paint and rotate, which also leads to the painting issue of the tab background.
Remove the sizes from the stylesheet and the proxystyle, then use those size values within tabSizeHint():
def tabSizeHint(self, index):
s = QtWidgets.QTabBar.tabSizeHint(self, index)
s.transpose()
s.setHeight(max(s.height(), 50))
s.setWidth(max(s.width(), 100))
return s
Since there's a small overlap between the tabbar and the tabwidget contents, you can ensure that they are correctly aligned by setting the offset of the ::pane and ::tab-bar pseudo elements:
QTabWidget::pane {top: 0px;}
QTabWidget::tab-bar {right: 0px;}
Also, ensure to apply the top frame stylesheet to QFrame objects only (and possibly its object name), otherwise every child widget will inherit it.
self.frame.setStyleSheet("QFrame#frame {background-color: rgb(59, 118, 150);}")
With all of this in mind, you'll end up with a cleaner look, correct tab widget/bar painting and positioning, layout flexibility and, most importantly, the possibility to edit the UI on the fly without further problems and headaches :-)

Implementing a preferences dialog window in PyQt

I'm trying to use an interface I designed in Qt designer as a means of letting the user edit preferences for my program.
I am currently able to display the GUI I made by connecting the following function to the preferences menu option:
def preferences(self):
preferences_dialog = QtGui.QDialog()
preferences_dialog.ui = Ui_Preferences()
preferences_dialog.ui.setupUi(preferences_dialog)
preferences_dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
preferences_dialog.exec_()
My question is this: what is a good way to implement this so that I can use the fields in the GUI to change values in my config file?
I also want to display the pre-existing values in the boxes before they are changed.
Should I make a new class that uses the above function as its __init__ method? I would imagine I might need a class that handles all of the processes for the window. Also, I am unsure of a good way to pass data between the file and the GUI without a bunch of specific code.
Use QSettings. Here's an example in PyQt5.
First, the main window's ui definition
# file ui_main.py
from PyQt5 import QtCore, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(584, 897)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 584, 21))
self.menubar.setObjectName("menubar")
self.menuPreferences = QtWidgets.QMenu(self.menubar)
self.menuPreferences.setObjectName("menuPreferences")
MainWindow.setMenuBar(self.menubar)
self.setPreferencesAction = QtWidgets.QAction(MainWindow)
self.setPreferencesAction.setObjectName("setPreferencesAction")
self.menuPreferences.addAction(self.setPreferencesAction)
self.menubar.addAction(self.menuPreferences.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.menuPreferences.setTitle(_translate("MainWindow", "Settings"))
self.setPreferencesAction.setText(_translate("MainWindow", "Preferences"))
and second, the preferences dialog ui definition:
# file ui_dialog.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(508, 300)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setGeometry(QtCore.QRect(150, 250, 341, 32))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.sl_value = QtWidgets.QSlider(Dialog)
self.sl_value.setGeometry(QtCore.QRect(220, 120, 161, 31))
self.sl_value.setOrientation(QtCore.Qt.Horizontal)
self.sl_value.setObjectName("sl_value")
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
The MainWindow keeps track of the configuration in a QSettings object, which is uniquely defined (and accessed) using the application and company strings fed into its constructor.
# file main.py
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.settings = QSettings(COMPANY_NAME, APPLICATION_NAME)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
when the preferences dialog box is triggered, the settings are loaded and passed along to the PreferencesDialog. If the Dialog returns successfully, the new settings are saved and written to storage using del
#pyqtSlot(bool)
def on_setPreferencesAction_triggered(self, triggered):
settings = self.settings
default_config_value = settings.value(CONFIG_KEY_1, defaultValue=None, type=str)
preference_dialog = PreferencesDialog(default_config_value=default_config_value, parent=self)
if preference_dialog.exec():
settings.setValue(CONFIG_KEY_1, preference_dialog.preferences[CONFIG_KEY_1])
# this writes the settings to storage
del settings
The PreferencesDialog constructor sets the values according to the parameters it receives, and a a pyqtSlot is attached to the appropriate signal to save the values in a dictionary.
To run the demo:
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Categories

Resources