I cannot display all the images of a list in a grid column. What am I doing wrong? - image

I am building a small diary that includes pictures. I want to arrange those pictures in a single column. When I run the code below, the first picture appears. Pressing Enter, the second picture appears but the previous one disappears and so on until the last picture.
Notice that the 'Press Enter to continue' will not be part of the code. It is there to trigger the posting of the picture. So does the Print(image). It shows the files being processed.
import tkinter as tk
from tkinter import *
from tkinter import ttk
from tkinter import Frame, Menu
from PIL import Image, ImageTk
import os
root = tk.Tk()
root.geometry('750x1000')
root.resizable(0,0)
root.columnconfigure(0, weight=5)
root.columnconfigure(1, weight=5)
root.columnconfigure(2, weight=5)
photo_list = []
photo_list = ['zendo1.png','hadock2.png','moon.png','sun.png','sailboat-sunset.png']
n=0
i=0
j=0
while n < 5:
img=Image.open(photo_list[n])
img= img.resize((150,150),Image.ANTIALIAS)
image=img
photoImg = ImageTk.PhotoImage(image)
photo_label= Label(root, image = photoImg)
print (image)
photo_label.grid(row=i, column=j)
verif_step = input("Please Press Enter to Continue ")
i=i+1
#j=j+1
n += 1
root.mainloop()
I tried with 'While' and 'For', plus looking up previous posts dealing with the same issue. But no cigar...

Related

window.read doesn't return events after creating any window in the matplotlib' toolbar

The code is based on: https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
One custom button is added to the toolbar and if we call, ex. sg.popup from callback function for this button main loop become broken - no events are returned from any button (Plot and Exit in the example).
import PySimpleGUI as sg
import numpy as np
import os
import sys
import matplotlib.backends
import base64
"""
Embedding the Matplotlib toolbar into your application
Based on:
https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Matplotlib_Embedded_Toolbar.py
"""
# ------------------------------- This is to include a matplotlib figure in a Tkinter canvas
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
if os.path.isfile("axsminmax.png") == False:
toolbarpng1 = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAABsklEQVRYhe3Xz2oUQRAG8F8mvoH4CuJbeDagQvBoLtldl+BJIko04j98BiX+IycPXox61zyFkKsevedkxkPXkHHipHd2nQ2KHxTM1lbX9011d/U0s2GE4Yw5psYtlGF3502+GcQ/wsoQNBfcDsLX2MVnPAvfRt/k4yDaRoFPYUX4Sqx0SXiqo4CveIyHOKj5D7CKb/jeMedMqCowNYo/JOTvFdB1DTTxRlp4/xZWcKGHvMtYywXdlEr6JBNX4BreY0c6E3Lr6X7kftAWUPX2bSxmyD9G7F5YiQ8ZEQvYahNRb6+5NxlFbL2ca+EbZMYWeFEXsRAJt6RDZdevHa7C24ghlf0czjZi9vAFl+P3GFdaRJyXqnx90j4w6VYrW57bsF89bMSAl/JTMNQ+BauZsYU0zSXWm39OKqKQFlxzEe5IU9qGRYcn5hHypohHxyQSREO8CxtkyElb+1jyCgNczAVNgSVc7SHvyWIsdcQTw/8PkrkLWJK20+8Oq0Lavpf6FHAad/CqMbbAc9zDmY45O+OGoxeTp+Hb7Ju8QvXtUL+a9X4ramLd4eV0bm/exChsavwEAXVwI8ngt8MAAAAASUVORK5CYII='
with open("axsminmax.png", "wb") as fh:
fh.write(base64.decodebytes(toolbarpng1))
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
if canvas.children:
for child in canvas.winfo_children():
child.destroy()
if canvas_toolbar.children:
for child in canvas_toolbar.winfo_children():
child.destroy()
figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
figure_canvas_agg.draw()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
def get_res_file_path(fname):
if hasattr(sys, "_MEIPASS"):
fpath = os.path.join(sys._MEIPASS, fname)
else:
basedir = os.path.abspath(os.getcwd())
fpath = basedir+'/'+fname
return fpath
def callback_func_P(NavigationToolbar2TK):
def wrapper():
print('Plot on min/max from toolbar')
sg.popup('This window blocks main loop.\nPlot, Exit and Alive? buttons not working after that.\nAnd Terminal window could be closed by x only!')
return wrapper
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
self.toolitems = NavigationToolbar2Tk.toolitems+((None, None, None, None),)
super(Toolbar, self).__init__(*args, **kwargs)
self._buttons["Plotmm"] = button = self._Button("Plotmm", get_res_file_path('axsminmax.png'), toggle=False, command=callback_func_P(self))
Tooltip = getattr(matplotlib.backends, '_backend_tk').ToolTip
Tooltip.createToolTip(button, "Plot data on min/max")
# ------------------------------- PySimpleGUI CODE
layout = [
[sg.T('Graph: y=sin(x)')],
[sg.B('Plot'), sg.B('Exit')],
[sg.T('Controls:')],
[sg.Canvas(key='controls_cv')],
[sg.T('Figure:')],
[sg.Column(
layout=[
[sg.Canvas(key='fig_cv',
# it's important that you set this size
size=(400 * 2, 400)
)]
],
background_color='#DAE0E6',
pad=(0, 0)
)],
[sg.B('Alive?')]
]
window = sg.Window('Graph with controls', layout)
while True:
event, values = window.read()
print(event, values)
if event in (sg.WIN_CLOSED, 'Exit'): # always, always give a way out!
break
elif event is 'Plot':
# ------------------------------- PASTE YOUR MATPLOTLIB CODE HERE
plt.figure(1)
fig = plt.gcf()
DPI = fig.get_dpi()
# ------------------------------- you have to play with this size to reduce the movement error when the mouse hovers over the figure, it's close to canvas size
fig.set_size_inches(404 * 2 / float(DPI), 404 / float(DPI))
# -------------------------------
x = np.linspace(0, 2 * np.pi)
y = np.sin(x)
plt.plot(x, y)
plt.title('y=sin(x)')
plt.xlabel('X')
plt.ylabel('Y')
plt.grid()
# ------------------------------- Instead of plt.show()
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig, window['controls_cv'].TKCanvas)
window.close()
If instead of sg.popup we'll create our own window the result will be same.
Steps to reproduce.
1.Start script.
2.Press button Plot.
3.Press most right toolbar' button (tooltip-'Plot data on min/max').
4.Press Plot or Exit buttons - nothing is happen.
Know nothing about the relation between function wrapper and tkinter.
My suggestion is to use method window.write_event_value to generate an event to main loop to do something about the GUI.
def callback_func_P(NavigationToolbar2TK):
def wrapper():
print('Plot on min/max from toolbar')
window.write_event_value('Popup', 'This window blocks main loop.\nPlot, Exit and Alive? buttons not working after that.\nAnd Terminal window could be closed by x only!')
return wrapper
and this in the main event loop
elif event == 'Popup':
sg.popup(values[event])

How to use a button to call a function to display text in Tkinter

I want to create a button which when pressed displays a message of the price_text variable in the get_btc function. I'm a GUI noob so just looking for some guidance apologies if there is already a post on this I've tried searching and haven't found a reliable answer.
My current code looks like this and is split in a directory like so
crypto_price--Model--scraper.py
|
GUI.py
The contents is:
Scraper.py:
from bs4 import BeautifulSoup
import datetime
import requests
import time
import lxml
def get_btc():
url = requests.get("https://uk.finance.yahoo.com/quote/BTC-GBP/")
soup = BeautifulSoup(url.text, "lxml")
finds = soup.find_all("div", class_="D(ib) Mend(20px)")[0].find('span')
for find in finds:
price=find
price_text= f"The price of BTC at {datetime.datetime.now()} is £{price}"
print(price_text)
time.sleep(5)
GUI.py:
from tkinter import *
from Model.scraper import get_btc
window = Tk()
window.title("BTC Price")
photo1 = PhotoImage(file="btc.png")
Label(window, image=photo1, bg="black").grid(row=0, column=0, sticky=E)
PriceButton = Button(text="Btc Price", command=get_btc())
PriceButton.place(x=0, y=0)
PriceButton.pack
window.mainloop()
You shouldn't call your function, it should be:
PriceButton = Button(text="Btc Price", command=get_btc)
The () for the function at the command has to be removed. When you use () your calling(invoking) the function, python runs the function as soon as it sees () with the function name, so remove it and the button will call the function for you each time, you press the button.

Updating a kivy label on a screen when dynamically generating it through a list?

Firstly, disclaimer: I am terribly new to programming and am trying to build my understanding with this project. Additionally, let me say I have searched the forums and found similar posts to mine, but none have the issue of updating a label that has been dynamically generated through a list.
My question is in my code, commented out, but to summarize: I generate buttons and labels for each item in a list. Then the buttons should add and subtract from the linked value in a dictionary. Currently the code does this, but the labels on screen don't update to reflect the new values. Can someone please assist with updating the value for "ordlayout.add_widget(ordlayout.lbl[str(i)])" when calling to updateup and updatedown?
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
from kivy.uix.dropdown import DropDown
from kivy.base import runTouchApp
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from functools import partial
#The first screen the app opens to. Contains all other screen branches.
class MainScreen(Screen):
pass
#NewOrder will be the screen used for choosing which
#items/and how many of each the customer wants added.
class NewOrder(Screen):
def __init__(self, **kwargs):
super(NewOrder, self).__init__(**kwargs)
#This will eventually read/create a list of strings from a user-modified file.
self.foods = ["Puppy", "Cat", "Fish"]
#I create a dictionary linking a number to each item.
self.countfoods = {}
for i in self.foods:
self.countfoods[i] = 0
#Now I create a grid layout to put on the screen.
ordlayout = GridLayout()
ordlayout.cols = 8
ordlayout.row_default_height=20
ordlayout.buttons={}
ordlayout.btns1 = {}
ordlayout.lbl = {}
#The items I want on the screen are 1.)First item from list. 2.) Minus button.
#3.) Current number of the item. 4.) Plus button.
#I want these four buttons for each item.
for i in self.countfoods:
#Adds text for first item.
ordlayout.add_widget(Label(text=i))
#Adds a button for minus, linked to a unique dict value.
ordlayout.buttons[str(i)] = Button(text="-")
ordlayout.lbl[str(i)] = Label(text=str((self.countfoods[i])))
#The below assigns the specific object location of each label
#to a variable for passing to ocuntup and countdown.
tempPlacement = str(ordlayout.lbl[str(i)])
ordlayout.buttons[str(i)].bind(on_press=partial(self.updatedown, i))
ordlayout.add_widget(ordlayout.buttons[str(i)])
#Add the value that I want to update.
ordlayout.add_widget(ordlayout.lbl[str(i)])
#Adds a button for addition, but doesn't properly link it to a specific value.
ordlayout.btns1[str(i)] = Button(text="+")
ordlayout.btns1[str(i)].bind(on_press=partial(self.updateup, i))
ordlayout.add_widget(ordlayout.btns1[str(i)])
#Add that grid wit
h values to the screen.
self.add_widget(ordlayout)
#Function used to change value down by one.
def updatedown(self, event, i):
self.countfoods[event] -= 1
print (self.countfoods)
#Function used to change value up by one.
def updateup(self, event, i):
self.countfoods[event] += 1
print (self.countfoods)
#AdminOpt will be the screen used for
class AdminOpt(Screen):
def __init__(self, **kwargs):
super(AdminOpt, self).__init__(**kwargs)
#Will allow for opening and checking of created orders.
class OrdHist(Screen):
pass
#This is purely the class used for managing the other screens.
class ScreenManagement(ScreenManager):
pass
Main = Builder.load_file("Order Handler2.kv")
class Customer:
def __init__(self, name, pricelist):
self.name = name
self.pricelist = pricelist
class SimpleKivy(App):
def build(self):
return Main
if __name__== "__main__":
SimpleKivy().run()
Haven't been able to test this (your question is missing your kv file), but something like this might work:
#Function used to change value down by one.
def updatedown(self, i, button):
self.countfoods[i] -= 1
self.ordlayout.lbl[str(i)].text = str(self.countfoods[i])
print (self.countfoods)
#Function used to change value up by one.
def updateup(self, i, button):
self.countfoods[i] += 1
self.ordlayout.lbl[str(i)].text = str(self.countfoods[i])
print (self.countfoods)
You will also need to replace every occurence of ordlayout with self.ordlayout in the __init__() method.
As an aside, you don't need to do str(i) for your dictionary keys. In fact, you can use lists instead of dictionaries, if you prefer.

Kivy and Python creating a slideshow that Moves when gets clicked

I am having trouble with making a picture slideshow that is operated with python and executed with kivy.
I am using asynch but I want to make the slideshow so that I open the photo and then when I click with the right it goes forward, but then if the mouse gets clicked on the left, then it goes to the previous page (picture).
Thanks for the help.
You can remove the comment# from the buttons and move left/right methods if you want but I think the carousel direction feature solves that problem. Mind you, I am a newbie so this is just my way of helping out. I figured I'll help some else since I have gotten a lot of help here. Thanks
from kivy.app import App
from kivy.loader import Loader
from kivy.lang import Builder
from kivy.base import runTouchApp
from kivy.clock import Clock
from kivy.properties import *
from kivy.uix.image import AsyncImage
from kivy.uix.gridlayout import GridLayout
from kivy.uix.carousel import Carousel
from kivy.uix.button import Button
Builder.load_string('''
<MyWidget>:
carousel: carousel
cols: 1
Button:
pos_hint: {"center_x":0.5, "center_y":0.1}
size_hint: .3,.1
font_size: 35
text: str(root.on_off)
on_release: root.start_slide()
Carousel:
pos_hint: {"center_x":0.5, "center_y":0.9}
id: carousel
direction: 'right'
loop: True
index: 0
''')
class MyWidget(GridLayout):
on_off = StringProperty('Start')
slide_count = NumericProperty(11)
def __init__(self, **kwargs):
super(MyWidget, self).__init__()
self.carousel = Carousel(direction='right')
self.add_widget(self.carousel)
for i in range(self.slide_count):
src = "http://placehold.it/480x270.png&text=slide-%d&.png" % i
image = AsyncImage(source=src, allow_stretch=True)
self.carousel.add_widget(image)
#self.start_slide()### Uncomment this to start slideshow when the app starts
def start_slide(self, *args):
if self.on_off == 'Start':
self.on_off = 'Stop'
self.clock = Clock.schedule_interval(self.slide_next, 3) ##move right every 3 seconds
return
if self.on_off == 'Stop':
self.on_off = 'Start'
Clock.unschedule(self.clock)
self.carousel.index = 0
def slide_next(self, *args):
if self.carousel.index == (self.slide_count - 1):
self.carousel.index = 0### This keeps the loops intact
#### if you want to end the slideshow at the last image, use 'Clock.unschedule(self.clock)' instead
return
self.carousel.load_next()
class SlideShowApp(App):
def build(self):
mywidget = MyWidget()
return mywidget
if __name__ == '__main__':
SlideShowApp().run()
Hope this is what you needed

How to Get a Window or Fullscreen Screenshot (without PIL)?

With python 3, I'd like to get a handle to another window (not part of my application) such that I can either:
directly capture that window as a screenshot, or
determine its position and size and capture it some other way
In case it is important, I am using Windows XP (edit: works in Windows 7 also).
I found this solution, but it is not quite what I need since it is full screen and more importantly, PIL to the best of my knowledge does not support 3.x yet.
Here's how you can do it using PIL on win32. Given a window handle (hwnd), you should only need the last 4 lines of code. The preceding simply search for a window with "firefox" in the title. Since PIL's source is available, you should be able to poke around the ImageGrab.grab(bbox) method and figure out the win32 code you need to make this happen.
from PIL import ImageGrab
import win32gui
toplist, winlist = [], []
def enum_cb(hwnd, results):
winlist.append((hwnd, win32gui.GetWindowText(hwnd)))
win32gui.EnumWindows(enum_cb, toplist)
firefox = [(hwnd, title) for hwnd, title in winlist if 'firefox' in title.lower()]
# just grab the hwnd for first window matching firefox
firefox = firefox[0]
hwnd = firefox[0]
win32gui.SetForegroundWindow(hwnd)
bbox = win32gui.GetWindowRect(hwnd)
img = ImageGrab.grab(bbox)
img.show()
Ars gave me all the pieces. I am just putting the pieces together here for anyone else who needs to get a screenshot in python 3.x. Next I need to figure out how to work with a win32 bitmap without having PIL to lean on.
Get a Screenshot (pass hwnd for a window instead of full screen):
def screenshot(hwnd = None):
import win32gui
import win32ui
import win32con
from time import sleep
if not hwnd:
hwnd=win32gui.GetDesktopWindow()
l,t,r,b=win32gui.GetWindowRect(hwnd)
h=b-t
w=r-l
hDC = win32gui.GetWindowDC(hwnd)
myDC=win32ui.CreateDCFromHandle(hDC)
newDC=myDC.CreateCompatibleDC()
myBitMap = win32ui.CreateBitmap()
myBitMap.CreateCompatibleBitmap(myDC, w, h)
newDC.SelectObject(myBitMap)
win32gui.SetForegroundWindow(hwnd)
sleep(.2) #lame way to allow screen to draw before taking shot
newDC.BitBlt((0,0),(w, h) , myDC, (0,0), win32con.SRCCOPY)
myBitMap.Paint(newDC)
myBitMap.SaveBitmapFile(newDC,'c:\\tmp.bmp')
Get a Window Handle by title (to pass to the above function):
def _get_windows_bytitle(title_text, exact = False):
def _window_callback(hwnd, all_windows):
all_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
windows = []
win32gui.EnumWindows(_window_callback, windows)
if exact:
return [hwnd for hwnd, title in windows if title_text == title]
else:
return [hwnd for hwnd, title in windows if title_text in title]
This will take a new opened window and make a screenshot of it and then crop it with PIL also possible to find your specific window with pygetwindow.getAllTitles() and then fill in your window name in z3 to get screenshot of only that window.
If you definitely not want to use PIL you can maximize window with pygetwindow module and then make a screenshot with pyautogui module.
Note: not tested on Windows XP (but tested on Windows 10)
import pygetwindow
import time
import os
import pyautogui
import PIL
# get screensize
x,y = pyautogui.size()
print(f"width={x}\theight={y}")
x2,y2 = pyautogui.size()
x2,y2=int(str(x2)),int(str(y2))
print(x2//2)
print(y2//2)
# find new window title
z1 = pygetwindow.getAllTitles()
time.sleep(1)
print(len(z1))
# test with pictures folder
os.startfile("C:\\Users\\yourname\\Pictures")
time.sleep(1)
z2 = pygetwindow.getAllTitles()
print(len(z2))
time.sleep(1)
z3 = [x for x in z2 if x not in z1]
z3 = ''.join(z3)
time.sleep(3)
# also able to edit z3 to specified window-title string like: "Sublime Text (UNREGISTERED)"
my = pygetwindow.getWindowsWithTitle(z3)[0]
# quarter of screen screensize
x3 = x2 // 2
y3 = y2 // 2
my.resizeTo(x3,y3)
# top-left
my.moveTo(0, 0)
time.sleep(3)
my.activate()
time.sleep(1)
# save screenshot
p = pyautogui.screenshot()
p.save(r'C:\\Users\\yourname\\Pictures\\\\p.png')
# edit screenshot
im = PIL.Image.open('C:\\Users\\yourname\\Pictures\\p.png')
im_crop = im.crop((0, 0, x3, y3))
im_crop.save('C:\\Users\\yourname\\Pictures\\p.jpg', quality=100)
# close window
time.sleep(1)
my.close()
The solution here gets a screenshot of a single Window (so can work if the Window is in the background).
Other solutions of this page take picture of the part of the screen the window is on, and thus need to bring the Window to the front first.
Python Screenshot of inactive window PrintWindow + win32gui

Resources