Kivy Rectangle source doesn't get updated? - python

(I already read a lot of other posts about this but they do not seem to help me (or I simply don't understand them) )
I have a function Add() in which another function Grid() gets called that creates a Grid.png file and saves that to my desktop. This Add() function gets called multiple times (through a button) and with that also the Grid() function within. Here is a little code snippet:
Width = 700
Height = 700
def __init__(self,**kwargs):
super(Drw, self).__init__(**kwargs)
self.CellCount = 1
time.sleep(0.5)
self.cellAdd= int(input("\nCells to add: "))
self.bg = ""
with self.canvas:
self.add = Button(text = "add", font_size =40, pos = (700,300))
self.sub = Button(text="sub", font_size=40, pos=(700, 400))
self.add.bind(on_press = self.Add)
self.sub.bind(on_press= self.Sub)
self.add_widget(self.sub)
self.add_widget(self.add)
def Add(self, instance):
self.CellCount += self.cellAdd
Grid(self.CellCount, self.Width, self.Height)
with self.canvas:
self.bg = Rectangle(source= r"C:\Users\Max\Desktop\Grid.png", pos=(0,0), size= (self.Width, self.Height))
self.L = Label(text=str(self.CellCount)+" columns", pos=(500, 300))
What happens is that the first time I press the "Add" button, it does what it should so Add() gets called and in turn Grid() gets called and creates a new image on my desktop. Then the "bg" (background) is created and the image is correctly displayed. This only works 1 time however. After that, when I continue to press Add, nothing happens even though the Grid.png is getting changed on my desktop everytime I press "Add". The image just doesnt get updated somehow. The path always remains the same so I dont understand why it does not change the image to the new one?
I already tried to manually update the source with
self.bg.source = r"C:\Users\Max\Desktop\Grid.png"
but that does not do anything. I am pretty new to Kivy so I apologize if this gets asked alot.
Thank you for reading!
EDIT
I fixed it with this:
def Add(self, instance):
self.CellCount += self.cellAdd
Grid(self.CellCount, self.Width, self.Height)
with self.canvas:
self.canvas.clear()
self.bg =Image(source= r"C:\Users\Max\Desktop\Grid.png", pos=(0,0), size= (self.Width, self.Height))
self.bg.reload()
self.L = Label(text=str(self.CellCount)+" columns", pos=(500, 300))
I still dont know why Cache.remove() doesnt work as it seems logical to me but at least .reload() works good enough. Thank you for your answers!

Probably the image source is being cached by Kivy's image loader, so you need to inform it of the update. Try from kivy.cache import Cache and Cache.remove("kv.texture", your_filename) (or omit the your_filename argument to clear the whole texture cache).

Related

Resizing Canvas on Tkinter first run issue

I want to make a program that begins as a small window, then when given a path to an image, it maximises the screen and places the image in the centre.
If you run the code below you will see that the window maximises, the image is loaded into memory, the code runs with no errors and self.open_image calls self.draw_image(self.pimg) which runs without error, however the image is not present on the canvas.
If I click the button "Fix" and call self.fix it calls self.draw_image(self.pimg) which runs without error and correctly draws the image.
How can you call the same function twice with the same arguments and get different results. What is different.
I get the feeling this is happening because something has taken place in the main loop that hasn't taken place at the end of self.__init__, so that when i call self.draw_image the second time self.cv.create_image is able to interact with something in the resizable canvas.
In this example I am happy to assume the program will always begin as a small window and become a maximised window untill it is closed, never being resized again, however in my real program I would like to make it more dynamic where the window handles resizing sensibly, this is just a minimum reproducible example. It is for this reason that I would like to use the ResizingCanvas class (or one like it) even though I feel that it is likely the cause of the issue I am experiencing.
I have tried using breakpoints and stepping through the code watching the variables get created but I cant see the difference between the self.cv the first time around and self.cv after I click the button.
I read about a similar issue here on this question and he suggests binding "<Configure>" To the canvas and passing the coords from the event to the canvas. However this has already been implemented in ResizingCanvas
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.root = Tk()
self.name = name # Filename
myframe = Frame(self.root)
myframe.pack(fill=BOTH, expand=YES)
self.cv = ResizingCanvas(myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
self.b = Button(self.cv, text = 'Fix', command = self.fix).grid(row=1,column=1)
self.open_img()
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.root.state("zoomed")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.root.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
What should happen in the video:
I click run and the image is displayed immediately, without having to click the fix button.
What does happen in the video:
I click run and the image is not displayed until I click the fix button, afterwhich it works.
Changing
self.root.state("zoomed") to self.root.state("normal")
in your code (I am working on Python3) I can only get:
[
the image above, played a little bit starting from How to get tkinter canvas to dynamically resize to window width?
and now the code seems to work with me:
from time import sleep
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent, **kwargs):
# Canvas.__init__(self,parent,**kwargs)
print(kwargs)
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
# self.height = self.winfo_reqheight()
# self.width = self.winfo_reqwidth()
self.height = self.winfo_height()
self.width = self.winfo_width()
# self.height = height
# self.width = width
# self.__dict__.update(kwargs)
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = (event.width)//self.width
hscale = (event.height)//self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.pippo = Tk()
self.name = name # Filename
self.myframe = Frame(self.pippo)
self.myframe.pack(side = BOTTOM, expand=YES)
# myframe.pack(fill=BOTH, expand='TRUE')
self.cv = ResizingCanvas(self.myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
# sleep(2)
self.b = Button(self.myframe, text = 'Fix', command = self.fix)#.grid(row=1,column=1)
self.b.pack(side=TOP)
self.open_img()
# self.pippo.mainloop() ## use it if you eliminate def run
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
# self.cv.create_image(self.pippo.winfo_screenwidth()/2,
# self.pippo.winfo_screenheight()/2, image=self.img, tags=('all'))
self.cv.create_image(self.pippo.winfo_width()/2,
self.pippo.winfo_reqheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.pippo.state("normal")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.pippo.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
don't know about your question though, but wanted to be sure your starting example works right. Let me know if it could be related to python/pillow/tkinter version or something else
Here my window image results before ad after pressing fix button :
At the end found out that your code does work as long as you use
self.root.attributes('-zoomed', True) instead of `self.root.state("zoomed")`
The problem is here. self.root.winfo_screenwidth()
Change it to self.cv.width. I don't know why.
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
Change the last line to
self.cv.create_image(self.cv.width/2,
self.cv.height/2, image=self.img, tags=('all'))
Fixes the issue.
Tk.winfo_screenwidth() according to https://tkdocs.com/shipman/universal.html returns the width of the screen, indepedant of the size of the window, so even if you have a small window on a 1920x1080 display, this function will return 1920.
self.cv.width returns the width of the canvas object.

Unexpected behavior of the Canvas

I have some code for example. That contains two buttons. I have bind the on_press event for btn, and on click I tried to change its Scale. But in fact this code somehow changes size of btn1 instead btn. I do not know why
class TutorialApp(App):
scale = 1
def btn_click(self, elem):
if self.scale < 0.2: return
sc = Scale()
sc.xyz = (0.8,0.8,1)
self.scale *= 0.8
print self.scale
elem.canvas.add(sc) #after и canvas has same behavior, but before changes scales of both buttons/ I don't know why
def build(self):
bl = Layout(text='APP')
bl.orientation = 'vertical'
btn = Button(text='OK', size_hint=(1,0.5))
btn1 = Button(text='OK1', size_hint=(1,0.5))
btn.bind(on_press = self.btn_click)
bl.add_widget(btn) #,pos=(100,100)
bl.add_widget(btn1) #,pos=(100,100)
return bl
I have tried to employ after or before, and by elem.canvas.after behavior is same as without them, by before I get changes size of the both buttons. Why is this happening?
The elem.canvas.add(sc) appends the Scale to the list of instructions for btn. So it doesn't affect the drawing of btn, but does affect the drawing of btn1 (since it is drawn after btn). Using canvas.before inserts the Scale before the btn drawing, so it affects both. Obviously, using canvas.after has the same effect as the original.

Tkinter - Image won't show up on button despite keeping global reference

I want to place a button in the upper right corner and have the button be an image. I understand about scoping/garbage-collection etc. and have seen all the other questions asked here that overlook this fact.
However, I have tried numerous methods including creating a self.photo and declaring photo as a global variable. I'm actually not even convinced that that's the issue, because I declare the photo in the same scope as I call the mainloop().
My code right now (which is mostly borrowed from Drag window when using overrideredirect since I'm not really familiar with tkinter):
import tkinter
pink="#DA02A7"
cyan="#02DAD8"
blue="#028BDA"
class Win(tkinter.Tk):
def __init__(self,master=None):
tkinter.Tk.__init__(self,master)
self.overrideredirect(True)
self._offsetx = 0
self._offsety = 0
self.bind('<Button-1>',self.clickwin)
self.bind('<B1-Motion>',self.dragwin)
self.geometry("500x500")
def dragwin(self,event):
x = self.winfo_pointerx() - self._offsetx
y = self.winfo_pointery() - self._offsety
self.geometry('+{x}+{y}'.format(x=x,y=y))
def clickwin(self,event):
self._offsetx = event.x
self._offsety = event.y
win = Win()
# put a close button
close_button = tkinter.Button(win, bd=0, command=win.destroy)
global photo
photo=tkinter.PhotoImage("close.gif")
close_button.config(image=photo, height="10", width="10")
# pack the widgets
close_button.pack(anchor=tkinter.NE)
win.configure(bg=pink)
win.mainloop()
The correct way to create the photoimage is by passing the path to the file parameter. Otherwise, your path gets assigned to the internal image name and thus no file will be associated with the image.
photo=tkinter.PhotoImage(file="close.gif")
I typically give PhotoImages a name and use the name in image parameters:
photo=tkinter.PhotoImage(name='close', file="close.gif")
close_button.config(image='close')
I'm not sure if this is the only way, but this works here.

Python use image as background (graphics.py)

So my professor is making us use Graphis.py(zelle) to make a GUI I've made all the buttons my issue is that the module doesn't have any functionality that allows a image to be the background only the color. Do you guys have any idea how I can modify it so I can set the background to a image? The setBackground method is the one I believe needs to be edited
class GraphWin(tk.Canvas):
"""A GraphWin is a toplevel window for displaying graphics."""
def __init__(self, title="Graphics Window",
width=200, height=200, autoflush=True):
master = tk.Toplevel(_root)
master.protocol("WM_DELETE_WINDOW", self.close)
tk.Canvas.__init__(self, master, width=width, height=height)
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.items = []
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self._onClick)
self.bind_all("<Key>", self._onKey)
self.height = height
self.width = width
self.autoflush = autoflush
self._mouseCallback = None
self.trans = None
self.closed = False
master.lift()
self.lastKey = ""
if autoflush: _root.update()
def __checkOpen(self):
if self.closed:
raise GraphicsError("window is closed")
def _onKey(self, evnt):
self.lastKey = evnt.keysym
def setBackground(self, color):
"""Set background color of the window"""
self.__checkOpen()
self.config(bg=color)
self.__autoflush()
If you store your image as a gif, for example, Python will usually be able to display it. Here are the steps. I will assume that you create a graphic window named "win".
First, save your image in a file such as TestPic.gif.
Second, import the image and assign it a name such as b:
b = Image(Point(100,100),"TestPic.gif")
The Point(100,100) is the point that the image will be centered on. Now you want to display it:
Image.draw(b,win)
That's pretty much it. You can manipulate the image of course by using standard Python Graphics commands, such as moving it around, etc.
For reference, look at the Graphics pdf at this link:
http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf
It spells everything out pretty well.

Class to position a window on the screen

I am new to Python and this is my fist Python class. I am using PyQt4 framework on Windows 7.
I don't know whether the few lines of code below is correctly written or not. I want to modify it further as:
In the arguments, I want to pass the name of another opened Window (.py) on the screen.
I will be passing the x-coord., y-coord. and the name of the window to position on the screen.
How to modify the code to fulfill these requirements?
Edited Further
class PositionWindow:
def __init__(self, xCoord, yCoord, windowName, parent = None):
self.x = xCoord
self.y = yCoord
self.wName = windowName;
def center(self):
screen = QtGui.QDesktopWidget().screenGeometry()
size = self.geometry()
self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
Can't you just use window.setGeometry(x_pos, y_pos, width, height)? A class seem overkill in this case.
See here for documentation.
You can also use
def main():
app = QtGui.QApplication(sys.argv)
gui = Program()
gui.move(380, 170)
gui.show()
app.exec_()
the gui.move() will position your application to the stated coordinates in the parenthesis

Categories

Resources