I'm currently trying to make simple image viewer, where you can adjust brightness, contrast, color and sharpness with scale. I'm doing it with tkinter and PIL. So far I have two classes, one for brightness and another for sharpness and I just can't find a way(I'm a beginner) to draw two scales in one window and one picture, which can be modified by this scales. All i could do was to draw scales, two pictures, where each scale modified their own picture.
Sending a code:
from tkinter import *
from PIL import Image, ImageTk, ImageEnhance
class Enhance(Frame):
def __init__(self, master, image, name, enhancer, lo, hi):
Frame.__init__(self, master)
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
self.enhancer = enhancer(image)
self.update("1.0")
w = Label(self, image=self.tkim)
w.grid(row = 0, column = 0, padx = 0, pady = 0)
s = Scale(self, label=name, orient=VERTICAL,from_=hi, to=lo, resolution=0.01,command=self.update)
s.set(self.value)
s.grid(row = 1, column = 1, padx = 0, pady = 0)
def update(self, value):
self.value = eval(value)
self.tkim.paste(self.enhancer.enhance(self.value))
class Enhance1(Frame):
def __init__(self, master, image, name, enhancer, lo, hi):
Frame.__init__(self, master)
self.tkim = ImageTk.PhotoImage(image.mode, image.size)
self.enhancer = enhancer(image)
self.update("1.0")
w = Label(self, image=self.tkim).grid(row = 0, column = 0, padx = 0, pady = 0)
s = Scale(self, label=name, orient=VERTICAL,from_=hi, to=lo, resolution=0.01,command=self.update)
s.set(self.value)
s.grid(row = 0, column = 1, padx = 0, pady = 0)
def update(self, value):
self.value = eval(value)
self.tkim.paste(self.enhancer.enhance(self.value))
root = Tk()
im = Image.open("plant.jpg") #choose your image
Enhance(root, im, "Brightness", ImageEnhance.Brightness, 0.0, 3.0).grid(row = 0, column = 0, padx = 0, pady = 0)
Enhance1(root, im, "Sharpness", ImageEnhance.Sharpness, -1.0, 5.0).grid(row = 0, column = 0, padx = 0, pady = 0)
root.mainloop()
It's a design problem, not a Python one. You have to decouple the scales (possibly with a "ScaleDrawer" class) and enhancers from your frame and image, and the Frame class should take a list of enhancers and a list of "ScaleDrawer" objects.
You are putting both of the frames that hold the scales in the same spot -- row 0, column 0. If you want them to appear side-by-side you need to put them in different columns, or use pack.
Second, when using grid you should use the sticky argument, so that widgets expand to fill their containers. Also, you need to use the grid_rowconfigure and grid_columnconfigure methods to give at least one row and one column a weight of 1 (one) or more, so that your GUI has proper resize behavior. For simple layouts where everything is left-to-right or top-to-bottom, pack requires a few less lines of code.
Third, since you want both sliders to affect the same image, you don't want to create a label with the image in each of your classes Enhance and Enhance1. Instead, create a single image and put it in a single label. So, at the highest level you might create three widgets: a Label, an instance of Enhance, and an instance of Enhance1.
Finally, you seem to have an indentation error. The method update isn't indented, so it's not part of the class definition. Also, since this method is part of a class that subclasses Frame, you shouldn't call this method update, because that's a built-in method of all tkinter widgets.
My recommendation is to start over, rather than try to fix what you have. Begin with a blank slate, and first try to get the basic layout working before worrying about the sliders. This lets you focus on one thing instead of trying to get everything working at once.
For example, start with something like this to get the basic structure of your program (notice that I am not doing a global import, and I made the main program a class too, to help cut down on the need for global variables.
import Tkinter as tk
class ImageViewer(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, background="green")
# for now, don't use images. This, so that we can get
# the basic structure working
self.im = None
self.tkim = None
# these three widgets make up our main layout
label = tk.Label(self, image=self.tkim, text="label")
e = Enhance(self, self.im)
e1 = Enhance1(self, self.im)
# grid works as well as pack in this case, but requires a
# little more code. For that reason I prefer pack for very
# simple layouts such as this.
label.pack(side="bottom", fill="both", expand=True)
e.pack(side="left", fill="both", expand=True)
e1.pack(side="right", fill="both", expand=True)
class Enhance(tk.Frame):
def __init__(self, master, image):
# we will be operating on this image, so save a
# reference to it
self.image = image
# width, height, and color are only temporary, they
# make it easy to see the frames before they have
# any content
self.image = image
tk.Frame.__init__(self, master, background="bisque", width=100, height=100)
class Enhance1(tk.Frame):
def __init__(self, master, image):
# we will be operating on this image, so save a
# reference to it
self.image = image
# width, height, and color are only temporary, they
# make it easy to see the frames before they have
# any content
tk.Frame.__init__(self, master, background="blue", width=100, height=100)
if __name__ == "__main__":
root = tk.Tk()
ImageViewer(root).pack(fill="both", expand=True)
root.mainloop()
Notice how the areas resize properly as the window is resized. Once we get that working, we can add widgets to the inner frames without having to worry about how they affect the main layout.
With this, you now have a good foundation for building up your GUI. Start by fleshing out just one of the Enhance classes, and make sure it's working exactly as you want. Then, do the same for the second one.
Related
I am aware that you cannot use different types of geometry managers within the same Tkinter window, such as .grid() and .pack(). I have a window that has been laid out using .grid() and I am now trying to add a status bar that would be snapped to the bottom of the window. The only method I have found online for this is to use .pack(side = BOTTOM), which will not work since the rest of the window uses .grid().
Is there a way that I can select the bottom of the window to place widgets from when using .grid()?
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
class sample(Frame):
def __init__(self,master=None):
Frame.__init__(self, master)
self.status = StringVar()
self.status.set("Initializing")
statusbar = Label(root,textvariable = self.status,relief = SUNKEN, anchor = W)
statusbar.pack(side = BOTTOM, fill = X)
self.parent1 = Frame()
self.parent1.pack(side = TOP)
self.createwidgets()
def createwidgets(self):
Label(self.parent1,text = "Grid 1,1").grid(row = 1, column = 1)
Label(self.parent1,text = "Grid 1,2").grid(row = 1, column = 2)
Label(self.parent1,text = "Grid 2,1").grid(row = 2, column = 1)
Label(self.parent1,text = "Grid 2,2").grid(row = 2, column = 2)
if __name__ == '__main__':
root = Tk()
app = sample(master=root)
app.mainloop()
So using labels since I was kinda lazy to do other stuff, you can do frames to ensure that each section of your window can be packed/grid as required. Frames will be a useful tool for you to use when trying to arrange your widgets. Note that using a class can make things a little easier when deciding your parents. So imagine each frame is a parent and their children can be packed as required. So I would recommend drawing out your desired GUI and see how you will arrange them. Also if you want to add another frame within a frame simply do:
self.level2 = Frame(self.parent1)
You can check out additional settings in the docs
http://effbot.org/tkinterbook/frame.htm
PS: I am using a class hence the self, if you don't want to use classes then its okay to just change it to be without a class. Classes make it nicer to read though
Just give it a row argument that is larger than any other row. Then, give a weight to at least one of the rows before it.
Even better is to use frames to organize your code. Pack the scrollbar on the bottom and a frame above it. Then, use grid for everything inside the frame.
Example:
# layout of the root window
main = tk.Frame(root)
statusbar = tk.Label(root, text="this is the statusbar", anchor="w")
statusbar.pack(side="bottom", fill="x")
main.pack(side="top", fill="both", expand=True)
# layout of the main window
for row in range(1, 10):
label = tk.Label(main, text=f"R{row}")
label.grid(row=row, sticky="nsew")
main.grid_rowconfigure(row, weight=1)
...
In Python/Tkinter, I am trying to get a value (integer) from a frame, do a calculation on it and return it as an output. I was able to make it work when using only a master frame and putting everything in that one frame. Now I want to separate it and another operation into their own frames.
My program converts board feet to Lineal feet (frame A) and
Lineal feet to board feet (frame B).
I get errors like: NameError: global name 'b_entry' is not defined. I have a feeling I might be doing things like referencing frames (self, master, a_frame, etc.) improperly.
Here's a simple example:
import Tkinter as tk
class App:
def __init__(self, master):
self.master = master
master.grid()
b_frame = tk.Frame(master).grid(row=0)
b_name = tk.Label(b_frame, text='Blah').grid(row=1, columnspan=4)
b_label = tk.Label(b_frame, text='Label').grid(row=2, column=0)
b_entry = tk.Entry(b_frame).grid(row=2, column=1)
b_output = tk.Label(b_frame, text='Output')
b_output.grid(row=3, columnspan=2)
b_button = tk.IntVar()
tk.Button(b_frame, text='calc', command=self.convert).grid(row=4, columnspan=2)
def convert(self):
a = int(b_entry.get())
result = int(a * a)
b_output.configure(text=result)
root = tk.Tk()
App(root)
root.mainloop()
I see two issues.
You should not assign a widget to a variable and pack/grid it on the same line. This causes the variable to have a value of None, because that's what pack and grid return. This will screw up your widget hierarchy because when b_frame is None, tk.Label(b_frame, ... will make the label a child of the Tk window instead of the Frame.
If you want a variable to be visible in all of a class' methods, you should assign it as an attribute of self.
So you should change lines like
b_entry = tk.Entry(b_frame).grid(row = 2, column = 1)
To
self.b_entry = tk.Entry(b_frame)
self.b_entry.grid(row = 2, column = 1)
Then you will be able to properly reference the entry in convert.
a = int(self.b_entry.get())
Here is my code:
import tkinter as tk
import sys
class MapCreator:
def __init__(self, root):
#initialize the root window
self.root = root
self.root.geometry('800x600+0+0')
self.root.resizable(width = False, height = False)
self.menubar = None
self.mapFrame = tk.Frame(self.root,bd = 2,bg = 'white')
self.mapFrame.grid(column = 3, row = 3)
#self.mapFrame.pack()
self.__create_menubar()
def __create_menubar(self):
#Create the menubar
self.menubar = tk.Menu()
#config , which used to change the options
self.root.configure(menu = self.menubar)
#create a File menu and add it to the menubar
#add a new menu
file_menu = tk.Menu(self.menubar, tearoff=False)
#File cascade
self.menubar.add_cascade(label="File", menu=file_menu)
#New
file_menu.add_command(label="New", command=self.__create_new_map)
#Open
file_menu.add_command(label="Open", command=self.__open_new_map)
#Svae
file_menu.add_command(label="Save", command=self.__save_cur_map)
#Save as
file_menu.add_command(label="Save as", command=self.__saveas_cur_map)
def __create_new_map(self):
#if not saved , tell the editor
pass
def __open_new_map(self):
#if not saved , tell the editor
pass
def __save_cur_map(self):
#first time save or not
pass
def __saveas_cur_map(self):
pass
pass
if __name__ == "__main__":
root = tk.Tk()
app = MapCreator(root)
root.mainloop()
I want to have a subframe to show other things. But I cannot find my subframe.
By the way, I'm not familiar with the grid() method.
So can someone tell me the reason why I cannot make the subframe show?
In tkinter most of the widgets and frames take the default size when not specified. You can specify the size explicitly (give it a height and width) or have the frame or widgets stretch to dimensions.
Here is a brief introduction to tkinters grid and size. This is what I prefer, rather than specifying the height and weight.
Imagine you are splitting the Parent Frame (or any kind of container) into a grid. The grid attribute of the child allows you to position your child into the specific part of the Parent's grid. Like for example, you want your child to be in (0,0) of the parent. then you say. child.grid(row=0, column = 0).
Note: You are not specifying the width and height of the grid. It re-sizes itself to the default size of the child widget. Button has a specific size. If the child is another frame and that frame is empty. It will not be visible because the size is 1px wide and 1px height.
rowspan, columnspan allows you to adjust how many rows or columns the child widget occupies. This is a similar concept to merging cells in Excel table. So when you say child.grid(row=0, column = 0, rowspan=2, columnspan=2) You are specifying that the widget is placed in (0,0) and occupies 2 rows and 2 columns.
Now that you have that, how do you make it stretch? This is done using stickys and weights. For the child widget, you specify the sticky. With this you are defining which directions you would like to stretch. child.grid(row=0, column = 0, sticky='ew') specifies that the widget can stretch horizontally. child.grid(row=0, column = 0, sticky 'nsew') specifies full stretch. Specifying the sticky does not stretch the widget, it just dictates what direction is can stretch. You specify the weight of the column and row of the parent grid to specify different levels of stretch. parent.grid_columnconfigure(0, weight=1) adds a weight of 1 to column 0. So column 0 is stretched. If you have multiple columns you want to stretch, then weight specifies the ratio of increase between those different columns. parent.grid_rowconfigure(0, weight=1) does the same for rows.
Given all that background. If you add the grid_rowconfigure and grid_columnconfigure to your root and add the sticky to your mapFrame. You get this:
def __init__(self, root):
#initialize the root window
self.root = root
self.root.geometry('800x600+0+0')
self.root.resizable(width = False, height = False)
self.menubar = None
self.mapFrame = tk.Frame(self.root,bd = 2,bg = 'white')
self.mapFrame.grid(row = 0, sticky='nsew')
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_rowconfigure(0, weight=1)
#self.mapFrame.pack()
self.__create_menubar()
And this should work :)
There are lots of other properties you can specify in your child.grid() statement. The other one I usually use are padx pady which is padding in the x,y direction and ipadx ipady which is internal padding. Refer to http://effbot.org/tkinterbook/grid.htm for more details
Your frame is showing up. However, because it doesn't have anything in it, it's natural size is only one pixel wide and one pixel tall, making it virtually invisible.
Put something in the frame, or give it an explicit width and height.
Your other option is to configure grid such that the frame "sticks" to the sides of the cell it is in, and the cell expands to fill any extra space in the window.
For example:
self.mapFrame.grid(column = 3, row = 3, sticky="nsew")
root.grid_rowconfigure(3, weight=1)
root.grid_columnconfigure(3, weight=1)
The follwing code
import Tkinter as tk
root = tk.Tk()
labelA = tk.Label(root, text="hello").grid(row=0, column=0)
labelB = tk.Label(root, text="world").grid(row=1, column=1)
root.mainloop()
produces
How can I add a partial border to the Label so that I have
I see a that borderwidth= is a possible option of Label but it handles the four borders.
NOTE: the question is not about padding the cell (which is the essence of the answer that was formerly linked in the duplication comment)
There's not an option or a real easy way to add a custom border, but what you can do is create a class that inherits from Tkinter's Frame class, which creates a Frame that holds a Label. You just have to color the Frame with the border color you want and keep it slightly bigger than the Label so it gives the appearance of a border.
Then, instead of calling the Label class when you need it, you call an instance of your custom Frame class and specify parameters that you set up in the class. Here's an example:
from Tkinter import *
class MyLabel(Frame):
'''inherit from Frame to make a label with customized border'''
def __init__(self, parent, myborderwidth=0, mybordercolor=None,
myborderplace='center', *args, **kwargs):
Frame.__init__(self, parent, bg=mybordercolor)
self.propagate(False) # prevent frame from auto-fitting to contents
self.label = Label(self, *args, **kwargs) # make the label
# pack label inside frame according to which side the border
# should be on. If it's not 'left' or 'right', center the label
# and multiply the border width by 2 to compensate
if myborderplace is 'left':
self.label.pack(side=RIGHT)
elif myborderplace is 'right':
self.label.pack(side=LEFT)
else:
self.label.pack()
myborderwidth = myborderwidth * 2
# set width and height of frame according to the req width
# and height of the label
self.config(width=self.label.winfo_reqwidth() + myborderwidth)
self.config(height=self.label.winfo_reqheight())
root=Tk()
MyLabel(root, text='Hello World', myborderwidth=4, mybordercolor='red',
myborderplace='left').pack()
root.mainloop()
You could simplify it some if you just need, for instance, a red border of 4 pixels on the right side, every time. Hope that helps.
I don't believe there's an easy way of just adding a left border. However, you can definitely fool by using a sunken label ;)
So for example:
root=Tk()
Label(root,text="hello").grid(row=1,column=1)
Label(root,text="world").grid(row=2,column=3)
Label(root,relief=SUNKEN,borderwidth=1,bg="red").grid(row=2,column=2)
Label(root).grid(row=2,column=1)
root.mainloop()
This will create a window like the one that you wanted to see.
I'm building a program that needs to display a large amount of text, and I need to have a scrollbar attached to a Text widget.
I'm using Windows 7 , python 3.3...
Here's a small(est possible) example of what I'm working with. I feel like I'm missing something really obvious here and this is driving me **ing bonkers.
import datetime
import tkinter as tk
import tkinter.messagebox as tkm
import sqlite3 as lite
class EntriesDisplayArea(tk.Text):
"""
Display area for the ViewAllEntriesInDatabaseWindow
"""
def __init__(self,parent):
tk.Text.__init__(self, parent,
borderwidth = 3,
height = 500,
width = 85,
wrap = tk.WORD)
self.parent = parent
class EntriesDisplayFrame(tk.Frame):
"""
Containing frame for the text DisplayArea
"""
def __init__(self, parent):
tk.Frame.__init__(self, parent, relief = tk.SUNKEN,
width = 200,
borderwidth = 2)
self.parent = parent
self.grid(row = 0, column = 0)
self.entriesDisplayArea = EntriesDisplayArea(self)
self.entriesDisplayArea.grid(row = 1, column = 0, sticky = 'ns')
self.scrollVertical = tk.Scrollbar(self, orient = tk.VERTICAL,
command = self.entriesDisplayArea.yview)
self.entriesDisplayArea.config(yscrollcommand = self.scrollVertical.set)
for i in range(1000):
self.entriesDisplayArea.insert(tk.END,'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdf')
self.scrollVertical.grid(row=1,column=1,sticky = 'ns')
class ViewAllEntriesInDatabaseWindow(tk.Toplevel):
"""
Window in which the user can view all of the entries entered ever
entered into the database.
"""
def __init__(self, parent = None):
tk.Toplevel.__init__(self,parent,
height = 400,
width = 400)
self.grid()
self.entriesDisplayFrame = EntriesDisplayFrame(self)
if __name__ == '__main__':
t0 = ViewAllEntriesInDatabaseWindow(None)
I think your problem exists because of two issues with your code. One, you're setting the height of the text widget to 500. That value represents characters rather than pixels, so you're setting it to a few thousand pixels tall. Second, you are only ever inserting a single line of text, albeit one that is 40,000 characters long. If you set the height to something more sane, such as 50 rather than 500, and insert line breaks in the data you're inserting, you'll see your scrollbar start to behave properly.
On an unrelated note, the call to self.grid() in the __init__ method of ViewAllEntriesInDatabaseWindow is completely useless. You can't pack, place or grid toplevel widgets into other widgets.
Finally, I recommend you do not have any class constructor call grid (or pack, or place) on itself -- this will make your code hard to maintain over time. When a widget is created, the parent widget should be responsible for calling grid, pack or place. Otherwise, if you decide to reorganize a window, you'll have to edit every child widget.