I'm trying to teach myself the tkinter module by programming minesweeper. I have created a grid with buttons and a method to set an image flag to cells. It works, in that when you press the right mouse button the image of the button changes as desired, but when you right click on the next button the image just moves to the next button, rather than creating a second flag. I want to be able to place a new flag image on each cell that I right click, rather than just shuffle the image around. Here's my code:
import tkinter as Tk
def main():
root = Tk.Tk()
root.geometry('{}x{}'.format(700, 700))
instance = Minesweeper(root, 10, 10)
root.mainloop()
class Minesweeper:
def __init__(self, parent, height, width):
self.top_frame = Tk.Frame(parent)
self.top_frame.place(anchor=Tk.CENTER, relx=0.5, rely=0.5)
self.frames = []
self.buttons = []
index = 0
for x in range(height):
for y in range(width):
self.frames.append(Tk.Frame(self.top_frame, height=50, width=50))
self.buttons.append(Tk.Button(self.frames[index], bg="white"))
self.frames[index].grid_propagate(False)
self.frames[index].columnconfigure(0, weight=1)
self.frames[index].rowconfigure(0, weight=1)
self.frames[index].grid(row=x, column=y)
self.buttons[index].grid(sticky="wens")
self.buttons[index].bind('<Button-3>', self.flag)
index += 1
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.configure(image=self.flag)
if __name__ == "__main__":
main()
Seems that the below fixed it:
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.image = self.flag # <---- this seemed to fix it
event.widget.configure(image=self.flag)
Related
I'm developing a calendar application
The top level window is a frame containing a panel that displays the calendar grid and a panel that contains a "Close" button.
I'm unable to obtain the size of the calendar grid panel.
When I add code to get the panel size, the result is (20,20), which cannot be correct
The screen size is (1920,1080) so I'm expecting something like (1920, 1000)
When I add the wx.lib.inspection module, I see the correct size being displayed. It is (1920, 968)
Can anyone shed some light how to get the correct size of the panel?
This is the code I have so far
import wx
class DrawFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title='Agenda', style= wx.CAPTION | wx.CLOSE_BOX)
self.drawpanel = DrawPanel(self)
self.buttonpanel = ButtonPanel(self)
self.framesizer = wx.BoxSizer(wx.VERTICAL)
self.framesizer.Add(self.drawpanel,1, flag=wx.EXPAND)
# Add an empty space 10 pixels high above and below the button panel
self.framesizer.Add((0,10),0)
self.framesizer.Add(self.buttonpanel,0, flag=wx.EXPAND)
self.framesizer.Add((0,10),0)
self.SetSizer(self.framesizer)
self.SetInitialSize()
self.Maximize()
self.Show()
def GetPanelSize(self):
return self.drawpanel.GetSize()
def OnClose(self, event):
self.Close()
class DrawPanel(wx.Panel):
# This panel's parent is DrawFrame. DrawFrame is the top level window.
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
self.parent = parent
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.x1, self.y1, self.x2, self.y2 = wx.GetClientDisplayRect()
b = self.x1, self.y1, self.x2, self.y2
print b
self.width, self.height = wx.GetDisplaySize()
c = self.width, self.height
print c
def OnPaint(self, event=None):
dc = wx.PaintDC(self)
dc.Clear()
dc.SetPen(wx.Pen(wx.BLACK, 2))
dc.SetBrush(wx.Brush('WHITE'))
"""
DrawRectangle (self, x, y, width, height)
Draw a rectangle with the given corner coordinate and size.
x and y specify the top left corner coordinates and both width and height are positive.
"""
dc.DrawRectangle(self.x1 + 5, self.y1, self.x2 - 10, self.y2 - 60)
dc.DrawLine(40, 100, 600, 100)
class ButtonPanel(wx.Panel):
# This panel's parent is DrawFrame. DrawFrame is the top level window.
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent)
self.parent=parent
self.buttonpanelsizer = wx.BoxSizer(wx.HORIZONTAL)
self.closebutton = wx.Button(self, label = 'Close')
self.Bind(wx.EVT_BUTTON, self.OnClose, self.closebutton)
self.buttonpanelsizer.AddStretchSpacer(prop=1)
self.buttonpanelsizer.Add(self.closebutton, 0, wx.ALIGN_CENTER)
self.SetSizer(self.buttonpanelsizer)
def OnClose(self, event):
self.parent.OnClose(event)
app = wx.App(False)
frame = DrawFrame()
print frame.GetPanelSize()
app.MainLoop()
Much appreciated,
Thanks
You are calling the GetPanelSize too early. Keep in mind that wxPython (and pretty much any GUI framework) is event based. That means that for it to work it must keep processing events, which in case of wxPython means that app.MainLoop() must run. So do not call GetPanelSize before calling app.MainLoop(). Instead, call it when you need it. Do you need it when you paint something? Just use dc.GetSize(). Do you need it elsewhere? Process the wx.EVT_SIZE event and store the current size. Possibly you will have to trigger some action in the EVT_SIZE handler.
I'm working on an annotation tool for some images and decided to use GTK for the task. I have a Gtk.DrawingArea() nested inside Gtk.Viewport() which is nested in Gtk.ScrolledWindow() to enable scrolling of the drawing area. The drawing area contains an image and shapes are drawn on top of the image using Cairo on each mouse click event.
If I understand correctly, scrolling by default causes a redrawing of Gtk.DrawingArea() which makes all of shapes disappear. Is there any way (other than keeping a list of coordinates and redrawing every shape on each scroll event) to maintain those shapes?
import gi
import math
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Test")
self.drag = False
self.drag_x = 0
self.drag_y = 0
self.pos = []
viewport = Gtk.Viewport()
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.expose)
self.pixbuf = GdkPixbuf.Pixbuf.new_from_file("anntool/test.jpg")
self.darea.set_size_request(self.pixbuf.get_width(), self.pixbuf.get_height());
self.maximize() # maximize window on load
grid = Gtk.Grid()
self.add(grid)
scrolled = Gtk.ScrolledWindow()
scrolled.set_hexpand(True)
scrolled.set_vexpand(True)
scrolled.set_kinetic_scrolling(True)
self.v_scroll = scrolled.get_vadjustment()
self.h_scroll = scrolled.get_hadjustment()
scrolled.add_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK)
scrolled.connect("button-release-event", self.release)
scrolled.connect("button-press-event", self.click)
scrolled.connect("motion-notify-event", self.mousemove)
# scrolled.connect("scroll_event", self.scroll)
viewport.add(self.darea)
scrolled.add(viewport)
grid.add(scrolled)
def click(self, widget, event):
if (event.button == 1):
cr = self.darea.get_parent_window().cairo_create()
x = self.h_scroll.get_value() + event.x
y = self.v_scroll.get_value() + event.y
cr.arc(x, y, 10, 0, 2 * math.pi)
cr.set_source_rgba(0.0, 0.6, 0.0, 1)
cr.fill()
if (event.button == 2):
self.drag = True
self.drag_x = event.x
self.drag_y = event.y
self.pos = [self.h_scroll.get_value(), self.v_scroll.get_value()]
def release(self, widget, event):
self.drag = False
default = Gdk.Cursor(Gdk.CursorType.ARROW)
widget.get_window().set_cursor(default)
def mousemove(self, widget, event):
if self.drag:
self.h_scroll.set_value(self.pos[0] + self.drag_x - event.x)
self.v_scroll.set_value(self.pos[1] + self.drag_y - event.y)
hand = Gdk.Cursor(Gdk.CursorType.HAND1)
widget.get_window().set_cursor(hand)
def scroll(self, widget, event):
print("scrolled")
def expose(self, widget, event):
Gdk.cairo_set_source_pixbuf(event, self.pixbuf, 0, 0)
event.paint()
win = MainWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
As Uli Schlachter pointed out, redraw is executed on each scroll event, therefore one need to keep track of added points (e.g. with a list) and redraw each point in expose() function in the code above.
I have adapted an image viewer (see code below) to allow me to get pixel information from a loaded image. You load an image using the 'Load image' button, then you can zoom in and out using the scroll wheel, and pan using mouse left click and drag. When you press the button 'Enter pixel info mode', the dragging is disabled (you can still zoom) and clicking on the image will give the pixel coordinate (integer pixel indices) and grayscale value of the pixel.
The problem is that if you rotate the image, by pressing the 'Rotate image' button, using the pixel info button no longer gives the correct pixel info. I imagine that the mapToScene method is not the right thing to use on a rotated image but can find no other way to do it. I have tried various things, such as using toImage() on the rotated pixmap and then replacing the original image with this, but nothing seems to work. What would be the best way to resolve this?
The code:
from PyQt4 import QtCore, QtGui
class PhotoViewer(QtGui.QGraphicsView):
photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
self._zoom = 0
self._empty = True
self._scene = QtGui.QGraphicsScene(self)
self._photo = QtGui.QGraphicsPixmapItem()
self._scene.addItem(self._photo)
self.setScene(self._scene)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
self.setFrameShape(QtGui.QFrame.NoFrame)
def fitInView(self):
rect = QtCore.QRectF(self._photo.pixmap().rect())
if not rect.isNull():
unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
factor = min(viewrect.width() / scenerect.width(),
viewrect.height() / scenerect.height())
self.scale(factor, factor)
self.centerOn(rect.center())
self._zoom = 0
def hasPhoto(self):
return not self._empty
def toggleDragMode(self):
if self.dragMode() == QtGui.QGraphicsView.ScrollHandDrag:
self.setDragMode(QtGui.QGraphicsView.NoDrag)
elif self.hasPhoto():
self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
def setPhoto(self, pixmap=None):
self._zoom = 0
if pixmap and not pixmap.isNull():
self._empty = False
self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
self._photo.setPixmap(pixmap)
self.fitInView()
else:
self._empty = True
self.setDragMode(QtGui.QGraphicsView.NoDrag)
self._photo.setPixmap(QtGui.QPixmap())
def wheelEvent(self, event):
if not self._photo.pixmap().isNull():
if event.delta() > 0:
factor = 1.25
self._zoom += 1
else:
factor = 0.8
self._zoom -= 1
if self._zoom > 0:
self.scale(factor, factor)
elif self._zoom == 0:
self.fitInView()
else:
self._zoom = 0
def mousePressEvent(self, event):
if (self.hasPhoto() and
self.dragMode() == QtGui.QGraphicsView.NoDrag and
self._photo.isUnderMouse()):
self.photoClicked.emit(QtCore.QPoint(event.pos()))
super(PhotoViewer, self).mousePressEvent(event)
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = PhotoViewer(self)
# 'Load image' button
self.btnLoad = QtGui.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
# Button to change from drag/pan to getting pixel info
self.btnPixInfo = QtGui.QToolButton(self)
self.btnPixInfo.setText('Enter pixel info mode')
self.btnPixInfo.clicked.connect(self.pixInfo)
self.editPixInfo = QtGui.QLineEdit(self)
self.editPixInfo.setReadOnly(True)
# Button to rotate image by 10 degrees
self.btnRotate = QtGui.QToolButton(self)
self.btnRotate.setText('Rotate image')
self.btnRotate.clicked.connect(self.rotateImage)
self.viewer.photoClicked.connect(self.photoClicked)
# Arrange layout
VBlayout = QtGui.QVBoxLayout(self)
VBlayout.addWidget(self.viewer)
HBlayout = QtGui.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
HBlayout.addWidget(self.btnRotate)
HBlayout.addWidget(self.btnPixInfo)
HBlayout.addWidget(self.editPixInfo)
VBlayout.addLayout(HBlayout)
def loadImage(self):
self.viewer.setPhoto(QtGui.QPixmap('pic.jpg'))
def pixInfo(self):
self.viewer.toggleDragMode()
def rotateImage(self):
self.viewer._photo.setRotation(10)
def photoClicked(self, pos):
pos = self.viewer.mapToScene(pos)
# p.s. I realise the following lines are probably a very convoluted way of getting
# a grayscale value from RGB, but I couldn't make it work any other way I tried
rot_image = self.viewer._photo.pixmap().toImage().pixel(pos.x(), pos.y())
colour = QtGui.QColor.fromRgb(rot_image)
gsval = QtGui.qGray(colour.red(), colour.green(), colour.blue())
self.editPixInfo.setText('X:%d, Y:%d Grayscale: %d' % (pos.x(), pos.y(), gsval))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 800, 600)
window.show()
sys.exit(app.exec_())
You need to map the scene coordinates to item coordinates:
pos = self.viewer._photo.mapFromScene(self.viewer.mapToScene(pos))
I'm writing simple GUI using wxPyhon and faced some problems.
My application does simple things: it draws triangle on the form and rotates it when user clicks arrow buttons or drags a mouse cursor over the form.
THe problems I see now are following:
1. Then I drag a mouse sursor fast the triangle rotates with keeping old image visible for a short time. When keeping moving a cursor fast for a while the drawing on the form is looking like 2 or 3 triangles.
2. If I expand the form to entire size of the screen the triangle moves unsmoothly, with small jumps from old appearance to a new one. I looked at coordinates of a mouse cursor during that rotating and noticed that they are tracked with gaps. Friend of mine said me that it is because I redraw the entire window of the application every time I wand to rotate the triangle a little bit. And that's why it works slowly and it slow down the tracking of a mouse cursor.
To refresh the view I'm using wx.Panel.Refresh() method. As drawing context I'm using wx.BufferedDC()
Please tell me how to draw CORRECTLY dynamicaly changing pictures/drawings on the wxPython forms, especially the way I make in that application.
I could place my code here, but it's too long. So if I must tell something more about my case - ask me please, I will answer.
Thanks !
class SimpleGraphics(wx.Panel):
def __init__(self, parent, size=(50, 50)):
super(SimpleGraphics, self).__init__(parent,
size=size,
style=wx.NO_BORDER)
self.color = "Black"
self.thickness = 2
self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)
self.MARGIN = 1 #px
self.points = [[0.0, 0.5], [0.5, 0.0], [-0.5, -0.5]]
self.pos = (0, 0)
self.cur_vector = Vector2D(1, 1)
self.InitBuffer()
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_IDLE, self.OnIdle)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyArrow)
# MOUSE TRACKING
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def InitBuffer(self):
self.client_size = self.GetClientSize()
self.buffer = wx.EmptyBitmap(self.client_size.width, self.client_size.height)
dc = wx.BufferedDC(None, self.buffer)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
self.DrawImage(dc)
self.reInitBuffer = False
def OnSize(self, event):
self.reInitBuffer = True
def repaint_the_view(self):
self.InitBuffer()
self.Refresh()
def OnIdle(self, event):
if self.reInitBuffer:
self.repaint_the_view()
def OnKeyArrow(self, event):
key_code = event.GetKeyCode()
if key_code == wx.WXK_LEFT:
self.rotate_points(degrees_to_rad(5))
elif key_code == wx.WXK_RIGHT:
self.rotate_points(degrees_to_rad(-5))
self.repaint_the_view()
event.Skip()
def OnLeftDown(self, event):
# get the mouse position and capture the mouse
self.pos = event.GetPositionTuple()
self.cur_vector = create_vector2d(self.pos[0], self.pos[1],
self.client_size.width / 2,
self.client_size.height / 2)
self.CaptureMouse()
def OnLeftUp(self, event):
#release the mouse
if self.HasCapture():
self.ReleaseMouse()
def OnMotion(self, event):
if event.Dragging() and event.LeftIsDown():
newPos = event.GetPositionTuple()
new_vector = create_vector2d(newPos[0], newPos[1],
self.client_size.width / 2,
self.client_size.height / 2)
if new_vector.lenth() > 0.00001:
c = cos_a(self.cur_vector, new_vector)
s = sin_a(self.cur_vector, new_vector)
rot_matr = rotation_matrix(s, c)
self.rotate_points(rot_matr=rot_matr)
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) # this line I've added after posting the question
self.repaint_the_view()
self.cur_vector = new_vector
event.Skip()
def OnPaint(self, event):
wx.BufferedPaintDC(self, self.buffer)
def DrawImage(self, dc):
dc.SetPen(self.pen)
new_points = self.convetr_points_to_virtual()
dc.DrawPolygon([wx.Point(x, y) for (x, y) in new_points])
def to_x(self, X_Log):
X_Window = self.MARGIN + (1.0 / 2) * (X_Log + 1) * (self.client_size.width - 2 * self.MARGIN)
return int(X_Window)
def to_y(self, Y_Log):
Y_Window = self.MARGIN + (-1.0 / 2) * (Y_Log - 1) * (self.client_size.height - 2 * self.MARGIN)
return int(Y_Window)
def convetr_points_to_virtual(self):
return [(self.to_x(x), self.to_y(y)) for (x, y) in self.points]
def rotate_points(self, angle_in_degrees=None, rot_matr=None):
if angle_in_degrees is None:
self.points = [rotate_point(x, y , rotator_matrix=rot_matr) for (x, y) in self.points]
else:
self.points = [rotate_point(x, y , angle_in_degrees) for (x, y) in self.points]
class SimpleGraphicsFrame(wx.Frame):
def __init__(self, parent, *args, **kwargs):
wx.Frame.__init__(self, parent, *args, **kwargs)
# Attributes
self.panel = SimpleGraphics(self)
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.panel, 1, wx.EXPAND)
self.SetSizer(sizer)
class SimpleGraphApp(wx.App):
def OnInit(self):
self.frame = SimpleGraphicsFrame(None,
title="Drawing Shapes",
size=(300, 400))
self.frame.Show()
return True
if __name__ == '__main__':
app = SimpleGraphApp(False)
app.MainLoop()
You call self.Refresh() from your OnKeyArrow and OnMotion events. Update your scene data in those methods and set some flag e.g. self.repaint_needed = True. Then in OnIdle repaint the scene if self.repaint_needed is True.
Now you try to repaint the window every time the event is received. Which may be a lot.
What you want to do is to update the scene information every time, but repaint the window only when wx indicates it has some "free time".
I'm not really accustomed to writing the UI in the code, so I need a few pointers.
I'm trying to create a simple, horizontal scrolling dialog on my N900, but I can't figure out how to do this.
This is what I have so far:
def __init__(self,parent = None):
QDialog.__init__(self,parent)
#if name == None:
self.setWindowTitle('Testing scrolling')
self.scrollArea = QScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setMinimumSize(100,150)
self.aWidget = QWidget(self.scrollArea)
self.aWidget.setMinimumSize(20,200)
self.aWidget.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding)
self.scrollArea.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding)
self.scrollArea.setWidget(self.aWidget)
scroller = self.scrollArea.property("kineticScroller").toPyObject()
scroller.setEnabled(True)
_layout = QGridLayout(self.aWidget)
_layout.setSpacing(60)
_layout.setMargin(11)
_layout.addWidget(QPushButton('Test0'),0,0)
_layout.addWidget(QPushButton('Test1'),0,1)
_layout.addWidget(QPushButton('Test2'),0,2)
_layout.addWidget(QPushButton('Test3'),0,3)
_layout.addWidget(QPushButton('Test4'),0,4)
_layout.addWidget(QPushButton('Test5'),0,5)
_layout.addWidget(QPushButton('Test6'),0,6)
Pls, check if an example below would help you out, it should create a dialog with horizontal scroll area and buttons in it.
class MyDialog(QDialog):
def __init__(self,parent = None):
QDialog.__init__(self,parent)
self.setWindowTitle('Testing scrolling')
self.setGeometry(250, 200, 350, 400)
widget = QWidget()
widgetLayout = QHBoxLayout()
for i in range(0, 25):
button = QPushButton("test button {0}".format(i))
widgetLayout.addWidget(button)
widget.setLayout(widgetLayout)
scrollArea = QScrollArea()
scrollArea.setWidget(widget)
dialogLayout = QVBoxLayout()
dialogLayout.addWidget(scrollArea)
self.setLayout(dialogLayout)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
sys.exit(app.exec_())
hope this helps, regards