PyQt5 move window when dragging frame - python

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.

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.

PyQt5: How to Re-Dock back a floated QDockWidget via a QPushButton?

I which to bring back to initial state an Undocked or floated QDockWidget with a QPushButton.
from PyQt5 import QtCore, QtGui, QtWidgets
class Mainwindow(object):
def setupUi(self, window):
window.setObjectName("window")
window.resize(309, 148)
self.centralwidget = QtWidgets.QWidget(window)
self.centralwidget.setObjectName("centralwidget")
self.Undock_btn = QtWidgets.QPushButton(self.centralwidget)
self.Undock_btn.setGeometry(QtCore.QRect(4, 4, 100, 22))
self.Undock_btn.setStyleSheet("background: rgba(255, 217, 90, 255)\n")
self.Undock_btn.setObjectName("Undock_btn")
self.ReDock_btn = QtWidgets.QPushButton(self.centralwidget)
self.ReDock_btn.setGeometry(QtCore.QRect(110, 4, 96, 22))
self.ReDock_btn.setStyleSheet("background:rgba(9, 17, 188, 109);")
self.ReDock_btn.setObjectName("ReDock_btn")
self.dockw = QtWidgets.QDockWidget(self.centralwidget)
self.dockw.setTitleBarWidget(None)
self.dockw.setGeometry(QtCore.QRect(4, 34, 200, 110))
self.dockw.setStyleSheet("background:rgba( 0,188, 0, 29);\n")
self.dockw.setObjectName("dockw")
self.dockWidgetContents = QtWidgets.QWidget()
self.dockWidgetContents.setObjectName("dockWidgetContents")
self.dockw.setWidget(self.dockWidgetContents)
window.setCentralWidget(self.centralwidget)
self.retranslateUi(window)
QtCore.QMetaObject.connectSlotsByName(window)
#-----------------------------------------------------------------
self.Undock_btn.setCheckable(True)
self.connexions()
def connexions(self):
self.Undock_btn.clicked.connect(self.Method_Float_it)
self.ReDock_btn.clicked.connect(self.Method_BringBack)
def Method_Float_it(self):
print("Method_Float_it")
self.dockw.setFloating(True)
if self.dockw.isFloating():
print("is Floating now...")
return True
def Method_BringBack(self):
self.dockw.setFloating(False)
self.dockw.RestoreState(True)
def retranslateUi(self, window):
_translate = QtCore.QCoreApplication.translate
window.setWindowTitle(_translate("window", "GUI"))
self.Undock_btn.setText(_translate("window", "UnDock Button"))
self.ReDock_btn.setText(_translate("window", "Bring back button"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
ui = Mainwindow()
ui.setupUi(window)
window.show()
sys.exit(app.exec_())
image of when it's floated after pressed QPushButton
The Problem is: after I undocked the QDockWidget with first button, the second button fails to bring it back to normal (initial state)
The "main" issue is that you're not correctly adding the dock widget to the window, as the right way to do it is using the addDockWidget() method of QMainWindow.
What you actually did was to create the dock widget as a child of the central widget, and that's not a valid approach.
Your first function "works" just because when setFloating(True) is called, QDockWidget changes its window flags and ensures that it becomes a top level window.
The restore function doesn't work because the dock widget has never been correctly added to the main window, so it has no reference to know where it should "dock back".
The solution, theoretically, would be to add this line after the dock widget is created:
window.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.dockw)
But, it would be only a partial solution, as your code has two other major problems.
You are not using layout managers; this will become a serious problem whenever any resizing happens to or inside the window, including trying to drag a floating dock widget on the sides of the window: for instance, if the window is not wide enough, dragging the dock widget on the side will result in partially (or completely) hiding its buttons;
You are modifying a file generated by pyuic; this is considered bad practice for lots of reasons, and while your program "works" right now, sooner or later (but most probably sooner) you'll face unexpected behavior and confusion about the implementation; those files are only intended to be imported, as explained in the guidelines about using Designer;
There are two other (relatively) minor issues:
I don't know what RestoreState is, but it certainly is not a member of QDockWidget, nor of Qt with the uppercase R, as the only restoreState() functions in Qt (for QMainWindow, QSplitter, etc) require a QByteArray, not a bool;
only classes and constants should have capitalized names, not functions (nor variables);
I fixed your code, it is working now as expected. All variables functions and methods should be either some_function_name or someFunctionName, the former is called C style and the latter Java style. Further, your class has to inherit from QMainWindow, and a QLayout has to be added to every QWidget. Then, the subwidgets are added to the QLayout with QLayout.addWidget. Following the code, where i added some comments for explanation.
#!/usr/bin/python3
#-*-coding: utf-8-*-
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Mainwindow(QtWidgets.QMainWindow):
def setupUI(self):
self.setObjectName("window")
self.resize(309, 148)
self.centralWid = QtWidgets.QWidget(self) # not naming it centralWidget, because that would override the centralWidget() function of QMainWindow
self.centralWid.setObjectName("centralwidget")
self.centralLay = QtWidgets.QHBoxLayout(self.centralWid) # create a layout
self.centralWid.setLayout(self.centralLay) # and set it on the central widget
self.setCentralWidget(self.centralWid) # set centralWidget as the centralWidget of the window
self.undockButton = QtWidgets.QPushButton(self.centralWid)
self.undockButton.setStyleSheet("background: rgba(255, 217, 90, 255);")
self.undockButton.setObjectName("undockbutton")
self.centralLay.addWidget(self.undockButton)
self.redockButton = QtWidgets.QPushButton(self.centralWid)
self.redockButton.setStyleSheet("background: rgba(9, 17, 188, 109);")
self.redockButton.setObjectName("redockButton")
self.centralLay.addWidget(self.redockButton)
self.dock = QtWidgets.QDockWidget("Dock title", self.centralWid)
self.dock.setTitleBarWidget(None)
self.dock.setStyleSheet("background: rgba( 0,188, 0, 29);")
self.dock.setObjectName("dock")
self.dockContents = QtWidgets.QWidget()
self.dockContents.setObjectName("dockcontents")
self.dock.setWidget(self.dockContents)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.dock)
self.translateUI()
QtCore.QMetaObject.connectSlotsByName(self)
self.connectSlots()
def connectSlots(self):
self.undockButton.clicked.connect(self.undock)
self.redockButton.clicked.connect(self.redock)
def undock(self):
self.dock.setFloating(True)
def redock(self):
if self.dock.isFloating():
self.dock.setFloating(False)
def translateUI(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("window", "Window"))
self.undockButton.setText(_translate("window", "Undock"))
self.redockButton.setText(_translate("window", "Redock"))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = Mainwindow()
mainWin.setupUI()
mainWin.show()
sys.exit(app.exec_())

How to access MainWindow.Widget from a subclass?

I have 3 Qlabels: label1, label2 and label3.
My idea is this: when mouse hover label1 or label2, label3.text will show 'mouse on label1' or 'mouse on label2' according to which label got mouse hovered.
I created a subclass 'CustomLabel' for label1 and label2, where I define the enterEvent function.
The problem is that I can't access label3 from that class.
MainWindow.ui.label3 is impossible to reach!
Here is the code, everything is working except that one line of code where I didnt manage to get access to label3.
I am a very beginner so probably I am missing something very simple.
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(688, 446)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label1 = CustomLabel(self.centralwidget)
self.label1.setGeometry(QtCore.QRect(110, 110, 121, 31))
self.label1.setObjectName("label1")
self.label2 = CustomLabel(self.centralwidget)
self.label2.setGeometry(QtCore.QRect(320, 110, 121, 31))
self.label2.setObjectName("label2")
self.label3 = QtWidgets.QLabel(self.centralwidget)
self.label3.setGeometry(QtCore.QRect(190, 280, 121, 31))
self.label3.setObjectName("label3")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label1.setText(_translate("MainWindow", "Label1"))
self.label2.setText(_translate("MainWindow", "Label2"))
self.label3.setText(_translate("MainWindow", "Label3"))
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
class CustomLabel(QtWidgets.QLabel):
def __init__(self,texte):
Custom_font = QtGui.QFont()
Custom_font.setPointSize(14)
super(CustomLabel,self).__init__(texte)
self.setFont(Custom_font)
def enterEvent(self,e):
print('here is ',self.text())
MainWindow.ui.label3.setText('mouse on ', self.text) # Error on this line, 'MainWindow' has no attribute ui
#MainWindow.label3.setText('mouse on ', self.text) # Error here too, 'MainWindow' has no attribute label3
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
"MainWindow.ui.label3 is impossible to reach" because label3 does not exist, nor does ui.
MainWindow is a class, a "template" for an instance.
So, the MainWindow class object has no attribute called ui (therefore, no ui.label3 also), but the mainWindow instance you create near the end of your code does.
To achieve what you want, there are at least two methods.
Use signals/slots to allow communication between the instances
Create a signal for the CustomLabel class, and emit it whenever the mouse enters it; then connect that signal from the main window:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.label1.entered.connect(self.labelEntered)
self.ui.label2.entered.connect(self.labelEntered)
def labelEntered(self, label):
self.ui.label3.setText('mouse on {}'.format(label.text()))
class CustomLabel(QtWidgets.QLabel):
entered = QtCore.pyqtSignal(object)
def __init__(self,texte):
Custom_font = QtGui.QFont()
Custom_font.setPointSize(14)
super(CustomLabel,self).__init__(texte)
self.setFont(Custom_font)
def enterEvent(self,e):
self.entered.emit(self)
Use an event filter to catch events
In this case, we install an event filter on the widgets we want to watch for events, and if that event is a mouse enter one, we'll update the third label accordingly:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.label1.installEventFilter(self)
self.ui.label2.installEventFilter(self)
def eventFilter(self, source, event):
if isinstance(source, CustomLabel) and event.type() == QtCore.QEvent.Enter:
# the "source" of the event is one of our custom labels, and the event
# type is an "Enter" one, so let's update the other label
self.ui.label3.setText('mouse on {}'.format(source.text()))
return super(MainWindow, self).eventFilter(source, event)
As a side note, it seems that you're trying to edit the contents of a file generated from pyuic (or, at least, you're probably trying to mimick their behavior). This is something that should never be done, as those files are only meant to be used as imported modules in your actual program (and their mimicking their behavior is not the best way to create your GUI from code).
Read more about using Designer to understand the correct ways to use files created by Designer.
If you need to extend the default Qt widgets by subclassing and you need to use those classes on Designer, do some research about using "promoted widgets".

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 :-)

Adding QTextEdit to QScrollArea

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)

Categories

Resources