I'm trying to display a table of items in wxpython using a wx.grid.Grid() inside a wx.BoxSizer. The table is large but I didn't want it to take up all the screen space so I put it in the boxsizer as shown in the code below.
def setOutputPanel(self):
self.outputBox = wx.grid.Grid(parent=self.pnl)
self.outputBox.CreateGrid(100, 100)
self.outputBox.DisableCellEditControl()
self.outputPanel.Add(self.outputBox, 1, wx.EXPAND)
self.graphControl = wx.Button(parent=self.pnl, label="close")
self.outputPanel.Add(self.graphControl, 0, wx.ALIGN_RIGHT)
self.outputText = wx.TextCtrl(parent=self.pnl, value="text control", style=wx.EXPAND)
self.outputPanel.Add(self.outputText, 1, wx.EXPAND)
The problem is that when I put the size for the outputText to 1
self.outputPanel.Add(self.outputText, 1, wx.EXPAND)
It doesn't show at all
link to TextCtrl#size1
I can get it to show if I set the size to 0.
self.outputPanel.Add(self.outputText, 0, wx.EXPAND)
But, it's far too small. I'd like it to be the same height at the table. link to TextCtrl#size0
So, after reading some more of the wxPython documentation I have found the solution.
If you go to this page, where they discuss some of the wxPython sizer features (the super class for wx.BoxSizer). On the page it says "Normally wx.Sizers will use wx.Window.GetEffectiveMinSize to determine what the minimal size of window items should be, and will use that size to calculate the layout." so I added the wx.FIXED_MINSIZE and now the grid doesn't have a minimum size.
This might seem like a bad idea as the grid can now shrink to a size that's too small. But, I've set the frame min size pretty large and the grid uses half the space, so I don't think It's going to be an issue.
Related
I'm trying to draw multiple paragraphs of text with PySide6's QPainter and QFontMetrics. I want to draw them with the same spacing as they would have if I drew them all in a single block of text, but the line spacing isn't quite right.
In the following example, the font metrics say that the font's line spacing is 17. When I measure a single line of text, the bounding rectangle is indeed 17 pixels high. However, when I measure two lines of text, the bounding rectangle is 35 pixels high, not 34. Where does the extra pixel come from, and can I see it on some property of the font or the font metrics?
from PySide6.QtGui import QFont, QFontMetrics
from PySide6.QtWidgets import QApplication
app = QApplication()
font = QFont()
metrics = QFontMetrics(font)
print(metrics.lineSpacing()) # 17
print(metrics.boundingRect(0, 0, 100, 100, 0, 'A').height()) # 17
print(metrics.boundingRect(0, 0, 100, 100, 0, 'A\nB').height()) # 35 != 17 * 2
print(metrics.leading()) # 0
print(metrics.ascent()) # 14
print(metrics.descent()) # 3
By the way, it isn't always one extra pixel. If I make the font bigger, the extra space increases.
Update
I thought I had figured this out with musicamante's suggestion of switching from QFontMetrics to QFontMetricsF, but there's still a difference.
from PySide6.QtCore import QRectF
from PySide6.QtGui import QFont, QFontMetricsF
from PySide6.QtWidgets import QApplication
app = QApplication()
font = QFont()
metrics = QFontMetricsF(font)
print(metrics.height()) # 16.8125
print(metrics.boundingRect(QRectF(0, 0, 100, 100),
0,
'A').getCoords()) # (0.0, 0.0, 9.9375, 16.8125)
print(metrics.boundingRect(QRectF(0, 0, 100, 100),
0,
'A\nB').getCoords()) # (0.0, 0.0, 9.9375, 34.8125)
# Note the height of that rect doesn't match the next calculation.
print(metrics.height() + metrics.lineSpacing()) # 34.046875
# I can't see any combination of these numbers that makes 34.8125
print(metrics.lineSpacing()) # 17.234375
print(metrics.leading()) # 0.421875
print(metrics.ascent()) # 13.984375
print(metrics.descent()) # 2.828125
First of all, the height of a bounding rect of a font metrics doesn't depend on the characters used, but on the font specifications.
Two lines of text don't have the double of the height() of the bounding rect of a single line: instead, you have to consider the lineSpacing().
In practice, the height of a bounding rect is normally the sum of:
the height() multiplied the number of lines;
the leading() multiplied by the number of spaces between the lines (aka: number of lines - 1);
Or, similarly, the sum of:
the ascent();
the lineSpacing() multiplied by the number of spaces between lines;
the descent();
Note that, obviously, the number of lines depends on the input text and the given options, for instance, if word wrapping was enabled and any of the source lines didn't fit the given source rectangle.
Also consider that most fonts are vectorial, meaning that their coordinates and metrics are proportional and in floating point values. QFontMetrics, instead, works with integer values for simplicity and optimization reasons, so you might get inconsistent results caused by rounding in cases for which the point size doesn't give rounded values: non integer numbers are generally "floored" (like int() in python).
In your case, the leading is probably more than 0 (but still less than 1), so you don't get a proper sum of the aforementioned heights.
Specifically, QFontMetrics.boundingRect() returns a QRect resulting by the QRectF.toAlignedRect() of the computed formatted text, which is always "the smallest possible integer rectangle that completely contains this rectangle".
If you need to get precise coordinates, you need to use QFontMetricsF, which is the floating point counterpart of the default basic QFontMetrics.
That said, if you plan on drawing formatted text with QPainter, then consider using QTextDocument or, at least, QTextLayout, which is consistent with the standard Qt text drawing and is generally faster, more reliable and "simpler" (well, once you get to know it). While it might seem a bit too complex than required, it's actually what Qt does when calling boundingRect(), so if you need custom painting, the QTextLayout option is actually better, especially if you can combine it with some smart caching (see QPicture) to avoid the common python bottleneck.
There is this very annoying extra bit on ttk(tkinter.ttk) scale here is the image at the end there is an extra bit, which would annoy some people if they notice it(you would kinda be able to spot it). I had it print out what number the scale was and it was at the max it could be. Here is the code of the scale
width = Scale(optionsframe, from_ = 1, to = 20, style = 'SCALEBG.Horizontal.TScale')
width.grid(row = 0, column = 7)
Here is an image without using style
The style code
scalebg = ttk.Style()
scalebg.configure('SCALEBG.Horizontal.TScale', foreground = '#2a2a2a', background = '#2a2a2a')
This occurs for both styles.
Edit
From the comments it looks like it only happens when the vista and xpnative are used, which in my opinion give the best GUI look. Is there way to create your own style which would remove the extra bit and looks identical to the one tkinter provieds.
This is a really simple question I'm just new to PyQt5 and am a bit confused on how QGridLayout works...
def init_main_page(self):
layout = QGridLayout()
b1 = buttons.QPushButton("0",self.main_page)
b2 = buttons.QPushButton("1",self.main_page)
b3 = buttons.QPushButton("2",self.main_page)
layout.addWidget(b1,0,0)
layout.addWidget(b2,5,0)
layout.addWidget(b3,1,0)
self.main_page.setLayout(layout)
The problem I am having is that no matter how high I make the x and y arguments in addwidget(QWidget,x,y), it b1 b2 and b3 always remain equidistant from each other. I'm trying figure out how to manipulate the position of the buttons whilst maintaining a proportional setup (so avoiding QPushButton.move()) and from what I've seen, QGridLayout is the best way to do this.
Setting the coordinates of widgets in a grid layout by "skipping" rows or columns is almost useless, as those are grid coordinates that only tell the layout manager in which "slots" the widgets will be: since there is nothing in the rows 2 to 4, that space will not be used.
To achieve what you want you need to set stretch factors and, possibly, use minimum heights for the specified rows.
layout.addWidget(b1, 0, 0)
layout.addWidget(b3, 1, 0)
layout.addWidget(b2, 2, 0)
layout.setRowStretch(2, 1)
layout.setRowMinimumHeight(2, 50)
But this might not be what you want, since it will place the third button in the vertical center of the row grid (leaving empty space at the bottom).
To avoid that there are two possible solutions:
add the widget by setting the alignment to the bottom:
layout.addWidget(b2, 2, 0, alignment=QtCore.Qt.AlignBottom)
add the last widget to the fourth row, and set stretch and minimum height for the third empty row.
layout.addWidget(b2, 3, 0)
layout.setRowStretch(2, 1)
layout.setRowMinimumHeight(2, 50)
I'm having some issues with QGridLayout in pyqt5. I'm trying to make a GUI that has a stack of buttons on one side, a table on the other side, and a plot that occupies the entire bottom of the window. This is the first program I've ever made, so I might have more issues than I know.
I've arranged the buttons within a QTableWidget, and the main QTableWidget contains several fields where users can enter data. I'd like the data entry table to be larger in size than the button table, but resizing it as in this answer doesn't seem to do anything. The button table is larger no matter the columnSpan entry I put in. What am I doing wrong?
Here are the relevant bits of code:
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setGeometry(50, 50, 700, 1000)
self.home()
def home(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.button_table = QTableWidget(self)
self.layer_add = QPushButton("Add layer", self)
self.plotter = QPushButton("plot transmission", self)
self.layer_table = QTableWidget(self)
self.graphWidget = pg.PlotWidget(self)
self.grid = QGridLayout()
self.grid.setSpacing(10)
self.grid.addWidget(self.button_table, 0, 0, 1, 1)
self.grid.addWidget(self.layer_table, 0, 1, 1, 3)
self.grid.addWidget(self.graphWidget, 1, 0, 1, 4)
self.centralWidget().setLayout(self.grid)
self.show()
I doodled in what I'd ideally like to have happen... here's a picture of what it looks like with the above code
and in red what I'd like to have happen.
Edit: I don't understand why, if I set the QGridLayout columnSpan to be 1 for the table on the left and 3 for the table on the right, the left-hand table is still significantly wider. I am open to either learning how to fix that, understanding how to make the left-hand table auto-shrink to the size of the buttons within it, or an alternative layout suggestion. Thanks for any help!
While you answered your own question, it seems that you changed the behavior by removing the first table (moreover, changing the resize mode of the first column stretch doesn't have much to do with your issue). So I'm answering to your [edited] question, even if it's missing the part in which you added the buttons to the first table.
The main problem was that you were setting a column span too big for the second table:
self.grid.addWidget(self.button_table, 0, 0, 1, 1)
self.grid.addWidget(self.layer_table, 0, 1, 1, 3) # <- 3 columns!
self.grid.addWidget(self.graphWidget, 1, 0, 1, 4)
In the code above, you're telling the layout that the layer_table will have a column span of 3 columns. Even if you are not actually using three columns, by doing this the layout thinks that the second table will (probably) occupy more space than the first.
Normally, a QGridLayout will use the columnStretch property for that, but since by default the stretch is 0 for all columns and rows, it will use the span as a reference.
In fact, using the following:
self.grid.addWidget(self.button_table, 0, 0, 1, 1)
self.grid.addWidget(self.layer_table, 0, 1, 1, 1) # <- 1 column!
self.grid.addWidget(self.graphWidget, 1, 0, 1, 2)
is the same as this:
self.grid.addWidget(self.button_table, 0, 0, 1, 1)
self.grid.addWidget(self.layer_table, 0, 1, 1, 3) # <- 3 columns!
self.grid.addWidget(self.graphWidget, 1, 0, 1, 4)
self.grid.setColumnStretch(0, 1)
self.grid.setColumnStretch(1, 1)
In the first case, the column span is 1 (one widget, one column), and, since the two widgets are of the same type, they will use half of the available horizontal space. In the second, the column span of the right table is 3 (as in your code), but the stretch is 1 for both the first and second column, and 0 for the third and fourth, meaning that a widget that occupies the second, third and fourth column will have the same available space than a widget that occupies the first, thus obtaining the horizontal space equally divided between those two widgets.
col1 | col2 | col3 | col4
1 | 1 | 0 | 0
Since the second table occupies columns 2 to 4, it will have a stretch of 1 (1 + 0 + 0). Stretches are used by layouts to equally divide the space between widgets (considering their size hints, their minimum size hints, or their minimum/maximum size whenever they're set): the stretches are summed integer values, and then the layout uses the proportions between the sum and those values to resize widgets.
To ensure that the first table uses only the minimum space required to show its contents, you need to do the following:
set the sizeAdjustPolicy (which is a property of every QAbstractScrollArea descendant, including every item view) to AdjustToContents; this will make the table "tell" the layout that its size hint is based on its minimum contents;
set the resize mode of the horizontal header to adjust all of its sections (as in columns) to their contents;
set the horizontal size policy of the table to Maximum; the term "maximum" might be counterintuitive, but it means that the widget's size cannot be larger than its size hint; still, it could be shrunk if any other widget requires space (but not less than the minimumSizeHint) so, alternatively, you could use Fixed (meaning that it cannot even shrink), but it's usually better to allow widgets to be shrunk anyway if the layout is too crowded and the user requires to make the window smaller than it is;
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setGeometry(50, 50, 700, 1000)
self.home()
def home(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.button_table = QTableWidget(self)
self.button_table.setRowCount(3)
self.button_table.setColumnCount(1)
# set the sizeHint of the table view (actually, its ancestor class,
# QAbstractScrollArea) to the minimum size required to show its contents
self.button_table.setSizeAdjustPolicy(self.button_table.AdjustToContents)
# set all sections of the horizontal headers to adjust themselves to
# their contents
self.button_table.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeToContents)
# get the current sizePolicy and set it to Maximum, meaning that it will
# use its sizeHint as "maximum": it can expand, but there's no need for
# that, so if any other sibling widget requires more space, it can use it
policy = self.button_table.sizePolicy()
policy.setHorizontalPolicy(policy.Maximum)
# apply the changed policy
self.button_table.setSizePolicy(policy)
self.layer_add = QPushButton("Add layer", self)
self.plotter = QPushButton("plot transmission", self)
# I restored the following lines, which were missing in your edit
self.button_table.setCellWidget(0, 0, self.layer_add)
self.button_table.setCellWidget(1, 0, self.plotter)
self.layer_table = QTableWidget(self)
self.graphWidget = pg.PlotWidget(self)
self.grid = QGridLayout()
self.grid.setSpacing(10)
self.grid.addWidget(self.button_table, 0, 0, 1, 1)
self.grid.addWidget(self.layer_table, 0, 1, 1, 1)
self.grid.addWidget(self.graphWidget, 1, 0, 1, 2)
self.centralWidget().setLayout(self.grid)
As you can see, now the left table only uses the minimum required width, based on the horizontal header width (plus the vertical header width), which in turn is based on the sum of the maximum width of each column.
Ahh turns out that using setColumnStretch on column 1 fixed this problem. I also changed the left table to a QVBoxLayout and put it in using QGridLayout.addLayout, so everything looks better now
I still don't quite understand why the two tables were unequal widths on QGridLayout though, regardless of the number of columns selected.
I'm getting myself thoroughly confused about how Gtk controls widget sizes inside a container such as a GtkBox. Please can someone guide me through the intricacies for this question?
I have a GtkBox containing two widgets - in the example these are just two GtkButtons.
The first widget should just fill the space available inside the GtkBox container.
The second widget I want to force always to be a physical square - thus as the square enlarges, the first widget will shrink in terms of width.
Some example code will help here:
from gi.repository import Gtk
def on_check_resize(window):
boxallocation = mainbox.get_allocation()
width = 0
height = 0
if boxallocation.height >= boxallocation.width:
width = boxallocation.height
height = width
if boxallocation.width >= boxallocation.height:
width = boxallocation.width
height = width
secondbtn_allocation = secondbtn.get_allocation()
secondbtn_allocation.width = width
secondbtn_allocation.height = height
secondbtn.set_allocation(secondbtn_allocation)
win = Gtk.Window(title="Containers Demo")
win.set_border_width(10)
win.connect("delete-event", Gtk.main_quit)
mainbox = Gtk.Box()
firstbtn = Gtk.Button(label="just fills the space")
mainbox.pack_start(firstbtn, True, True, 0)
secondbtn = Gtk.Button(label="square")
mainbox.pack_start(secondbtn, False, True, 1)
win.add(mainbox)
win.connect("check_resize", on_check_resize)
win.show_all()
initial = firstbtn.get_allocation()
initial.height = initial.width
firstbtn.set_size_request(initial.width, initial.height)
Gtk.main()
When you expand the window - the GtkBox will likewise expand. That's good. The first button similarly also expands. Good also. However, the second button never is square even though I'm using the set_allocation method for the GtkWidget.
I cannot use the set_size_request (or could I?) because when I shrink the window, the window cannot resize due to the altered minimum size of the second button.
The reason why I'm trying to figure out how containers manage spacing is that I'm looking to eventually implement something like this iTunes example:
i.e. you can see the cover is always square - but the music details will shrink or expand to depending upon the space available.
Instead of Gtk.Box, use Gtk.Grid and set the hexpand and vexpand properties on the buttons that you pack into the grid.
Also, consider using a Gtk.AspectFrame for the square button.