I wrote a little dialog which only contains a TableWigdet. How can I determine the horizontal size of the table ? I want to resize the dialog window so that the table is displayed without a horizontal scrollbar.
As far as I know, there's no easy way to do it. You have to sum over the widths of the table's columns and then add space for the headers. You also have to add space for the vertical scrollbar and the widget frame. Here is one way to do it,
class myTableWidget(QtGui.QTableWidget):
def sizeHint(self):
width = 0
for i in range(self.columnCount()):
width += self.columnWidth(i)
width += self.verticalHeader().sizeHint().width()
width += self.verticalScrollBar().sizeHint().width()
width += self.frameWidth()*2
return QtCore.QSize(width,self.height())
You could use something like that (commented enough I hope):
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyTableWidget(QTableWidget):
def __init__(self, x, y, parent = None):
super(MyTableWidget, self).__init__(x, y, parent)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
# To force the width to use sizeHint().width()
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
# To readjust the size automatically...
# ... when columns are added or resized
self.horizontalHeader().geometriesChanged \
.connect(self.updateGeometryAsync)
self.horizontalHeader().sectionResized \
.connect(self.updateGeometryAsync)
# ... when a row header label changes and makes the
# width of the vertical header change too
self.model().headerDataChanged.connect(self.updateGeometryAsync)
def updateGeometryAsync(self):
QTimer.singleShot(0, self.updateGeometry)
def sizeHint(self):
height = QTableWidget.sizeHint(self).height()
# length() includes the width of all its sections
width = self.horizontalHeader().length()
# you add the actual size of the vertical header and scrollbar
# (not the sizeHint which would only be the preferred size)
width += self.verticalHeader().width()
width += self.verticalScrollBar().width()
# and the margins which include the frameWidth and the extra
# margins that would be set via a stylesheet or something else
margins = self.contentsMargins()
width += margins.left() + margins.right()
return QSize(width, height)
When a row header changes, the width of the whole vertical header changes, but not right away after the signal headerDataChanged is emitted. That's why I used a QTimer to call the updateGeometry (which must be called when sizeHint changes) after QTableWidget has actually updated the vertical header width.
Related
I'm making a program and I need a image and an entry field. However when I try to put them in the same column it defaults the to the top left no matter the value. I tried column as various values and it either goes to the top left if they are the same or one goes to the center-ish and one is on the left. is there something I'm missing. Any help would be greatly appreciated.
from tkinter import *
import tkinter as tk
from PIL import Image,ImageTk
pg3 = Tk()
img1 = ImageTk.PhotoImage(Image.open("download.png"))
pg3.attributes("-fullscreen", True)
tk.Label(pg3, image = img1, anchor = "c").grid(row=0, column = 1)
e1 = tk.Entry(pg3)
e1.grid(row=1, column=1)
pg3.mainloop()
Use pack.
BaseWidget.pack() automatically centers all your elements.
P.S. If you want to horizontally center a few elements, you can use a Frame to grid the elements and then pack the frame.
Another way will be using pg3.columnconfigure(1, weight=1). It is an alias of grid_columnconfigure.
def grid_columnconfigure(self, index, cnf={}, **kw):
"""Configure column INDEX of a grid.
Valid resources are minsize (minimum size of the column),
weight (how much does additional space propagate to this column)
and pad (how much space to let additionally)."""
So you can set the weight to 1 to center it.
Context
I have put in place column freezing in a wx.grid.Grid, using FreezeTo method.
def __init__(self, parent):
# relevant lines
self.grid = wx.grid.Grid(self.sbox_grid, size=(1000, 800))
self.grid.CreateGrid(self.row_number, self.col_number)
self.grid.FreezeTo(0, self.frozen_column_number)
The freezing by itself works well, as soon as I keep the standard label renderer (*).
The first few columns I have frozen always stay visible, and moving the horizontal scrollbar by hand is also ok.
(*) I was initially using the GridWithLabelRenderersMixin of wx.lib.mixins.gridlabelrenderer, but it totally breaks consistency between column label width and column width. Anyway I can deal with the standard renderer, so it is not really a problem.
I faced several issues, now all solved and detailed below.
Capture the cell position for frozen columns: cells or labels (SOLVED)
For cells, the window can be captured with GetFrozenColGridWindow.
So mouseover can be done simply with:
if widget == self.grid.GetFrozenColGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever your want with row, col
For labels, the window exists but is NOT accessible with a method.
With a GetChildren on the grid, I have found that it is the last of the list (corresponding to the latest defined).
So it is not very reliable, but a relatively good placeholder for the missing GetGridFrozenColLabelWindow method.
wlist = self.grid.GetChildren()
frozen_col_label_window = wlist[-1]
if widget == frozen_col_label_window:
x = event.GetX()
y = event.GetY()
col = self.grid.XToCol(x, y)
# do stuff with col
Mouse position from non-frozen columns (labels or cells) is shifted (SOLVED)
The effective position for non-frozen columns labels or cells is shifted from the total width of all the frozen columns.
This one is easily handled by a shift in position, computed before calls to YToRow or XToCol methods.
The following code shows the position corrections:
class Report(wx.Panel):
def _freeze_x_shit(self):
"""Returns the horizontal position offset induced by columns freeze"""
offset = 0
for col in range(self.frozen_column_number):
offset += self.grid.GetColSize(col)
return offset
def on_mouse_over(self, event):
widget = event.GetEventObject()
# grid header
if widget == self.grid.GetGridColLabelWindow():
x = event.GetX()
y = event.GetY()
x += self._freeze_x_shit() # <-- position correction here
col = self.grid.XToCol(x, y)
# do whatever grid processing using col value
# grid cells
elif widget == self.grid.GetGridWindow():
(x, y) = self.grid.CalcUnscrolledPosition(event.GetX(), event.GetY())
x += self._freeze_x_shit() # <-- and also here
row = self.grid.YToRow(y)
col = self.grid.XToCol(x)
# do whatever grid cell processing using row and col values
event.Skip()
HOME keyboard key not working as intended (SOLVED)
I generally use the HOME key to immediately go at the utmost left of the grid, and the END key to go far right. This is the normal behavior with a non-frozen grid.
The END key does its jobs, but not the HOME key.
When pushing HOME on any grid cell, I got two effects:
the selected cell becomes the first column: this is OK
but the scrollbar position is not changed at all: I would expect the scroll position to be fully left
I have corrected it by a simple remapping using EVT_KEY_DOWN event:
def __init__(self, parent):
self.grid.Bind(wx.EVT_KEY_DOWN, self.on_key_event)
def on_key_event(self, event):
"""Remap the HOME key so it scrolls the grid to the left, as it did without the frozen columns
:param event: wx.EVT_KEY_DOWN event on the grid
:return:
"""
key_code = event.GetKeyCode()
if key_code == wx.WXK_HOME:
self.grid.Scroll(0, -1)
event.Skip()
my 3 issues concerning column Freeze in a grid are now solved. I have edited my initial post with my solutions.
I want to have a space between the Entry widget and the ScrollText Widget. I thought it would be as simple as setting row=2 for the grid. The strange thing is no matter what I set for row the ScrollText widget always starts at row 1. I suppose I could add an empty text widget to make the space but I am confused as to why setting the row is not working for me.
from tkinter import *
from tkinter import scrolledtext
main = Tk()
main.title("Grid Prob")
main.geometry('750x750')
text = StringVar()
entSearch = Entry(main, textvariable = text, width = 50, font='arial 12',
highlightthickness=1)
entSearch.grid(row = 0, column = 1)
txt = """This is some text to be entered into the text widget. It should
word wrap when the text exceeds the width of the widget."""
textw = scrolledtext.ScrolledText(main,width=70,height=33)
# No matter what number I put for row the scroll widget always starts in
# row 1
textw.grid(column=1, row=3)
textw.config(background="light grey", foreground="black",
font='arial 12', wrap='word')
textw.insert(END, txt)
main.mainloop()
If I interpreted what Bryan Oakley said in the comments above correctly the problem is that since my code didn't put anything into row 2 by default it (row 2) has a height of 0. This means that I am putting my ScrollText Widget into row 3 as intended but since row 2 has a height of 0 visually it looks like it's being placed in row 2. I added a blank label to my code with a height of one and placed it on row 2. This provided the space that I was looking for. Here is the additional code I added.
label_1 = Label(main, width = "70", height = "1")
label_1.grid(row=1, columnspan=3)
It seems weird that tkinter makes you add widgets just to provide a space. If anyone knows of a better way to do this please let me know.
I am making a table of about 10 cells with headings in them. They will not fit accross the page unless I use multi_cell option. However I cant figure out How to get a multi_cell side by side. When I make a new one it autmatically goes to the next line
from fpdf import FPDF
import webbrowser
pdf=FPDF()
pdf.add_page()
pdf.set_font('Arial','B',16)
pdf.multi_cell(40,10,'Hello World!,how are you today',1,0)
pdf.multi_cell(100,10,'This cell needs to beside the other',1,0)
pdf.output('tuto1.pdf','F')
webbrowser.open_new('tuto1.pdf')
You will have to keep track of x and y coordinates:
from fpdf import FPDF
import webbrowser
pdf=FPDF()
pdf.add_page()
pdf.set_font('Arial','B',16)
# Save top coordinate
top = pdf.y
# Calculate x position of next cell
offset = pdf.x + 40
pdf.multi_cell(40,10,'Hello World!,how are you today',1,0)
# Reset y coordinate
pdf.y = top
# Move to computed offset
pdf.x = offset
pdf.multi_cell(100,10,'This cell needs to beside the other',1,0)
pdf.output('tuto1.pdf','F')
webbrowser.open_new('tuto1.pdf')
NOTE : This is an alternate approach to the above problem
For my requirement, i need some of the columns to have higher column width and some columns with lower width. Fixed column width for all the columns is making my table to go out of the pdf margin. Multi cell wraps the text and increase the column height only for certain columns and not every column. So my approach was to use the enumerate function and dynamically adjust the column width according to the needs as shown below.
data1 = list(csv.reader(csvfile))
print(data1) ##[[row1],[row2],[row3],[row4]]
## row1, row2, etc are the rows from csv
for row in data1:
for x,y in enumerate(row):
if (x == 0) or (x == 1): ## dynamically change the column width with certain conditions
pdf.cell(2.0, 0.15, str(y), align = 'C', border=1) ## width = 2.0
else:
pdf.cell(0.60, 0.15, str(y), align = 'L', border=1) ## width = 0.60
Hope this helps.
I'm putting together a widget in PyQt's Qt Designer, but struggling to make elements that should stay as close to each other as possible do so after applying a grid layout.
For example, in this layout, I've set the question mark (which acts as the tooltip) to be 2 px from the right hand edge of each of the input elements:
When I apply a grid layout, these spaces massively (and inconsistently across all 3 lines) increase:
I've tried applying a horizontal layout to the rows of elements, but even this causes the elements to spread out more horizontally in an inconsistent manner.
Am I missing something obvious? Is there a way to force elements to stay a certain distance for neighbouring elements?
EDIT / UPDATE
Thanks to three_pineapples' suggestion, I've put some horizontal-spacers in (although I'm not sure if I've done it in the way he intended?)
This has solved it to some extent, but:
the gap between elements and the question mark is now consistent, but 6 px on all rows!
the gap between other elements is not consistent (the gap between the QLineEdit and the File Browse button is 12 px, not the 5 that I intended here)
the height of the spacers are the same height as the rows, but QtDesigner seems to have added in a weird orphaned row above the file select row. Although I'm sure it's nothing to be concerned about I do prefer to understand how things work and why it's doing it...
Have you tried calling setSpacing(0) on the QGridLayout to reduce the spacing between items? (there's also setHorizontalSpacing(x) and setVerticalSpacing(y) if you prefer). Also, setContentsMargins(0, 0, 0, 0) would eliminate the outer margins on the grid, which I think is not what you're looking for here but might be related or useful for someone stumbling upon this.
Snippet:
from PyQt4 import QtGui
class MyClass(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
p1 = QtGui.QPushButton('One', self)
p2 = QtGui.QPushButton('Two', self)
p3 = QtGui.QPushButton('Three', self)
p4 = QtGui.QPushButton('Four', self)
grid = QtGui.QGridLayout(self)
grid.addWidget(p1, 0, 0)
grid.addWidget(p2, 0, 1)
grid.addWidget(p3, 1, 0)
grid.addWidget(p4, 1, 1)
grid.setContentsMargins(0, 0, 0, 0) # this seems to be the outer padding of the grid
grid.setHorizontalSpacing(0) # I think this is what you're looking for
grid.setVerticalSpacing(0)
# grid.setSpacing(0) # this would set vertical & horizontal spacing at once
self.setLayout(grid)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = MyClass()
window.show()
sys.exit(app.exec_())
I'm not used to using QtDesigner but there should be ways of setting these properties there, if you prefer.