I'm trying to create a Collapsible Pane, that is also scrollable, for when the contained button-list is larger than the screen. So I put a ScrolledWindow inside the Pane - and got this error:
Gtk-WARNING: Attempting to add a widget with type GtkScrolledWindow to a GtkExpander, but as a GtkBin subclass a GtkExpander can only contain one widget at a time; it already contains a widget of type wxPizza
What, they don't like Pizza? :) My first reading of that, makes me think they are incompatible widgets, as both are presumably of type GtkExpander. But this question - Scrollbars not showing - appears to use both widget-types. With some differences - first, they're putting the CollapsiblePane inside the ScrolledWindow, and second, it's actually a PyCollapsiblePane, from the AGW library of WxPython.
So is it possible, with some combination of widgets, to have a collapsible object, that when expanded, contains a scrolling list of objects? And if so, how? Or are the two widgets just incompatible (in that order)?
Found the problem. Turns out that a Collapsible-Pane has an inner-panel that is intended to be used for children.
class BadCollapsingPanel(wx.CollapsiblePane):
def __init__(self, parent):
super().__init__(parent, wx.ID_ANY, style=wx.SUNKEN_BORDER)
self.parent = parent
self.inner_pane = self.GetPane()
self.inner_pane.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
# self.very_bad_panel = ScrollingPanel(self) # Breaks
self.very_bad_panel = ScrollingPanel(self.inner_pane) # Works
Related
I am creating a Tkinter/Python3 application where the main window inherits from Notebook (i need tabs), and each tab should be a custom class inheriting from Frame (I would then dynamically use matplotlib to create custom graphs).
Unfortunately I don't seem to be able to have Notebook accept my custom Frames.
Following very reduced snippet of code:
#!/usr/bin/env python3
from tkinter import *
from tkinter.ttk import Notebook
class MyFrame1(Frame):
def __init__(self, master=None, mytext=""):
super().__init__(master)
self.create_widgets(mytext)
def create_widgets(self, mytext):
self.label = Label(self.master, text=mytext, anchor=W)
# this is not placed relative to the Frame, but to the
# master
# 1. How I get the relative coordinates inside the frame
# to be 10, 10 of the frame area?
self.label.place(x=10, y=10, width=128, height=24)
class MyNotebook(Notebook):
def __init__(self, master=None):
super().__init__(master)
self.create_widgets()
def create_widgets(self):
self.f1 = MyFrame1(self, "abc")
# once the UI is drawn, the label "def" seems to overlay
# "abc" even when "f1" is selected
# 2. Why is self.f2 always shown even when self.f1 is
# selected?
self.f2 = MyFrame1(self, "def")
self.add(self.f1, text="f1")
self.add(self.f2, text="f2")
# Without this command nothing gets drawn
# 3. Why is this? Is this equivalent of 'pack' but for
# pixel driven layout?
self.place(width=640, height=480)
def main():
root = Tk()
root.minsize(640, 480)
root.geometry("640x480")
app = MyNotebook(master=root)
# this works as intended the label is indeed placed
# in the frame at 10, 10
#app = MyFrame1(master=root, mytext="123abc")
app.mainloop()
return None
if __name__ == "__main__":
main()
As per comments I have the following main question: why aren't my custom instances of MyFrame1 properly displayed inside MyNotebook?
Sub questions:
How can I get relative coordinate areas of where the frame is located when place my elements (in this case a Label)?
Why even when self.f1 tab is selected in the UI, I can still see the content of self.f2 tab?
Is self.place required in order to show all sub-elements when not using pack?
If I dynamically create Tkinter elements after the MyNotebook is initialized, will those be bound to respective tabs?
Not sure what I'm doing wrong?
Thanks!
Not sure what I'm doing wrong?
Your create_widgets method needs to add widgets to self, not self.master.
How can I get relative coordinate areas of where the frame is located when place my elements (in this case a Label)?
I don't understand what you mean by this. When you use place, coordinates will be interpreted relative to the frame. However, I strongly advise against using place. Both pack and grid will trigger the frame to resize to fit its children which almost always results in a more responsive UI
Why even when self.f1 tab is selected in the UI, I can still see the content of self.f2 tab?
Because you added internal widgets to self.master instead of self.
Is self.place required in order to show all sub-elements when not using pack?
No. It is required to use a geometry manager but it doesn't have to be place. Usually, place is the least desirable geometry manager to use. pack and grid are almost always better choices except for some very specific situations.
If I dynamically create Tkinter elements after the MyNotebook is initialized, will those be bound to respective tabs?
They will be in whatever tab you put them in.
Finally, I would suggest that you remove self.place in create_widgets. Instead, call pack, place, or grid in the same block of code that creates an instance of that class.
It's a bad practice for a widget to add itself to another widget's layout. The code that creates the widget should be the code that adds the widget to the layout.
I've seen some usage of self.destroy() within classes but I couldn't get it working with what I wanted it to do.
I have the class resultsPage that shows results obtained on another page. I have made the displayResults(pageNo) function to show these when resultsPage is visible. The problem arises with the back and next buttons which are made to go between pages of results. All widgets are created on top of each other but I want to remove them all then create the new ones. I added self.destroy() to try and fix this but it didn't work.
I'm not sure if it's to do with the placement of where I'm defining my functions but I have had a play around with where they're defined and it hasn't changed the error message.
This is a simplified example of my code:
class resultsPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def onShowFrame(self, event):
def displayResults(pageNo):
self.destroy()
#Create widgets related to pageNo
#Create back and next buttons e.g.
back = tk.Button(self, text="<=",
command=lambda: displayResults(pageNo - 1))
displayResults(1)
The error I get is: _tkinter.TclError: bad window path name ".!frame.!previewresultspage"
If it helps, I can post my full code but I thought I'd generalise it so it's more helpful to others.
You are deleting the widget in onShowFrame, and then immediately try to create a new widget with it as the parent. You can't use a deleted widget as the parent of another widget.
As pointed out, using self.destroy() in this case, will not work. To achieve the goal of deleting all widgets on the frame, you can use a loop (credit to #stovfl):
for widget in self.grid_slaves():
widget.destroy()
I have a
class Main(QtGui.QMainWindow):
That is able to click>spawn a x number of windows that are:
class dataWindow(QtGui.QWidget)
Is there a way in PyQt to now find all spawned dataWindow's and get their objectName?
each window has unique objectName.
I tried going via :
a= self.findChild(QWidget, self.newDataWids[0]["window_name"]) - as I have all names stored in dict upon creation
but it only returns None. I think its because the dataWindow are not parented to Main window class I believe... so I either have to parent them - not sure how. Or somehow find them out in the "wild"...
Any ideas would be great.
Regards, Dariusz
Edit_1: A glitch in my code bugged out my current attempt. After relooking I managed to get it to work. I simply stored the window in temporary dictionary and then used that to retrieve access to window.
You parent objects by passing in the parent to their constructor. You'll have to check the documentation for each widget to get the correct argument position.
widget = QtGui.QWidget(self)
btn = QtGui.QPushButton('Button Text', self)
But really, you shouldn't have to do a search for children to get the child windows. Your main window should be keeping handles to them.
def __init__(...)
...
self._windows = []
def createSubWindow(self):
window = WindowClass(self)
self._windows.append(window)
Im making a simplish widget that can act as a container for other widgets. One of the features of the widget is that you can expand/collapse it by clicking on it. My current method is basically looking up all child widgets of the layout and hiding them. I'm looking for any help on how to handle this properly - my current implementation has at least one serious caveat: that you can't add widgets while it's collapsed (they're added in an 'unhidden' state)
heres the setCollapsed method that is run when the widget is clicked
def collapsed(self):
return self._isCollapsed
def setCollapsed(self, collapseBool):
self._isCollapsed = collapseBool
if self.layout()!=None:
childWidgets = [self.layout().itemAt(i).widget() for i in range(self.layout().count())]
for w in childWidgets:
if isinstance(w,QtGui.QWidget):
w.setHidden(collapseBool)
if collapseBool:
self._cachedMargin = self.layout().margin()
self.layout().setMargin(0)
else:
self.layout().setMargin(self._cachedMargin)
Rather than hiding all child widgets individually, I would just hide a single parent item.
I'm using Python and Tkinter to create a GUI for a program I'm writing, and I'm having a couple of problems.
I have three objects descended from LabelFrame in an object descended from Frame. One of the LabelFrame descendants is two columns of corresponding Label and Entry objects.
The problem is that there are a varying number of Label and Entry pairs, and there can be more than fit on the screen. I need a way to make a scrollbar for this LabelFrame so that everything fits on the screen. I've tried various ways of making a Scrollbar object, but nothing seems to work. How can I bind a scrollbar to this frame?
Also, I need to be able to refresh or reload this LabelFrame when the load_message() method is called, but it just redisplays the new pairs on top of the old ones (so when there are less pairs in the new set, the old set is still visible at the bottom). I've tried using grid_forget() but either nothing changes or the whole frame doesn't display. How can I forget this display and then redisplay it?
Here is the code for this class:
class freq_frame(LabelFrame):
def __init__(self, master = None, text = 'Substitutions'):
LabelFrame.__init__(self, master, text = text)
self.grid()
def load_message(self):
self.frequency = get_freq(message)
self.create_widgets()
def create_widgets(self):
self.label_list = [Label(self, text = get_label(char, self.frequency[char]), justify = LEFT) for char in self.frequency.keys()]
self.entry_list = [Entry(self, width = 1) for char in self.frequency.keys()]
for n in range(len(self.label_list)):
self.label_list[n].grid(column = 0, row = n)
for n in range(len(self.entry_list)):
self.entry_list[n].grid(column = 1, row = n)
If anyone can help with either of these problems, I'd appreciate it.
Also, this question seems like it might be a little thin, but I don't know what to add. Don't hesitate to ask for more information (but be specific).
Thanks!
Labelframes don't support scrolling. So the short answer to your question is "you can't". It sounds obvious, but if the documentation for a widget doesn't say it supports scrolling, it doesn't support scrolling.
However, there is a simple solution. First, add a canvas as a child to the labelframe and pack it so that it fills the labelframe. Attach scrollbars to the canvas and add them to the labelframe too. Then embed a frame within the canvas, add your widgets to that inner frame, and then adjust the scrollregion of the canvas to match the size of the frame after you've added all the inner labels and entries.
It sounds complicated, but it's really very straight-forward.
As for re-creating the widgets when you call load_message, calling grid_forget only removes them from view, it doesn't actually destroy the widgets. Over time you could potentially end up with hundreds of non-visible widgets which is almost certainly not what you want.
Instead, you want to first destroy all the existing widgets. That's pretty easy if they all are in the same parent, since you can ask the parent for a list of all its children. Just iterate over that list to delete each child, then add any new children. An even easier solution is to destroy and recreate that inner frame that contains the labels and entries. When you delete a widget, all child widgets get automatically destroyed. So, delete that inner frame, create a new one, and add your labels and entries again.