2010-08-21 19 views
7

Quel code lié à Python (PyGTK, Glade, Tkinter, PyQT, wxPython, Cairo, ...) pourriez-vous facilement utiliser pour créer une interface graphique pour faire tout ou partie de ce qui suit?Comment dessiner une grille et des rectangles en Python?

  1. Une partie de la GUI a une grille carrée inamovible.
  2. L'utilisateur peut appuyer sur un bouton pour créer un rectangle redimensionnable.
  3. L'utilisateur peut faire glisser le rectangle n'importe où sur la grille et il s'accroche à la grille.

Répondre

2

L'Eaxmple de DiagramScene fourni avec PyQt implémente la plupart des fonctionnalités que vous souhaitez. Il a une grille d'arrière-plan fixe, vous pouvez créer un objet rectangle mais il n'est pas redimensionnable et ne s'aligne pas sur la grille.

Ce SO article contient des conseils sur le redimensionnement d'objets graphiques avec la souris. C'est pour C++ Qt mais la technique devrait être facile à reproduire dans PyQt.

Pour snap-to-grid, je ne pense pas qu'il existe une fonctionnalité intégrée. Vous devrez probablement réimplémenter la fonction itemChange (modification GraphicsItemChange, const QVariant &). Pseudocode:

if (object not possitioned exactly on the grid): 
    (possition the item on the grid) 

Repossitioning l'élément fera itemChange pour s'appeler à nouveau, mais c'est ok parce que l'article sera possitioned correctement et ne sera pas déplacé à nouveau, de sorte que vous ne serez pas coincé dans une boucle sans fin.

+0

L'exemple DiagramScene répète une image pour créer une grille. Il peut ou non être difficile d'accrocher des objets à une telle grille. –

+0

C'est vrai, peut-être mieux de créer une grille de carrés. Je le fais dans une de mes applications en utilisant un QGraphicsItemGroup avec chaque cellule de la grille appartenant au groupe. –

0

Ces actions ne sont pas si difficiles. Tout ce dont vous avez vraiment besoin est la détection de coup, ce qui n'est pas difficile (est-ce que le curseur est sur la bonne zone? La partie la plus difficile est de trouver un widget canvas approprié pour le toolkit en cours d'utilisation.

+0

Pour Tkinter ce que vous dites est la partie difficile est assez facile: utiliser le widget Canvas. –

0

Je cherchais un certain temps pour quelque chose comme ça, et finalement réussi à concocter un exemple de travail « minimal » avec Python wx, en utilisant wx.lib.ogl et ses classes Diagram et ShapeCanvas.Le code (ci-dessous) résulte quelque chose comme ceci:

test.png

Note:

  • L'application commence par le cercle ajouté; appuyez sur ESPACE pour ajouter des rectangles à une position aléatoire
  • Cliquez sur un objet pour le sélectionner (pour afficher les poignées); pour le désélectionner, cliquez à nouveau sur l'objet (en cliquant sur l'arrière-plan n'a aucun effet) - c'est la fonctionnalité de ogl
  • La grille est dessinée "manuellement"; cependant l'accrochage à la grille est la fonctionnalité de ogl
  • L'accrochage à la grille ne fonctionne automatiquement que lorsque vous déplacez des formes avec la traînée de la souris; à d'autres fins, vous devez l'appeler manuellement
  • Snap-to-grid - ainsi que le redimensionnement de la forme par des poignées - fonctionne par rapport au centre de chaque forme (pas sûr si ogl permet de changer cette ancre, par exemple, en bas coin gauche)

l'exemple utilise une classe MyPanel qui fait son propre dessin, et hérite à la fois de ogl.ShapeCanvas et de wx.Panel (bien que le mixin avec wx.Panel peut être abandonné, et le code fonctionne toujours le même) - qui est ensuite ajouté à un wx.Frame. Notez les commentaires de code pour certaines mises en garde (comme l'utilisation de ogl.ShapeCanvas bloquant tous les événements clés, sauf si un SetFocus est d'abord effectué sur ce widget).

Le 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()