Why, in this really basic code snippet, the data label (lb_data) is not appearing in its parent widget (fr_body), and instead appears in fr_header?
(The heights, widths and reliefs are just there to show the location of the Frame widgets)
fr_header=ttk.Frame(root,width=100,height=100,relief=GROOVE).grid(row=0,column=0)
lb_title=ttk.Label(fr_header,text="Title Title Title").grid(row=0,column=0)
fr_body=ttk.Frame(root,width=150,height=150,relief=SUNKEN).grid(row=1,column=0)
lb_data=ttk.Label(fr_body,text="Data").grid(row=0,column=0)
The .grid method returns None. So, fr_header is actually being assigned the value None. When you pass that to your label Tkinter sees the None and assumes you want to make the master widget the root widget (Tk()).
I never create a widget and grid at the same time as then you lose your easy handle on the widget you just created. Do it this way instead:
fr_header=ttk.Frame(root,width=100,height=100,relief=GROOVE)
fr_header.grid(row=0,column=0)
Related
I have a list of frames that each have an optionmenu with the same list of choices. When a choice is made in that specific optionmenu, I've only been able to get the last entry widget to change, not the corresponding one. In other widgets I've been able to use something like "lambda F=F:function(args)" but that isn't working here.
I've tried a trace on the variable in the option menu, I've tried wrapper functions, I've tried every combination of a lambda in the command section of the optionmenu widget. Most approaches create errors, some, like the one attached, modify the bottom frame/entry but not the correct corresponding one.
This doesn't seem like it should be too hard. If the option for the top frame selected is "Continuous" or "Discrete", the entry next to it should be 'normal' state with "?..?" in the box, if it is categorical, it should change to be 'disabled' with no contents. I could do this easily if I could somehow pass the Frame dictionary key to the "updateOnChange" function, but I can't, it only allows a single argument to be passed and that is the string value of mType[F].
from tkinter import *
def updateOnChange(type):
print(type)
if type.upper()=='CATEGORICAL':
rangeEntry[F].delete(0,END)
rangeEntry[F].config(state='disabled')
print("runCat")
else:
rangeEntry[F].config(state='normal')
rangeEntry[F].delete(0,END)
rangeEntry[F].insert(0,'?..?')
print("runCont")
mType={}
frame={}
om={}
rangeEntry={}
root=Tk()
Frames=['FrameOne','FrameTwo']
miningTypes=['Continuous','Categorical','Discrete']
for F in Frames:
mType[F]=StringVar(root)
if F=='FrameOne':
mType[F].set("Continuous")
else:
mType[F].set("Categorical")
frame[F]=Frame(root,borderwidth=3,relief=SUNKEN)
frame[F].pack(side=TOP,fill=X)
rangeEntry[F]=Entry(frame[F],width=20,font=("Arial",12))
om[F]=OptionMenu(frame[F],mType[F],*miningTypes,command=updateOnChange)
om[F].pack(side=LEFT)
rangeEntry[F].pack(side=LEFT)
mainloop()
``
Your updateOnChange function hard-coded the entry to be changed as rangeEntry[F], which points to the last Entry widget created in your for loop. To properly associate each entry, you should pass the widget as a parameter:
def updateOnChange(type, entry):
if type.upper()=='CATEGORICAL':
entry.delete(0,END)
entry.config(state='disabled')
print("runCat")
else:
entry.config(state='normal')
entry.delete(0,END)
entry.insert(0,'?..?')
print("runCont")
And then pass the parameter in your command:
om[F]= OptionMenu(frame[F],mType[F],*miningTypes,command=lambda e, i=rangeEntry[F]: updateOnChange(e, i))
[Edits noted:]
I want to hook into the ScrolledText widget so that when a user clicks anywhere in the scrollbar (or even scrolls it with the mouse wheel, hopefully) I can have it call a callback function where I can
[Add: "set a flag, and then return to let the ScrolledText widget do its thing."]
[Delete: " do something first (like turn off automatic scrolling) before the scrolling action takes place."]
Is this possible?
Thanks
Do you want to do something like turning off automatic scrolling, or is that actually what you want to do?
If you want to turn automatic scrolling on or off, just check the position of the text before inserting text. If it's at the end, add the text and autoscroll. If it's not at the end, add the text but don't scroll. This will work even if they scrolled by some other mechanism such as by using the page up / page down keys.
You can check a couple of different ways. I think the way I've always done it (not at my desktop right now to check, and it's been a few years...) is to call dlineinfo on the last character. If the last character is not visible, this command will return None. You can also use the yview command to see if the viewable range extends to the bottom. It returns two numbers that are a fraction between zero and one, for the first and last visible line.
While the user can't turn auto-scrolling on or off by clicking a button, this is arguably better because it will "just happen" when they scroll back to see something.
Not without reaching inside the ScrolledText to get at the Scrollbar and the Text and hook their bindings.
And, while you can do that, at that point, why even use ScrolledText? The whole point is that it's does the scroll bindings automagically without you having to understand them. If you don't want that, just use a Scrollbar and a Text directly. Tkinter Scrollbar Patterns explains how to do this in detail, but really, if you don't want to do anything unusual, it's just connecting a message from each one to a method on the other.
For example:
from Tkinter import *
def yscroll(*args):
print('yscroll: {}'.format(args))
scrollbar.set(*args)
def yview(*args):
print('view: {}'.format(args))
textbox.yview(*args)
root = Tk()
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)
textbox = Text(root, yscrollcommand=yscroll)
for i in range(1000):
textbox.insert(END, '{}\n'.format(i))
textbox.pack(side=LEFT, fill=BOTH)
scrollbar.config(command=yview)
mainloop()
If you can't muddle out the details from the (sometimes confusing and incomplete) docs, play around with it. Basically, yview is called whenever the scrollbar is moved, and yscroll is called whenever the view is scrolled. The arguments to yscroll are obvious; those to yview less so, but the docs do explain them pretty well.
Note that, when you've set things up normally, dragging the scrollbar or swiping the trackpad or rolling the mousewheel over the scrollbar sends a yview, which makes our code call textbox.yview, which then sends a yscroll, and that does not cause a new yview (otherwise, there would be an infinite loop). So, you see both methods get called. On the other hand, swiping the trackpad or rolling the mousewheel over the text, or using the keyboard to move off the bottom, sends yscroll, which again does not cause a yview, so in this case you only see one of the two methods.
So, for example, if you change yview to not call textbox.yview, you can drag the scrollbar all you want, but the text view won't move. And if you change yscroll to not call scrollbar.set, you can swipe around the text all you want, but the scrollbar won't move.
If you want a horizontal scrollbar as well, everything is the same except with x in place of y. But ScrolledText doesn't do horizontal scrolling, so I assume you don't want it.
If you really do want to dig into ScrolledText, you can look at the source for your version, which is pretty trivial if you understand the example above. In fact, it's basically just an OO wrapper around the example above.
In at least 2.7 and 3.3, the ScrolledText is itself the Text, and its self.vbar is the Scrollbar. It sets yscrollcommand=self.vbar.set in its superclass initialization, and sets self.vbar['command'] = self.yview after vbar is constructed. And that's it.
So, just remove the explicit scrollbar creation, and access it as textbox.vbar, and the same hooking code as above works the same way:
from Tkinter import *
from ScrolledText import *
def yscroll(*args):
print('yscroll: {}'.format(args))
textbox.vbar.set(*args)
def yview(*args):
print('yview: {}'.format(args))
textbox.yview(*args)
root = Tk()
textbox = ScrolledText(root)
for i in range(1000):
textbox.insert(END, '{}\n'.format(i))
textbox.pack(side=LEFT, fill=BOTH)
textbox['yscrollcommand'] = yscroll
textbox.vbar.config(command=yview)
mainloop()
Just be aware that this (the fact that textbox is a normal Text, and textbox.vbar is its attached Scrollbar) isn't documented anywhere, so it could theoretically change one day.
I am running into a problem with a tkinter program, I have the LabelFrame grouping a set of labels and entries, however, it is not grouping my widgets. My code for the LabelFrame is as follows:
(edit: i managed to get the Label to display, however, it is not grouping my widgets.)
root=Tk()
message_frame=LabelFrame(root,text="testing",padx=0,pady=0,width=100,height=100).grid(padx=5,pady=10)
message_label=Label(message_frame,text="Message").grid(row=1,column=0,sticky=W)
pub_label=Label(message_frame,text="Public Key").grid(row=2,column=0,sticky=W)
priv_label=Label(message_frame,text="Public Key").grid(row=3,column=0,sticky=W)
message_entry=Entry(message_frame,textvariable=message,width=50).grid(row=1,column=1,sticky=W)
pub_entry=Entry(message_frame,textvariable=pub_key,width=50).grid(row=2,column=1,sticky=W)
priv_entry=Entry(message_frame,textvariable=private_key,width=50).grid(row=3,column=1,sticky=W)
In Tkinter, the typical workflow is to create a widget and then place it using some geometry manager on two separate lines.
If I'm not mistaken, the .grid method on Tkinter widgets returns None. So if you print message_frame right after you create it, you will probably see that it is None. When you use that passed to the next widgets, they assume you want to put it on the root widget...
The easy fix is to do something like:
message_frame=LabelFrame(root,text="testing",padx=0,pady=0,width=100,height=100)
message_frame.grid(row=0,column=0)
And you probably want to do the same with all the widgets since I doubt you actually want pub_label = priv_label = None ...
Give the frame some size attributes:
from Tkinter import *
root = Tk()
message_frame = LabelFrame(root,text="testing",padx=0,pady=0,width=100,height=100).grid(row=0,column=0,padx=5,pady=10)
Once the width and height are defined, the frame shows up fine.
If you post some of your frame's contents, it might make it clearer if this is not the issue.
I'm trying to make a QScrollView with around 100 different labels and buttons.
But after I add them, the scrollbars do not appear.
here is an example of the code I wrote:
self.btn = {}
self.scroll = QScrollView(self)
self.scroll.setGeometry(QRect(0,0,300,300))
self.scroll.enableClipper(True)
vp = self.scroll.viewport()
for i in range(0,100):
self.btn[i] = QPushButton(vp)
for i in range(0,100):
self.scroll.addChild(self.btn[i],1,50*i)
self.scroll.setVScrollBarMode(QScrollView.AlwaysOn)
make the scrollbar appear but not to work.
The buttons get added to the QScrollView but I can't scroll down to see them all
what am I doing wrong? I'm using qt3.
You don't add all your little items to the scrollview. You have to insert a single, large container (a QFrame derived class, for example) into the scrollview that contains all your smaller widgets.
Actually you just need to give the scroll-view a layout and add your widgets to this. Adding them as sub-widgets of one big widget within the scrollview will do this for you, but it's messier.
I'm attempting to write a basic Tkinter GUI that has a Text widget at the top, then a Button widget left aligned under it, then another Text widget underneath the button. The problem I'm having is, after packing the Button widget to the left, when I then go to pack the second Text widget, it puts it next to the button on the right, rather than underneath the button. This happens regardless of what I set the side argument to for the second Text widget Here's a simple piece of code that demonstrates this behaviour:
from Tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(side=LEFT)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
So how would I go about setting up the second Text widget so that it appears below the button, rather than to the right of it?
There are generally two solutions to layout problems:
switch to using grid. It becomes real easy to do layouts like what you are trying to accomplish. Grid can solve probably 95% of all layout issues (it's amazing when you think about it -- Tk does with one manager what most toolkits need half a dozen to accomplish!)
use multiple frames. If some widgets need to be stacked top-to-bottom and some left-to-right you can't always get what you want packing everything in a single frame. Use one frame for the top-to-bottom parts of the layout and additional frames for the left-to-right content.
Also realize that widgets don't have to be children of the widget in which they are packed/gridded. You can use the "in" parameter to put widgets in some other container than their parent.
For example, in your specific example you can create three frames, top, middle, bottom. Pack these top-to-bottom in your toplevel window. Then you can pack the first text widget in the top, the button or buttons horizontally in the middle, and the other text widget in the bottom.
The advantage to such an approach is that it makes it much easier to change the layout in the future (which in my experience always happens at some point). You don't have to re-parent any of your widgets, just pack/place/grid them in some other container.
In your short example it doesn't make much difference, but for complex apps this strategy can be a life saver.
My best advice is this: layout isn't an afterthought. Do a little planning, maybe even spend five minutes drawing on some graph paper. First decide on the major regions of your app and use a frame or some other container for each (paned window, notebook, etc). Once you have those, do the same divide-and-conquer approach for each section. This lets you use different types of layout for different sections of your app. Toolbars get horizontal layout, forms might get vertical layout, etc.
I was initially misunderstanding how packing worked and didn't realise that the entire left side was being "claimed" when i did x.pack(side=LEFT). What I found after reading this and the answer by Alex here is that I was not really after having x packed to the left side at all, but rather having it anchored to the left, using anchor=W (W for West) instead of side=LEFT. My revised code snippet which does what I was after looks like this:
from tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(anchor=W)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
This way x is not "claiming" the left side anymore, it's just aligned to the left (or West) within its block of space.
Packing happens in the order the .pack methods are called, so once x has "claimed" the left side, that's it -- it will take up the left portion of its parent and everything else within its parent will be to its right. You need a Frame to "mediate", e.g....:
from Tkinter import *
root = Tk()
w = Button(root, text="Mysterious W")
w.pack()
f = Frame(root)
x = Button(f, text="Hi there!")
x.pack()
y = Button(f, text="I be Y")
y.pack(side=BOTTOM)
f.pack(side=LEFT)
root.mainloop()
(changed Texts to Buttons for more immediate visibility of layout only -- the Tkinter on this Mac doesn't show Texts clearly until they have focus, but Buttons are quite clear;-).
Do it the same way that WebView does using the Mosaic Canvas Widget Sets internals(which are very similar to Tk). The trick is that the second identical named Frame Object works as a Block Level Float(inline:block;) for everything placed after it and everything that calls "fr" already will automatically begin over inside of it.
You can have many doing this of TOP aligned widgets and simply add another identical named Frame where you want to break between side=LEFT's. Works after Bottom also.
fr=Frame(root)
fr.pack(fill=X, side=TOP)
block1=Label(fr)
block1.pack(side=LEFT)
block2=Label(fr)
block2.pack(side=LEFT)
block3=Button(fr)
block3.pack(side=LEFT)
# NAME IT THE SAME ID NAME AS THE FIRST MAIN FRAME...
fr=Frame(root)
fr.pack(fill=X, side=TOP)
# These NOW jump into the second Frame breaking the side=LEFT in new Frame
block4=Label(fr)
block4.pack(side=LEFT)
block5=Label(fr)
block5.pack(side=LEFT)
# AND THEY CONTINUE GOING side=LEFT AFTERWARDS.