WxPython - Resize WxFrame when adding new content? - python

Pretty much exactly as it sounds. I have buttons in a Wx.Frame that are created on the fly and I'd like the parent frame to increase in height as I add new buttons. The height is already being acquire from the total number of buttons multiplied by an integer equal the each button's height, but I don't know how to get the frame to change size based on that when new buttons are added.
As a side question the current method I have for updating the buttons creates a nasty flicker and I was wondering if anyone had any ideas for fixing that.
import wx
import mmap
import re
class pt:
with open('note.txt', "r+") as note:
buf = mmap.mmap(note.fileno(), 0)
TL = 0
readline = buf.readline
while readline():
TL += 1
readlist = note.readlines()
note.closed
class MainWindow(wx.Frame):
def __init__(self, parent, title):
w, h = wx.GetDisplaySize()
self.x = w * 0
self.y = h - bdepth
self.container = wx.Frame.__init__(self, parent, title = title, pos = (self.x, self.y), size = (224, bdepth), style = wx.STAY_ON_TOP)
self.__DoButtons()
self.Show(True)
def __DoButtons(self):
for i, line in enumerate(pt.readlist):
strip = line.rstrip('\n')
todo = strip.lstrip('!')
self.check = re.match('!', strip)
self.priority = re.search('(\!$)', strip)
if self.check is None and self.priority is None:
bullet = wx.Image('bullet.bmp', wx.BITMAP_TYPE_BMP)
solid = wx.EmptyBitmap(200,64,-1)
dc = wx.MemoryDC()
dc.SelectObject(solid)
solidpen = wx.Pen(wx.Colour(75,75,75),wx.SOLID)
dc.SetPen(solidpen)
dc.DrawRectangle(0, 0, 200, 64)
dc.SetTextForeground(wx.Colour(255, 255, 255))
dc.DrawBitmap(wx.BitmapFromImage(bullet, 32), 10, 28)
dc.DrawText(todo, 30, 24)
dc.SelectObject(wx.NullBitmap)
hover = wx.EmptyBitmap(200,64,-1)
dc = wx.MemoryDC()
dc.SelectObject(hover)
hoverpen = wx.Pen(wx.Colour(100,100,100),wx.SOLID)
dc.SetPen(hoverpen)
dc.DrawRectangle(0, 0, 200, 64)
dc.SetTextForeground(wx.Colour(255, 255, 255))
dc.DrawBitmap(wx.BitmapFromImage(bullet, 32), 10, 28)
dc.DrawText(todo, 30, 24)
dc.SelectObject(wx.NullBitmap)
bmp = solid
elif self.priority is None:
checkmark = wx.Image('check.bmp', wx.BITMAP_TYPE_BMP)
checked = wx.EmptyBitmap(200,64,-1)
dc = wx.MemoryDC()
dc.SelectObject(checked)
checkedpen = wx.Pen(wx.Colour(50,50,50),wx.SOLID)
dc.SetPen(checkedpen)
dc.DrawRectangle(0, 0, 200, 50)
dc.SetTextForeground(wx.Colour(200, 255, 0))
dc.DrawBitmap(wx.BitmapFromImage(checkmark, 32), 6, 24)
dc.DrawText(todo, 30, 24)
dc.SelectObject(wx.NullBitmap)
bmp = checked
else:
exclaim = wx.Image('exclaim.bmp', wx.BITMAP_TYPE_BMP)
important = wx.EmptyBitmap(200,64,-1)
dc = wx.MemoryDC()
dc.SelectObject(important)
importantpen = wx.Pen(wx.Colour(75,75,75),wx.SOLID)
dc.SetPen(importantpen)
dc.DrawRectangle(0, 0, 200, 50)
dc.SetTextForeground(wx.Colour(255, 180, 0))
dc.DrawBitmap(wx.BitmapFromImage(exclaim, 32), 6, 24)
dc.DrawText(todo, 30, 24)
dc.SelectObject(wx.NullBitmap)
importanthover = wx.EmptyBitmap(200,64,-1)
dc = wx.MemoryDC()
dc.SelectObject(importanthover)
importanthoverpen = wx.Pen(wx.Colour(100,100,100),wx.SOLID)
dc.SetPen(importanthoverpen)
dc.DrawRectangle(0, 0, 200, 50)
dc.SetTextForeground(wx.Colour(255, 180, 0))
dc.DrawBitmap(wx.BitmapFromImage(exclaim, 32), 6, 24)
dc.DrawText(todo, 30, 24)
dc.SelectObject(wx.NullBitmap)
bmp = important
b = wx.BitmapButton(self, i + 800, bmp, (10, i * 64), (bmp.GetWidth(), bmp.GetHeight()), style = wx.NO_BORDER)
if self.check is None and self.priority is None:
b.SetBitmapHover(hover)
elif self.priority is None:
b.SetBitmapHover(checked)
else:
b.SetBitmapHover(importanthover)
self.input = wx.TextCtrl(self, -1, "", (16, pt.TL * 64 + 4), (184, 24))
self.Bind(wx.EVT_TEXT_ENTER, self.OnEnter, self.input)
def OnClick(self, event):
button = event.GetEventObject()
button.None
print('cheese')
def OnEnter(self, event):
value = self.input.GetValue()
pt.readlist.append('\n' + value)
self.__DoButtons()
with open('note.txt', "r+") as note:
for item in pt.readlist:
note.write("%s" % item)
note.closed
bdepth = pt.TL * 64 + 32
app = wx.App(False)
frame = MainWindow(None, "Sample editor")
app.SetTopWindow(frame)
app.MainLoop()

AFAIK there no way automatically resize the frame, but you can manually reset the size of your frame with SetSize()
e.g.
w, h = self.GetClientSize()
self.SetSize((w, h + height_of_your_new_button))
To the get the desired result though with minimum hassle you'll need to use sizers, I don't think theres ever a good reason to use absolute positioning. I would also recommend using a panel, which provides tab traversal between widgets and cross platform consistency of layout.
Zetcode Sizer Tutorial
wxPython Sizer Tutorial

Don't double-prefix your methods unless you know what you're doing. This is not directly related to your question, but it'll result in bugs you won't understand later.
See this stackoverflow question and the python documentation what/why.

Related

How to get rid of God object if it's calculates majority of functions

How can I split this class into several simpler ones, using the principle of single responsibility?
There is this awful class:
# engine for game logic
import HB_classLib as cllib
from card_mech import *
from pygame import Color, display
import itertools as itools
from turn import Turn
def genSlots(details, screen):
"""Generating slots for cards"""
base = [10, 570, 85, 670]
for i in range(8):
typlpx = 420 + (i - 4) * 95 if i >= 4 else base[0] + i * 95
l1 = [typlpx, base[1], typlpx + 75, base[1]]
l2 = [typlpx, base[1], typlpx, base[3]]
l3 = [typlpx, base[3], typlpx + 75, base[3]]
l4 = [typlpx + 75, base[1], typlpx + 75, base[3]]
details.append(cllib.CardSlot(screen, [l1, l2, l3, l4],
Color("white"), bn=3))
class Engine:
"""Engine which will calculate gameClasses's interactions"""
def __init__(self, screen, usnames, mode=1):
self.names_frame = (cllib.Text(usnames[0].get_value(), 36, Color("white")),
cllib.Text(usnames[1].get_value(), 36, Color("white")))
self.screen = screen
self.mode = mode
self.mod_buttons = [cllib.Button((260, 340), (300, 90), Color("gold"),
"Divided field", None, 54, xIndF=40, yIndF=30),
cllib.Button((260, 450), (300, 90), Color("yellow"),
"No man's land", None, 54, xIndF=20, yIndF=30)]
self.start_cards = [MeleeCard(Image_fn="images/card1.jpg", pos=(10, 575), health=5, attack=3),
CavalryCard(Image_fn="images/card2.xcf", pos=(108, 575), health=3, attack=3),
CavalryCard(Image_fn="images/card3.xcf", pos=(198, 575), health=5, attack=4)]
self.pl = Player()
self.pl2 = Player()
self.map = cllib.Map()
self.currlvlmap = cllib.LvlMap()
self.borderline = cllib.Line(self.screen, [400, 0, 400, 480], "black", 3)
self.blno_mans_land = [cllib.Line(self.screen, [240, 0, 240, 480], "blue", 3),
cllib.Line(self.screen, [560, 0, 560, 480], "red", 3)]
self.turnButton = cllib.Button((360, 485), (80, 80), Color("#E0FFFF"),
"End Turn", None, 24,
xIndF=5, yIndF=35, shape='round', fontColor="#800000")
self.turnArrow = cllib.Image("images/turn_arrow.jpg", (360, 680), imSize=(90, 120))
self.turnFight = cllib.Image("images/fight.jpg", (360, 680), imSize=(90, 120))
self.turneng = Turn(self.pl, self.pl2, self)
self.details = [self.currlvlmap, self.turnButton, self.turnArrow, self.start_cards]
self.cardpos_acc = [i.pos for i in self.start_cards]
def up_phase(self):
"""Increase phase ratio unconditionly"""
+self.turneng
def check_turns(self, transformed, is_increase=True):
self.up_phase() if is_increase else None
self.turneng.do_logic()
match self.turneng.phase:
case 0:
self.details[2] = cllib.Image(self.turnArrow.image,
(360, 680), imSize=(90, 120)
)
case 1:
self.details[2] = cllib.Image(transformed,
(360, 680), imSize=(90, 120))
case 2:
self.details[2] = cllib.Image(self.turnFight.image,
(360, 680), imSize=(90, 120)
)
def start(self):
"""Create default settings and init needed classes, draw map with mods buttons"""
self.pl.hand.fill([self.start_cards[0], self.start_cards[1]])
self.pl2.hand.fill([self.start_cards[2]])
self.map.fill_with(self.mod_buttons)
self.map.draw(self.screen)
def turn_level(self):
"""Turn on level, generate and draw level's map, setting up selected mode"""
self.map.should_draw = False
for btn in self.mod_buttons:
btn.keepOn = False
self.currlvlmap.set(self.screen)
genSlots(self.details, self.screen)
def update_map(self):
self.map.draw(self.screen)
def update_level(self):
nested = lambda b: b is self.start_cards or isinstance(b, list | tuple)
for det in self.details:
det.draw(self.screen) if not nested(det) else [atom.draw(self.screen) for atom in det]
blitter = lambda align, factor: (
self.screen.blit(name.image, (align + index * factor - len(str(name.text)) * 6, 500))
for index, name in enumerate(self.names_frame)
)
match self.mode:
case 1:
self.borderline.draw()
blitter(200, 400)
case 2:
for el in self.blno_mans_land: el.draw()
blitter(120, 560)
display.flip()
def fight(self):
pass
I tried to separate this class into a Starter class, an Updater class, a TurnChecker class, and a FightMechanicsExecutor class, but the result was that each class was dependent on each other and, of course, this structure was not viable.
In addition, I have already moved the function genSlots outside the class, but this doesn't greatly simplify the code.

Is there any method in python to link two blocks dynamically

I want to draw some blocks and connect them through arrow.
I did it, but if I move the coordinates of blocks I also need to change arrow coordinates.
Is there any method to bind them, like if I move the block position connecting arrow will also adjust automatically?
Can you suggest any other method in python to which will work?
from tkinter import *
top = Tk()
top.geometry("800x600")
#creating a simple canvas
a = Canvas(top,bg = "White",height = "515", width = "1000")
a.create_rectangle(430, 255, 550, 275,fill="White")
a.create_text(490,265,text="School",fill="black",font=('Helvetica 8 bold'))
a.create_rectangle(430, 205, 480, 225,fill="White")
a.create_text(455,215,text="Boys",fill="black",font=('Helvetica 8 bold'))
a.create_rectangle(480, 160, 540, 180,fill="White")
a.create_text(510,170,text="Girls",fill="black",font=('Helvetica 8 bold'))
#School to Boys
a.create_line(450, 225, 450, 255, arrow= BOTH)
#School to COM
a.create_line(510, 180, 510, 255, arrow= BOTH)
a.pack(expand=True)
top.mainloop()
No, there isn't any method given by tkinter to link blocks. But, you can create them yourself. First, create a rectangle and text and assign them the same tag (note: each block must have a unique tag).
Now, if you want to move the blocks using the mouse, you will have to use the canvas.find_withtag("current") to get the currently selected block and canvas.move(tagorid, x, y) to move the block. Once it is moved use widget.generate_event() to generate a custom virtual event.
You will now have to use canvas.tag_bind(unqiue_tagn, "<<customevent>>", update_pos) to call the update_pos function when the event is generated, the update_pos should update the connection position.
Here is a sample code.
import tkinter as tk
class NodeScene(tk.Canvas):
def __init__(self, *args, **kwargs):
super(NodeScene, self).__init__(*args, **kwargs)
self.bind("<Button-1>", self.click_pos)
self.bind("<B1-Motion>", self.selected_item)
self.prev_x, self.prev_y = 0, 0
def click_pos(self, event):
self.prev_x, self.prev_y = event.x, event.y
def selected_item(self, event):
_current = self.find_withtag("current")
assoc_tags = self.itemcget(_current, "tags").split(" ")[0]
if _current and "node" in assoc_tags:
new_pos_x, new_pos_y = event.x - self.prev_x, event.y - self.prev_y
self.move(assoc_tags, new_pos_x, new_pos_y)
self.event_generate("<<moved>>", when="tail")
self.prev_x, self.prev_y = event.x, event.y
class Node:
def __init__(self, canvas: tk.Canvas, text: str, pos=(50, 50)):
self.tag_id = f"node{id(self)}"
self.canvas = canvas
self.c_rect = self.canvas.create_rectangle(0, 0, 0, 0, fill="white", tags=(self.tag_id))
c_text = self.canvas.create_text(*pos, text=text, fill="black", font=("Helvetica 8 bold"), tags=(self.tag_id))
padding = 10
text_rect = list(self.canvas.bbox(c_text))
text_rect[0] -= padding
text_rect[1] -= padding
text_rect[2] += padding
text_rect[3] += padding
self.canvas.coords(self.c_rect, *text_rect)
def bbox(self):
return self.canvas.bbox(self.c_rect)
def top(self):
bbox = self.bbox()
return (bbox[2] + bbox[0]) / 2, bbox[1]
def bottom(self):
bbox = self.bbox()
return (bbox[2] + bbox[0]) / 2, bbox[3]
class Connection:
def __init__(self, canvas, node1: Node, node2: Node, pos_x): # pos_x is the offset from center
self.node1 = node1
self.node2 = node2
self.pos_x_offset = pos_x
self.canvas = canvas
self.connection_id = self.canvas.create_line(0, 0, 0, 0, arrow= tk.BOTH)
self.canvas.tag_bind(self.node1.tag_id, "<<moved>>", self.update_conn_pos, add="+")
self.canvas.tag_bind(self.node2.tag_id, "<<moved>>", self.update_conn_pos, add="+")
self.update_conn_pos()
def update_conn_pos(self, event=None):
""" updates the connection position """
node1_btm = self.node1.bottom()
node2_top = list(self.node2.top())
node2_top[0] += self.pos_x_offset
self.canvas.coords(self.connection_id, *node1_btm, *node2_top)
root = tk.Tk()
canvas = NodeScene(root)
canvas.pack(expand=True, fill="both")
node = Node(canvas, "School", pos=(100, 150))
node1 = Node(canvas, "boys", pos=(80, 80))
node2 = Node(canvas, "girls", pos=(120, 30))
connection = Connection(canvas, node1, node, -20)
connection2 = Connection(canvas, node2, node, 20)
root.mainloop()

Tkinter/Pillow - How to make (0, 0) in the same place as in Pillow

So I was trying to figure out who my coordinates were messed up when adding text to an image using canvas coordinates , until I noticed that (0, 0) is off:
How would I put the origin of the canvas in the same place as Pillow does(or at least account for this when adding the text)?
class PictureWindow(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.parent = parent
self.x = self.y = 0
self.rect = None
self.tex = None
self.start_x = None
self.start_y = None
image = Image.open(file)
smaller_image = image.resize((round(image.size[0]/2), round(image.size[1]/2)))
img = ImageTk.PhotoImage(smaller_image)
self.canvas = tk.Canvas(self, width=img.width(), height=img.height())
self.canvas.img = img
self.canvas.create_image(0, 0, image=img, anchor=tk.NW)
self.canvas.pack(expand=True)
self.canvas.create_text((0, 0), text="(0, 0)", fill=self._from_rgb((225, 225, 225)))
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
self.finished = tk.Button(self, text="Add Watermark", command=self.Watermark)
self.finished.pack()
self.go_back = tk.Button(self, text="Go back to change settings", command=self.go_away)
self.go_back.pack()
def _from_rgb(self, rgb):
r, g, b = rgb
return f'#{r:02x}{g:02x}{b:02x}'
def go_away(self):
self.withdraw()
def on_button_press(self, event):
self.start_x = event.x
self.start_y = event.y
self.canvas.delete(self.tex)
self.tex = None
if not self.rect:
self.rect = self.canvas.create_rectangle(self.x, self.y, 1, 1, fill=self._from_rgb((249, 0, 0)), stipple='gray12')
def on_move_press(self, event):
self.curX, self.curY = (event.x, event.y)
self.text_x = ((self.start_x + self.curX) / 2)
self.text_y = ((self.start_y + self.curY) / 2)
self.canvas.coords(self.rect, self.start_x, self.start_y, self.curX, self.curY)
def on_button_release(self, event):
font_preview_size = int(font_size)
font_preview_almost = (font_preview_size)
font_preview = int(font_preview_almost)
if not self.tex:
self.tex = self.canvas.create_text((self.text_x, self.text_y), text=watermark_text, font=('Gotham Medium', font_preview), fill=self._from_rgb((color)))
def Watermark(self):
self.font_size_var = font_size
Font_Size = int(self.font_size_var)
A = alpha
R = int(color[0])
B = int(color[1])
G = int(color[2])
img = Image.open(file).convert("RGBA")
img_down = img.resize((int(img.width/2), int(img.height/2)), resample=Image.NEAREST)
img_down.x, img_down.y = img_down.size
txt = Image.new('RGBA', img_down.size, (225,225,225,0))
if custom_font == "yes":
font = ImageFont.truetype(font_file, Font_Size)
draw = ImageDraw.Draw(txt)
text = watermark_text
if custom_font == "no":
font = ImageFont.truetype('arial.ttf', Font_Size)
draw = ImageDraw.Draw(txt)
text = watermark_text
self.final_x = self.canvas.canvasx(self.text_x)
self.final_y = self.canvas.canvasx(self.text_x)
draw.text((self.final_x, self.final_y), text, font=font, fill=(R, G, B, A))
comp = Image.alpha_composite(img_down, txt)
img_up = comp.resize((int(comp.width*2), int(comp.height*2)), resample=Image.NEAREST)
img_up.save(save_file)
img_up.show()
p.s. if anyone knows why when the pillow text alpha is set to 0, the text is never added, please let me know!
I'll just post Bryan Oakley's comment as an answer. Pillow has an anchor option and all I needed to do was set it to mm

Radial Menu With Pyside/PyQt

Basically I am trying to create a radial menu like this
I was using QPainter and here is a attempt from my side. But I can't figure out how to add a click event on the pixmaps. Is there any lib available for this ?
Images Link
from PySide2 import QtWidgets, QtGui, QtCore
import sys
import os
RESPATH = "/{your_folder}/radialMenu/res"
class RadialMenu(QtWidgets.QGraphicsRectItem):
addButton = 1
disableButton = 2
clearButton = 3
exportButton = 4
infoButton = 5
runButton = 6
scriptsButton = 7
def __init__(self, parent=None):
super(RadialMenu, self).__init__(parent)
def paint(self, painter, option, widget=None):
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setBrush(QtGui.QBrush(QtGui.QColor(71, 71, 71, 0)))
tempPen = QtGui.QPen(QtGui.QColor(178, 141, 58), 20.0, QtCore.Qt.CustomDashLine)
tempPen.setDashPattern([4, 4, 4, 4])
painter.setPen(tempPen)
painter.drawEllipse(0, 0, 150, 150)
topX = 0
topY = 0
pixmap1 = QtGui.QPixmap(os.path.join(RESPATH, "add.png"))
painter.drawPixmap(topX + 50, topY - 8, pixmap1)
pixmap2 = QtGui.QPixmap(os.path.join(RESPATH, "disable.png"))
painter.drawPixmap(topX + 90, topY - 5, pixmap2)
pixmap3 = QtGui.QPixmap(os.path.join(RESPATH, "clear.png"))
painter.drawPixmap(topX - 10, topY + 70, pixmap3)
pixmap4 = QtGui.QPixmap(os.path.join(RESPATH, "export.png"))
pixmap4 = pixmap4.transformed(QtGui.QTransform().rotate(15))
painter.drawPixmap(topX - 2, topY + 100, pixmap4)
pixmap5 = QtGui.QPixmap(os.path.join(RESPATH, "info.png"))
painter.drawPixmap(topX + 20, topY + 125, pixmap5)
pixmap6 = QtGui.QPixmap(os.path.join(RESPATH, "run.png"))
painter.drawPixmap(topX + 113, topY + 125, pixmap6)
pixmap6 = QtGui.QPixmap(os.path.join(RESPATH, "scripts.png"))
painter.drawPixmap(topX + 137, topY + 85, pixmap6)
class RadialTest(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.scene=QtWidgets.QGraphicsScene(self)
buttonItem = RadialMenu()
self.scene.addItem(buttonItem)
buttonItem.setPos(100,100)
buttonItem.setZValue(1000)
self.scene.update()
self.view = QtWidgets.QGraphicsView(self.scene, self)
self.scene.setSceneRect(0, 0, 300, 300)
self.setGeometry(50, 50, 305, 305)
self.show()
if __name__ == "__main__":
app=QtWidgets.QApplication(sys.argv)
firstScene = RadialTest()
sys.exit(app.exec_())
This code will give this result
For this kind of objects, keeping a hierarchical object structure is always suggested. Also, when dealing with object that possibly have "fixed" sizes (like images, but not only), fixed positioning can be tricky expecially with newer system that support different DPI screen values.
With this approach I'm not using mapped images at all (but button icons can be still set), instead I chose to use a purely geometrical concept, using pixel "radius" values and angles for each button.
from PyQt5 import QtWidgets, QtGui, QtCore
from math import sqrt
class RadialMenu(QtWidgets.QGraphicsObject):
buttonClicked = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptHoverEvents(True)
self.buttons = {}
def addButton(self, id, innerRadius, size, startAngle, angleSize, pen=None,
brush=None, icon=None):
# if a button already exists with the same id, remove it
if id in self.buttons:
oldItem = self.buttons.pop(id)
if self.scene():
self.scene().removeItem(oldItem)
oldItem.setParent(None)
# compute the extents of the inner and outer "circles"
startRect = QtCore.QRectF(
-innerRadius, -innerRadius, innerRadius * 2, innerRadius * 2)
outerRadius = innerRadius + size
endRect = QtCore.QRectF(
-outerRadius, -outerRadius, outerRadius * 2, outerRadius * 2)
# create the circle section path
path = QtGui.QPainterPath()
# move to the start angle, using the outer circle
path.moveTo(QtCore.QLineF.fromPolar(outerRadius, startAngle).p2())
# draw the arc to the end of the angle size
path.arcTo(endRect, startAngle, angleSize)
# draw a line that connects to the inner circle
path.lineTo(QtCore.QLineF.fromPolar(innerRadius, startAngle + angleSize).p2())
# draw the inner circle arc back to the start angle
path.arcTo(startRect, startAngle + angleSize, -angleSize)
# close the path back to the starting position; theoretically unnecessary,
# but better safe than sorry
path.closeSubpath()
# create a child item for the "arc"
item = QtWidgets.QGraphicsPathItem(path, self)
item.setPen(pen if pen else (QtGui.QPen(QtCore.Qt.transparent)))
item.setBrush(brush if brush else QtGui.QColor(180, 140, 70))
self.buttons[id] = item
if icon is not None:
# the maximum available size is at 45 degrees, use the Pythagorean
# theorem to compute it and create a new pixmap based on the icon
iconSize = int(sqrt(size ** 2 / 2))
pixmap = icon.pixmap(iconSize)
# create the child icon (pixmap) item
iconItem = QtWidgets.QGraphicsPixmapItem(pixmap, self)
# push it above the "arc" item
iconItem.setZValue(item.zValue() + 1)
# find the mid of the angle and put the icon there
midAngle = startAngle + angleSize / 2
iconPos = QtCore.QLineF.fromPolar(innerRadius + size * .5, midAngle).p2()
iconItem.setPos(iconPos)
# use the center of the pixmap as the offset for centering
iconItem.setOffset(-pixmap.rect().center())
def itemAtPos(self, pos):
for button in self.buttons.values():
if button.shape().contains(pos):
return button
def checkHover(self, pos):
hoverButton = self.itemAtPos(pos)
for button in self.buttons.values():
# set a visible border only for the hovered item
button.setPen(QtCore.Qt.red if button == hoverButton else QtCore.Qt.transparent)
def hoverEnterEvent(self, event):
self.checkHover(event.pos())
def hoverMoveEvent(self, event):
self.checkHover(event.pos())
def hoverLeaveEvent(self, event):
for button in self.buttons.values():
button.setPen(QtCore.Qt.transparent)
def mousePressEvent(self, event):
clickButton = self.itemAtPos(event.pos())
if clickButton:
for id, btn in self.buttons.items():
if btn == clickButton:
self.buttonClicked.emit(id)
def boundingRect(self):
return self.childrenBoundingRect()
def paint(self, qp, option, widget):
# required for QGraphicsObject subclasses
pass
ButtonData = [
(50, 40, QtWidgets.QStyle.SP_MessageBoxInformation),
(90, 40, QtWidgets.QStyle.SP_MessageBoxQuestion),
(180, 20, QtWidgets.QStyle.SP_FileDialogBack),
(200, 20, QtWidgets.QStyle.SP_DialogOkButton),
(220, 20, QtWidgets.QStyle.SP_DialogOpenButton),
(290, 30, QtWidgets.QStyle.SP_ArrowDown),
(320, 30, QtWidgets.QStyle.SP_ArrowUp),
]
class RadialTest(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.scene = QtWidgets.QGraphicsScene(self)
buttonItem = RadialMenu()
self.scene.addItem(buttonItem)
buttonItem.buttonClicked.connect(self.buttonClicked)
for index, (startAngle, extent, icon) in enumerate(ButtonData):
icon = self.style().standardIcon(icon, None, self)
buttonItem.addButton(index, 64, 20, startAngle, extent, icon=icon)
buttonItem.setPos(150, 150)
buttonItem.setZValue(1000)
self.view = QtWidgets.QGraphicsView(self.scene, self)
self.view.setRenderHints(QtGui.QPainter.Antialiasing)
self.scene.setSceneRect(0, 0, 300, 300)
self.setGeometry(50, 50, 305, 305)
self.show()
def buttonClicked(self, id):
print('Button id {} has been clicked'.format(id))
And this is the result:

WxPython BitmapButton not clickable

So, I am trying to make this program for a project I was assigned. The code is still a draft and I really didn't know anything about wxPython when I was assigned this project.
Anyway. What this program does, is create an application that manages photo albums. I got it to create/remove folders and be able to change its root directory and move the program files elsewhere. I also got it to generate bitmap buttons for each 'album' and place them in a FlexGridSizer.
My problem is that these Bitmap Buttons are unclickable.
class RightPanel(wx.Panel):
global path
def __init__(self, parent):
a = wx.GetDisplaySize()
width = 3 * a[0] / 4
height = 3 * a[1] / 4
wx.Panel.__init__(self, parent=parent,
size=(3*width/4, height),
style=wx.EXPAND)
self.SetBackgroundColour('dark grey')
self.widgetSizer = wx.BoxSizer(wx.VERTICAL)
class MasterPanel(wx.Panel):
global delete, CurrentDirg, locale
delete = False
a = wx.GetDisplaySize()
width = 3 * a[0] / 4
height = 3 * a[1] / 4
id = {}
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.MasterPanel = wx.Panel(self,
wx.ID_ANY,
size=(self.width, self.height),
)
self.SetBackgroundColour('light grey')
self.sizer = wx.BoxSizer(wx.VERTICAL)
splitter1 = wx.SplitterWindow(self)
splitter2 = wx.SplitterWindow(splitter1)
left_pt = LeftPanelTop(splitter2)
left_pb = LeftPanelBottom(splitter2)
self.rightP = RightPanel(splitter1)
self.boxsizer2 = wx.BoxSizer(wx.VERTICAL)
splitter2.SetSashGravity(0.5)
splitter2.SplitHorizontally(left_pt, left_pb)
splitter1.SplitVertically(splitter2, self.rightP)
splitter1.SetSashGravity(0.5)
self.gSizer = wx.FlexGridSizer(0, 5, 10, 10)
self.dir_search()
self.boxsizer2.Add(self.gSizer, 1, wx.EXPAND|wx.ALL)
self.rightP.SetSizer(self.boxsizer2)
self.boxsizer2.Layout()
self.sizer.Add(splitter1, 1, wx.EXPAND)
self.SetSizer(self.sizer)
def dir_search(self):
global path, delete
try:
if self.id != {} or delete == True:
sizer = self.gSizer
for i in sizer.GetChildren():
sizer.Hide(0)
sizer.Remove(0)
self.boxsizer2.Layout()
self.gSizer.Layout()
self.id = {}
with open('albums.dir', mode='r', buffering=1) as alb:
names = alb.readlines()
for i in range(len(names)):
names[i] = names[i].rstrip('\n')
paths = [path + '\\' + i for i in names]
counter = 0
for i in paths:
self.dirimcreate(i, counter)
counter += 1
print(self.id)
except Exception as E:
print(E)
sizer = self.gSizer
while sizer.GetChildren():
sizer.Hide(0)
sizer.Remove(0)
self.boxsizer2.Layout()
def dirimcreate(self, path, counter):
pic = wx.Image('input.ico', wx.BITMAP_TYPE_ANY)
pic = pic.Scale(self.width / 10, self.width / 10, wx.IMAGE_QUALITY_HIGH)
pic = pic.ConvertToBitmap()
self.saasda = wx.BitmapButton(self.rightP,
wx.ID_ANY,
pic,
size=(self.width / 10, self.width / 10),
style=wx.NO_BORDER
)
self.saasda.Bind(wx.EVT_BUTTON, self.chdir)
self.saasda.SetDefault()
self.saasda.myname = self.saasda.GetId()
self.id[self.saasda.GetId()] = path
self.gSizer.Add(self.saasda, 0, wx.ALL, 5)
self.boxsizer2.Layout()
def chdir(self, event):
self.Info(message='You clicked a button')
This is what the result looks like.
Thank you in advance.
I found the solution!
It seems that the code for the MasterPanel class included a size parameter in the init, which created an invisible panel that covered everything else, rendering it unclickable.

Categories

Resources