So I am new to QtGui and looking up how to do things, and I found this neat example on QTreeView. When I got it working on my own, I noticed that it didn't fill the space as I'd anticipated:
So I have been searching for answers, and not finding much in either Python or C++ resources. I've been checking the documentation a lot, but still not quite finding what I'm searching for.
So it seems clear that something doesn't have the correct size policy, but I am having a hard time figuring out what. I have so far eliminated a couple of potential candidates:
The QWidget instance holding the QTreeView instance is correctly spanning the layout it is in (the QWidget spans the width of the QGroupBox minus a little for margins).
Since QTreeView's parent widget is the correct dimensions, I figured it's something more local to QTreeView, but when I use the setSizePolicy, none of the policies I've used seem to resolve the issue. Possibly multiple steps I'm unaware of?
The QTreeView's inherited viewport (from QAbstractScrollArea is much smaller than I expect. Calling QTreeView's setViewport() method with a new and empty QWidget only redraws the non-header contents background in gray instead of white, and I suspect that this is close but not where I need to look.
QTreeView has other children (besides viewport)that I am still investigating.
Most of what I have tried I left commented out in my code below.
This is my source code to reproduce:
import sys
from PySide.QtGui import *
class TreeTime(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.main_widget = QWidget()
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
self.setCentralWidget(self.main_widget)
self.statusBar()
self.make_tree()
self.show()
def make_tree(self):
# init widgets
self.tgb = QGroupBox("[Tree Group Box Title]")
self.main_layout.addWidget(self.tgb)
tgb_layout = QVBoxLayout()
self.tgb.setLayout(tgb_layout)
tgb_widget = QWidget()
tgb_layout.addWidget(tgb_widget)
debug_btn = QPushButton("DEBUG")
tgb_layout.addWidget(debug_btn)
view = QTreeView(parent=tgb_widget)
# view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
view.setSelectionBehavior(QAbstractItemView.SelectRows)
model = QStandardItemModel()
model.setHorizontalHeaderLabels(['col1', 'col2', 'col3'])
view.setModel(model)
view.setUniformRowHeights(True)
# populate data
for i in range(10):
parent1 = QStandardItem('Family {}. Some long status text for sp'.format(i))
for j in range(3):
child1 = QStandardItem('Child {}'.format(i*3+j))
child2 = QStandardItem('row: {}, col: {}'.format(i, j+1))
child3 = QStandardItem('row: {}, col: {}'.format(i, j+2))
parent1.appendRow([child1, child2, child3])
model.appendRow(parent1)
# span container columns
view.setFirstColumnSpanned(i, view.rootIndex(), True)
# expand third container
index = model.indexFromItem(parent1)
view.expand(index)
# select last row
selmod = view.selectionModel()
index2 = model.indexFromItem(child3)
selmod.select(index2, QItemSelectionModel.Select|QItemSelectionModel.Rows)
def print_debug_info():
print('')
for child in view.children():
print("child "+repr(child)) #not sure what all these are yet
print('')
print('self.main_widget.frameSize: '+repr(self.main_widget.frameSize()))
print('view.parent().parent().frameSize(): '+repr(view.parent().parent().frameSize())) #group box
# print('self.frameSize: '+repr(self.frameSize()))
print('self.tgb.frameSize: '+repr(self.tgb.frameSize()))
print('view.parent(): '+repr(view.parent()))
print('view.parent().frameSize(): '+repr(view.parent().frameSize()))
# print('view.parent().frameSize(): '+repr(view.parent().frameSize())+" (before)")
# print('view.parent().adjustSize(): '+repr(view.parent().adjustSize()))
# print('view.parent().frameSize(): '+repr(view.parent().frameSize())+" (after)")
print('view.viewport(): '+repr(view.viewport()))
print('view.viewport().frameSize(): '+repr(view.viewport().frameSize()))
# print('view.parent().parent().parent().frameSize(): '+repr(view.parent().parent().parent().frameSize()))
# print('calling setViewport: '+repr(view.setViewport(QWidget())))
# view.adjustSize()
debug_btn.clicked.connect(print_debug_info)
def sayHello(self):
self.statusBar().showMessage("Hello World!")
import time; time.sleep(2)
self.statusBar().showMessage("")
def sayWords(self, words):
self.statusBar().showMessage(words)
if __name__ == '__main__':
app = QApplication([])
tt = TreeTime()
sys.exit(app.exec_())
I am using a Windows 8.1 machine and Python 3.4.3, PySide version 1.2.2 - any help will be much appreciated! (also, please let me know if I left out any important details)
UPDATE (5/19/2015): I tried moving my DEBUG button outside the QGroupBox, and the result was the QTreeView being collapsed into a completely nonlegible size so you couldn't even tell what the object was anymore, so it seems to be minimizing the space used, even when I uncomment the line:
view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
One friend has suggested this may simply be an issue with windows and not my code, but I don't have anything to back that up.
UPDATE 5/19/2015: I have implemented the advice provided by #titusjan, but I have the same problem/behavior.
You need to remove the redundant tp_widget and add view to tgb_layout:
def make_tree(self):
# init widgets
self.tgb = QGroupBox("[Tree Group Box Title]")
self.main_layout.addWidget(self.tgb)
tgb_layout = QVBoxLayout()
self.tgb.setLayout(tgb_layout)
view = QTreeView()
tgb_layout.addWidget(view)
...
debug_btn = QPushButton("DEBUG")
tgb_layout.addWidget(debug_btn)
Note that when you add widgets to a layout, they will be automatically re-parented to the parent of the layout (whenever it gets one), so it's not really necessary set one in the constructor.
Also note that this:
tgb_layout = QVBoxLayout(self.tgb)
is exactly equivalent to this:
tgb_layout = QVBoxLayout()
self.tgb.setLayout(tgb_layout)
because the layout will always be re-parented to the widget it's set on.
You must use the setLayout method to link the layout to the widget. So change...
self.main_layout = QVBoxLayout(self.main_widget)
into
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
Similar for the tgb_view layout (which I would rename to tgb_layout for clarity).
Finally you forgot to add the tree view to this layout, so add:
tgb_view.addWidget(view)
I've put all the relevant modified code below for convenience.
def initUI(self):
self.main_widget = QWidget()
self.main_layout = QVBoxLayout()
self.main_widget.setLayout(self.main_layout)
self.setCentralWidget(self.main_widget)
self.statusBar()
self.make_tree()
self.show()
def make_tree(self):
# init widgets
self.tgb = QGroupBox("[Tree Group Box Title]")
self.main_layout.addWidget(self.tgb)
tgb_view = QVBoxLayout()
self.tgb.setLayout(tgb_view)
tgb_widget = QWidget()
tgb_view.addWidget(tgb_widget)
debug_btn = QPushButton("DEBUG")
tgb_view.addWidget(debug_btn)
view = QTreeView(parent=tgb_widget)
tgb_view.addWidget(view)
...
The size policy stuff is not necessary, the defaults are fine.
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()
In tkinter, I'd like to have a frame similar to LabelFrame, but with a Checkbutton as the label.
After reading the LabelFrame sources, I saw the labelwidget option and tried
checkbutton = tk.Checkbutton(text=text)
checkbutton_frame = tk.LabelFrame(window, labelwidget=checkbutton)
It works visually, but the Checkbutton ends up being a child of window (window.winfo_children()). Alternatively:
checkbutton_frame = tk.LabelFrame(window)
checkbutton = tk.Checkbutton(checkbutton_frame, text="Hello, frame")
checkbutton_frame.configure(labelwidget=checkbutton)
makes the Checkbutton a child of the LabelFrame. The default, built-in Label of LabelFrame doesn't appear in winfo_children. Because I set padx and pady recursively after adding all my widgets, having the Checkbutton in winfo_children breaks the layout by moving the Checkbutton out of its anchored position. Is there a nice, clean way to substitute a Checkbutton for the Label in LabelFrame that maintains consistent (i.e. lack of any) descendant relationship? Answers with other workarounds are welcome.
Minimal reproduction
import tkinter as tk
def pack_configure_recursive(widget, **kwargs):
stack = list(widget.winfo_children())
while stack:
descendent = stack.pop()
try:
stack.extend(descendent.winfo_children())
except Exception:
pass
try:
descendent.pack_configure(**kwargs)
except Exception:
pass
class CheckbuttonFrame(tk.LabelFrame):
def __init__(self, master=None, **kwargs):
def pop_kwarg(name):
nonlocal kwargs
if name in kwargs:
result = kwargs[name]
del kwargs[name]
return result
else:
return None
text = pop_kwarg("text")
self.variable = pop_kwarg("variable")
self.command = pop_kwarg("command")
tk.LabelFrame.__init__(self, master, **kwargs)
self.configure(labelwidget=tk.Checkbutton(text=text, variable=self.variable, command=self.handler)) # Use this to make a child of the window
# self.configure(labelwidget=tk.Checkbutton(self, text=text, variable=self.variable, command=self.handler)) # Use this to make a child of the frame
def update_state(self):
if self.variable:
state = 'normal' if self.variable.get() else 'disable'
stack = list(self.winfo_children())
while stack:
descendent = stack.pop()
try:
stack.extend(descendent.winfo_children())
except Exception:
pass
try:
descendent.configure(state=state)
except tk.TclError:
pass
def handler(self):
self.update_state()
if self.command:
external_command = self.command
external_command()
window = tk.Tk()
option = tk.IntVar()
checkbutton_frame = CheckbuttonFrame(window, text="My option group", variable=option)
checkbutton_frame.pack()
tk.Label(checkbutton_frame, text="More widgets here").pack()
checkbutton_frame.update_state()
pack_configure_recursive(window, padx=2, pady=2) # This line break the layout
window.mainloop()
The default, built-in Label of LabelFrame doesn't appear in winfo_children
That is because the built-in label of the LabelFrame isn't a widget.
Is there a nice, clean way to substitute a Checkbutton for the Label in LabelFrame that maintains consistent (i.e. lack of any) descendant relationship? Answers with other workarounds are welcome.
No, there is not. The widget must be a descendant of something, there is simply no getting around that. And logically, it should probably be the descendant of the labelframe itself.
Any code you have to automatically re-pack widgets will need to skip over this widget.
Your code to call pack_configure should probably just ignore any widget not managed by pack, which you can do like this:
if descendent.winfo_manager() == "pack":
descendent.pack_configure(**kwargs)
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 use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()
I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()
i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *
There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.
I'm trying to make a tool window for Maya, in which I can right-click anywhere, and if I click 'add', a rectangle widget shows up at my cursor position.
Now my right-click functionality works. I can also get my cursor position in addPicker() function. But I am having problem with placing newly-created widgets. If I add a layout and add the newly-created widgets to it, they actually show up. However, if I didn't create a layout for those widgets, no matter what position I tested, nothing shows up in my window.
Hopefully someone has some ideas. Thank you all in advance.
A right-click screenshot:
class RightClickMenu(QtGui.QMenu):
def __init__(self, *args, **kwargs):
super(RightClickMenu, self).__init__(*args)
self.parentWidget().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.parentWidget().customContextMenuRequested.connect(self.menuPos)
def menuPos(self, *args):
self.exec_(QtGui.QCursor.pos())
class Ui_window(object):
def setupUi(self, window):
window.setObjectName("window")
window.resize(555, 900)
self.widget_base = QtGui.QWidget()
self.verticalLayout_window = QtGui.QVBoxLayout(window)
self.verticalLayout_window.addWidget(self.widget_base)
self.menu_popup = RightClickMenu(self.widget_base)
self.menu_popup.setObjectName("popupMenu")
self.verticalLayout_widget = QtGui.QVBoxLayout(self.widget_base)
# Action - add picker
addAction = QtGui.QAction('Add Picker', self.widget_base)
addAction.setShortcut('Ctrl+A')
addAction.setStatusTip('Add Picker')
addAction.triggered.connect(self.addPicker)
self.menu_popup.addAction(addAction)
# Action - delete picker
deleteAction = QtGui.QAction('Delete Picker', self.widget_base)
deleteAction.setShortcut('Ctrl+D')
deleteAction.setStatusTip('Delete Picker')
deleteAction.triggered.connect(self.deletePicker)
self.menu_popup.addAction(deleteAction)
def addPicker(self):
cursorPos = QtGui.QCursor.pos()
localPos = self.widget_base.mapFromGlobal(cursorPos)
######################################################################
# how??? below doesn't work.
self.pushButton = QtGui.QPushButton(self.widget_base)
self.pushButton.setGeometry(QtCore.QRect(220, 50, 75, 23))
self.pushButton.setObjectName("pushButton")
def deletePicker(self):
print 'delete'
def run():
import sys
try:
Ui_window.close()
except:
pass
pickerWindow = QtGui.QDialog()
ui = Ui_window()
ui.setupUi(pickerWindow)
pickerWindow.show()
pickerWindow.exec_()
Surprising solution (see this question):
self.pushButton.show()