Something like this, would make the widget appear normally:
Label(self, text = 'hello', visible ='yes')
While something like this, would make the widget not appear at all:
Label(self, text = 'hello', visible ='no')
You may be interested by the pack_forget and grid_forget methods of a widget. In the following example, the button disappear when clicked
from Tkinter import *
def hide_me(event):
event.widget.pack_forget()
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', hide_me)
btn.pack()
btn2=Button(root, text="Click too")
btn2.bind('<Button-1>', hide_me)
btn2.pack()
root.mainloop()
One option, as explained in another answer, is to use pack_forget or grid_forget. Another option is to use lift and lower. This changes the stacking order of widgets. The net effect is that you can hide widgets behind sibling widgets (or descendants of siblings). When you want them to be visible you lift them, and when you want them to be invisible you lower them.
The advantage (or disadvantage...) is that they still take up space in their master. If you "forget" a widget, the other widgets might readjust their size or orientation, but if you raise or lower them they will not.
Here is a simple example:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, text="Hello, world")
button1 = tk.Button(self, text="Click to hide label",
command=self.hide_label)
button2 = tk.Button(self, text="Click to show label",
command=self.show_label)
self.label.pack(in_=self.frame)
button1.pack(in_=self.frame)
button2.pack(in_=self.frame)
def show_label(self, event=None):
self.label.lift(self.frame)
def hide_label(self, event=None):
self.label.lower(self.frame)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I know this is a couple of years late, but this is the 3rd Google response now for "Tkinter hide Label" as of 10/27/13... So if anyone like myself a few weeks ago is building a simple GUI and just wants some text to appear without swapping it out for another widget via "lower" or "lift" methods, I'd like to offer a workaround I use (Python2.7,Windows):
from Tkinter import *
class Top(Toplevel):
def __init__(self, parent, title = "How to Cheat and Hide Text"):
Toplevel.__init__(self,parent)
parent.geometry("250x250+100+150")
if title:
self.title(title)
parent.withdraw()
self.parent = parent
self.result = None
dialog = Frame(self)
self.initial_focus = self.dialog(dialog)
dialog.pack()
def dialog(self,parent):
self.parent = parent
self.L1 = Label(parent,text = "Hello, World!",state = DISABLED, disabledforeground = parent.cget('bg'))
self.L1.pack()
self.B1 = Button(parent, text = "Are You Alive???", command = self.hello)
self.B1.pack()
def hello(self):
self.L1['state']="normal"
if __name__ == '__main__':
root=Tk()
ds = Top(root)
root.mainloop()
The idea here is that you can set the color of the DISABLED text to the background ('bg') of the parent using ".cget('bg')" http://effbot.org/tkinterbook/widget.htm rendering it "invisible". The button callback resets the Label to the default foreground color and the text is once again visible.
Downsides here are that you still have to allocate the space for the text even though you can't read it, and at least on my computer, the text doesn't perfectly blend to the background. Maybe with some tweaking the color thing could be better and for compact GUIs, blank space allocation shouldn't be too much of a hassle for a short blurb.
See Default window colour Tkinter and hex colour codes for the info about how I found out about the color stuff.
I'm also extremely late to the party, but I'll leave my version of the answer here for others who may have gotten here, like I did, searching for how to hide something that was placed on the screen with the .place() function, and not .pack() neither .grid().
In short, you can hide a widget by setting the width and height to zero, like this:
widget.place(anchor="nw", x=0, y=0, width=0, height=0)
To give a bit of context so you can see what my requirement was and how I got here.
In my program, I have a window that needs to display several things that I've organized into 2 frames, something like this:
[WINDOW - app]
[FRAME 1 - hMainWndFrame]
[Buttons and other controls (widgets)]
[FRAME 2 - hJTensWndFrame]
[other Buttons and controls (widgets)]
Only one frame needs to be visible at a time, so on application initialisation, i have something like this:
hMainWndFrame = Frame(app, bg="#aababd")
hMainWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
...
hJTensWndFrame = Frame(app, bg="#aababd")
I'm using .place() instead of .pack() or .grid() because i specifically want to set precise coordinates on the window for each widget. So, when i want to hide the main frame and display the other one (along with all the other controls), all i have to do is call the .place() function again, on each frame, but specifying zero for width and height for the one i want to hide and the necessary width and height for the one i want to show, such as:
hMainWndFrame.place(anchor="nw", x=0, y=0, width=0, height=0)
hJTensWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
Now it's true, I only tested this on Frames, not on other widgets, but I guess it should work on everything.
For hiding a widget you can use function pack_forget() and to again show it you can use pack() function and implement them both in separate functions.
from Tkinter import *
root = Tk()
label=Label(root,text="I was Hidden")
def labelactive():
label.pack()
def labeldeactive():
label.pack_forget()
Button(root,text="Show",command=labelactive).pack()
Button(root,text="Hide",command=labeldeactive).pack()
root.mainloop()
I was not using grid or pack.
I used just place for my widgets as their size and positioning was fixed.
I wanted to implement hide/show functionality on frame.
Here is demo
from tkinter import *
window=Tk()
window.geometry("1366x768+1+1")
def toggle_graph_visibility():
graph_state_chosen=show_graph_checkbox_value.get()
if graph_state_chosen==0:
frame.place_forget()
else:
frame.place(x=1025,y=165)
score_pixel = PhotoImage(width=300, height=430)
show_graph_checkbox_value = IntVar(value=1)
frame=Frame(window,width=300,height=430)
graph_canvas = Canvas(frame, width = 300, height = 430,scrollregion=(0,0,300,300))
my_canvas=graph_canvas.create_image(20, 20, anchor=NW, image=score_pixel)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.config(command=graph_canvas.yview)
vbar.pack(side=RIGHT,fill=Y)
graph_canvas.config(yscrollcommand=vbar.set)
graph_canvas.pack(side=LEFT,expand=True,fill=BOTH)
frame.place(x=1025,y=165)
Checkbutton(window, text="show graph",variable=show_graph_checkbox_value,command=toggle_graph_visibility).place(x=900,y=165)
window.mainloop()
Note that in above example when 'show graph' is ticked then there is vertical scrollbar.
Graph disappears when checkbox is unselected.
I was fitting some bar graph in that area which I have not shown to keep example simple.
Most important thing to learn from above is the use of frame.place_forget() to hide and frame.place(x=x_pos,y=y_pos) to show back the content.
For someone who hate OOP like me (This is based on Bryan Oakley's answer)
import tkinter as tk
def show_label():
label1.lift()
def hide_label():
label1.lower()
root = tk.Tk()
frame1 = tk.Frame(root)
frame1.pack()
label1 = tk.Label(root, text="Hello, world")
label1.pack(in_=frame1)
button1 = tk.Button(root, text="Click to hide label",command=hide_label)
button2 = tk.Button(root, text="Click to show label", command=show_label)
button1.pack(in_=frame1)
button2.pack(in_=frame1)
root.mainloop()
import tkinter as tk
...
x = tk.Label(text='Hello', visible=True)
def visiblelabel(lb, visible):
lb.config(visible=visible)
visiblelabel(x, False) # Hide
visiblelabel(x, True) # Show
P.S. config can change any attribute:
x.config(text='Hello') # Text: Hello
x.config(text='Bye', font=('Arial', 20, 'bold')) # Text: Bye, Font: Arial Bold 20
x.config(bg='red', fg='white') # Background: red, Foreground: white
It's a bypass of StringVar, IntVar etc.
Related
I must use tkinter library to create GUI.
I have this code:
# -*- coding: UTF-8 -*-
import tkinter as tk
class Application(tk.Frame):
resx=1600
resy=900
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack(fill="both", expand=1)
self.createWidgets()
master.minsize(self.resx, self.resy)
master.maxsize(self.resx, self.resy)
def createWidgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Create new window"
self.hi_there["command"] = self.PlayMode
self.hi_there.pack(side="top")
def ShowMenu(self, master):
print("Here I need DELETE master, in my case PlayM")
def PlayMode(self):
PlayM = tk.Tk()
PlayM.minsize(self.resx, self.resy)
PlayM.maxsize(self.resx, self.resy)
PlayM.title("Game")
bf= tk.Frame(PlayM, bg="blue")
bf.pack(side="bottom", fill=tk.X, expand = 1)
lbTEST=tk.Label(bf)
lbTEST["text"] = "TESTING"
lbTEST.pack()
mf = tk.Frame(PlayM,bg="red")
mf.pack(side="right", fill=tk.Y, expand=1)
self.LogOut = tk.Button(mf)
self.LogOut["text"] = "LOGOUT"
self.LogOut.pack()
self.LogOut["command"] = self.ShowMenu(PlayM)
root = tk.Tk()
app = Application(master=root)
app.master.title("Useless think")
app.mainloop()
I need something like this picture:
I don't know why my code is not working. When I pack my bf (bottom frame) and set side = "bottom", but it appears in the middle of the screen. Why?
Same with side = "right" when I pack mf (menu frame)
And I have one more question. About logout button. I set command's method "ShowMenu".
When I run my code, this method is started automatically only once, but when I click to button nothing happens. Why?
First, you have a critical flaw in your code. You should not be creating more than one instance of Tk. If you need to create additional windows, create instances of Toplevel.
When I pack my bf (bottom frame) and set side = "bottom", but it appears in the middle of the screen. Why?
You set expand to 1 for both mf and mf so each will end up taking half of the available space in the window. If you simply set expand to 0 (zero) or False for bf, it will only take up as much space as necessary.
bf.pack(side="bottom", fill=tk.X, expand = 0)
As a general rule, you should only set a true value for expand on a single widget (the "hero" widget), unless you want to distribute extra space equally among widgets.
When I run my code, this method is started automatically only once, but when I click to button nothing happens. Why?
Because you're telling it to run. Take a look at this code:
self.LogOut["command"] = self.ShowMenu(PlayM)
It is exactly the same as this code:
result = self.ShowMenu(PlayM)
self.logOut["command"] = result
See the problem?
The command attribute requires a reference to a function. Roughly translated, that means you can't use (). If you need to pass in an argument to the command, the typical solution is to use functools.partial or lambda to create a reference to an anonymous function that will call the real function with the argument:
self.logOut["command"] = lambda arg=PlayM: self.ShowMenu(arg)
Is there any specific reason why you use pack method instead of grid?
You could configure your Application frame like:
self.grid_rowconfigure(0, weight=1) #make the first "upper" row expand
self.grid_rowconfigure(1, weight=0) #leave the second "lower" row and do not expand it
self.grid_columnconfigure(0, weight=0) # first column, do not expand
self.grid_columnconfigure(1, weight=1) # second columnd, DO expand
self.grid_columnconfigure(0, weight=0) # third column, do not expand
on your mainframe/application class and then call:
bf.grid(row=1, column=0, columnspan=3, sticky=tk.NW+tk.SE) # span over three columns
lf.grid(row=0, column=0, sticky=tk.NW+tk.SE) # default is to span over one column
rf.grid(row=0, column=2, sticky=tk.NW+tk.SE)
Edit
I am very sorry, i forgot to mention about the logout button.
You call the event handler at binding time, therefore it will be executed there.
If you want to have it pass values either use:
command=lambda parent=PlayM: self.ShowMenu(parent)
use a class object to store youre parent reference to at creation time self._Parent = PlayM and use this inside ShowMenu
I personally prefer storing objects for single values. if you have many windows to destroy, I would use the lambda.
Apart from the problems already mentioned by Bryan Oakley and the solution given by R4PH43L using grid, here's a possible solution for your layout only using pack.
The idea is that by using side="left" (or side="right") and then side="top" (or side="bottom") does not work the way you may be expecting.
Within a frame you should just be using values for side (when packing the widgets in that same frame) which are either vertical ("top" or "bottom") or horizontal ("right" or "left"), but not both. Thus to accomplish layouts like yours using only pack you need additional frames!
# -*- coding: UTF-8 -*-
import tkinter as tk
class Application(tk.Frame):
resx=400
resy=200
def __init__(self, master=None):
tk.Frame.__init__(self, master, bg="red")
self.pack(fill="both", expand=1)
self.create_widgets()
master.geometry("400x200+100+500")
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Create new window"
self.hi_there["command"] = self.play_mode
self.hi_there.pack(side="top")
def show_menu(self, master):
print("Here I need DELETE master, in my case play_m")
def play_mode(self):
# Using a Toplevel instead of a Tk
# You should have just one Tk in your app
play_m = tk.Toplevel()
play_m.geometry("400x200+100+500")
play_m.title("Game")
top_frame = tk.Frame(play_m, bg="blue")
top_frame.pack(side="top", fill="both", expand=True)
left_top_frame = tk.Frame(top_frame, bg="white")
left_top_frame_label = tk.Label(left_top_frame, text="left top frame")
left_top_frame_label.pack()
left_top_frame.pack(side="left", fill="y")
middle_top_frame = tk.Frame(top_frame, bg="black")
middle_top_frame_button = tk.Button(middle_top_frame, text="Logout", command=play_m.destroy)
middle_top_frame_button.pack()
middle_top_frame.pack(side="left", fill="both", expand=True)
right_top_frame = tk.Frame(top_frame, bg="white")
right_top_frame_label = tk.Label(right_top_frame, text="right top frame")
right_top_frame_label.pack()
right_top_frame.pack(side="right", fill="y")
bottom_frame = tk.Frame(play_m, bg="yellow")
bottom_frame_label = tk.Label(bottom_frame, text="bottom frame")
bottom_frame_label.pack()
bottom_frame.pack(side="top", fill="both")
root = tk.Tk()
app = Application(master=root)
app.master.title("Useless think")
app.mainloop()
Here's the result of the main Tk window on a OS X (Sierra):
Second toplevel
I changed a little bit the sizes for the sake of exposition. I also renamed the methods to use _ and lower case letters.
I have problem with set in Scrollbar inside Text widget in Tkinter. I know, that it's preferable to use grid to locate widgets but I want to set my widget in absolute location (x,y - red dot on GUI picture) with specified height and width.
My code:
from Tkinter import *
from ttk import *
class NotebookDemo(Frame):
def __init__(self):
Frame.__init__(self)
self.pack(expand=1, fill=BOTH)
self.master.title('Sample')
self.master.geometry("650x550+100+50")
self._initUI()
def _initUI(self):
self._createPanel()
def _createPanel(self):
# create frame inside top level frame
panel = Frame(self)
panel.pack(side=TOP, fill=BOTH, expand=1)
# create the notebook
nb = Notebook(panel)
nb.pack(fill=BOTH, expand=1, padx=2, pady=3)
self._FirstTab(nb)
def _FirstTab(self, nb):
# frame to hold content
frame = Frame(nb)
#textbox
txtOutput = Text(frame, wrap = NONE, height = 17, width = 70)
txtOutput.place(x=10, y=75)
#button
btnStart = Button(frame, text = 'Start', underline=0)
btnStart.place(x=220, y=380)
#scrollbar
#vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
#txtOutput['yscroll'] = vscroll.set
#vscroll.pack(side=RIGHT, fill=Y)
#txtOutput.pack(fill=BOTH, expand=Y)
#add to notebook (underline = index for short-cut character)
nb.add(frame, text='TAB 1', underline=0, padding=2)
if __name__ == '__main__':
app = NotebookDemo()
app.mainloop()
If I uncomment this part of code (set Scrollbar):
vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side=RIGHT, fill=Y)
My Scrollbar is located inside all window, not inside Text box:
But of course I want to have the Scrollbar inside the Text box widget (black border).
If I use pack function to textbox:
txtOutput.pack(fill=BOTH, expand=Y)
text widget fill in the whole window...:
I really don't know how fix this problem.
Any help will be appreciated.
Thank you!
EDIT:
Of course I can use place method with Scrollbar too, but I can't change length of them, because it hasn't attribute length.
vscroll.place(x=573, y=75)
While I rarely recommend place, it is quite powerful when you take advantage of the configuration options. For example, you can use in_ to specify a widget that this widget is to be placed relative to. You can use relx to specify a relative x coordinate, and you can use relheight to specify a height.
In your case you can try something like this:
vscroll.place(in_=txtOutput, relx=1.0, relheight=1.0, bordermode="outside")
If you want the illusion that the scrollbar is embedded inside the text widget as is (or used to be) common on some platforms, I recommend placing the text widget and scrollbar in a frame.You can use pack to put the widgets in the frame, and continue to use place to place the combination anywhere you want.
For example:
txtFrame = Frame(frame, borderwidth=1, relief="sunken")
txtOutput = Text(txtFrame, wrap = NONE, height = 17, width = 70, borderwidth=0)
vscroll = Scrollbar(txtFrame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side="right", fill="y")
txtOutput.pack(side="left", fill="both", expand=True)
txtFrame.place(x=10, y=75)
Different geometry managers like place and pack don't mix so well. I see four options for you:
Use a parent frame
Create a new Frame that you place at the exact same position as you did with the text box. In this frame, you can use another geometry manager (I'd prefer pack) to make the layout appear as you want.
Use ScrolledText
Use the ScrolledText Tkinter module to have the solution above in a premade form. Note that this widget doesn't use ttk so the scrollbar style does not really adapt to the OS' look. Just use import ScrolledText and replace the Text creation in your code with ScrolledText.ScrolledText(...).
Use place for the scrollbar
If you are using place for the text widget, use place for the scrollbar too. place has options that allow you to place a widget relative to another widget both in location and size (ie: you can place the scrollbar along the right edge of the text widget, and cause it to be exactly as tall as the text widget). See Bryan's answer.
Don't use place
Simple as that. Use grid or pack instead, unless you really need to use place.
Is there a proper way to nest widgets inside of a ttk.Button? It supports specifying a label (a str) and image (a PhotoImage) which I assume is implemented using children widgets.
Here's an example where I'm adding a left-aligned and a right-aligned label to a button.
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
normal_button = ttk.Button(root, text="Normal Button")
normal_button.pack(fill=tk.X)
custom_button = ttk.Button(root)
custom_button.pack(fill=tk.X)
left_label = ttk.Label(custom_button, text="Left")
left_label.pack(side=tk.LEFT, padx=16, pady=4)
right_label = ttk.Label(custom_button, text="Right")
right_label.pack(side=tk.RIGHT, padx=16, pady=4)
root.mainloop()
This sort of works, but there are some quirks:
When hovering over the button, the button's background is highlighted but the nested labels keep their unhighlighted backgrounds.
If I click within either nested label, the button will press down, but will not become unpressed.
When the button is pressed, the nested labels will not shift giving the illusion of a button being pressed.
Is there a proper way to pack widgets inside of a button?
As I said in a comment you can create your own widget.
Here is a simple example with tk.Frame and tk.Label (ttk.Label needs more work with ttk.Style).
I bind events <Enter> and <Leave> to change frame and labels backgrounds.
For more widgets you could keep them in a list and use a for loop to change the background.
import tkinter as tk
import tkinter.ttk as ttk
class MyButton(tk.Frame):
def __init__(self, master, bg_hover='red', bg_normal=None, **options):
tk.Frame.__init__(self, master, **options)
self.bg_normal = bg_normal
self.bg_hover = bg_hover
# use default color if bg_normal is `None`
if not self.bg_normal:
self.bg_normal = self['bg']
# add first label
self.left_label = tk.Label(self, text="Left")
self.left_label.pack(side=tk.LEFT, padx=16, pady=4)
# add second label
self.right_label = tk.Label(self, text="Right")
self.right_label.pack(side=tk.RIGHT, padx=16, pady=4)
# bind events
self.bind('<Enter>', self.on_enter)
self.bind('<Leave>', self.on_leave)
def on_enter(self, event=None):
# change all backgrounds on mouse enter
self['bg'] = self.bg_hover
self.left_label['bg'] = self.bg_hover
self.right_label['bg'] = self.bg_hover
def on_leave(self, event=None):
# change all backgrounds on mouse leave
self['bg'] = self.bg_normal
self.left_label['bg'] = self.bg_normal
self.right_label['bg'] = self.bg_normal
root = tk.Tk()
normal_button = ttk.Button(root, text="Normal Button")
normal_button.pack(fill=tk.X)
my_button = MyButton(root)
my_button.pack()
root.mainloop()
There is no proper way to pack widgets inside a button. Buttons aren't designed for that feature. As you've seen, you can indeed use pack or grid to put widgets inside of buttons. However, you'll have to add custom bindings to make it appear as if it's all one button widget.
I'm trying to have it so that multiple objects on a canvas in Tkinter can be resized/repositioned using a spinbox, with the value in the spinbox being used as a multiplier to the original coordinates. To make matters slightly more complicated, the spinbox is not visible by default, it's in a Toplevel window that can be opened when a button is pressed.
To summarise:
I need to alter the coordinates of objects on a canvas using a spinbox value as a multiplier (or otherwise) which itself is in a Toplevel window, and have these alterations displayed in 'real time' on the canvas.
For context, I've included the key peripheral code responsible for setting up the objects etc.
Essential Parts of UI module:
import Canvas_1 (module for drawing shapes)
root=Tk()
#root geometry, title set up
#UI then commands set up
canvasBlank=Canvas(root, width... etc) #Blank canvas that is drawn at start
canvasBlank.grid(row... etc)
canvasBlank.bind('Button-3', rightclickcanvas) #Right click function that opens a popup for canvas options
#Other misc commands, I'm using a menubar with drop down options over actual Tk.Buttons
#'New' option in menubar has Command to create objects in UI like:
def createObject():
Objects=MyObjects(root, width... etc)
Objects.grid(row... etc) #Same as layout for canvasBlank
Objects.bind('<Button-3>', rightclickcanvas)
Objectslist.append(Objects) #Stop garbage disposal and makes sure the canvas displays
-The MyObjects Class (in seperate module) has a form similar to:
from Coordinate_Generator import * #imports coordinate arrays
class MyObjects(tk.Canvas)
def __init__(self, master, **kw)
tk.Canvas.__init__(self, master, **kw)
self.create_oval(coordinates[0], dimensions[0], fill... etc)
self.create_oval(coordinates[1], dimensions[1], fill... etc)
#A series of bindings relating to moving objects under mouse clicks
The coordinates are determined using 'a', an arbitrary value. I try to multiply:
scaler=[]
a=70*scaler[-1]
This method doesn't seem to work either, and if it did, it also means potentially drawing a very large number of canvases over one another which I would like to avoid. I'm hoping this demonstrates the method I need to try and use more clearly. I have written a bit of code using the advice given, and while it may be useful for another part of the program I'm planning, it doesn't quite achieve what I am after. So I've cobbled together this 'Demonstration'to maybe illustrate what it is I'm trying to do.
Working Code (SOLUTION)
from Tkinter import *
from numpy import *
import Tkinter as tk
scale=1
class Demonstrator:
def __init__(self, master=None):
global full_coordinates, dimensions, scale
self.master=master
self.master.title( "Demonstrator 2")
self.master.grid()
self.master.rowconfigure(0, weight=1)
self.master.columnconfigure(0, weight=1)
self.canvas = Canvas(self.master, width=300, height=300, bg='grey')
self.canvas.grid(row=0, rowspan=3, column=0)
self.canvas.create_rectangle(full_coordinates[0],dimensions[0], activefill='blue', fill='red')
self.canvas.create_rectangle(full_coordinates[1],dimensions[1], activefill='blue', fill='red')
self.canvas.create_line(full_coordinates[0],full_coordinates[1], fill='red')
a=9*scale
Originx=10
Originy=35
coordinates1=[]
coordinates2=[]
x,y,i=Originx,Originy,1
x1,y1,i=Originx,Originy,1
while len(coordinates1)<=25:
coordinates1.append((x,y))
coordinates2.append((x1,y1))
i+=1
if i % 2 == 0:
x,y=x+a,y
x1,y1=x1,y1+a
else:
x,y=x,y+a
x1,y1=x1+a,y1
full_coordinates=list(set(coordinates1+coordinates2))
b=array(full_coordinates)
k=b+10
dimensions=k.tolist()
class Settings:
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.top.title('Settings')
self.spinbox_Label= tk.Label(top, text='Change Scale Factor?')
self.spinbox_Label.grid(row=0, column=0, columnspan=2)
self.spinbox_Label= tk.Label(top, width=30, text='Scale factor:')
self.spinbox_Label.grid(row=1, column=0)
self.spinbox= tk.Spinbox(top, from_=1, to=10, increment=0.1, command=self.change)
self.spinbox.grid(row=1, column=1)
def change(self):
global scale
scale=float(self.spinbox.get())
MG=Demonstrator(root) #This just generates a new Demonstrator with original coordinates
def onClick():
inputDialog = Settings(root)
root.wait_window(inputDialog.top)
def onClick2():
print scale
class coords:
global full_coordinates, dimensions, scale
print scale
a=9*scale
Originx=10
Originy=35
coordinates1=[]
coordinates2=[]
x,y,i=Originx,Originy,1
x1,y1,i=Originx,Originy,1
while len(coordinates1)<=25:
coordinates1.append((x,y))
coordinates2.append((x1,y1))
i+=1
if i % 2 == 0:
x,y=x+a,y
x1,y1=x1,y1+a
else:
x,y=x,y+a
x1,y1=x1+a,y1
full_coordinates=list(set(coordinates1+coordinates2))
b=array(full_coordinates)
k=b+10
dimensions=k.tolist()
root=Tk()
root.minsize=(700,700)
root.geometry=('600x600')
MG=Demonstrator(root)
mainButton2 = tk.Button(root, width=20, text='Print "scale"', command=onClick2)
mainButton2.grid(row=1, column=1)
mainButton = tk.Button(root, width=20, text='Settings', command=onClick)
mainButton.grid(row=2, column=1)
root.mainloop()
mainButton2.grid(row=1, column=1)
mainButton = tk.Button(root, width=20, text='Settings', command=onClick)
mainButton.grid(row=2, column=1)
root.mainloop()
The Question:
What is the best way to go about changing the size (by altering the coordinates) of the objects on the canvas using a spinbox?
I hope this is enough to info, of course I can supply more if necessary. I also apologise in advance for the formatting of this question, I'm new to this :)
(Solution added)
Any help would be awesome. Cheers.
Mark
There's nothing special about the solution. You simply need to define a callback for the spinbox that adjusts the coordinates of the canvas items (which can be done with the coords method of the canvas).
First, you might want to create a dict to contain the base width and height of each item. The keys to this dictionary could also be tags associated with canvas items. For example:
self.base_dimensions = {
"obj1": (10,10),
"obj2": (20,20),
...
}
Next, create items on a canvas using those keys as tags. For example:
...
self.canvas.create_rectangle(..., tags=("obj1",))
self.canvas.create_rectangle(..., tags=("obj2",))
...
Finally, you can save the spinbox widgets in a dictionary using the same keys (so you can associate a spinbox with a canvas object), and assign the spinbox a callback to do the resizing. For example:
self.spinbox = {
"obj1": tk.Spinbox(..., command=lambda self.do_resize("obj1")),
"obj2": tk.Spinbox(..., command=lambda self.do_resize("obj2")),
...
}
Given a tag, your callback can use that to get the reference to the spinbox widget and get it's value, and then use the tag to tell the canvas object which item(s) to resize. For example:
def do_scale(self, tag):
factor = int(self.spinbox[tag].get())
(width, height) = self.default[tag]
(x0,y0,x1,y1) = self.canvas.coords(tag)
width = factor * width
height = factor * height
x1 = x0 + width
y1 = y0 + height
self.canvas.coords(tag, x0,y0,x1,y1)
Of course, there are endless ways to organize your data; what I've shown isn't the best way nor the only way. It might not even work for how you have your code organized. Whatever you choose, it boils down to being able to get the value out of the spinbox and using it to adjust the coordinates of the canvas items.
I'm trying to create a simple word processor for starters to learn Python a bit better.
I'm using the Tkinter Text widget for the main editing program, the only problem is the height and width are defined by characters.
This creates a problem when I change fonts, for not all fonts are the same width.
Every time the font is changed, the Text widget re-sizes, although technically it is the same width and height. This looks ridiculous when trying to type up something, I'm trying to make the word processor as nice as possible.
Is there a way to define the width and height in pixels?
the .grid_propagate(False) is not useful for the size is technically not changing, only the character width.
I'm trying to stay away from wxPython for now, since everything I've done up to this point has been in Tkinter.
I have done endless hours of extensive googling but have found no solutions.
You are wrong when you say you can't use grid_propagate(False), because you can. grid_propagate is related to the actual size, not the size attribute. Also, if you simply give your application a fixed size using wm_geometry, font changes won't affect the size of the window.
Here's an example using grid_propagate, which sets the container to a fixed size in pixels:
import Tkinter as tk
import tkFont
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self._textFont = tkFont.Font(name="TextFont")
self._textFont.configure(**tkFont.nametofont("TkDefaultFont").configure())
toolbar = tk.Frame(self, borderwidth=0)
container = tk.Frame(self, borderwidth=1, relief="sunken",
width=600, height=600)
container.grid_propagate(False)
toolbar.pack(side="top", fill="x")
container.pack(side="bottom", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
text = tk.Text(container, font="TextFont")
text.grid(row=0, column=0, sticky="nsew")
zoomin = tk.Button(toolbar, text="+", command=self.zoom_in)
zoomout = tk.Button(toolbar, text="-", command=self.zoom_out)
zoomin.pack(side="left")
zoomout.pack(side="left")
text.insert("end", '''Press te + and - buttons to increase or decrease the font size''')
def zoom_in(self):
font = tkFont.nametofont("TextFont")
size = font.actual()["size"]+2
font.configure(size=size)
def zoom_out(self):
font = tkFont.nametofont("TextFont")
size = font.actual()["size"]-2
font.configure(size=max(size, 8))
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
..tested this: this works on the frame size, but not on the (scrolled)text-widgets size.
my setup is a bit more complicated, with a masterframe for 2 sites, mainframes for each site and frames with various content, i.a. a scrolledtext widget.
adding grid_propagate, *_row- and *_columnconfigure warded the masterframe from resizing, even with adding grid options to main- and contentframes, what results in warding the mainframe to resize.. when changing the font, the widgetsize also changes - and, in the last example, the scrolledtext-widget disappears behind the frame on its right side (containing other widgets)..
Use the pack geometry manager to pack the widgets.
I was creating a notepad and wanted the font sample area to display the font size.
So I created a label frame and added a label in it - i.e. AabYyZz - to display the font sample.
When I increased the font size the size of the label frame also increased, so I've tried the .pack_propagate method and it worked.