I'm a beginner and i'm trying to make paint with python turtle but my code gives an error. I've tried everything I could think of but it still isn't working.
from turtle import *
from menuitem import MenuItem
def changePenColor(c):
"""Changes the system turtle's color to c."""
color(c)
def createMenu(callBack):
"""Displays 6 menu items to respond to the given callback function."""
x = - (window_width() / 2) + 30
y = 100
colors = ('red', 'green', 'blue', 'yellow', 'black', 'purple')
shape = "circle"
for color in colors:
MenuItem(x, y, shape, color, callBack)
y -= 30
def main():
"""Creates a menu for selecting colors."""
reset()
shape("turtle")
createMenu(color)
return "done!"
if __name__=='__main__':
msg = main()
print(msg)
mainloop()
And this code in a different file:
from turtle import Turtle
class MenuItem(Turtle):
"""Represents a menu item."""
def __init__(self, x, y, shape, color, callBack):
"""Sets the initial state of a menu item."""
Turtle.__init__(x, y, self, shape = shape, visible = False)
self.speed(0)
self.up()
self.goto(x, y)
self.color(color, color)
self._callBack=callBack
self.onclick(lambda x,y: self._callBack(color))
self.showturtle()
If anyone knows what I can do to fix this, I'd be happy to know.
Thanks 😊
Your code is somewhat confused. Specifically:
from turtle import *
Just don't. Particularly in a module. Import as little as you need to get the job done.
createMenu(color)
This should be createMenu(changePenColor) and changePenColor() should be defined in the main module, not the MenuItem class module.
Turtle.__init__(x, y, self, shape = shape, visible = False)
first three arguments to __init__ shouldn't be there and you should use super, all as #Evan notes.
reset()
self._callBack=callBack
These two statments are effectively no-ops and can be left out.
Below is my rework of your code that I believe does what you're attempting to do. For example purposes, instead of the main module, I just used a if __name__ == '__main__': for testing:
from turtle import Screen, Turtle
COLORS = ('red', 'green', 'blue', 'yellow', 'black', 'purple')
CURSOR_SIZE = 20
class MenuItem(Turtle):
''' Represents a menu item. '''
def __init__(self, x, y, shape, color, callBack):
''' Sets the initial state of a menu item '''
super().__init__(shape=shape, visible=False)
self.penup()
self.goto(x, y)
self.color(color)
self.onclick(lambda x, y: callBack(color))
self.showturtle()
def createMenu(callBack):
''' Displays 6 menu items to respond to the given callback function. '''
screen = Screen()
x = CURSOR_SIZE * 1.5 - screen.window_width() / 2
y = 100
for color in COLORS:
MenuItem(x, y, 'circle', color, callBack)
y -= CURSOR_SIZE * 1.5
if __name__ == '__main__':
from turtle import getscreen, getturtle
def changePenColor(c):
''' Changes the turtle's color to c. '''
turtle.color(c)
screen = getscreen() # singular screen instance
turtle = getturtle() # default turtle
turtle.shape('turtle')
# Create a menu for selecting colors.
createMenu(changePenColor)
screen.mainloop()
In your first line of the __init__ function on your MenuItem class, use this
super().__init__(shape=shape, visible=False)
instead of
Turtle.__init__(x, y, self, shape = shape, visible = False)
You don't need to pass in x, y, or self, because you are already setting the position by saying self.goto(x, y). Also, use super() instead of Turtle, because you need to initialize the superclass, not just another instance of Turtle. By saying Turtle.__init__(...) you are creating an instance of that object and doing nothing with it. By saying super().__init__(...), you are initializing the superclass of your object, which is what you always need to do when you are subclassing an object (in this case Turtle).
Note: your __init__ function needs to be indented, but I'll assume that was a pasting error.
Related
I intend to make a Py code which creates a tkinter dot that turns on a key press and deletes on a key press of couple keys.
The dot already is functional but i need it switch on and off on certain keypresses/mouse clicks which means i need an outside tkinter.mainloop() Update function.
The Update function with a while in it to constantly check if conditions to turn it off/on are present. But the Tkinter widget Somehow gets applied to the screen Only when the function nds. Like widget could be created but it will only take effect when function ends. And i need to turn it off/on dynamically.
I have tried to use a tkinter.after() with additional one at the end of called function only to find out an error of Recursion depth. What i expected to happen was that the function would be called over and over again, instead it runs that function like a while loop. I also have tried Asyncio.run() but it would result not making it visible till the function ends at least once. And I need to change it dynamically.
from tkinter import *
from tkinter import Canvas
from winsound import Beep
from time import sleep
import asyncio
import keyboard
import mouse
root = Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
class tk_Dot():
def __init__(self,x=-1,y=-1,radius=4,color="red"):
self.x = x
if x == -1:
self.x = width/2-radius//2
print(self.x)
self.y = y
if y == -1:
self.y = height/2+radius//2
print(self.y)
self.radius=radius
self.color = color
self.lines = []
self.count = 1
def line(self,i):
return canvas.create_line(self.x, self.y-i, self.x+self.radius, self.y-i, fill=self.color)
def create(self):
self.lines = []
for i in range(0,self.radius):
self.lines.append(self.line(i))
def delete(self):
for i in range(0,self.radius):
canvas.delete(self.lines[i])
canvas.dtag(self.lines[i])
opacity_of_tk_window = 1 # From 0 to 1 0 meaning completely transparent 1 meaning everything created in canvas will give the color it was given
root.attributes('-alpha',opacity_of_tk_window)
# Invisible Tkinter window label
root.overrideredirect(True)
# Makes Transparent background
transparent_color = '#f0f0f0'
root.wm_attributes('-transparent', transparent_color)
canvas = Canvas()
# Rectangle filled with color that is specified above as the transparent color so practically making transparent background
canvas.create_rectangle(0, 0, width, height, fill=transparent_color)
canvas.pack(fill=BOTH, expand=1)
radius = 2
radius = 1+radius\*2
# Create a dot class
game_dot = tk_Dot(width/2-radius//2+1,height/2+1+radius//2,radius,"Red")
# Create a Dot at the middle of the calorant crosshair
# game_dot.create()
# Delete the dot
# game_dot.delete()
def Update():
game_dot.create()
print("Dot should be visible by now")
print("Is it?")
sleep(5) #sec
print("Oh yeah after the function ends.") # the problem
def Delete():
game_dot.delete()
root.geometry('%dx%d+%d+%d' % (width, height, -2,-2))
# Tkinter window always on top
root.attributes('-topmost',True)
root.after(1000,Update())
root.mainloop()
I'm trying to create a rectangle with text on top, but the text shows below the rectangle.
How can I make the text go to the front layer?
My code so far:
from turtle import Turtle
class Rectangle(Turtle):
def __init__(self, x, y, width, height, color='white'):
super().__init__()
self.penup()
self.goto(x, y)
self.color(color)
self.shape('square')
self.shapesize(stretch_wid=(height / 20), stretch_len=(width / 20))
class Writer(Turtle):
def __init__(self, x, y):
super().__init__()
self.penup()
self.goto(x, y)
self.hideturtle()
def writetext(self, text, font="Arial", size=8, textType="normal", color="black", x=None, y=None):
if x is None:
x = self.xcor()
if y is None:
y = self.ycor()
self.goto(x, y)
self.color(color)
self.write(text, move=False, align="center", font=(font, size, textType))
class Button(Rectangle):
def __init__(self, position, width, height, text, onclick, color="gray"):
position = list(position)
super().__init__(position[0], position[1], width, height, color)
writer = Writer(position[0], position[1])
writer.writetext(text, color="white")
self.goto(position[0], position[1])
self.color(color)
self.onclick(onclick)
def start_game(_arg1=None, _arg2=None): # requires arguments since Python turtle module passes two in when calling it using onclick
print('game started')
Button((0, 0), 50, 20, 'click me to start the game', start_game)
I've been searching on google for over half an hour and couldn't find anything
I believe the design has some fundamental flaws that arise from subclassing Turtle.
The issue is that the Button drawing is happening outside of the constructor where the text is written. The drawing function is called automatically by the turtle library. Regardless of whether you're hooking into Turtle's classes or not, a constructor isn't typically the ideal place to draw things for this reason. I don't see a clean way to make a Button out of two separate turtles with subclassing.
A quick (but poor) fix is to override the internal method that turtle calls to update the object so your button can inject the text after the super() call draws the rectangle (you could also try to hook into drawing with _drawturtle):
class Button(Rectangle):
# ....
def _update(self):
super()._update()
Writer(*self.position()).writetext("foobar", color="white")
But since the leading _ indicates a private, internal method, this is circumventing the turtle API -- not a good idea.
A second try would be to disable the internal update loop with turtle.tracer(0) and take control of the update loop manually, but then that seems to defeat the purpose of subclassing Turtle, which is that you want things to "just work" automatically.
There's a deeper problem here, though, which is that once you get your text on top of the button as shown above in the workaround, the text blocks clicks from being detected by the rectangle underneath it.
After playing around with it quite a bit, the best alternatives I came up with were to create an image with the text pre-rendered in, or else use a global click listener and use the coordinates to determine if the click occurred on the button. The following code isn't reusable, but it's a proof of concept that you could abstract into a function or class by parameterizing everything:
import turtle
from turtle import Turtle
def handle_btn_click(x, y):
if (x > -btn_size and y > -btn_size / 2 and
x < btn_size and y < btn_size / 2):
print("game started")
turtle.tracer(1)
turtle.clearscreen()
turtle.bye()
turtle.tracer(0)
r = Turtle(shape="square")
btn_size = 80
r.shapesize(btn_size // 20, btn_size // 10)
r.color("black")
turtle.update()
t = Turtle()
t.color("white")
t.ht()
text = "click me to start the game"
t.write(text, move=False, align="center", font=("Arial", 8, "normal"))
turtle.Screen().onclick(handle_btn_click)
turtle.mainloop()
As a final note, if you're only adding a single button, I suggest keeping it simple and imperative. A class structure is often overkill for most turtle apps, and the large parameter lists are a pain to deal with for only a single instance.
The text shows below the button seems to happen when you call any methods of the Button class after you wrote the text.
Try the following code:
class Button(Rectangle):
def __init__(self, position, width, height, text, onclick, color="gray"):
#...
writer.writetext(text, color="red")
self.goto(position[0], position[1])
self.color(color)
self.onclick(onclick)
writer.writetext("abc", color="blue")
If you set turtle.tracer with a bit of delay, you can actually see the grey button being created first, then the text in red on the grey button, then the grey button is brought back up by the three self. methods, then the text "abc" in blue written on top of everything.
Still, as mentioned in ggorlen's answer, the text will block the button from being clicked. Please refer to ggorlen's answer for the alternative solution.
Beginner here trying to make sense of classes. Below is the code for my class Cell:
import tkinter
import random
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()
class Cell:
def __init__(self, x, y, r):
self.x = random(x)
self.y = random(y)
self.r = 200
def show(self):
canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")
top.mainloop()
I'm attempting to draw the cell in my main program by calling the function show from the class. Here is the code for my main window:
import tkinter
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()
from Cell import Cell
cell = Cell()
cell.show()
top.mainloop()
This is resulting in the canvas being drawn correctly, but the oval is nowhere to be found. I am not getting any errors either.
Any help would be appreciated. Thank you!
====================
Turns out, I misunderstood the arguments for create_oval. I found some code that converts the clunky create_oval function into a function which receives a set of coordinates for the center of the oval and a radius.
In addition to this, the help I received in understanding classes and other Python functionality helped significantly as well. Thanks to those who helped!
This is my revised code which works as intended.
import tkinter as tk
import random
top = tk.Tk()
canvas = tk.Canvas(top, width=400, height=400, bg="grey")
canvas.grid()
def _create_circle(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle
class Cell:
def __init__(self, canvas, x, y, r):
self.canvas = canvas
self.x = x
self.y = y
self.r = r
def show(self):
self.canvas.create_circle(self.x, self. y, self.r, fill = "blue")
cell = Cell(canvas, random.randrange(50, 350), random.randrange(50, 350), 25)
cell.show()
top.mainloop()
The problem here is that your main script and your Cell module are both creating a new Tk instance, adding a Canvas to it, and then calling its mainloop method.
If you trace through the order in which statements get executed, you'll find that the cell = Cell() and cell.show() don't happen until after the first top.mainloop() returns, and mainloop() doesn't return until you quit the program. (In fact, if your code did get that far, it would fail with a TypeError, which I'll get to below.)
But, more generally, you only want one Tk in your program, and everyone else should refer to that.
And, in this case, you want the same for the Canvas: just one of them, packed onto the one Tk main window.
So, how can Cell.show refer to the canvas global from another module?
The best solution is to not refer to it as a global at all, and instead pass it in to the initializer, the same way you do with x, y, and r:
class Cell:
def __init__(self, canvas, x, y, r):
self.canvas = canvas
self.x = random(x)
self.y = random(y)
self.r = 200
def show(self):
self.canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")
And then in the main script:
cell = Cell(canvas, ?, ?. ?)
cell.show()
But notice those ?s I put there. Your Cell class definition demands x, y, and r values in its initializer, but your Cell() constructor call doesn't pass any. That will raise a TypeError complaining that you're missing required arguments.
What do you want to pass here? Since the canvas is 400x400, maybe you want to pass something like 400, 400, 200? If so:
cell = Cell(canvas, 400, 400, 200)
cell.show()
Going back to that initializer, you've got some other problems there:
self.x = random(x)
self.y = random(y)
That random is a module. You can't call a module. You probably wanted something like this:
self.x = random.randrange(x)
That calls a function from the random module, one which is defined to return a random number in range(0, x), which seems like what you want.
Also:
self.r = 200
Why take an r parameter, just to ignore it? You probably wanted this:
self.r = r
Or, maybe you didn't actually want x, y, and r as parameters? Maybe you want to hardcode randrange(400), randrange(400), and 200, or maybe you want to compute them from the width and height of the canvas parameter, or… you can do almost anything you want, you just have to think through what you want, and make sure the interface you declare in the def matches the way you call it in the Cell(…) later.
I think your execution path is never getting to the cell.show() line.
When you import Cell, you have code at the top level top.mainloop(). This enters the main loop and never exits, so you never get to the lines below it.
It's a good rule of thumb to avoid putting code at the base level. Leave that for defining classes and functions. If you want code to run when the file is called like a script, put it in a if __name__ == __main__: condition.
You also had some syntax issues using random and calling the Cell constructor.
The example below works as expected.
import tkinter
import random
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()
class Cell:
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def show(self):
canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")
if __name__ == "__main__":
cell = Cell(100, 50, 5)
cell.show()
top.mainloop()
I'm new to Python, I still have issues with the semantics of class inheritance.
The following is the relevant class from the module games.py module that I am importing:
class Text(Sprite):
"""
Alphanumeric values displayed on the screen.
"""
def __init__(self, value, size, color, angle=0,
x=0, y=0,
top=None, bottom=None, left=None, right=None,
dx=0, dy=0,
interval=1, is_collideable=True):
self._size = size
self._color = color
self._value = value
self._font = pygame.font.Font(None, self._size)
Sprite.__init__(self, self._create_surface(), angle,
x, y,
top, bottom, left, right,
dx, dy,
interval, is_collideable)
and the following is from where I'm trying to call it in my own program:
self.scorebox = games.Text (value = self.scorevar,
pygame.font.Font(ardarlingopentype, 50),
color = color.white,
x = 550,
y = 50)
As you can see the syntax is wrong, but how do I go about fixing this such that I can inherit the class Text from my own program and make FONT an accessible argument that I can change?
Thanks.
Your problem is, that you you are ordering the arguments incorrectly: there are positional and keyword arguments. All keywords arguments must succeed the positional arguments.
This would work:
self.scorebox = games.Text (
pygame.font.Font(ardarlingopentype, 50),
value = self.scorevar,
color = color.white,
x = 550,
y = 50
)
Not sure(note that you can't used not named arguments after named and/or mix them - you have used not named argument after 'value') but seems that you need to modify code the following way:
class Text(Sprite):
"""
Alphanumeric values displayed on the screen.
"""
def __init__(self, value, size, color, angle=0,
x=0, y=0,
top=None, bottom=None, left=None, right=None, font=None,
dx=0, dy=0,
interval=1, is_collideable=True):
self._size = size
self._color = color
self._value = value
if font:
self.font_ = font
else:
self._font = pygame.font.Font(None, self._size)
Sprite.__init__(self, self._create_surface(), angle,
x, y,
top, bottom, left, right,
dx, dy,
interval, is_collideable)
And then:
import pygame
import games
self.scorebox = games.Text (value = self.scorevar,
size = 50,
color = color.white,
x = 550,
y = 50)
OR:
import pygame
import games
self.scorebox = games.Text (value = self.scorevar,
size = 50,
font = pygame.font.Font(ardarlingopentype, 50),
color = color.white,
x = 550,
y = 50)
So guys I wrote to the developers of the Livewires package; and I was fortunate enough to receive a reply from one of them.
First, make a backup copy of games.py and put it somewhere safe. That
way if you do make a mistake, you can always recover the original
code.
Now our games.py is written on top of the PyGame library, which does
provide a way of setting the font. As you might have guessed, it's to
do with that line reading:
> self._font = pygame.font.Font(None, self._size)
The documentation is available online at
http://www.pygame.org/docs/ref/font.html#pygame.font.Font but I'll
just quickly summarise here. pygame.font.Font() creates a new PyGame
font object, which PyGame uses to tell it how to draw text. The
"None" parameter tells it to use the default font, but you can replace
that with the full name of a font file instead. The easiest way to do
that is to modify the Text classes initialiser to pass it as an
optional parameter.
class Text(Sprite):
def __init__(self, value, size, color, angle=0,
> x=0, y=0,
> top=None, bottom=None, left=None, right=None,
> dx=0, dy=0,
> interval=1, is_collideable=True,
> fontfile=None):
> self._size = size
> self._color = color
> self._value = value
> self._font = pygame.font.Font(fontfile, self._size)
> Sprite.__init__(self, self._create_surface(), angle,
> x, y,
> top, bottom, left, right,
> dx, dy,
> interval, is_collideable)
You would then create your Text object by calling 'Text(blah blah
blah, fontfile="/some/font/file/name.ttf")' or whatever the filename
is. Any other Text objects that don't specify a "fontfile" will
automatically use "None" instead, which will give them the default
font exactly as before.
So what's the fully-qualified pathname of the font file for
"TimesNewRoman"? I have no idea what it would be on your computer.
Fortunately PyGame provides a way of not having to know:
pygame.font.match_font(). You can use that in your own program
(rather than modifying games.py any more), but you will have to either
"import pygame.font" for yourself or call it
"games.pygame.font.match_font()" -- either should work equally well.
What Python-related code (PyGTK, Glade, Tkinter, PyQT, wxPython, Cairo, ...) could you easily use to create a GUI to do some or all of the following?
Part of the GUI has an immovable square grid.
The user can press a button to create a resizable rectangle.
The user can drag the rectangle anywhere on the grid, and it will snap to the grid.
The DiagramScene Eaxmple that comes with PyQt implements much of the functionality you want. It has a fixed background grid, you can create a rectangle object but it's not resizable and doesn't snap to grid.
This SO article has advice on resizing graphical objects with the mouse. It's for C++ Qt but the technique should be easy to replicate in PyQt.
For snap-to-grid I don't think there is any built-in functionality. You would probably need to reimplement the itemChange(GraphicsItemChange change, const QVariant &value) function. Pseudocode:
if (object not possitioned exactly on the grid):
(possition the item on the grid)
Repossitioning the item will cause itemChange to get called again, but that's ok because the item will be possitioned correctly and won't be moved again, so you'll not be stuck in an endless loop.
I was looking for a while for something like this, and finally managed to cook up a "minimal" working example with Python wx, utilizing wx.lib.ogl and its Diagram and ShapeCanvas classes. The code (below) results with something like this:
Note:
The app starts with the circle added; press SPACE to add rectangles at random position
Click an object to select it (to show handles); to deselect it, click the object again (clicking the background has no effect) - this is functionality of ogl
The grid is drawn "manually"; however the snapping-to-grid is functionality of ogl
Snap-to-grid only works automatically when moving shapes with mouse drag; for other purposes you must manually call it
Snap-to-grid - as well as resizing of shape by handles - works in respect to the center of each shape (not sure if ogl allows for changing that anchor to, say, bottom left corner)
The example uses a MyPanel class that does its own drawing, and inherits both from ogl.ShapeCanvas and from wx.Panel (though the mixin with wx.Panel can be dropped, and the code will still work the same) - which is then added to a wx.Frame. Note the code comments for some caveats (such as the use of ogl.ShapeCanvas blocking all key events, unless a SetFocus is performed on that widget first).
The code:
import wx
import wx.lib.ogl as ogl
import random
# tested on wxPython 2.8.11.0, Python 2.7.1+, Ubuntu 11.04
# started from:
# http://stackoverflow.com/questions/25756896/drawing-to-panel-inside-of-frame-in-wxpython/27804975#27804975
# see also:
# wxPython-2.8.11.0-demo/demo/OGL.py
# https://www.daniweb.com/software-development/python/threads/186203/creating-editable-drawing-objects-in-wxpython
# http://gscept.com/svn/Docs/PSE/Milestone%203/code/trunk/python_test/src/oglEditor.py
# http://nullege.com/codes/search/wx.lib.ogl.Diagram
# http://nullege.com/codes/show/src%40w%40e%40web2cms-HEAD%40web2py%40gluon%40contrib%40pyfpdf%40designer.py/465/wx.lib.ogl.Diagram/python
# https://www.daniweb.com/software-development/python/threads/204969/setfocus-on-canvas-not-working
# http://stackoverflow.com/questions/3538769/how-do-you-draw-a-grid-and-rectangles-in-python
# http://stackoverflow.com/questions/7794496/snapping-to-pixels-in-wxpython
# ogl.ShapeCanvas must go first, else TypeError: Cannot create a consistent method resolution
class MyPanel(ogl.ShapeCanvas, wx.Panel):#(wx.PyPanel): #PyPanel also works
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="MyPanel"):
super(MyPanel, self).__init__(parent, id, pos, size, style, name)
self.gridsize = 20 # in pixels
# must have these (w. Diagram) if using ogl.ShapeCanvas:
self.diagram = ogl.Diagram()
self.SetDiagram(self.diagram)
self.diagram.SetCanvas(self)
# set up snap to grid - note, like this it works only for drag (relative to shape center), not for resize via handles!
self.diagram.SetGridSpacing( self.gridsize )
self.diagram.SetSnapToGrid( True )
# initialize array of shapes with one element
self.shapes = []
self.MyAddShape(
ogl.CircleShape(85), # diameter - drag marquee will not be visible if (diameter mod gridsize == 0), as it will overlap with the grid lines
60, 60, wx.Pen(wx.BLUE, 3), wx.GREEN_BRUSH, "Circle"
)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_PAINT, self.OnPaint)
wx.EVT_KEY_DOWN(self, self.OnKeyPressedM)
def OnKeyPressedM(self, event):
keyCode = event.GetKeyCode()
print("MyPanel.OnKeyPressedM: %d"%(keyCode) )
# insert a rectangle here on [SPACE]:
if keyCode == wx.WXK_SPACE:
randx = random.randint(1, 300)
randy = random.randint(1, 200)
if self.diagram.GetSnapToGrid():
randx, randy = self.Snap(randx, randy) # must do snapping (if desired) manually, here at insertion!
self.MyAddShape(
ogl.RectangleShape(60, 20),
randx, randy, wx.BLACK_PEN, wx.LIGHT_GREY_BRUSH, "Rect %d"%(len(self.shapes))
)
self.Refresh(False)
event.Skip() # must have this, to have the MyFrame.OnKeyPressed trigger as well!
def OnSize(self, event):
#print("OnSize" +str(event))
self.Refresh() # must have here!
event.Skip()
def DrawBackgroundGrid(self):
dc = wx.PaintDC(self)
#print(dc)
rect = self.GetClientRect()
rx, ry, rw, rh = rect
dc.SetBrush(wx.Brush(self.GetForegroundColour()))
dc.SetPen(wx.Pen(self.GetForegroundColour()))
# draw ("tile") the grid
x = rx
while x < rx+rw:
y = ry
dc.DrawLine(x, ry, x, ry+rh) # long (vertical) lines
while y < ry+rh:
dc.DrawLine(x, y, x+self.gridsize, y) # short (horizontal) lines
y = y + self.gridsize
x = x + self.gridsize
def OnPaint(self, event):
dc = wx.PaintDC(self) # works
self.DrawBackgroundGrid()
# self.Refresh() # recurses here - don't use!
# self.diagram.GetCanvas().Refresh() # blocks here - don't use!
self.diagram.GetCanvas().Redraw(dc) # this to redraw the elements on top of the grid, drawn just before
# MyAddShape is from OGL.py:
def MyAddShape(self, shape, x, y, pen, brush, text):
# Composites have to be moved for all children to get in place
if isinstance(shape, ogl.CompositeShape):
dc = wx.ClientDC(self)
self.PrepareDC(dc)
shape.Move(dc, x, y)
else:
shape.SetDraggable(True, True)
shape.SetCanvas(self)
shape.SetX(x)
shape.SetY(y)
if pen: shape.SetPen(pen)
if brush: shape.SetBrush(brush)
if text:
for line in text.split('\n'):
shape.AddText(line)
#shape.SetShadowMode(ogl.SHADOW_RIGHT)
self.diagram.AddShape(shape)
shape.Show(True)
evthandler = MyEvtHandler(self)
evthandler.SetShape(shape)
evthandler.SetPreviousHandler(shape.GetEventHandler())
shape.SetEventHandler(evthandler)
self.shapes.append(shape)
return shape
# copyfrom OGL.pyl; modded
class MyEvtHandler(ogl.ShapeEvtHandler):
def __init__(self, parent): #
ogl.ShapeEvtHandler.__init__(self)
self.parent = parent
def UpdateStatusBar(self, shape):
x, y = shape.GetX(), shape.GetY()
width, height = shape.GetBoundingBoxMax()
self.parent.Refresh(False) # do here, to redraw the background after a drag move, or scale of shape
print("Pos: (%d, %d) Size: (%d, %d)" % (x, y, width, height))
def OnLeftClick(self, x, y, keys=0, attachment=0):
# note: to deselect a selected shape, don't click the background, but click the shape again
shape = self.GetShape()
canvas = shape.GetCanvas()
dc = wx.ClientDC(canvas)
canvas.PrepareDC(dc)
if shape.Selected():
shape.Select(False, dc)
#canvas.Redraw(dc)
canvas.Refresh(False)
else:
redraw = False
shapeList = canvas.GetDiagram().GetShapeList()
toUnselect = []
for s in shapeList:
if s.Selected():
# If we unselect it now then some of the objects in
# shapeList will become invalid (the control points are
# shapes too!) and bad things will happen...
toUnselect.append(s)
shape.Select(True, dc)
if toUnselect:
for s in toUnselect:
s.Select(False, dc)
##canvas.Redraw(dc)
canvas.Refresh(False)
self.UpdateStatusBar(shape)
def OnEndDragLeft(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
if not shape.Selected():
self.OnLeftClick(x, y, keys, attachment)
self.UpdateStatusBar(shape)
def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
self.UpdateStatusBar(self.GetShape())
def OnMovePost(self, dc, x, y, oldX, oldY, display):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
self.UpdateStatusBar(shape)
if "wxMac" in wx.PlatformInfo:
shape.GetCanvas().Refresh(False)
def OnRightClick(self, *dontcare):
#self.log.WriteText("%s\n" % self.GetShape())
print("OnRightClick")
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Custom Panel Grid Demo")
# This creates some pens and brushes that the OGL library uses.
# (else "global name 'BlackForegroundPen' is not defined")
# It should be called after the app object has been created, but
# before OGL is used.
ogl.OGLInitialize()
self.SetSize((300, 200))
self.panel = MyPanel(self) #wx.Panel(self)
self.panel.SetBackgroundColour(wx.Colour(250,250,250))
self.panel.SetForegroundColour(wx.Colour(127,127,127))
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.panel, 1, wx.EXPAND | wx.ALL, 0)
self.SetSizer(sizer_1)
self.SetAutoLayout(1)
self.Layout()
self.Show(1)
# NOTE: on my dev versions, using ogl.Diagram causes _all_
# key press events, from *anywhere*, to stop propagating!
# Doing a .SetFocus on the ogl.ShapeCanvas panel,
# finally makes the Key events propagate!
# (troubleshoot via run.py from wx python demo)
self.panel.SetFocus()
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyPressed) # EVT_CHAR_HOOK EVT_KEY_DOWN
def OnKeyPressed(self, event):
print("MyFrame.OnKeyPressed (just testing)")
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
Those actions are not that difficult. All you really need for that is hit detection, which is not hard (is the cursor over the correct area? Okay, perform the operation then). The harder part is finding an appropriate canvas widget for the toolkit in use.