canvas.move not moving in python - python

I have a circle that I want to move around a map, I am in the early stages, but I've looked in many places and can't figure out why the circle isn't moving. my code is
import Tkinter as Tkinter
class gameScreen:
def moveup(self, event):
self.canvas.move(self.char, -100, 0)
self.canvas.focus(self.char)
self.canvas.update()
def __init__(self, master):
self.master = master
master.title("Game")
master.resizable(width=False, height=False)
self.img = tkinter.PhotoImage(file = "platformer.gif")
self.canvas = tkinter.Canvas(master, width=self.img.width(),
height=self.img.height())
self.canvas.pack(expand="YES",fill="both")
self.canvas.create_image(0, 0,anchor="nw", image = self.img)
self.char = tkinter.PhotoImage(file = "hero.gif")
self.canvas.create_oval(0, 0, 50, 50, fill="red")
self.x = 0
self.y = 0
master.bind("<Up>", self.moveup)
root = tkinter.Tk()
my_gui = gameScreen(root)
root.mainloop()`

You have to give the move method an id or tag for an object on the canvas. The id is returned when you create the canvas item.
class gameScreen:
def moveup(self, event):
...
self.canvas.move(self.canvas_item, -100, 0)
...
def __init__(self, master):
...
self.canvas_item = self.canvas.create_oval(...)
...

Related

How can I use 2 classes on a tkinter button?

The Btn class is supposed to overlay an image on a button. In this case, it's supposed to highlight the button once hovered over it. I'm trying to also get a tooltip message to pop up CreateToolTip when hovering over the button. At the moment, it is only displaying one of the classes at a time. Is there a way I can display both without them clashing?
Here's my code:
from tkinter import Button, Toplevel, Label, PhotoImage
class Btn(Button):
def __init__(self, root, img1, img2, *args, **kwargs):
super().__init__(root, *args, **kwargs)
self.img = PhotoImage(file=img1).subsample(2, 2)
self.img2 = PhotoImage(file=img2).subsample(2, 2)
self['image'] = self.img
self.bind('<Enter>', self.enter)
self.bind('<Leave>', self.leave)
def enter(self, event):
self.config(image=self.img2)
def leave(self, event):
self.config(image=self.img)
class CreateToolTip(Btn):
def __init__(self, widget, text='widget info'):
self.waittime = 600 #miliseconds
self.wraplength = 180 #pixels
self.widget = widget
self.text = text
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Leave>", self.leave)
self.widget.bind("<ButtonPress>", self.leave)
self.id = None
self.tw = None
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.widget.after(self.waittime, self.showtip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
def showtip(self, event=None):
x = y = 0
x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() + 20
y += self.widget.winfo_rooty() + 40
# creates a toplevel window
self.tw = Toplevel(self.widget)
# Leaves only the label and removes the app window
self.tw.wm_overrideredirect(True)
self.tw.wm_geometry("+%d+%d" % (x, y))
label = Label(self.tw, text=self.text, justify='left',
background="#ffffff", relief='solid', borderwidth=1,
wraplength = self.wraplength)
label.pack(ipadx=1)
def hidetip(self):
tw = self.tw
self.tw= None
if tw:
tw.destroy()
I imported the classes from another Python file. In my main project this is how they are used:
play = 'Button IMG/Play.png'
playHover = 'Button IMG/MouseHoverAni/Play.png'
playBtn = Btn(toolBar, img1=play, img2=playHover, borderwidth=0, command=run)
playBtn.pack(side=LEFT)
CreateToolTip(playBtn, 'Play')

How can I change size of circle in pyqt5

How can I change size of circle in first window, using slider from second window. Is there an option to send value from slider to first window and put this value in painter.drawEllipse fuction?
class ThirdWindow(QWidget):
def __init__(self):
super().__init__()
hbox = QHBoxLayout()
sld = QSlider(Qt.Horizontal, self)
sld.setRange(0, 100)
sld.setFocusPolicy(Qt.NoFocus)
sld.setPageStep(5)
sld.valueChanged.connect(self.updateLabel)
self.label = QLabel('0', self)
self.label.setAlignment(Qt.AlignCenter | Qt.AlignVCenter)
self.label.setMinimumWidth(80)
hbox.addWidget(sld)
hbox.addSpacing(15)
hbox.addWidget(self.label)
self.setLayout(hbox)
self.setGeometry(600, 60, 500, 500)
self.setWindowTitle('QSlider')
self.show()
def updateLabel(self, value):
self.label.setText(str(value))
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle("Dialogi")
self.w = ThirdWindow()
actionFile = self.menuBar().addMenu("Dialog")
action = actionFile.addAction("Zmień tło")
action1 = actionFile.addAction("Zmień grubość koła")
action1.triggered.connect(self.w.show)
self.setGeometry(100, 60, 300, 300)
self.setStyleSheet("background-color: Green")
def paintEvent(self, event):
painter = QPainter(self)
painter.setPen(QPen(Qt.gray, 8, Qt.SolidLine))
painter.drawEllipse(100, 100, 100, 100)
You can communicate with the parent class (in this case the class where the circle is located i.e. Window by calling self.parentWidget()
so you can call that inside your updateLabel function and pass the value. However to repaint the surface you must have a variable initiated in the main Window
Hence your code should look like-
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle("Dialogi")
self.w = ThirdWindow()
actionFile = self.menuBar().addMenu("Dialog")
action = actionFile.addAction("Zmień tło")
action1 = actionFile.addAction("Zmień grubość koła")
action1.triggered.connect(self.w.show)
self.setGeometry(100, 60, 300, 300)
self.setStyleSheet("background-color: Green")
self.radius = 100 #add a variable radius to keep track of the circle radius
def paintEvent(self, event):
painter = QPainter(self)
painter.setPen(QPen(Qt.gray, 8, Qt.SolidLine))
#change function to include radius
painter.drawEllipse(100, 100, self.radius, self.radius)
and in the other widget change the updateLabel function to change the radius and call repaint
class ThirdWindow(QWidget):
def updateLabel(self, value):
self.label.setText(str(value))
self.parentWidget().radius = value
self.parentWidget().repaint()

Should I re-write my Tkinter in PyQt, or vice versa?

I wrote a Tkinter app, and I wanted to add screen snipping, so I found a separate program from GitHub (screen-snip) written in PyQt, which I was importing and using in my Tkinter app. Then I decided to combine the programs in order to ask an SO question about why they aren't totally working together. I've learned not to combine Tk and Qt.
So now my question is, should I rewrite my program in Qt, or Tk?
Which is better for this situation?
My currently mixed-Tk/Qt program works when you select an image file, but now the screen-snip portion with Qt class MyWidget(QtWidgets.QWidget): causes it to freeze and then crash.
I think the problem might be a result of mixing Qt with Tk, but I'm not sure.
I originally had two instances of tkinter running, which allowed me to get the screen ship with a new window, but caused trouble with the button window, so I replaced this by trying to use tk.Toplevel
class MyWidget(QtWidgets.QWidget):
def __init__(self, master):
super().__init__()
self.master = master
self.window = tk.Toplevel(self.master)
and that's when I ran into trouble. The widget no longer works at all, and the program crashes without any clues or errors. Any idea why?
Simplified Code:
import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk, Image, ImageGrab
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import numpy as np
import cv2
class ButtonImg:
def __init__(self, master):
self.newWindow = None
self.master = master
self.fontA = ("arial", 20, "bold")
self.canvas = tk.Canvas(height = 5)
self.canvas.pack()
self.button = tk.Button(bg="#61B5DA", height = 5, text = "Select Image",
font = self.fontA, command = self.changeImage)
self.button.pack(fill="both")
def changeImage(self):
print('open second window')
self.newWindow = tk.Toplevel(self.master)
img = AcquireImage(self.newWindow)
self.master.wait_window(self.newWindow)
print('close second window')
if img.image_selected: # check if image was selected
self.image = img.image_selected
self.button.configure(image=self.image, height=self.image.height())
class AcquireImage:
def __init__(self, master):
self.master = master
self.fontA = ("arial", 20, "bold")
self.frame = tk.Frame(master, bg="#96beed")
self.frame.pack(fill="both", expand=True)
self.button1 = tk.Button(self.frame, text="Select Image File", padx=5, pady=5, bg="#6179DA",
font = self.fontA, command =lambda: self.show_dialogs(1))
self.button1.grid(row=0, column=0, sticky="nsew")
self.button2 = tk.Button(self.frame, text="Get Screen Snip", padx=5, pady=5, bg="#6179DA",
font = self.fontA, command=lambda: self.show_dialogs(2))
self.button2.grid(row=0, column=1, sticky="nsew")
self.image_selected = None
def show_dialogs(self, method):
if method == 1:
ret = filedialog.askopenfilename() #filedialog.askopenfilename(initialdir='/home/user/images/')
if ret:
self.image_selected = ImageTk.PhotoImage(file = ret)
self.master.destroy()
elif method == 2:
newWin = MyWidget(self.master)
newWin.show()
ret = newWin.img
if ret:
self.image_selected = ImageTk.PhotoImage(file = ret)
class MyWidget(QtWidgets.QWidget):
def __init__(self, master):
super().__init__()
self.master = master
self.window = tk.Toplevel(self.master)
screen_width = self.thirdWin.winfo_screenwidth()
screen_height = self.thirdWin.winfo_screenheight()
self.setGeometry(0, 0, screen_width, screen_height)
self.setWindowTitle(' ')
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.img = None
self.setWindowOpacity(0.3)
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.CrossCursor)
)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
print('Capture the screen...')
self.show()
def getRect(self):
# a commodity function that always return a correctly sized
# rectangle, with normalized coordinates
width = self.end.x() - self.begin.x()
height = abs(width * 2 / 3)
if self.end.y() < self.begin.y():
height *= -1
return QtCore.QRect(self.begin.x(), self.begin.y(),
width, height).normalized()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(QtGui.QPen(QtGui.QColor('black'), 3))
qp.setBrush(QtGui.QColor(128, 128, 255, 128))
qp.drawRect(self.getRect())
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = self.begin
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.close()
rect = self.getRect()
self.img = ImageGrab.grab(bbox=(
rect.topLeft().x(),
rect.topLeft().y(),
rect.bottomRight().x(),
rect.bottomRight().y()
))
#self.img.save('capture.png')
self.img = cv2.cvtColor(np.array(self.img), cv2.COLOR_BGR2RGB)
cv2.imshow('Captured Image', self.img)
cv2.waitKey(0)
#cv2.destroyAllWindows()
if __name__ == '__main__':
root = tk.Tk()
app = ButtonImg(root)
root.mainloop()
As said in the comments, the best is to use a single GUI toolkit so you need either to rewrite your code for Qt or rewrite the snipping code using tkinter. I don't know Qt much so I cannot help you with the first option. However, the screenshot is actually taken using PIL, not some Qt specific method, so the snipping code can be rewritten in tkinter.
All you need is a fullscreen toplevel containing a canvas with a draggable rectangle, like in Drawing rectangle using mouse events in Tkinter. To make the toplevel fullscreen: toplevel.attributes('-fullscreen', True)
The toplevel needs to be partially transparent, which can be achieved with toplevel.attributes('-alpha', <value>). I am using Linux (with XFCE desktop environment) and I need to add toplevel.attributes('-type', 'dock') to make the transparency work.
All put together in a class, this give:
import sys
import tkinter as tk
from PIL import ImageGrab
import cv2
import numpy as np
class MyWidget(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.configure(cursor='cross')
if sys.platform == 'linux':
self.attributes('-type', 'dock') # to make transparency work in Linux
self.attributes('-fullscreen', True)
self.attributes('-alpha', 0.3)
self.canvas = tk.Canvas(self, bg='white')
self.canvas.pack(fill='both', expand=True)
self.begin_x = 0
self.begin_y = 0
self.end_x = 0
self.end_y = 0
self.canvas.create_rectangle(0, 0, 0, 0, outline='gray', width=3, fill='blue', tags='snip_rect')
self.canvas.bind('<ButtonPress-1>', self.mousePressEvent)
self.canvas.bind('<B1-Motion>', self.mouseMoveEvent)
self.canvas.bind('<ButtonRelease-1>', self.mouseReleaseEvent)
print('Capture the screen...')
def mousePressEvent(self, event):
self.begin_x = event.x
self.begin_y = event.y
self.end_x = self.begin_x
self.end_y = self.begin_y
self.canvas.coords('snip_rect', self.begin_x, self.begin_y, self.end_x, self.end_y)
def mouseMoveEvent(self, event):
self.end_x = event.x
self.end_y = event.y
self.canvas.coords('snip_rect', self.begin_x, self.begin_y, self.end_x, self.end_y)
def mouseReleaseEvent(self, event):
self.destroy()
self.master.update_idletasks()
self.master.after(100) # give time for screen to be refreshed so as not to see the blue box on the screenshot
x1 = min(self.begin_x, self.end_x)
y1 = min(self.begin_y, self.end_y)
x2 = max(self.begin_x, self.end_x)
y2 = max(self.begin_y, self.end_y)
img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
self.img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
cv2.imshow('Captured Image', self.img)
cv2.waitKey(0)
if __name__ == '__main__':
root = tk.Tk()
tk.Button(root, text='Snip', command=lambda: MyWidget(root)).pack()
root.mainloop()

Implementing a Canvas in PyQt5

I have been trying to learn to using PyQt5.
I want to implement a canvas below the "Menu Bar"
class gui(QDialog):
def __init__(self, parent=None):
super(gui, self).__init__(parent)
self.createTopLayout()
self.painter = canvas(self)
mainLayout = QGridLayout()
mainLayout.addLayout(self.topLayout, 0, 0, 1, 2)
mainLayout.addWidget(self.painter, 1, 0, 6, 2)
self.setLayout(mainLayout)
def createTopLayout(self):
self.topLayout = QHBoxLayout()
button1 = QPushButton("b1")
button2 = QPushButton("b2")
button3 = QPushButton("b3")
styleComboBox = QComboBox()
styleComboBox.addItems(QStyleFactory.keys())
styleLabel = QLabel("&Style:")
styleLabel.setBuddy(styleComboBox)
self.topLayout.addWidget(styleLabel)
self.topLayout.addWidget(styleComboBox)
self.topLayout.addStretch(1)
self.topLayout.addWidget(button1)
self.topLayout.addWidget(button2)
self.topLayout.addWidget(button3)
Where my canvas is defined as
class canvas(QMainWindow):
def __init__(self, parent=None):
super(canvas, self).__init__(parent)
self.setGeometry(100, 100, 1000, 700)
def paintEvent(self, e):
cir = circle() #circle class creates a circle with random center and radius both between 0 to 100
painter = QPainter(self)
painter.setPen(QPen(Qt.red, 1, Qt.SolidLine))
painter.drawEllipse(self, cir.center.x, cir.center.y, cir.radius, cir.radius)
but for me canvas doesnt render at all let alone the ellipse.
You should not use QMainWindow as a canvas, instead use a QWidget. On the other hand setGeometry will not work if you use layout since the latter handles the geometry, instead it establishes a fixed size and adequate margins. On the other hand it is recommended that the name of the classes begin with capital letters, considering the above the solution is:
class Canvas(QWidget):
def __init__(self, parent=None):
super(Canvas, self).__init__(parent)
self.setFixedSize(1000, 700)
self.setContentsMargins(100, 100, 100, 100)
def paintEvent(self, e):
# ...

PyQt is slow in handling a large number of objects

I present here a simple pyqt code, but I developed my whole design and algo upon this. The problem, is that PyQt is unable to manage a large number of objects when created. This is simple code for drawing many rectangles inside a big one. And it has Zoom and pan feature too.
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
item = None
class Rectangle(QGraphicsItem):
def __init__(self, parent, x, y, width, height, scene = None, fill=None):
self.parent = parent
super(Rectangle, self).__init__()
self.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
rect = QRectF(x,y,width,height)
self.dimen = [x,y,width,height]
self.rect = rect
scene.addItem(self)
self.show_its_ui()
#Function to get relative position
def get_relative_location(self,loc):
self.loc = QPointF(loc[0],loc[1])
global item
self.loc = self.mapFromItem(item, self.loc)
self.loc = (self.loc.x(),self.loc.y())
self.loc = list(self.loc)
self.dimen[0] = self.loc[0]
self.dimen[1] = self.loc[1]
#Showing its UI in the form of a rectangle
def show_its_ui(self):
#First gets its relative location and set it.
if(item is not None):
self.get_relative_location([self.dimen[0],self.dimen[1]])
#Make a Final rectangle
rect = QRectF(self.dimen[0],self.dimen[1],self.dimen[2],self.dimen[3])
self.rect = rect
def boundingRect(self):
return self.rect.adjusted(-2, -2, 2, 2)
def parentWidget(self):
return self.scene().views()[0]
def paint(self, painter, option, widget):
pen = QPen(Qt.SolidLine)
pen.setColor(Qt.black)
pen.setWidth(1)
painter.setBrush(QtGui.QColor(255, 50, 90, 200))
painter.drawRect(self.rect)
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.TextAntialiasing)
def wheelEvent(self, event):
factor = 1.41 ** (event.delta() / 240.0)
self.scale(factor, factor)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.Width = 800
self.Height = 500
self.view = GraphicsView()
self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, self.Width, self.Height)
self.view.setScene(self.scene)
self.initUI()
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.view)
mainWidget = QtGui.QWidget()
mainWidget.setLayout(hbox)
self.setCentralWidget(mainWidget)
def initUI(self):
self.group = QGraphicsItemGroup()
self.group.setFlags(QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
global item
item = self.group
self.group.setFiltersChildEvents(True)
self.group.setHandlesChildEvents(False)
self._link1 = Rectangle(self, 10, 10, self.Width, self.Height,self.scene)
self._link1.setFiltersChildEvents(True)
self._link1.setHandlesChildEvents(False)
self.group.addToGroup(self._link1)
self.scene.addItem(self.group)
self.addObjects()
#Here I added objects in the big canvas
def addObjects(self):
xpos = 20
ypos = 20
#Change the below to 5 each
width = 50
height = 50
#Generate many rectangles
while(ypos < 450):
xpos = 20
while(xpos < 750):
t = Rectangle(self._link1,xpos,ypos,width,height,self.scene,True)
self.group.addToGroup(t)
xpos += (width+2)
ypos += (height+2)
app = QtGui.QApplication(sys.argv)
mainWindow = MainWindow()
method = mainWindow
mainWindow.show()
sys.exit(app.exec_())
Try changing, the width and height values present in addObjects function of the mainwindow class, say to 5 and 5 respectively from 50. Then the whole application run at very slow speed, when you pan/zoom. Please share your suggestions in rectifying this.

Categories

Resources