【问题标题】:wxPython for image and buttons (resizable)用于图像和按钮的 wxPython(可调整大小)
【发布时间】:2014-02-16 13:05:46
【问题描述】:

我想把这样的图片放在wx.Panel

我还是有点迷茫:如何实现这样的 clickable(带有切换按钮)和 rescalable Canvas?

编辑:我从Rescale image when parent is resized in wxPython 开始了一些富有成效的事情,但现在我完全不知道如何继续(检测点击,使用直接 DC 绘画更新按钮?),这就是赏金的原因.

【问题讨论】:

  • 我应该使用FloatCanvas吗?

标签: python image user-interface wxpython wxwidgets


【解决方案1】:

我无法回答缩放问题,但我记得一个用于执行任意图像目标命中检查(不需要按钮)的老技巧是这样的:

1) 创建一个与可见的大小相同的空白不可见图像。

2)当您在主图像上绘制目标时,使用所有相同值像素(但每个目标的唯一值)绘制一个形状相同的“阴影”到不可见的。一个“把手”,如果你愿意的话。

3) 当您在主图像上单击鼠标时,使用坐标从您的不可见阴影图像中获取相同的像素。该值将是目标的句柄。

一听就很简单,不是吗?

【讨论】:

    【解决方案2】:

    我解决了这个问题:

    import wx
    from floatcanvas import FloatCanvas
    
    class MyPanel(wx.Panel):
        def __init__(self, parent):
            super(MyPanel, self).__init__(parent)
            self.sizer = wx.BoxSizer(wx.VERTICAL)
            self.SetSizer(self.sizer)
    
            # add a canvas
            self.Canvas = FloatCanvas.FloatCanvas(self, BackgroundColor = "LIGHT GREY")
            self.Canvas.Bind(wx.EVT_SIZE, self.OnSize)
            self.sizer.Add(self.Canvas, -1, flag=wx.EXPAND)
    
            # add a toggle button
            image_dis = wx.Image('file_disabled.png')
            image_ena = wx.Image('file_enabled.png')
            img_dis = self.Canvas.AddScaledBitmap(image_dis, (x,-y), Height=image_dis.GetHeight(), Position = 'tl')
            img_ena = self.Canvas.AddScaledBitmap(image_ena, (x,-y), Height=image_ena.GetHeight(), Position = 'tl')
            img_dis.other = img_ena
            img_ena.other = img_dis
            img_ena.Visible = False
    
            # bind the toggle button event 
            img_dis.Bind(FloatCanvas.EVT_FC_LEFT_UP, self.OnToggle)
            img_ena.Bind(FloatCanvas.EVT_FC_LEFT_UP, self.OnToggle)
    
        def OnToggle(self, button):
            button.other.Visible = True
            button.Visible = False
            self.Canvas.Draw(True)
    
        def OnSize(self, event):
            event.Skip()
            wx.CallLater(1, self.Canvas.ZoomToBB)  
    

    【讨论】:

      【解决方案3】:

      我最近编写了一些代码用于练习。它可能以某种方式符合您的要求。 代码又丑又乱,因为我是 python 新手。

      支持:

      1. 背景上的图片可拖动
      2. 双击动画图像
      3. 背景和图片可调整大小

      注意:

      1. 你需要有 pygame 来运行代码
      2. 您可以通过替换 PyGamePseudoImage() 来加载真实图像
      3. 放大/缩小时图像坐标调整不够平滑

      代码:

      import wx
      import pygame
      
      BLACK = (  0,   0,   0)
      WHITE = (255, 255, 255)
      BLUE =  (  0,   0, 255)
      GREEN = (  0, 255,   0)
      RED =   (255,   0,   0)
      
      pygame.font.init()
      try:
          regular_font_file = os.path.join(os.path.dirname(__file__), "Vera.ttf")
          bold_font_file = os.path.join(os.path.dirname(__file__), "VeraBd.ttf")
      
          # Check for cx_Freeze
          #
          if "frozen" in sys.__dict__.keys() and sys.frozen:
      
              regular_font_file = os.path.join(sys.path[1], "Vera.ttf")
              bold_font_file = os.path.join(sys.path[1], "VeraBd.ttf")
      
          BIG_FONT = pygame.font.Font(regular_font_file, 30)
          SMALL_FONT = pygame.font.Font(regular_font_file, 12)
          BOLD_FONT = pygame.font.Font(bold_font_file, 12)
      
      except:
          # TODO: log used font: pygame.font.get_default_font()
          #print("Could not load {0}".format(os.path.join(os.path.dirname(__file__), "Vera.ttf")))
          BIG_FONT = pygame.font.Font(None, 40)
          SMALL_FONT = BOLD_FONT = pygame.font.Font(None, 20)
      
      
      class PyGamePseudoImage():
          def __init__(self, size, color):
              self.screen = pygame.Surface(size, 0, 32)
              self.screen.fill(color)
      
          def getImage(self):
              return self.screen
      
      class __MouseMixin:
      
          def onLeftUp(self, event):
              pass
      
          def onLeftDown(self, event):
              pass
      
          def onLeftDClick(self, event):
              pass
      
          def onRightUp(self, event):
              pass
      
          def onRightDown(self, event):
              pass
      
          def onDragging(self, event):
              pass
      
          def onMouseEnter(self, event):
              pass
      
          def OnMouseHandler(self, event):
              event.Skip()
      
              if event.LeftUp():
                  self.onLeftUp(event)
              elif event.LeftDown():
                  self.onLeftDown(event)
              elif event.LeftDClick():
                  self.onLeftDClick(event)
              elif event.RightUp():
                  self.onRightUp(event)
              elif event.RightDown():
                  self.onRightDown(event)
              elif event.Dragging() and event.LeftIsDown():
                  self.onDragging(event)
      
              pass
      
      
      class DragSprite(__MouseMixin, pygame.sprite.Sprite):
          SPRITE_BUTTON, SPRITE_TRANSPORTER = range(2)
      
          def __init__(self, parent=None):
              pygame.sprite.Sprite.__init__(self)
              self.is_select = 0
              self.lastPos = 0
              self.lastUpdate = 0
              self.parent = parent
      
          def setLastPos(self, pos):
              self.lastPos = pos
      
          def move(self, pos):
              dx = pos[0] - self.lastPos[0]
              dy = pos[1] - self.lastPos[1]
              self.lastPos = pos
              center = (self.rect.center[0] + dx, self.rect.center[1] + dy)
              self.rect.center = center
              return
      
          def isSelected(self):
              return self.is_select
      
          def setSelect(self, is_select):
              self.is_select = is_select
              return
      
          def update(self, current_time):
              return
      
      def drawBoader(image, rect):
          W,H = (rect.width, rect.height)
          yellow = (255, 255, 0)
          pygame.draw.rect(image, yellow, (0,0,W-2,H-2), 2)
      
      class ButtonSprite(DragSprite):
          def __init__(self, parent=None, initPos=(0,0), width=50, height=50, dicts=None):
              DragSprite.__init__(self, parent)
              self.type = DragSprite.SPRITE_BUTTON
              self.resourceCfgDict = dicts
              self.imageResource = {}
              self.status = 0
              self.index = 0
      
              self.parent = parent
              self.initPos = (initPos[0], initPos[1])
              self.width = width
              self.height = height
              self.rectOnLoad = pygame.Rect(initPos, (width, height))
              self.rect = self.rectOnLoad.copy()
      
              self.operationOn = None
              self.operationOff = None
      
              self.operationDic = {"on": self.getOperationOnItem, "off": self.getOperationOffItem}
              self.guiCfg = None
      
              for dic in dicts:
                  self.loadImgResource(dic)
      
              self.setCurrentResource("off")
      
          def getOperationOnItem(self):
              return self.operationOn
      
          def getOperationOffItem(self):
              return self.operationOff
      
          def loadImgResource(self, dict):
              """
                  load image with pygame lib
              """
              key = dict[0]
              file_name = dict[1]
      
              #image_file = pygame.image.load(file_name) #use this to load real image
              image_file = PyGamePseudoImage((500,500), file_name).getImage()
              imagedata = pygame.image.tostring(image_file, "RGBA")
              imagesize = image_file.get_size()
              imageSurface = pygame.image.fromstring(imagedata, imagesize , "RGBA")
      
              self.imageResource[key] = (file_name, imageSurface)
      
          def resizeResource(self, src, size):
              return pygame.transform.smoothscale(src, size)
      
          def setCurrentResource(self, status):
              self.currentStatus = status
              self.imageOnLoad = self.resizeResource(self.imageResource[status][1], (self.width, self.height))
              self.image = pygame.transform.scale(self.imageOnLoad, (self.rect.width, self.rect.height))
      
          def switchResource(self, index):
              self.setCurrentResource(index)
      
          def onZoomUpdate(self, zoomRatio):
              parentRect = pygame.Rect(self.parent.GetRect())
              dx = self.rectOnLoad.centerx - parentRect.centerx
              dy = self.rectOnLoad.centery - parentRect.centery
      
              self.rect.centerx = parentRect.centerx + dx*zoomRatio
              self.rect.centery = parentRect.centery + dy*zoomRatio
      
              self.rect.height = self.imageOnLoad.get_rect().height * zoomRatio
              self.rect.width = self.imageOnLoad.get_rect().width * zoomRatio
      
              self.image = pygame.transform.scale(self.imageOnLoad, (self.rect.width, self.rect.height))
      
          def update(self, current_time, ratio):
              if self.isSelected():
                  drawBoader(self.image, self.image.get_rect())
              else:
                  pass
                  #self.image = self.imageOnLoad.copy()
      
          def onRightUp(self, event):
              print "onRightUp"
              event.Skip(False)
              pass
      
          def onLeftDClick(self, event):
              if self.currentStatus == "on":
                  self.setCurrentResource("off")
              elif self.currentStatus == "off":
                  self.setCurrentResource("on")
      
              return
      
          def move(self, pos):
              DragSprite.move(self, pos)
      
              parentRect = pygame.Rect(self.parent.GetRect())
              centerDx = self.rect.centerx - parentRect.centerx
              centerDy = self.rect.centery - parentRect.centery
      
              self.rectOnLoad.centerx = parentRect.centerx + centerDx/self.parent.zoomRatio
              self.rectOnLoad.centery = parentRect.centery + centerDy/self.parent.zoomRatio
      
      
      class MyHmiPanel(wx.Panel):
          def __init__(self, parent, ID):
              wx.Window.__init__(self, parent, ID)
              self.parent = parent
              self.hwnd = self.GetHandle()
              self.size = self.GetSizeTuple()
              self.size_dirty = True
              self.rootSpriteGroup = pygame.sprite.LayeredUpdates()
      
              self.timer = wx.Timer(self)
              self.Bind(wx.EVT_PAINT, self.OnPaint)
              self.Bind(wx.EVT_TIMER, self.Update, self.timer)
              self.Bind(wx.EVT_SIZE, self.OnSize)
              self.fps = 60.0
              self.timespacing = 1000.0 / self.fps
              self.timer.Start(self.timespacing, False)
              self.previous_time = 0
              self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
      
              self.selectedSprite = None
      
              self.zoomRatio = 1
              self.background = None
              self.bgRect = None
              self.backgroundOnUpdate = None
              self.bgRetOnUpdate = None
      
              self.loadBackground()
              self.addTestSprite()
      
          def loadBackground(self):
              #self.background = pygame.image.load(image_file) #use this to load real image
              self.background = PyGamePseudoImage((500,500), (255, 0, 0)).getImage()
              self.bgRect = self.background.get_rect()
              self.backgroundOnUpdate = self.background.copy()
              self.bgRetOnUpdate = self.bgRect.copy()
      
          def resizeUpdateBackground(self):
              self.bgRect.center = self.screen.get_rect().center
              self.bgRetOnUpdate = self.bgRect.copy()
      
          def zoomUpdateBackground(self, zoomRatio):
              self.bgRetOnUpdate.width = self.bgRect.width * zoomRatio
              self.bgRetOnUpdate.height = self.bgRect.height * zoomRatio
              self.bgRetOnUpdate.width = self.bgRect.width * zoomRatio
              self.bgRetOnUpdate.center = self.screen.get_rect().center
              self.backgroundOnUpdate = pygame.transform.scale(self.background, (self.bgRetOnUpdate.width, self.bgRetOnUpdate.height))
      
          def drawBackground(self, screen):
              screen.blit(self.backgroundOnUpdate, self.bgRetOnUpdate)
      
          def addTestSprite(self):
              #self.rootSpriteGroup.add(ButtonSprite(self, initPos=(100, 100), width=100, height=100, dicts= [('on', btn_red_on), ('off', btn_red_off)]))
              #self.rootSpriteGroup.add(ButtonSprite(self, initPos=(200, 200), width=100, height=100, dicts= [('on', btn_red_on), ('off', btn_red_off)]))
              self.rootSpriteGroup.add(ButtonSprite(self, initPos=(100, 100), width=100, height=100, dicts= [('on', GREEN), ('off', BLUE)]))
              self.rootSpriteGroup.add(ButtonSprite(self, initPos=(200, 200), width=100, height=100, dicts= [('on', GREEN), ('off', BLUE)]))
      
          def Update(self, event):
              self.Redraw()
              return
      
          def Redraw(self):
              if  self.size[0] == 0  or  self.size[1] == 0:
                  return
      
              if self.size_dirty:
                  self.screen = pygame.Surface(self.size, 0, 32)
                  self.resizeUpdateBackground()
                  self.size_dirty = False
      
              self.screen.fill((0,0,0))
              self.drawBackground(self.screen)
      
              w, h = self.screen.get_size()
              current_time = pygame.time.get_ticks()
      
              self.previous_time = current_time
              self.rootSpriteGroup.update(current_time, self.zoomRatio)
              self.rootSpriteGroup.draw(self.screen)
      
              s = pygame.image.tostring(self.screen, 'RGB')  # Convert the surface to an RGB string
              #img = wx.ImageFromData(self.size[0], self.size[1], s)  # Load this string into a wx image
              img = wx.ImageFromData(w, h, s)  # Load this string into a wx image
      
              #if img.IsOk() is not True:
                 # return
              bmp = wx.BitmapFromImage(img)  # Get the image in bitmap form
              dc = wx.ClientDC(self)  # Device context for drawing the bitmap
              dc = wx.BufferedDC( dc)
              dc.DrawBitmap(bmp, 0, 0, 1)  # Blit the bitmap image to the display
      
      
          def checkCollide(self, event):
              x , y = (event.GetX(),event.GetY())
      
              mousePoint = pygame.sprite.Sprite()
              mousePoint.rect = pygame.Rect(x, y, 1, 1)
              copoint = pygame.sprite.spritecollide(mousePoint, self.rootSpriteGroup, None)
      
              if copoint:
                  copoint = copoint[-1]
      
              return copoint
      
          def removeSelectedSprite(self):
              if self.selectedSprite:
                  self.selectedSprite.setSelect(0)
                  self.selectedSprite = None
      
          def setNewSelectedSprite(self, sprite):
              self.removeSelectedSprite()
              sprite.setSelect(1)
              self.selectedSprite = sprite
      
          def onSelectSprite(self, event, onMouseObj):
              if onMouseObj:
                  if self.selectedSprite:
                      if onMouseObj != self.selectedSprite:
                          self.setNewSelectedSprite(onMouseObj)
                  else:
                      self.setNewSelectedSprite(onMouseObj)
      
                  self.selectedSprite.setLastPos((event.GetX(),event.GetY()))
              else:
                  self.removeSelectedSprite()
      
          def OnMouse(self, event):
              onMouseObj = self.checkCollide(event)
              event.Skip()
      
              if onMouseObj:
                  onMouseObj.OnMouseHandler(event)
      
              if not event.GetSkipped():
                  print "event dropped "
                  return
      
              if event.LeftDown():
                  self.onSelectSprite(event, onMouseObj)
              elif event.LeftUp():
                  pass
              elif event.RightUp():
                  self.onSelectSprite(event, onMouseObj)
              elif event.RightDown():
                  self.onSelectSprite(event, onMouseObj)
              elif event.Dragging() and event.LeftIsDown():
                  if self.selectedSprite:
                      self.selectedSprite.move((event.GetX(),event.GetY()))
      
          def OnPaint(self, event):
              self.Redraw()
              event.Skip()  # Make sure the parent frame gets told to redraw as well
      
          def OnSize(self, event):
              self.size = self.GetSizeTuple()
              self.size_dirty = True
      
          def Kill(self, event):
              self.Unbind(event=wx.EVT_PAINT, handler=self.OnPaint)
              self.Unbind(event=wx.EVT_TIMER, handler=self.Update, source=self.timer)
      
          def onZoomIn(self):
              self.zoomRatio += 0.2
              self.onZoomUpdate()
      
          def onZoomReset(self):
              self.zoomRatio = 1
              self.onZoomUpdate()
      
          def onZoomOut(self):
              if self.zoomRatio > 0.2:
                  self.zoomRatio -= 0.2
              self.onZoomUpdate()
      
          def onZoomUpdate(self):
              self.zoomUpdateBackground(self.zoomRatio)
              for s in self.rootSpriteGroup.sprites():
                  s.onZoomUpdate(self.zoomRatio)
      
      
      class TestFrame ( wx.Frame ):
          def __init__( self, parent, fSize ):
              wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = fSize, style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
      
              self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
      
              fgSizer1 = wx.FlexGridSizer( 2, 1, 0, 0 )
              fgSizer1.AddGrowableCol( 0 )
              fgSizer1.AddGrowableRow( 0 )
              fgSizer1.SetFlexibleDirection( wx.VERTICAL )
              fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_ALL )
      
      
              self.panelMain = MyHmiPanel(self, -1)
      
              fgSizer1.Add( self.panelMain, 1, wx.EXPAND |wx.ALL, 5 )
      
              self.m_panel4 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
              bSizer3 = wx.BoxSizer( wx.HORIZONTAL )
      
              self.bZoomIn = wx.Button( self.m_panel4, wx.ID_ANY, u"Zoom In", wx.DefaultPosition, wx.DefaultSize, 0 )
              bSizer3.Add( self.bZoomIn, 0, wx.ALL, 5 )
      
              self.bReset = wx.Button( self.m_panel4, wx.ID_ANY, u"Reset", wx.DefaultPosition, wx.DefaultSize, 0 )
              bSizer3.Add( self.bReset, 0, wx.ALL, 5 )
      
              self.bZoomOut = wx.Button( self.m_panel4, wx.ID_ANY, u"Zoom Out", wx.DefaultPosition, wx.DefaultSize, 0 )
              bSizer3.Add( self.bZoomOut, 0, wx.ALL, 5 )
      
              self.m_panel4.SetSizer( bSizer3 )
              self.m_panel4.Layout()
              bSizer3.Fit( self.m_panel4 )
              fgSizer1.Add( self.m_panel4, 1, wx.EXPAND |wx.ALL, 5 )
      
              self.SetSizer( fgSizer1 )
              self.Layout()
              self.Centre( wx.BOTH )
      
              self.bZoomIn.Bind( wx.EVT_BUTTON, self.onZoomIn )
              self.bReset.Bind( wx.EVT_BUTTON, self.onZoomReset )
              self.bZoomOut.Bind( wx.EVT_BUTTON, self.onZoomOut )
      
          def __del__( self ):
              pass
      
          def onZoomIn( self, event ):
              self.panelMain.onZoomIn()
              event.Skip()
      
          def onZoomReset( self, event ):
              self.panelMain.onZoomReset()
              event.Skip()
      
          def onZoomOut( self, event ):
              self.panelMain.onZoomOut()
              event.Skip()
      
      
      if __name__=='__main__':
              app = wx.App(redirect=False)
              frame = TestFrame(None, (800, 600))
              frame.SetPosition((100, 100))
              frame.Show()
              app.MainLoop()
      

      【讨论】:

      • 感谢@user3239580,我现在就去。 (我有一个wx._core.PyAssertionError: C++ assertion "!m_cols || idx < (size_t)m_cols" failed at ..\..\src\common\sizer.cpp(1980) in wxFlexGridSizer::AddGrowableCol(): invalid column index你知道原因吗?)
      • 抱歉,请删除这行“fgSizer1.AddGrowableCol(1)”,尽管它适用于我的 wxpython 2.8.12.1 和 python 2.7.3
      • 如果我删除这一行,我会得到另一个IndexError: tuple index out of range。你有什么想法吗?
      • 能告诉我你的wxpython版本和python版本吗?我会试试的。
      • 我使用 Python 2.7 (x32)(在 Windows 64 上),我同时安装了 wxpython 2.8 和 3.0 版本(我不记得叫哪一个了)
      【解决方案4】:

      根据已经写了多少,您可能想看看 FloatCanvas(它在 wxPython 库中)。

      如果您已完成大部分代码库,则可以使用命中测试,这很简单。只需制作一个以 [x][y] 坐标为键的字典,以 BitmapTogglebutton 作为其值。

      这里有一些类似的代码(我已经有一段时间没有使用 wxPython,所以它可能不是 100%):

      def onLeftDown( event ):
          x,y = event.GetX(), event.GetY()
          hitmap_x = hitmap.get(x,None)
          if hitmap_x:
              btn = hitmap_x.get(y, None)
          ...stuff with btn like toggles
      

      【讨论】:

        【解决方案5】:

        您必须实现自己的命中测试,即能够确定每只动物的位置——这是困难的部分,wxWidgets 中确实没有任何东西可以帮助您。剩下的就比较简单了,你甚至可以使用现有的wxMouseEventsManager来避免自己编写样板代码(但如果你不能,你至少可以看看它的实现,这完全是在wxWidgets本身中完成的,看看你需要做什么)。

        【讨论】:

        • 我想将每个动物创建为BitmapToggleButton,然后切换和更新图像非常容易。对我来说真正的问题是:当父面板调整大小时,如何重新缩放所有(背景图像+每个动物的按钮)......
        • 如果我这样做,我肯定会画整个东西并自己处理鼠标事件。重新调整整个事物的需要会使定位实际的切换按钮变得很痛苦。考虑一下,如果您将画布的大小调整为恰好大小的一半,则任何在奇数像素上具有边缘的动物“按钮” 无法正确定位,因为它现在必须位于一半-像素位置,我认为你不能在 wx 中做到这一点(据我所知)。你有足够少的动物,你应该能够在没有任何花哨的东西(如四叉树之类)的情况下逃脱。
        • 如何重绘现有的StaticBitmap,例如尺寸更小?我应该重新调用 StaticBitmap 吗?那么最后,我会有成千上万的StaticBitmap,不是吗?
        • 再次强调,使用独立控件不是一个好主意,您应该直接绘制图片并自己处理事件。基于使用BitmapToggleButton 的方法根本行不通。
        猜你喜欢
        • 2011-07-04
        • 1970-01-01
        • 2022-12-09
        • 1970-01-01
        • 1970-01-01
        • 2021-06-19
        • 2011-08-16
        • 2014-05-12
        • 1970-01-01
        相关资源
        最近更新 更多