Why does QTableView have blank margins and how can I remove them? - python

The following PyQt program produces a window containing a QTableView with a margin space to its bottom and right (but not top and left) - even though the main-window is told to adjust itself to its contents size:
I was expecting to see:
If I increase the size of the window from the original position, I see:
What is the purpose of that region? Can it be eliminated? If so, how?
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QApplication, QMainWindow, QTableView
class TableModel(QtCore.QAbstractTableModel):
def __init__(self):
super().__init__()
def data(self, index, role=None):
if role == Qt.DisplayRole:
return 42
def rowCount(self, index):
return 3
def columnCount(self, index):
return 4
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.table = QTableView()
self.model = TableModel()
self.table.setModel(self.model)
self.table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
self.setCentralWidget(self.table)
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

The blank areas are normal and expected if you don't specify how the elements of the table should be resized. There are many different configurations to suit different use-cases, so you may need to experiment a little to get the exact behaviour you want.
The initial margins along the right and bottom edges are there to allow for scrollbars. If you don't want scrollbars, you can switch them off like this:
self.table.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarAlwaysOff)
self.table.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarAlwaysOff)
However, the blank areas will still be there when the window is resized - but you can deal with that by setting the section resize mode, like this:
self.table.horizontalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.Stretch)
self.table.verticalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.Stretch)
This might result in an unexpected initial size for the window, so it may be advisable to set an appropriate default:
self.resize(400, 200)
For further details, see the documentation for QTableView and QHeaderView.

Related

PYQT5 set all labels to black

I have several checkboxes which control various procedures, once the code has run all checkboxes which were successful are coloured green e.g. self.cbMarkerDetection.setStyleSheet("color: green;") as it goes to the next job I want to reset the colour back to black. Obviously I could do self.cbMarkerDetection.setStyleSheet("color: black;") for every checkbox... but I'm hoping there is some way to do something like:
for checkbox in allcheckboxes:
self.checkbox.setStyleSheet("color: black;")
A simple coded example, two check boxes, when selected it goes green in colour, when I click the other check box I want all to change back to black, imagine checkboxes a-g, when checkbox (or a button if you like) is selected/checked the selected goes green when checkbox z (or a button) is selected/clicked all the existing check boxes turn black. As stated above, I could list all the checkboxes and set them black, but I'm looking for a quicker way with less code:
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QLabel, QCheckBox, QWidget
from PyQt5.QtCore import QSize
class ExampleWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(140, 40))
self.setWindowTitle("Checkbox")
self.a = QCheckBox("box1",self)
self.a.stateChanged.connect(self.clickBox)
self.a.move(20,20)
self.a.resize(320,40)
self.b = QCheckBox("box2",self)
self.b.stateChanged.connect(self.clickBox)
self.b.move(20,80)
self.b.resize(320,40)
def clickBox(self, state):
if state == QtCore.Qt.Checked:
print('Checked')
self.checkbox.setStyleSheet("color: green;")
else:
print('Unchecked')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = ExampleWindow()
mainWin.show()
sys.exit( app.exec_() )
The simplest solution is to use findChildren():
for checkbox in self.findChildren(QCheckBox, options=Qt.FindDirectChildrenOnly):
checkbox.setStyleSheet("color: black;")
If you have other checkboxes in your widget that should not included in the list, you can create an empty subclass and use that for the filter:
class MyCheckBox(QCheckBox): pass
# ...
self.a = MyCheckBox("box1", self)
# etc...
Then you use findChildren with the custom class name:
for checkbox in self.findChildren(MyCheckBox, options=Qt.FindDirectChildrenOnly):
checkbox.setStyleSheet("color: black;")
Note that using fixed geometries is discouraged, and you should prefer layout managers instead.

QDockWidget QSizePolicy to get side dock to maintain it's sizeHint unless manually resized?

In the example below, the behavior I want is when resizing the QMainWindow it should give as much space as possible to the left widget and keep the right widget at a width of 200px (the sizeHint), but I don't want to use the Fixed sizePolicy because I want the user to be able to resize the right dock manually if desired.
Minimum only has the GrowFlag which says "The widget can be expanded, but there is no advantage to it being larger" and Expanding has the ExpandFlag which says "The widget can make use of extra space, so it should get as much space as possible".... but when I resize the window the right widget is immediately enlarged instead of the left widget getting the extra space. Why is this happening?
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt
app = QtWidgets.QApplication([])
main = QtWidgets.QMainWindow()
class MyWidget(QtWidgets.QPushButton):
def __init__(self,name, w,h):
super(MyWidget, self).__init__(None)
self.w = w
self.h = h
def sizeHint(self) -> QtCore.QSize:
return QtCore.QSize(self.w,self.h)
widget1 = MyWidget("ONE",600,200)
widget2 = MyWidget("ONE",200,200)
dock1 = QtWidgets.QDockWidget()
dock2 = QtWidgets.QDockWidget()
dock1.setWidget(widget1)
dock2.setWidget(widget2)
widget1.setSizePolicy(QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Expanding)
widget2.setSizePolicy(QtWidgets.QSizePolicy.Minimum,QtWidgets.QSizePolicy.Minimum)
main.addDockWidget(Qt.LeftDockWidgetArea, dock1)
main.addDockWidget(Qt.RightDockWidgetArea, dock2)
main.show()
app.exec_()
https://www.screencast.com/t/Eo8qZvrd

Accessing and Editing UI Elements between Different Classes in PyQT5

I am in the process of writing code that generates a main 'TopLevel' window where information is entered and then, upon the click of a pushbutton, another window opens and this information is sent through and accessed/edited/interacted with in that window. I have seemingly tried every way I can find on this website and others to try to access ui elements (be they labels, text inputs or, in this specific case, a dictionary) between different classes and nothing has worked so far.
I've edited the code below so that all the relevant elements are available and this version runs up to the point that the error is produced. The user types whatever they want in the box underneath "Input dictionary elements", clicks "Done" to add it to the list underneath "List of dictionary elements" and can then press "Expand" under the leftmost box to open the second window that I want to pass the list to.
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDesktopWidget, QMessageBox, QPushButton, QAction, qApp, QMenu, QLineEdit, QLabel, QTextEdit, QComboBox, QScrollArea, QVBoxLayout, QScrollBar, QListWidget
from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtCore import pyqtSlot, QCoreApplication, QMetaObject, QRect
def setKey(dictionary, key, value):
if key not in dictionary:
dictionary[key] = value
elif type(dictionary[key]) == list:
dictionary[key].append(value)
else:
dictionary[key] = [dictionary[key], value]
class TopLevel(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Top Level')
self.resize(800,550)
topLevelLabel = QLabel(self)
topLevelLabel.setText('Top level of tree')
self.topLevelBox = QTextEdit(self)
self.topLevelBox.resize(200,50)
self.topLevelBox.move(60,100)
topLevelLabel.move(60,70)
topLevelExpandButton = QPushButton('Expand', self)
topLevelExpandButton.resize(50,30)
topLevelExpandButton.move(100,150)
topLevelExpandButton.pressed.connect(self.open_New_Window_Expand)
selectSCLabel = QLabel(self)
selectSCLabel.setText('Input dictionary elements')
self.selectSCBox = QTextEdit(self)
self.selectSCBox.resize(300,50)
self.selectSCBox.move(300,100)
selectSCLabel.move(300,60)
selectSCLabel.resize(400,50)
selectSCConfirmationButton = QPushButton('Done', self)
selectSCConfirmationButton.resize(40,30)
selectSCConfirmationButton.move(560,150)
selectSCConfirmationButton.pressed.connect(self.selectSCConfirmationButtonPressed)
listOfSelectedSCLabel = QLabel(self)
listOfSelectedSCLabel.setText('List of dictionary elements')
listOfSelectedSCLabel.setWordWrap(True)
listOfSelectedSCLabel.move(60,190)
listOfSelectedSCLabel.resize(200,30)
self.listOfSelectedSCArea = QScrollArea(self)
self.placeholderListItem = QWidget()
self.vboxOfListItems = QVBoxLayout()
self.listOfSelectedSCArea.move(60,220)
self.listOfSelectedSCArea.resize(200,300)
self.listOfSelectedSCArea.setVerticalScrollBarPolicy(0)
self.listOfSelectedSCArea.setHorizontalScrollBarPolicy(0)
self.listOfSelectedSCArea.setWidgetResizable(True)
self.listOfSelectedSCDictionary = {}
self.SCIterate = 1
self.show()
def open_New_Window_Expand(self):
self.new_window = QMainWindow
self.ui = NewLevelExpand()
self.show()
def selectSCConfirmationButtonPressed(self):
newSCName = self.selectSCBox.toPlainText()
nameToLabel = QLabel(self)
nameToLabel.setText(newSCName)
self.vboxOfListItems.addWidget(nameToLabel)
self.placeholderListItem.setLayout(self.vboxOfListItems)
self.listOfSelectedSCArea.setWidget(self.placeholderListItem)
setKey(self.listOfSelectedSCDictionary, self.SCIterate, newSCName)
self.SCIterate = self.SCIterate + 1
print(self.listOfSelectedSCDictionary)
class NewLevelExpand(TopLevel):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self, parent = TopLevel):
self.setWindowTitle('Expanded Level')
self.resize(800,550)
print(self.listOfSelectedSCDictionary)
self.show()
def main():
app = QApplication(sys.argv)
tl = TopLevel()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
With tinkering with this code, I have generated numerous different error messages but the code above produces the message: "AttributeError: 'NewLevelExpand' object has no attribute 'listOfSelectedSCDictionary'"
As I say, I feel I have tried everything relating to this topic on stackoverflow and nothing has worked. Any advice and guidance, pointing out stupid errors, suggestions to changing my coding style to make it clearer, etc. would be massively appreciated.
Premise: I cannot help you a lot with the logic behind your program; I've to admit, it's a bit confused, and I don't understand how you're going to pass data to the new window, nor how you are going to use it.
The error you're facing is because NewLevelExpand is a subclass of TopLevel, and when you call its super().__init__() this results in calling the ancestor's __init__().
In the TopLevel class, __init__() calls initUI, which creates the whole UI and the listOfSelectedSCDictionary dictionary.
The problem is that you are overriding initUI in the subclass, so the result is that the base class initUI is never called (meaning that the UI nor the dictionary are actually created).
The solution to this specific problem is to call the base class implementation of initUI in the subclass:
class NewLevelExpand(TopLevel):
# ...
def initUI(self):
super().initUI()
self.setWindowTitle('Expanded Level')
self.resize(800,550)
print(self.listOfSelectedSCDictionary)

Python PyQt5 how to show the full QMenuBar with a QWidget

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)

Pyside - Select all text when QLineEdit gets focus

I am new to Qt/PySide. I want QLineEdit to select all text in it when it gets focus. After getting focus and selecting all text, it should select all text only after focus is lost and gained again. It should not select all text when I change cursor position after QLineEdit gains focus. How do I do that?
Update: My current code improved as suggested by Ashwani Kumar. I still can't get it to work though:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class MyLineEdit(QLineEdit):
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
def focusInEvent(self, e):
self.selectAll()
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(MyLineEdit())
layout.addWidget(MyLineEdit())
top.setLayout(layout)
top.show()
app.exec_()
With focusInEvent, when you click the widget, it gets executed, but since you click, it removes the selected text.
To overcome this, we must use the mousePressEvent, this can be done two ways:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class MyLineEdit(QLineEdit):
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
def mousePressEvent(self, e):
self.selectAll()
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(MyLineEdit())
layout.addWidget(MyLineEdit())
top.setLayout(layout)
top.show()
app.exec_()
Or you can do it by simply overriding the base QLineEdit class:
txt_demo = QtGui.QLineEdit()
txt_demo.mousePressEvent = lambda _ : txt_demo.selectAll()
However, since we are modifying the mousePressEvent, whenever you try to click text, it will always select all first.
For future visitors, I am posting code that is working for me. As I am a newbie I am not sure if the code contains any malpractices. If it does feel free to comment and I'll update my code/answer. Code:
import sys
from PySide.QtGui import QLineEdit, QApplication, QVBoxLayout, QWidget
class LineEdit(QLineEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
self.readyToEdit = True
def mousePressEvent(self, e, Parent=None):
super(LineEdit, self).mousePressEvent(e) #required to deselect on 2e click
if self.readyToEdit:
self.selectAll()
self.readyToEdit = False
def focusOutEvent(self, e):
super(LineEdit, self).focusOutEvent(e) #required to remove cursor on focusOut
self.deselect()
self.readyToEdit = True
app = QApplication(sys.argv)
top = QWidget()
layout = QVBoxLayout()
layout.addWidget(LineEdit())
layout.addWidget(LineEdit())
top.setLayout(layout)
top.show()
app.exec_()
You have to subclass the QLineEdit and then use the new class instead of QLineEdit.
e.g: -
class MyLineEdit(QtGui.QLineEdit):
def __init__(self, parent=None)
super(MyLineEdit, self).__init__(parent)
def focusInEvent(self, e):
self.selectAll()
lineedit = MyLineEdit()
QTimer solution as seen on QtCentre:
import types
from PyQt4 import QtCore
def bind(func, to):
"Bind function to instance, unbind if needed"
return types.MethodType(func.__func__ if hasattr(func, "__self__") else func, to)
...
self.txtSrc.focusInEvent = bind(lambda w, e: QtCore.QTimer.singleShot(0, w.selectAll), self.txtSrc)
Also the provided solution doesn't require to subclass QLineEdit.
These answers don't really provide the sort of standard ergonomics you'd probably want (and users might expect). For example, if you single-click once on a QLE which is not currently all-selected, and then single-click again, typically you'd want the first click to select-all, and the second click to allow you to place the cursor in the specific spot you have chosen.
This can be achieved simply by doing this:
def mousePressEvent(self, event):
already_select_all = self.text() == self.selectedText()
super().mousePressEvent(event)
if not already_select_all:
self.selectAll()
The question in fact asks about gaining focus, not specifically by mouse-clicking, and indeed, if you are a keyboardist or generally musophobic you'll probably also want the whole text to be selected any time the QLE gains focus, e.g. by tabbing or by use of a QLabel "buddy" mnemonic. This seems to do the job:
class MyLineEdit(QtWidgets.QLineEdit):
def __init__(self, *args):
super().__init__(*args)
self.focus_in_reason = None
def focusInEvent(self, event):
super().focusInEvent(event)
self.selectAll()
self.focus_in_reason = event.reason()
def mousePressEvent(self, event):
super().mousePressEvent(event)
if self.focus_in_reason == QtCore.Qt.MouseFocusReason:
self.selectAll()
self.focus_in_reason = None

Categories

Resources