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_())
Related
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.
I want to update the label with images but when I start to update the label, nothing works until the updating is done for example I want to pause updating or closing the window. It worked perfect when I used tkinter but it doesn't work same method in Pyqt5. Here is the code I have
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QDialog, QMainWindow,QMessageBox,QGroupBox,QSlider,QPushButton,QRadioButton,QLabel,QCheckBox,QFrame,QWidget,QTabWidget,QProgressBar,QTextBrowser,QTableWidget
from PyQt5.QtCore import QCoreApplication, Qt, QSize,QThread, QRect,QSize
from PyQt5.QtGui import QIcon, QFont,QPixmap,QIcon
import cv2
import os
import glob
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 800)
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(165, 125, 61, 16))
self.label.setObjectName("label")
self.btn1 = QPushButton(Form)
self.image_frame = QtWidgets.QLabel(Form)
self.btn1.setGeometry(QRect(350, 200, 111, 31))
self.btn1.clicked.connect(self.load_images_from_folder)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.btn1.setText(_translate("Form","Btn"))
def load_images_from_folder(self):
for filename in glob.glob('path/*.jpg'):
img = cv2.imread(filename, cv2.IMREAD_UNCHANGED)
img = cv2.resize(img,(200,200))
print(filename)
self.showimg(img)
def showimg(self,img):
self.image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
self.image = QtGui.QImage(self.image.data, self.image.shape[1], self.image.shape[0], QtGui.QImage.Format_RGB888).rgbSwapped()
self.image_frame.setPixmap(QtGui.QPixmap.fromImage(self.image))
self.image_frame.setGeometry(QRect(70, 310, 461, 441))
Form.update()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
After I clicked Btn label updates but I can't click Btn again until the updating is finished
In UI environments, control has to be returned as quickly as possible to the main thread. If you have lots of image processing, this means that the function that loads the image (which also calls the function that sets the image) will not release control to the main thread until it's finished.
If you don't, the result is that the UI will be completely frozen (no updates, no user input) until that function returns.
The solution is to use threads, but you also need to consider that UI elements are involved, and no access is allowed to UI elements from external threads. So, the actual solution is to use QThreads and custom signals: Qt signals are thread safe, and they are queued whenever two objects belonging to different threads are trying to communicate.
In this specific case, the thread will be a QThread with a custom signal (which has to be emitted whenever a new image is ready):
class ImageLoader(QtCore.QThread):
imageReady = QtCore.pyqtSignal(object)
def __init__(self):
super().__init__()
self.images = []
def run(self):
while True:
while self.images:
img = cv2.imread(self.images.pop(), cv2.IMREAD_UNCHANGED)
img = cv2.resize(img,(200,200))
self.imageReady.emit(img)
def loadImages(self, imageList):
# if you want to *clear* the current queue
self.images[:] = imageList
# otherwise, just append:
# self.images.extend(imageList)
The implementation then requires to create the thread instance and add the images to the queue in load_images_from_folder:
class Ui_Form(object):
def setupUi(self, Form):
# ...
self.imageLoader = ImageLoader()
self.imageLoader.imageReady.connect(self.showimg)
self.imageLoader.start()
def load_images_from_folder(self):
self.imageLoader.loadImages(glob.glob('path/*.jpg'))
Important notes:
editing files generated with pyuic is considered bad practice, as it almost always leads to unexpected behavior and bugs that are difficult to track; the comment in the header of those files is pretty clear: you should not edit them; instead, create those files, leave them as they are, and then import them in your main script, as explained in the official guidelines about using Designer;
accessing Form (Form.update()), which is fundamentally a global variable, is another bad practice; just like editing pyuic files, it's something that should never be done unless you really know what you're doing (and if you do know, you won't probably do it at all); in any case, setting the pixmap on a label automatically schedules an update, so that function call is completely useless since we're properly using threading, which allows the main event loop to request updates on the UI;
setting fixed geometries is yet another practice usually discouraged; learn how to use layout managers and how to use them in Designer instead; if you want the QLabel to have a fixed size, use setFixedSize();
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 :-)
I'm getting this weird result when using QMenuBar I've used this exact code before for the QMenuBar and it worked perfectly. But it doesn't show more than 1 QMenu
This is my code:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
from functools import partial
class MainMenu(QWidget):
def __init__(self, parent = None):
super(MainMenu, self).__init__(parent)
# background = QWidget(self)
lay = QVBoxLayout(self)
lay.setContentsMargins(5, 35, 5, 5)
self.menu()
self.setWindowTitle('Control Panel')
self.setWindowIcon(self.style().standardIcon(getattr(QStyle, 'SP_DialogNoButton')))
self.grid = QGridLayout()
lay.addLayout(self.grid)
self.setLayout(lay)
self.setMinimumSize(400, 320)
def menu(self):
menubar = QMenuBar(self)
viewMenu = menubar.addMenu('View')
viewStatAct = QAction('Dark mode', self, checkable=True)
viewStatAct.setStatusTip('enable/disable Dark mode')
viewMenu.addAction(viewStatAct)
settingsMenu = menubar.addMenu('Configuration')
email = QAction('Set Email', self)
settingsMenu.addAction(email)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainMenu()
main.show()
sys.exit(app.exec_())
Result:
I am aware that I am using QWidget when I should be using QMainWindow But is there a workaround???
(I apologize in advance for the terrible quality of the image, there is no good way to take a picture of a QMenuBar)
The problem is that with a QWidget you are not using the "private" layout that a QMainWindow has, which automatically resizes specific children widgets (including the menubar, the statusbar, the dock widgets, the toolbars and, obviously, the "centralWidget").
Remember that a QMainWindow has its own layout (which can't and shouldn't be changed), because it needs that specific custom layout to lay out the aforementioned widgets. If you want to set a layout for the main window, you'll need to apply it to its centralWidget.
Read carefully how the Main Window Framework behaves; as the documentation reports:
Note: Creating a main window without a central widget is not supported. You must have a central widget even if it is just a placeholder.
In order to work around that when using a basic QWidget, you'll have to manually resize the children widgets accordingly. In your case, you only need to resize the menubar, as long as you have a reference to it:
def menu(self):
self.menubar = QMenuBar(self)
# any other function has to be run against the *self.menubar* object
viewMenu = self.menubar.addMenu('View')
# etcetera...
def resizeEvent(self, event):
# calling the base class resizeEvent function is not usually
# required, but it is for certain widgets (especially item views
# or scroll areas), so just call it anyway, just to be sure, as
# it's a good habit to do that for most widget classes
super(MainMenu, self).resizeEvent(event)
# now that we have a direct reference to the menubar widget, we are
# also able to resize it, allowing all actions to be shown (as long
# as they are within the provided size
self.menubar.resize(self.width(), self.menubar.height())
Note: you can also "find" the menubar by means of self.findChild(QtWidgets.QMenuBar) or using the objectName, but using an instance attribute is usually an easier and better solution.
Set minimum width
self.setMinimumSize(320,240)
I recently started using qt to build a python GUI. I have two problems I can't quite find the solutions to. the code below is a sample of what I need to build.
1: Check which radio button from a list of radio buttons in a vertical layout has been clicked. In the GUI it only selects one radio button out of all others available in the layout. How do I perceive which has been clicked?
2:I would like to add the clicked value to a JSON object but I believe that is a simple if statement of if this then that. Unless it's more complicated in which case please push me in the right direction.
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(596, 466)
self.verticalLayoutWidget = QtWidgets.QWidget(Dialog)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(180, 70, 61, 80))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.that = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.that.setObjectName("that")
self.verticalLayout.addWidget(self.that)
self.thi = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.thi.setObjectName("thi")
self.verticalLayout.addWidget(self.thi)
self.sure = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.sure.setObjectName("sure")
self.verticalLayout.addWidget(self.sure)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.that.setText(_translate("Dialog", "that"))
self.thi.setText(_translate("Dialog", "this"))
self.sure.setText(_translate("Dialog", "sure"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
There is a nice way to solve this using Qt Designer, which allows you to group your buttons into a QButtonGroup, and then connect to its buttonClicked signal to get the button that was clicked.
All you need to do is, in Qt Designer, select all the buttons (using Ctrl+click), then right-click one of the buttons and select Assign to button group -> New button group. This will create a new button-group object and automatically add all the buttons to it.
After re-generating your gui module, you can then do somehting like this:
ui.radioButtonGroup.buttonClicked.connect(radioButtonClicked)
def radioButtonClicked(button):
print(button.text())
I think you need something like this (not tested)
# Set Default
self.thi.setChecked(True)
# create a signal
QtCore.QObject.connect(self.that,
QtCore.SIGNAL("toggled(bool)"),
self.radio_clicked)
then create a function
def self.radio_clicked(self):
print 'ive been clicked' # work from here