Draw a scale which ranges from red to green in Tkinter - python-3.x

I would like to draw a scale which ranges from red to green. I managed to do a scale which ranges from green to yellow. Now I have 2 possible solutions :
Either drawing 2 gradients : one from red to yellow and one from yellow to green. Then I can link the 2 drawings.
Or the better solution I think : Drawing one gradient from red to green with a checkpoint on the yellow.
How to implement this second solution ?
Here is my code from green to yellow. You can test it.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
f = GradientFrame(root)
f.pack(fill="both", expand=True)
class GradientFrame(tk.Canvas):
'''A gradient frame which uses a canvas to draw the background'''
def __init__(self, parent, borderwidth=1, relief="sunken"):
tk.Canvas.__init__(self, parent, borderwidth=borderwidth, relief=relief)
self._color1 = "green"
self._color2 = "yellow"
self._color3 = "red"
self.bind("<Configure>", self._draw_gradient)
def _draw_gradient(self, event=None):
'''Draw the gradient'''
self.delete("gradient")
width = 200
height = 50
limit = width
(r1,g1,b1) = self.winfo_rgb(self._color1)
(r2,g2,b2) = self.winfo_rgb(self._color2)
(r3,g3,b3) = self.winfo_rgb(self._color3)
r_ratio = float((r2-r1)) / limit
g_ratio = float((g2-g1)) / limit
b_ratio = float((b2-b1)) / limit
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(0,i,height,i, tags=("gradient",), fill=color)
self.lower("gradient")
for i in range(limit):
nr = int(r1 + (r_ratio * i))
ng = int(g1 + (g_ratio * i))
nb = int(b1 + (b_ratio * i))
color = "#%4.4x%4.4x%4.4x" % (nr,ng,nb)
self.create_line(0,i,height,i, tags=("gradient",), fill=color)
self.lower("gradient")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Without the generalization or general aesthetics of the above code, I offer you a simple solution (implemented against the red to green gradient) here:
import tkinter
def rgb(r, g, b):
return "#%s%s%s" % tuple([hex(c)[2:].rjust(2, "0") for c in (r, g, b)])
root = tkinter.Tk()
root.title("Gradient")
gradient1 = tkinter.Canvas(root, width=255*2, height=50)
gradient1.pack()
gradient2 = tkinter.Canvas(root, width=255*2, height=50)
gradient2.pack()
for x in range(0, 256):
r = x
g = 255-x
gradient1.create_rectangle(x*2, 0, x*2 + 2, 50, fill=rgb(r, g, 0),
outline=rgb(r, g, 0))
for x in range(0, 256):
r = x*2 if x < 128 else 255
g = 255 if x < 128 else 255 - (x-128)*2
gradient2.create_rectangle(x*2, 0, x*2 + 2, 50, fill=rgb(r, g, 0),
outline=rgb(r, g, 0))
I believe it's what you meant by "yellow checkpoints". Thanks for the challenge! :^)

Related

What actually happens in this code? I use the Python Turtle library to draw the fireworks, and I try to use random RGB color

import turtle
import random
t = turtle.Turtle()
w = turtle.Turtle()
t.speed(0)
w.speed(0)
w.penup()
t.penup()
def randcolor():
col1 = random.randint(0,255)
col2 = random.randint(0,255)
col3 = random.randint(0,255)
randcol = (col1, col2, col3)
return randcol
def drawfw1(angle):
x = random.randint(0, 100)
y = random.randint(0, 100)
t.goto(x, y)
t.pendown()
for _ in range(random.randint(30,100)):
t.fd(200)
t.left(angle)
t.penup()
def drawfw2(angle):
x = random.randint(0, 100)
y = random.randint(0, 100)
w.goto(x, y)
w.pendown()
for _ in range(random.randint(30,100)):
w.fd(200)
w.left(angle)
w.penup()
while True:
for _ in range(2):
t.pencolor(randcolor())
w.pencolor(randcolor())
angle = random.randint(99,179)
angle2 = random.randint(99,179)
drawfw1(angle)
drawfw2(angle2)
t.clear()
w.clear()
This code is to program the random drawing firework
I actually trying to do something with this, and I know it was right.
But then, the visual studio is not working and the turtle library
also. How can I fix this problems.
This appears to be due to a common turtle color error. Turtle supports two color modes, with the RGB values as integers from 0 - 255 or as floats from 0.0 to 1.0. The float mode is the default. To switch to the other mode, you need to do:
colormode(255)
Below is a simplification of your code with this fix.
from turtle import Screen, Turtle
from random import randrange
def randcolor():
red = randrange(256)
green = randrange(256)
blue = randrange(256)
return (red, green, blue)
def drawfw(angle):
x = randrange(100)
y = randrange(100)
turtle.goto(x, y)
turtle.pendown()
for _ in range(randrange(30, 100)):
turtle.forward(200)
turtle.left(angle)
turtle.penup()
screen = Screen()
screen.colormode(255)
turtle = Turtle()
turtle.speed('fastest')
turtle.penup()
while True:
for _ in range(4):
turtle.pencolor(randcolor())
angle = randrange(99, 180)
drawfw(angle)
turtle.clear()

PysimpleGUI Calc not using function properly

Hey everybody really scratching my head trying to figure out what is wrong with my code, it does not return any errors it just seems to skip over the calculating portion and prints the error message I included to indicate wrong user input, even though my user inputs are valid.
I've tried moving the if and defining statements about the variables around to see if that would work.
I know the variables are defined first above the calculate function and again in the function, the reason I have it this way is because otherwise I would get an error that they were undefined unless they came first above everything else.
#Weight Converter Calculator with GUI
import PySimpleGUI as sg
def h():
h = height
def g():
g = diameter
if g == 9:
diameter = .7
if g == 11:
diameter == .4
if g == 11.5:
diameter = .38
def u():
u = typeunit
if u == ft:
typeunit = 1
if u == rl:
typeunit = 50
if u == pl:
typeunit = 450
def a():
a = amount
def calc_weight(h, g, u, a):
try:
h, g, u, a = float(h), float(g), float(u), float(a)
h = height
a = amount
g = diameter
u = typeunit
if g == 9:
g = .7
if g == 11:
g = .4
if g == 11.5:
g = .38
if u == 'ft':
u = 1
if u == 'rl':
u = 50
if u == 'pl':
u = 450
weight = h * g * u * a
if weight >= 47001:
standard = 'too heavy for a truck! '
elif weight <= 47000:
standard = 'will fit onto a truck! '
except (ValueError, ZeroDivisionError):
return None
else:
return f'Weight: {weight}, {standard}'
layout = [
[sg.Text('Please enter your desired Mesh Height, Gauge, Unit, Amount')],
[sg.Text('Mesh Height in FT', size =(15, 1)), sg.Input(key = h)],
[sg.Text('Gauge 9, 11, 11.5', size =(15, 1)), sg.Input(key = g)],
[sg.Text('Unit "ft" for sq ft, "rl" for roll, and "pl" for pallet', size =(15, 3)), sg.Input(key = u)],
[sg.Text('Amount', size =(15, 1)), sg.Input(key = a)],
[sg.Text('', key='weight', size=(20, 2))],
[sg.Submit(), sg.Cancel()]
]
window = sg.Window('Chain Link Weight Calculator', layout)
sg.theme('DarkAmber')
while True:
event, value = window.Read()
if event == 'Submit':
weight = calc_weight(value[h], value[g], value[u], value[a],)
if weight:
window.Element('weight').Update(weight, text_color='white')
else:
window.Element('weight').Update('Input is incorrect! ', text_color='red')
elif event == 'Cancel':
break
window.Close()
It looks like there're some issues
Using function name as key of element, like h, g, u and a and those function useless
Variables height, amount, diameter and typeunit not defined in function calc_weight
Variables h, g, u, a reset in function calc_weight
Just demo script here,
import PySimpleGUI as sg
def find_true(sequence):
return sequence.index(True)
def calc_weight(values):
try:
h, a = float(values['h']), float(values['a'])
index1 = find_true([values[key] for key in ('G1', 'G2', 'G3')])
index2 = find_true([values[key] for key in ('U1', 'U2', 'U3')])
g, u = guages[index1], units[index2]
weight = h*g*u*a
standard = 'too heavy for a truck!' if weight>= 47001 else 'will fit onto a truck!'
result = f'Weight: {weight}, {standard}'
except:
result = None
return result
guages = [0.7, 0.4, 0.38]
units = [1, 50, 450]
sg.theme('DarkAmber')
layout = [
[sg.Text('Please enter your desired Mesh Height, Gauge, Unit, Amount')],
[sg.Text('Mesh Height in FT', size =(15, 1)), sg.Input(key='h')],
[sg.Text('Gauge', size=(15, 1)),
sg.Radio("9", 'Guage', size=(8, 1), key='G1', default=True),
sg.Radio("11", 'Guage', size=(8, 1), key='G2'),
sg.Radio("11.5", 'Guage', size=(8, 1), key='G3'),],
[sg.Text('Unit', size=(15, 1)),
sg.Radio("sq ft", 'Unit', size=(8, 1), key='U1', default=True),
sg.Radio("roll", 'Unit', size=(8, 1), key='U2'),
sg.Radio("pallet", 'Unit', size=(8, 1), key='U3'),],
[sg.Text('Amount', size =(15, 1)), sg.Input(key='a')],
[sg.Text('', key='weight', size=(45, 2))],
[sg.Submit(), sg.Cancel()]
]
window = sg.Window('Chain Link Weight Calculator', layout)
while True:
event, values = window.Read()
if event in (sg.WINDOW_CLOSED, 'Cancel'):
break
elif event == 'Submit':
weight = calc_weight(values)
if weight:
window['weight'].update(weight, text_color='white')
else:
window['weight'].update('Input is incorrect!', text_color='white')
window.Close()

Squares vertices in a 22x16 grid in pygame

This is my code for creating a 22x16 grid in pygame:
import pygame
pygame.init()
screen = pygame.display.set_mode((440, 320))
class Grid:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
pygame.draw.rect(screen, GREY, [self.x * WIDTH, self.y * HEIGHT, WIDTH, HEIGHT], 1)
pygame.display.update()
cols = 22
rows = 16
WIDTH = 20
HEIGHT = 20
BLACK = (0, 0, 0)
GREY = (127,127,127)
grid = [0 for i in range(cols)]
for node in range(cols):
grid[node] = [0 for node in range(rows)]
for x in range(cols):
for y in range(rows):
grid[x][y] = Grid(x, y)
for x in range(cols):
for y in range(rows):
grid[x][y].draw()
while True:
event = pygame.event.get()
if event == pygame.QUIT:
pygame.quit()
How do I find the verices of each square in this grid and draw them?
Drawing the verices is not that important but would be nice to see them to get a better understanding for later.
The corner points (vertices) of the tile with the column index column and the row index row can be computed as follows:
tl = column * WIDTH, row * HEIGHT
tr = (column+1) * WIDTH, row * HEIGHT
bl = column * WIDTH, (row+1) * HEIGHT
br = (column+1) * WIDTH, (row+1) * HEIGHT
However, this can be simplified by using a pygame.Rect object:
tile_rect = pygame.Rect(column * WIDTH, row * HEIGHT, WIDTH, HEIGHT)
tl = tile_rect.topleft
tr = tile_rect.topright
bl = tile_rect.bottomleft
tt = tile_rect.bottomright

PyQt - Oriented Flow Layout

I'm trying to adapt this PyQt implementation of FlowLayout to allow vertical flow as well as horizontal. This is my current implementation:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class FlowLayout(QLayout):
def __init__(self, orientation=Qt.Horizontal, parent=None, margin=0, spacing=-1):
super().__init__(parent)
self.orientation = orientation
if parent is not None:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self.itemList = []
def __del__(self):
item = self.takeAt(0)
while item:
item = self.takeAt(0)
def addItem(self, item):
self.itemList.append(item)
def count(self):
return len(self.itemList)
def itemAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList[index]
return None
def takeAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList.pop(index)
return None
def expandingDirections(self):
return Qt.Orientations(Qt.Orientation(0))
def hasHeightForWidth(self):
return self.orientation == Qt.Horizontal
def heightForWidth(self, width):
return self.doLayout(QRect(0, 0, width, 0), True)
def hasWidthForHeight(self):
return self.orientation == Qt.Vertical
def widthForHeight(self, height):
return self.doLayout(QRect(0, 0, 0, height), True)
def setGeometry(self, rect):
super().setGeometry(rect)
self.doLayout(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QSize()
for item in self.itemList:
size = size.expandedTo(item.minimumSize())
margin, _, _, _ = self.getContentsMargins()
size += QSize(2 * margin, 2 * margin)
return size
def doLayout(self, rect, testOnly):
x = rect.x()
y = rect.y()
offset = 0
horizontal = self.orientation == Qt.Horizontal
for item in self.itemList:
wid = item.widget()
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
if horizontal:
next = x + item.sizeHint().width() + spaceX
if next - spaceX > rect.right() and offset > 0:
x = rect.x()
y += offset + spaceY
next = x + item.sizeHint().width() + spaceX
offset = 0
else:
next = y + item.sizeHint().height() + spaceY
if next - spaceY > rect.bottom() and offset > 0:
x += offset + spaceX
y = rect.y()
next = y + item.sizeHint().height() + spaceY
offset = 0
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
if horizontal:
x = next
offset = max(offset, item.sizeHint().height())
else:
y = next
offset = max(offset, item.sizeHint().width())
return y + offset - rect.y() if horizontal else x + offset - rect.x()
if __name__ == '__main__':
class Window(QWidget):
def __init__(self):
super().__init__()
#flowLayout = FlowLayout(orientation=Qt.Horizontal)
flowLayout = FlowLayout(orientation=Qt.Vertical)
flowLayout.addWidget(QPushButton("Short"))
flowLayout.addWidget(QPushButton("Longer"))
flowLayout.addWidget(QPushButton("Different text"))
flowLayout.addWidget(QPushButton("More text"))
flowLayout.addWidget(QPushButton("Even longer button text"))
self.setLayout(flowLayout)
self.setWindowTitle("Flow Layout")
import sys
app = QApplication(sys.argv)
mainWin = Window()
mainWin.show()
sys.exit(app.exec_())
This implementation has 2 (likely related) problems when handling vertical layouts:
QLayout has the hasHeightForWidth and heightForWidth methods, but not their inverses hasWidthForHeight and widthForHeight. I implemented the latter two methods regardless, but I doubt they're ever actually getting called.
When using the horizontal variant of the layout, the window is automatically appropriately sized to contain all the items. When using the vertical variant, this is not the case. However, the vertical layout does work properly if you manually resize the window.
How do I properly implement a vertical flow layout?
As you already found out, Qt layouts don't support widthForHeight, and, in general, these kinds of layouts are discouraged, mostly because they tend to behave erratically in complex situation with nested layouts and mixed widget size policies. Even when being very careful about their implementation, you might end up in recursive calls to size hints, policies etc.
That said, a partial solution is to still return a height for width, but position the widgets vertically instead of horizontally.
def doLayout(self, rect, testOnly):
x = rect.x()
y = rect.y()
lineHeight = columnWidth = heightForWidth = 0
for item in self.itemList:
wid = item.widget()
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
if self.orientation == Qt.Horizontal:
nextX = x + item.sizeHint().width() + spaceX
if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x()
y = y + lineHeight + spaceY
nextX = x + item.sizeHint().width() + spaceX
lineHeight = 0
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x = nextX
lineHeight = max(lineHeight, item.sizeHint().height())
else:
nextY = y + item.sizeHint().height() + spaceY
if nextY - spaceY > rect.bottom() and columnWidth > 0:
x = x + columnWidth + spaceX
y = rect.y()
nextY = y + item.sizeHint().height() + spaceY
columnWidth = 0
heightForWidth += item.sizeHint().height() + spaceY
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
y = nextY
columnWidth = max(columnWidth, item.sizeHint().width())
if self.orientation == Qt.Horizontal:
return y + lineHeight - rect.y()
else:
return heightForWidth - rect.y()
This is how the widget appears as soon as it's shown (which is almost the same as the horizontal flow):
Now, resizing to allow less vertical space:
And even smaller height:
While the answer provided by #musicamente works, it is incomplete:
What is missing is the widthForHeight mecanism: as items are added into the layout, the minimumWidth of the container widget is not updated.
For some reason, Qt decided that heightForWidth mecanism should exist but not widthForHeight.
It would seem that when using the heightForWidth mecanism, the minimumHeight of the parent widget is automatically updated via the Qt framework (I may be wrong but I think it is the case).
In the example provided by #musicamente, as the main window is resizable this limitation is not easilly seen.
However when using a QScrollArea, this limitation is cleary observable as the scrollbar doesn't show up and the view is truncated.
So we need to determine which row of the FlowLayout is the widest and set the minimumWidth of the parent widget accordingly.
I've implemented it like so:
As the items are placed, that they are assigned i and j indexes which represent their position in a 2D array.
Then once all of them are placed, we determine the width of the widest row (including spacing between items) and let the parent widget know using a dedicated signal which can be connected to the setMinimumWidth method.
My solution might not be perfect nor a great implementation, but it is the best alternative I found so far to achieve what I wanted.
The following code will provide a working version, while I don't find my solution very elegant, it works.
If you have ideas on how to optimize it feel free to improve my implementation by making a PR on my GitHub : https://github.com/azsde/BatchMkvToolbox/tree/main/ui/customLayout
class FlowLayout(QLayout):
widthChanged = pyqtSignal(int)
def __init__(self, parent=None, margin=0, spacing=-1, orientation=Qt.Horizontal):
super(FlowLayout, self).__init__(parent)
if parent is not None:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self.itemList = []
self.orientation = orientation
def __del__(self):
item = self.takeAt(0)
while item:
item = self.takeAt(0)
def addItem(self, item):
self.itemList.append(item)
def count(self):
return len(self.itemList)
def itemAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList[index]
return None
def takeAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList.pop(index)
return None
def expandingDirections(self):
return Qt.Orientations(Qt.Orientation(0))
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
if (self.orientation == Qt.Horizontal):
return self.doLayoutHorizontal(QRect(0, 0, width, 0), True)
elif (self.orientation == Qt.Vertical):
return self.doLayoutVertical(QRect(0, 0, width, 0), True)
def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
if (self.orientation == Qt.Horizontal):
self.doLayoutHorizontal(rect, False)
elif (self.orientation == Qt.Vertical):
self.doLayoutVertical(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QSize()
for item in self.itemList:
size = size.expandedTo(item.minimumSize())
margin, _, _, _ = self.getContentsMargins()
size += QSize(2 * margin, 2 * margin)
return size
def doLayoutHorizontal(self, rect, testOnly):
# Get initial coordinates of the drawing region (should be 0, 0)
x = rect.x()
y = rect.y()
lineHeight = 0
i = 0
for item in self.itemList:
wid = item.widget()
# Space X and Y is item spacing horizontally and vertically
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
# Determine the coordinate we want to place the item at
# It should be placed at : initial coordinate of the rect + width of the item + spacing
nextX = x + item.sizeHint().width() + spaceX
# If the calculated nextX is greater than the outer bound...
if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x() # Reset X coordinate to origin of drawing region
y = y + lineHeight + spaceY # Move Y coordinate to the next line
nextX = x + item.sizeHint().width() + spaceX # Recalculate nextX based on the new X coordinate
lineHeight = 0
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
x = nextX # Store the next starting X coordinate for next item
lineHeight = max(lineHeight, item.sizeHint().height())
i = i + 1
return y + lineHeight - rect.y()
def doLayoutVertical(self, rect, testOnly):
# Get initial coordinates of the drawing region (should be 0, 0)
x = rect.x()
y = rect.y()
# Initalize column width and line height
columnWidth = 0
lineHeight = 0
# Space between items
spaceX = 0
spaceY = 0
# Variables that will represent the position of the widgets in a 2D Array
i = 0
j = 0
for item in self.itemList:
wid = item.widget()
# Space X and Y is item spacing horizontally and vertically
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
# Determine the coordinate we want to place the item at
# It should be placed at : initial coordinate of the rect + width of the item + spacing
nextY = y + item.sizeHint().height() + spaceY
# If the calculated nextY is greater than the outer bound, move to the next column
if nextY - spaceY > rect.bottom() and columnWidth > 0:
y = rect.y() # Reset y coordinate to origin of drawing region
x = x + columnWidth + spaceX # Move X coordinate to the next column
nextY = y + item.sizeHint().height() + spaceY # Recalculate nextX based on the new X coordinate
# Reset the column width
columnWidth = 0
# Set indexes of the item for the 2D array
j += 1
i = 0
# Assign 2D array indexes
item.x_index = i
item.y_index = j
# Only call setGeometry (which place the actual widget using coordinates) if testOnly is false
# For some reason, Qt framework calls the doLayout methods with testOnly set to true (WTF ??)
if not testOnly:
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
y = nextY # Store the next starting Y coordinate for next item
columnWidth = max(columnWidth, item.sizeHint().width()) # Update the width of the column
lineHeight = max(lineHeight, item.sizeHint().height()) # Update the height of the line
i += 1 # Increment i
# Only call setGeometry (which place the actual widget using coordinates) if testOnly is false
# For some reason, Qt framework calls the doLayout methods with testOnly set to true (WTF ??)
if not testOnly:
self.calculateMaxWidth(i)
self.widthChanged.emit(self.totalMaxWidth + spaceX * self.itemsOnWidestRow)
return lineHeight
# Method to calculate the maximum width among each "row" of the flow layout
# This will be useful to let the UI know the total width of the flow layout
def calculateMaxWidth(self, numberOfRows):
# Init variables
self.totalMaxWidth = 0
self.itemsOnWidestRow = 0
# For each "row", calculate the total width by adding the width of each item
# and then update the totalMaxWidth if the calculated width is greater than the current value
# Also update the number of items on the widest row
for i in range(numberOfRows):
rowWidth = 0
itemsOnWidestRow = 0
for item in self.itemList:
# Only compare items from the same row
if (item.x_index == i):
rowWidth += item.sizeHint().width()
itemsOnWidestRow += 1
if (rowWidth > self.totalMaxWidth):
self.totalMaxWidth = rowWidth
self.itemsOnWidestRow = itemsOnWidestRow
To use it do the following:
When declaring a FlowLayout, specify its orientation :
myFlowLayout = FlowLayout(containerWidget, orientation=Qt.Vertical)
Connect the FlowLayout's widthChanged signal to the setMinimumWidth method of the container:
myFlowLayout.widthChanged.connect(containerWidget.setMinimumWidth)

Tkinter create shrinking circle on each mouse click, how to make it work with multiple clicks?

I am creating a simple program which draws a shrinking circle of random color on every clicked location by each mouse click. Each click creates a circle of diameter 50 which starts shrinking till 0 immediately. Each click is supposed to create new shrinking circle.
However, my program stops shrinking of first circle after I click and create another circle. It completely shrinks only the last created circle. All others remain still.
I believe the problem lies in function itself. It calls the same function which is not finished. How to make it run multiple times (on each click separately)? Or do I have it wrong with local and global variables?
Here is my code so far:
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
global x, y, farba, circ, r
r = 50 #circle diameter
x, y = event.x, event.y #clicked position
color = '#{:06x}'.format(random.randrange(256 ** 3)) #random color picker
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color) #print circle
print(x, y, farba) #check clicked coordinates, not important
if r < 50: #reset size after each circle
r = 50
shrink()
def shrink():
global circ, x, y, r
print(r) #check if countdown runs correctly
if r > 0:
r -= 1 #diameter shrinking
c.coords(circ, x-r, y-r, x+r, y+r) #changing circle size
c.after(100, shrink) #timer, size 1pt smaller until size is 0
c.bind('<Button-1>', klik)
tkinter.mainloop()
If you move everything into a class then each circle will be its own instance and will not interfere with each other.
Take a look at the below modified version of your code. It is probably not perfect but should be a good foundation for you to work with.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
class create_circles():
def __init__(self, event):
self.r = 50
self.x, self.y = event.x, event.y
self.color = '#{:06x}'.format(random.randrange(256 ** 3))
self.circ = c.create_oval(self.x - self.r, self.y - self.r, self.x + self.r, self.y + self.r, fill=self.color)
self.shrink()
def shrink(self):
if self.r > 0:
self.r -= 1
c.coords(self.circ, self.x-self.r, self.y-self.r, self.x+self.r, self.y+self.r)
c.after(100, self.shrink)
c.bind('<Button-1>', create_circles)
tkinter.mainloop()
There is another way you can do this outside of a class.
You can use a nested function and avoid global. Your issues in your question is actually being caused because everything relies on global variables.
Try this below code for a non-class option.
import tkinter
import random
c = tkinter.Canvas(width = 400, height = 300)
c.pack()
def klik(event):
r = 50
x, y = event.x, event.y
color = '#{:06x}'.format(random.randrange(256 ** 3))
circ = c.create_oval(x - r, y - r, x + r, y + r, fill=color)
def shrink(r, x, y, color, circ):
if r > 0:
r -= 1
c.coords(circ, x-r, y-r, x+r, y+r)
c.after(100, shrink, r, x, y, color, circ)
shrink(r, x, y, color, circ)
c.bind('<Button-1>', klik)
tkinter.mainloop()
As noted, you do not need classes to solve this nor nested functions. The key, as #LioraHaydont was hinting at, is you need to use local, rather than global variables:
import tkinter as tk
from random import randrange
def klik(event):
r = 50 # circle radius
x, y = event.x, event.y # clicked position
color = '#{:06x}'.format(randrange(256 ** 3)) # random color picker
c = canvas.create_oval(x - r, y - r, x + r, y + r, fill=color) # print circle
canvas.after(100, shrink, c, x, y, r)
def shrink(c, x, y, r):
if r > 0:
r -= 1 # radius shrinking
canvas.coords(c, x - r, y - r, x + r, y + r) # changing circle size
canvas.after(100, shrink, c, x, y, r) # timer, size 1pt smaller until size is 0
canvas = tk.Canvas(width=400, height=300)
canvas.pack()
canvas.bind('<Button-1>', klik)
tk.mainloop()

Resources