I'm creating a simple dialog box for my PySide application. Within this dialog, there are going to be multiple inputs that the user will have to fill out. Associated with those inputs are labels that go alongside the left of the labels. Right now I create the label, input pair using a separate class:
class inputLayout(PyGui.QHBoxLayout):
def __init__(self, Label, parent):
super(inputLayout, self).__init__()
label = PyGui.QLabel()
label.setText(Label)
self.addWidget(label)
self.__input = PyGui.QTextEdit()
self.addWidget(self.__input)
parent.addLayout(self)
and then add it to the master layout like so:
layout = PyGui.QVBoxLayout()
self.amp = inputLayout('Amplitude', layout)
self.test = inputLayout('test', layout)
self.test2 = inputLayout('test2', layout)
The problem is that when PySide does its automagic, it get something like the following:
Like my image suggests, I'd rather have the Label take up 1/3 (or some other proportional rate of my choice) to make it look more unified. How do I size the layout using this proportionality, or ratio?
I am aware of this question, however I'm not looking to statically set the size of the label, but rather do it dynamically using a ratio.
Here's a small example that should solve your problem. The key is in two parts:
Use a QGridLayout, which sets the column width to the width of the widest widget in its column (unless defined otherwise). This ensures that everything is aligned nicely along the vertical axis.
Set a stretch factor. This determines how an element should resize when their parent is resized. By default it's 0, so you don't have to set it, I just added it for illustration purposes. By setting the text_edit's column to 1, it will start stretching. By playing with the factors, you can make one column grow faster than the other.
from PySide import QtGui
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.input_widget = InputWidget(self)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.input_widget)
self.setLayout(self.layout)
self.setCentralWidget(self.input_widget)
class InputWidget(QtGui.QWidget):
def __init__(self, parent):
super(InputWidget, self).__init__(parent)
self.grid_layout = QtGui.QGridLayout()
self.labels = ["amp", "more text", "blabla"]
self.text_edits = []
self.qlabels = []
for row, label in enumerate(self.labels):
label = QtGui.QLabel(label)
self.qlabels.append(label)
self.grid_layout.addWidget(label, row, 0)
text_edit = QtGui.QTextEdit()
self.text_edits.append(text_edit)
self.grid_layout.addWidget(text_edit, row, 1)
self.grid_layout.setColumnStretch(0, 0)
self.grid_layout.setColumnStretch(1, 2)
self.setLayout(self.grid_layout)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
If you want to access the text from the text_edit, you could search for the label's index in self.labels and then use that label in self.text_edits to retrieve the corresponding text_edit. Alternatively, once you close the dialog, you could loop through both self.labels and self.text_edits and create a dictionary that maps the label to the text from the text_edit.
results = {}
for label, text_edit in zip(self.labels, self.text_edits):
results[label] = text_edit.text()
Related
I'm trying to learn pyqt5 in python by creating a small application. For one of the windows, I need to add a vertical scroll bar to the window. Now, this window has a table made using QLabel and QLineEdit. Check the picture to get exactly how it looks like.
As you can see there are a lot of chemicals, which goes below the window screen. I have tried numerous approaches but somehow couldn't get the result. If I am able to get the scroll, all the elements get aligned one under another (QVBoxLayout) which is not the way I want the elements to be aligned.
Here's the code I'm using
class ChemicalWindow(QWidget):
def __init__(self,chemicals,data):
super().__init__()
self.layout = QVBoxLayout()
self.setWindowTitle("Chemicals")
self.setMinimumSize(QSize(600,600))
self.setStyleSheet("background-color:#eaf4f4;")
self.chemicals = chemicals
self.data = data
self.createBody()
self.createButtons()
def createBody(self):
headerLabel = QLabel('Chemicals',scroll_widget)
headerLabel.move(265,10)
headerLabel.resize(70,40)
headerLabel.setStyleSheet("color:#000;")
tcLabel = QLabel('Tc',scroll_widget)
tcLabel.move(200,50)
tcLabel.resize(60,30)
tcLabel.setStyleSheet("color:#000;")
pcLabel = QLabel('Pc',scroll_widget)
pcLabel.move(280,50)
pcLabel.resize(60,30)
pcLabel.setStyleSheet("color:#000;")
cpLabel = QLabel('Cp',scroll_widget)
cpLabel.move(360,50)
cpLabel.resize(60,30)
cpLabel.setStyleSheet("color:#000;")
self.chemical_names = self.chemicals.keys()
y_position = 90
# List for keeping chemical inputs variables in form of dict of list -> {A:[chemical_a_tc,chemical_a_pc,chemical_a_cp],
# B:[chemical_b_tc,chemical_b_pc,...],...}
self.chemical_inputs = dict()
# Creating labels for the chemical names
for name in self.chemical_names:
chemicalLabel = QLabel(name,scroll_widget)
chemicalLabel.move(70,y_position)
chemicalLabel.resize(75,30)
chemicalLabel.setStyleSheet("color:#000;")
chemicalLabel.setToolTip(name)
y_position += 40
current_chemical_inputs = dict()
for chemical_input in self.chemicals[name]:
current_chemical_inputs[chemical_input] = QLineEdit(scroll_widget)
self.chemical_inputs[name] = current_chemical_inputs
position_y = 90
for individual_chemical in self.chemical_inputs:
position_x = 160
for chemical_input in self.chemical_inputs[individual_chemical]:
self.chemical_inputs[individual_chemical][chemical_input].setText(str(self.data['chemicals'][individual_chemical][chemical_input]))
self.chemical_inputs[individual_chemical][chemical_input].move(position_x,position_y)
self.chemical_inputs[individual_chemical][chemical_input].resize(80,30)
self.chemical_inputs[individual_chemical][chemical_input].setStyleSheet("color:#000;background-color:#a9d6e5;padding:2px;")
position_x += 90
position_y += 40
def createButtons(self):
close_button = QPushButton('Close',self)
close_button.move(510,550)
close_button.resize(70,30)
close_button.setStyleSheet("background-color:#00509d;color:#fff;")
close_button.clicked.connect(self.closeButton)
def closeButton(self):
self.close()
What am I doing wrong?
Firstly, instead of using .move() to manually place your widgets, you should be using a QLayout (ex. QHBoxLayout or QVBoxLayout). This will automatically space your labels, and you can modify it by adjusting stretch and adding spacers (QSpacerItem). For more complex layouts, you can either nest multiple box layouts, or use a QGridLayout.
Now to address the scrolling:
First, you want to create your scroll area. Make this widget the central widget. Remember to set setWidgetResizable to True.
scroller = QScrollArea()
scroller.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroller.resize(self.width(),self.height())
scroller.setWidgetResizable(True)
self.setCentralWidget(scroller)
Next, create your container and add it to the scroll area. All your layout elements (labels, buttons, etc.) should be placed in this container.
self.container = QWidget()
scroller.setWidget(self.container)
Here's the full sample program I created:
import sys
from PyQt5.QtWidgets import QMainWindow, QWidget, QScrollArea, QVBoxLayout, QLabel, QApplication
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(1100, 800)
scroller = QScrollArea()
scroller.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.container = QWidget()
scroller.setWidget(self.container)
scroller.resize(self.width(),self.height())
scroller.setWidgetResizable(True)
self.setCentralWidget(scroller)
self.holderColumn=QVBoxLayout()
txtList=["apple","banana","orange","triangle","circle","square","moon","star","sun","delta"]
objs=list()
for i in txtList:
tempLabel=QLabel()
tempLabel.setText(i)
tempLabel.setFixedSize(300,300)
objs.append(tempLabel)
self.holderColumn.addWidget(tempLabel)
self.container.setLayout(self.holderColumn)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
I am trying to create an own ttk Theme based on my company's CI. I took the Sun Valley theme as starting point and swapped out graphics, fonts and colors.
However I am stuck on the Label frame. I am trying to position the Label within the frame, kind of like a heading. I.e. there should be some margin between top edge and label, and appropriate top-padding for the content (child widgets).
Now:
+-- Label ------
| ...
Desired:
+---------------
| Label
| ...
I tried to set the padding option:
within the Layout
on TLabelframe itself
on TLabelframe.Label
but the label did not move a pixel. How to achieve this?
Generally I am very confused about what identifiers and options are legal within ttk:style layout, ttk:style element and ttk:style configure, because documentation is hazy and scattered all over the 'net, and there are no error messages whatsoever. Any helpful tips?
Edit: What I found out since posting:
The Labelframe label is a separate widget altogether, with the class TLabelframe.Label.
It is possible to override its layout and add a spacer on top, shifting the text down.
However, the label widget is v-centered on the frame line. If its height increases, it pushes "upward" as much as downward. I found no way to alter the alignment w.r.t. to the actual frame.
It might be possible to replace Labelframe altogether with a custom Frame subclass with the desired layout. But that means changing the "client" code in many places. :-/
This can be done by changing the layout definitions so that the text element is held by the Labelframe layout and the Layoutframe.Label no longer draws the text element. Adding a bit of padding ensures the contained widgets leave the label clear.
Example code:
import sys
import tkinter as tk
import tkinter.ttk as ttk
class CustomLabelframe(ttk.Labelframe):
def __init__(self, master, **kwargs):
"""Initialize the widget with the custom style."""
kwargs["style"] = "Custom.Labelframe"
super(CustomLabelframe, self).__init__(master, **kwargs)
#staticmethod
def register(master):
style = ttk.Style(master)
layout = CustomLabelframe.modify_layout(style.layout("TLabelframe"), "Custom")
style.layout('Custom.Labelframe.Label', [
('Custom.Label.fill', {'sticky': 'nswe'})])
style.layout('Custom.Labelframe', [
('Custom.Labelframe.border', {'sticky': 'nswe', 'children': [
('Custom.Labelframe.text', {'side': 'top'}),
('Custom.Labelframe.padding', {'side': 'top', 'expand': True})
]})
])
if (style.configure('TLabelframe')):
style.configure("Custom.Labelframe", **style.configure("TLabelframe"))
# Add space to the top to prevent child widgets overwriting the label.
style.configure("Custom.Labelframe", padding=(0,12,0,0))
style.map("Custom.Labelframe", **style.map("TLabelframe"))
master.bind("<<ThemeChanged>>", lambda ev: CustomLabelframe.register(ev.widget))
#staticmethod
def modify_layout(layout, prefix):
"""Copy a style layout and rename the elements with a prefix."""
result = []
for item in layout:
element,desc = item
if "children" in desc:
desc["children"] = HistoryCombobox.modify_layout(desc["children"], prefix)
result.append((f"{prefix}.{element}",desc))
return result
class App(ttk.Frame):
"""Test application for the custom widget."""
def __init__(self, master, **kwargs):
super(App, self).__init__(master, **kwargs)
self.master.wm_geometry("640x480")
frame = self.create_themesframe()
frame.pack(side=tk.TOP, fill=tk.BOTH)
for count in range(3):
frame = CustomLabelframe(self, text=f"Frame {count}", width=160, height=80)
frame.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
button = ttk.Button(frame, text="Test")
button.pack(side=tk.LEFT)
self.pack(side=tk.TOP, expand=True, fill=tk.BOTH)
def create_themesframe(self):
frame = ttk.Frame(self)
label = ttk.Label(frame, text="Theme: ")
themes = ttk.Combobox(frame, values=style.theme_names(), state="readonly")
themes.current(themes.cget("values").index(style.theme_use()))
themes.bind("<<ComboboxSelected>>", lambda ev: style.theme_use(ev.widget.get()))
label.pack(side=tk.LEFT)
themes.pack(side=tk.LEFT)
return frame
def main(args=None):
global root, app, style
root = tk.Tk()
style = ttk.Style(root)
CustomLabelframe.register(root)
app = App(root)
try:
import idlelib.pyshell
sys.argv = [sys.argv[0], "-n"]
root.bind("<Control-i>", lambda ev: idlelib.pyshell.main())
except Exception as e:
print(e)
root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
It is relatively easy to place ttk.Labelframe text below, on or above the relief graphic. This example uses the text attribute but labelwidget can also be used.
In order for the relief to be visible the background color of Labelframe.Label must be set to "".
import tkinter as tk
from tkinter import font
from tkinter import ttk
message = "Hello World"
master = tk.Tk()
style = ttk.Style(master)
style.theme_use(themename = "default")
actualFont = font.Font(
family = "Courier New", size = 20, weight = "bold")
style.configure(
"TLabelframe.Label", background = "", font = actualFont)
frame = ttk.LabelFrame(
master, labelanchor = "n", text = message)
frame.grid(sticky = tk.NSEW)
frame.rowconfigure(0, weight = 1)
frame.columnconfigure(0, weight = 1)
def change_heading():
if frame["text"][0] == "\n":
frame["text"] = f"{message}\n"
else:
frame["text"] = f"\n{message}"
button = tk.Button(
frame, text = "Change", command = change_heading)
button.grid(sticky = "nsew")
master.mainloop()
Looking through the source of the Labelframe widget, I found that:
The label is either placed vertically-centered on the frame's border, or flush above it, depending on the -labeloutside config option. (for default NW anchor)
i.e. by adding whitespace on top of the text by any means, the label box will extend upwards the same amount as downwards, creating a "dead space" above the frame.
There might still be a way to get it "inside" by increasing the border width, but I couldn't get it to work.
I now used the labeloutside option to make a "tab-like" heading.
# ... (define $images array much earlier) ...
ttk::style element create Labelframe.border image $images(card2) \
-border 6 -padding 6 -sticky nsew
ttk::style configure TLabelframe -padding {8 8 8 8} -labeloutside 1 -labelmargins {2 2 2 0}
ttk::style element create Label.fill image $images(header2) -height 31 -padding {8 0 16 0} -border 1
With suitable images, this is nearly what I was aiming for, only that the header does not stretch across the full frame width. Tkinter elements use a "9-patch"-like subdivision strategy for images, so you can make stretchable frames using the -border argument for element create.
Result is approximately this:
+-------------+
| Heading |
+-------------+----------------+
| ... |
How to modify this current setup to enable resizing(horizontally and vertically) between the layouts shown below? Let's say I want to resize the lists in the right toward the left by dragging them using the mouse, I want the image to shrink and the lists to expand and same applies for in between the 2 lists.
Here's the code:
from PyQt5.QtWidgets import (QMainWindow, QApplication, QDesktopWidget, QHBoxLayout, QVBoxLayout, QWidget,
QLabel, QListWidget)
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
import sys
class TestWindow(QMainWindow):
def __init__(self, left_ratio, right_ratio, window_title):
super().__init__()
self.left_ratio = left_ratio
self.right_ratio = right_ratio
self.current_image = None
self.window_title = window_title
self.setWindowTitle(self.window_title)
win_rectangle = self.frameGeometry()
center_point = QDesktopWidget().availableGeometry().center()
win_rectangle.moveCenter(center_point)
self.move(win_rectangle.topLeft())
self.tools = self.addToolBar('Tools')
self.left_widgets = {'Image': QLabel()}
self.right_widgets = {'List1t': QLabel('List1'), 'List1l': QListWidget(),
'List2t': QLabel('List2'), 'List2l': QListWidget()}
self.central_widget = QWidget(self)
self.main_layout = QHBoxLayout()
self.left_layout = QVBoxLayout()
self.right_layout = QVBoxLayout()
self.adjust_widgets()
self.adjust_layouts()
self.show()
def adjust_layouts(self):
self.main_layout.addLayout(self.left_layout, self.left_ratio)
self.main_layout.addLayout(self.right_layout, self.right_ratio)
self.central_widget.setLayout(self.main_layout)
self.setCentralWidget(self.central_widget)
def adjust_widgets(self):
self.left_layout.addWidget(self.left_widgets['Image'])
self.left_widgets['Image'].setPixmap(QPixmap('test.jpg').scaled(500, 400, Qt.IgnoreAspectRatio,
Qt.SmoothTransformation))
for widget in self.right_widgets.values():
self.right_layout.addWidget(widget)
if __name__ == '__main__':
test = QApplication(sys.argv)
test_window = TestWindow(6, 4, 'Test')
sys.exit(test.exec_())
One way to rescale the image to an arbitrary size while maintaining its aspect ratio is to subclass QWidget and override sizeHint and paintEvent and use that instead of a QLabel for displaying the image, e.g.
class PixmapWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self._pixmap = None
def sizeHint(self):
if self._pixmap:
return self._pixmap.size()
else:
return QSize()
def setPixmap(self, pixmap):
self._pixmap = pixmap
self.update()
def paintEvent(self, event):
painter = QPainter(self)
super().paintEvent(event)
if self._pixmap:
size = self._pixmap.size().scaled(self.size(), Qt.KeepAspectRatio)
offset = (self.size() - size)/2
rect = QRect(offset.width(), offset.height(), size.width(), size.height())
painter.drawPixmap(rect, self._pixmap)
Since you are subclassing QMainWindow you could use DockWidgets to display the lists instead of adding them to the layout of the central widget, e.g.
class TestWindow(QMainWindow):
def __init__(self, left_ratio, right_ratio, window_title):
super().__init__()
#self.left_ratio = left_ratio <--- not needed since image and lists
#self.right_ratio = right_ratio <--- are not sharing a layout anymore
...
# use PixmapWidget instead of QLabel for showing image
# refactor dictionary for storing lists to make adding DockWidgets easier
self.left_widgets = {'Image': PixmapWidget()}
self.right_widgets = {'List1': QListWidget(),
'List2': QListWidget()}
self.central_widget = QWidget(self)
# self.main_layout = QHBoxLayout() <-- not needed anymore
self.left_layout = QVBoxLayout()
self.adjust_widgets()
self.adjust_layouts()
self.show()
def adjust_layouts(self):
self.central_widget.setLayout(self.left_layout)
self.setCentralWidget(self.central_widget)
def adjust_widgets(self):
self.left_layout.addWidget(self.left_widgets['Image'])
self.left_widgets['Image'].setPixmap(QPixmap('test.jpg').scaled(500, 400, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
self.dock_widgets = []
for text, widget in self.right_widgets.items():
dock_widget = QDockWidget(text)
dock_widget.setFeatures(QDockWidget.NoDockWidgetFeatures)
dock_widget.setWidget(widget)
self.addDockWidget(Qt.RightDockWidgetArea, dock_widget)
self.dock_widgets.append(dock_widget)
Screenshots
You need to use QSplitter.
It acts almost like a box layout, but has handles that allow the resizing of each item.
Be aware that you can only add widgets to a QSplitter, not layouts, so if you need to add a "section" (a label and a widget) that can resize its contents, you'll have to create a container widget with its own layout.
Also note that using dictionaries for these kind of things is highly discouraged. For versions of Python older than 3.7, dictionary order is completely arbitrary, and while sometimes it might be consistent (for example, when keys are integers), it usually isn't: with your code some times the labels were put all together, sometimes the widgets were inverted, etc., so if somebody is using your program with <=3.6 your interface won't be consistent. Consider that, while python 3.6 will reach end of life in 2022, it's possible that even after that a lot of people will still be using previous versions.
If you need a way to group objects, it's better to use a list or a tuple, as I did in the following example.
If you really "need" to use a key based group, then you can use OrderedDict, but it's most likely that there's just something wrong with the logic behind that need to begin with.
class TestWindow(QMainWindow):
def __init__(self, left_ratio, right_ratio, window_title):
super().__init__()
self.left_ratio = left_ratio
self.right_ratio = right_ratio
self.current_image = None
self.window_title = window_title
self.setWindowTitle(self.window_title)
win_rectangle = self.frameGeometry()
center_point = QDesktopWidget().availableGeometry().center()
win_rectangle.moveCenter(center_point)
self.move(win_rectangle.topLeft())
self.tools = self.addToolBar('Tools')
self.left_widgets = {'Image': QLabel()}
self.right_widgets = [(QLabel('List1'), QListWidget()),
(QLabel('List2'), QListWidget())]
self.central_widget = QSplitter(Qt.Horizontal, self)
self.setCentralWidget(self.central_widget)
self.right_splitter = QSplitter(Qt.Vertical, self)
self.adjust_widgets()
self.central_widget.setStretchFactor(0, left_ratio)
self.central_widget.setStretchFactor(1, right_ratio)
self.show()
def adjust_widgets(self):
self.central_widget.addWidget(self.left_widgets['Image'])
self.left_widgets['Image'].setPixmap(QPixmap('test.jpg').scaled(500, 400, Qt.IgnoreAspectRatio,
Qt.SmoothTransformation))
self.left_widgets['Image'].setScaledContents(True)
self.central_widget.addWidget(self.right_splitter)
for label, widget in self.right_widgets:
container = QWidget()
layout = QVBoxLayout(container)
layout.addWidget(label)
layout.addWidget(widget)
self.right_splitter.addWidget(container)
I am new to PyQt perhaps thats why facing this issue. I am trying to make a component inserter for excel sheets. For this purpose I am using QT for interface and using Qlabels within parent class of QMainWindow. On the basis of item selected from Qlist Widget, few Qlabels have to change on mainwindow dynamically Component inserter
As can be seen in above picture, the labels like WCAP-; Part Number and all below needs to change dynamically when the selected items change(when select button is clicked). But what happening is if I choose a different item from list, the previous Label stays and the new label is overlapping it as can be seen from picture below showing overlapping of labels
The code below shows that whenever button "Select" is pressed", label2 (Qlabel2) is formed, how can i delete the previous label whenever select button is pressed so that new Label dynamically replaces the old label.
Thanks a lot in advance.
def Display(self):
self.close()
label1 = QtGui.QLabel("Select the sheet",self)
label1.move(0,15)
self.listwidget = QtGui.QListWidget(self)
self.listwidget.move(0,40)
self.listwidget.resize(150,150)
for i in range(len(self.sheetnames)):
self.listwidget.addItem("%s"%self.sheetnames[i])
btn = QtGui.QPushButton('Select',self)
btn.resize(50,50)
btn.move(170,40)
btn.clicked.connect(self.Selected)
self.show()
def Selected(self):
self.close()
selecteditem = self.listwidget.currentItem().text()
self.sheetindex = self.sheetnames.index(selecteditem)
print self.sheetindex
aa = self.loadsheet.sheet_by_name(selecteditem)
global label2
label2 = QtGui.QLabel("",self)
label2.setText(selecteditem)
label2.move(0,190)
self.show()
self.InputParameters(aa)
You see a new QLabel because you create a new one every time you call Selected. I would initiate the UI at the creation of the widget (in the __init__ method):
def __init__(self):
self.label2 = QtGui.QLabel("",self)
And only update the text of the Qlabel when Selected is executed:
def Selected(self):
self.label2.setText(selecteditem)
About reinitializing all labels with an unknown number of labels and removing the old ones, you might want to look at QLabel.setParent(None). I wrote you a little example:
from PyQt4 import QtGui, QtCore
import sys
class test(QtGui.QWidget):
def __init__(self,parent=None):
self.widget=QtGui.QWidget.__init__(self, parent)
# Button to add labels
self.btnAdd = QtGui.QPushButton('Add')
self.btnAdd.connect(self.btnAdd, QtCore.SIGNAL('clicked()'),self.btnAddPressed)
# Button to remove labels
self.btnRemove = QtGui.QPushButton('Remove')
self.btnRemove.connect(self.btnRemove, QtCore.SIGNAL('clicked()'), self.btnRemovePressed)
# List to keep track of labels
self.labels=[]
# Layout
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.btnAdd)
self.hbox.addWidget(self.btnRemove)
self.setLayout(self.hbox)
self.show()
def btnAddPressed(self):
"""Adds a new label."""
self.labels.append(QtGui.QLabel("lbl"+str(len(self.labels)+1), self))
self.hbox.addWidget(self.labels[-1])
def btnRemovePressed(self):
"""Removes last label."""
self.labels[-1].setParent(None)
self.labels.pop(-1)
def main():
#Creating application
app = QtGui.QApplication(sys.argv)
main_win = test()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I'm having problem with adjusting the width of the label to reflect current width of the window. When the window size changes I'd like label to fill the rest of the width that is left after other widgets in row consume width they need.
Putting the label in a Frame and using grid_propagate(False) does not seem to work.
Consider following code:
import tkinter as tk
import tkinter.ttk as ttk
class PixelLabel(ttk.Frame):
def __init__(self,master, w, h=20, *args, **kwargs):
'''
creates label inside frame,
then frame is set NOT to adjust to child(label) size
and the label keeps extending inside frame to fill it all,
whatever long text inside it is
'''
ttk.Frame.__init__(self, master, width=w, height=h,borderwidth=1)
#self.config(highlightbackground="blue")
self.grid_propagate(False) # don't shrink
self.label = ttk.Label(*args, **kwargs)
self.label.grid(sticky='nswe')
def resize(self,parent,*other_lenghts):
'''
resizes label to take rest of the width from parent
that other childs are not using
'''
parent.update()
new_width = parent.winfo_width()
print(new_width)
for lenght in other_lenghts:
new_width -= lenght
print(new_width)
self.configure(width = new_width)
root = tk.Tk()
master = ttk.Frame(root)
master.grid()
label = ttk.Label(master,text='aaa',borderwidth=1, relief='sunken')
label.grid(row=0,column=0)
label1_width = 7
label1 = ttk.Label(master,text='bbbb',borderwidth=1, relief='sunken',width=label1_width)
label1.grid(row=0,column=1)
label2 = ttk.Label(master,text='ccccccccccccccccccccccccccccccccccccc',borderwidth=1, relief='sunken')
label2.grid(row=0,column=2)
label3_width = 9
label2 = ttk.Label(master,text='ddddd',borderwidth=1, relief='sunken',width=label2_width)
label2.grid(row=0,column=3)
label4 = ttk.Label(master,text='ee',borderwidth=1, relief='sunken')
label4.grid(row=1,column=0)
label5 = ttk.Label(master,text='f',borderwidth=1, relief='sunken')
label5.grid(row=1,column=1,sticky='we')
nest_frame = ttk.Frame(master)
nest_frame.grid(row=2,columnspan=4)
label8_width = 9
label8 = ttk.Label(nest_frame,text='xxxxx',borderwidth=1, relief='sunken',width=label8_width)
label8.grid(row=0,column=0)
label9 = PixelLabel(nest_frame, 5, text='should be next to xxxxx but is not?',borderwidth=1, relief='sunken')
label9.grid(row=0,column=1)
label9.resize(root,label2_width)
root.mainloop()
Why label9 does not appear next to label8
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
It's not clear why you are using a label in a frame. I suspect this is an XY problem. You can get labels to consume extra space without resorting to putting labels inside frames. However, since you posted some very specific code with very specific questions, that's what I'll address.
Why label9 does not appear next to label8
Because you are creating the label as a child of the root window rather than a child of the frame. You need to create the label as a child of self inside PixelLabel:
class PixelLabel(...):
def __init__(...):
...
self.label = ttk.Label(self, ...)
...
How to make label9 resize to meet current window size (this code is just a sample, I would like to be able to resize label9 as the window size changes dynamically when functions are reshaping the window)
There are a couple more problems. First, you need to give column zero of the frame inside PixelFrame a non-zero weight so that it uses all available space (or, switch to pack).
class PixelLabel(...):
def __init__(...):
...
self.grid_columnconfigure(0, weight=1)
...
Second, you need to use the sticky attribute when placing nest_frame in the window so that it expands to fill its space:
nest_frame.grid(..., sticky="ew")