在 wxPython 中跨面板拖放图像

Posted

技术标签:

【中文标题】在 wxPython 中跨面板拖放图像【英文标题】:Dragging and Dropping Images Across Panels in wxPython 【发布时间】:2013-02-12 17:13:13 【问题描述】:

我目前不确定如何实现跨面板拖放某些对象(在本例中为 png)。我查看了 wxPython 示例中提供的相关 DragImage 示例,这里的大部分代码都是从中派生的。但是,如果您运行下面的代码(您需要生成一个或两个示例 PNG),我有三个面板:顶部的面板,我希望在其中连续加载 PNG,然后是两个它下面的面板。将在顶部面板中排列 PNG 的代码行目前已被注释掉(在 MechanismPanel 类下,位于底部),因为它阻止了我获得任何鼠标事件。我不确定是否可以跨面板拖放图像,或者是否可以,我是否正确。

编辑:对我正在寻找的内容的更简洁的解释。顶部面板中的图像,您可以在其中将其中一张图像拖出并将其添加到下方面板之一。将顶部面板视为要从中绘制的一行小部件,将底部的两个面板视为排列小部件的位置。为了帮助区分问题,我还有一个关于在顶部面板中拖动和复制图像here的问题。

import os
import glob

import wx
import wx.lib.scrolledpanel as scrolled

class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent)
        frm_pnl = MainPanel(self)

        self.Show()

class DragShape:
    def __init__(self, bmp):
        self.bmp = bmp
        self.pos = (0,0)
        self.shown = True
        self.text = None
        self.fullscreen = False

    def HitTest(self, pt):
        rect = self.GetRect()
        return rect.InsideXY(pt.x, pt.y)

    def GetRect(self):
        return wx.Rect(self.pos[0], self.pos[1], self.bmp.GetWidth(), self.bmp.GetHeight())

    def Draw(self, dc, op = wx.COPY):
        if self.bmp.Ok():
            memDC = wx.MemoryDC()
            memDC.SelectObject(self.bmp)

            dc.Blit(self.pos[0], self.pos[1],
                    self.bmp.GetWidth(), self.bmp.GetHeight(),
                    memDC, 0, 0, op, True)

            return True
        else:
            return False

class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, size = (900, 700))

        self.shapes = []

        #panel for mechanisms
        mechPnl = MechanismPanel(self)

        mechSzr = wx.BoxSizer(wx.HORIZONTAL)
        mechSzr.Add(mechPnl, 1)

        #panels for timeline
        posPnl = IdTimelinePanel(self)
        timelinePnl = TimelinePanel(self)

        mainSzr = wx.BoxSizer(wx.HORIZONTAL)
        mainSzr.Add(posPnl, 1, wx.EXPAND)
        mainSzr.Add(timelinePnl, 1, wx.EXPAND)

        selfSizer = wx.BoxSizer(wx.VERTICAL)
        selfSizer.Add(mechSzr, 0, wx.EXPAND)
        selfSizer.Add(mainSzr, 0, wx.EXPAND)
        selfSizer.Layout()
        self.SetSizer(selfSizer)



        self.dragImage = None
        self.dragShape = None
        self.hiliteShape = None

        self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))


        #self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        mechPnl.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        mechPnl.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        mechPnl.Bind(wx.EVT_MOTION, self.OnMotion)
        mechPnl.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
        timelinePnl.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        timelinePnl.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        timelinePnl.Bind(wx.EVT_MOTION, self.OnMotion)
        timelinePnl.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)

    # The mouse is moving
    def OnMotion(self, evt):
        print "On motion!"

        # Ignore mouse movement if we're not dragging.
        if not self.dragShape or not evt.Dragging() or not evt.LeftIsDown():
            return

        # if we have a shape, but haven't started dragging yet
        if self.dragShape and not self.dragImage:

            # only start the drag after having moved a couple pixels
            tolerance = 2
            pt = evt.GetPosition()
            dx = abs(pt.x - self.dragStartPos.x)
            dy = abs(pt.y - self.dragStartPos.y)
            if dx <= tolerance and dy <= tolerance:
                return

            # refresh the area of the window where the shape was so it
            # will get erased.
            self.dragShape.shown = False
            self.RefreshRect(self.dragShape.GetRect(), True)
            self.Update()

            if self.dragShape.text:
                self.dragImage = wx.DragString(self.dragShape.text,
                                              wx.StockCursor(wx.CURSOR_HAND))
            else:
                self.dragImage = wx.DragImage(self.dragShape.bmp,
                                             wx.StockCursor(wx.CURSOR_HAND))

            hotspot = self.dragStartPos - self.dragShape.pos
            self.dragImage.BeginDrag(hotspot, self, self.dragShape.fullscreen)

            self.dragImage.Move(pt)
            self.dragImage.Show()


        # if we have shape and image then move it, posibly highlighting another shape.
        elif self.dragShape and self.dragImage:
            onShape = self.FindShape(evt.GetPosition())
            unhiliteOld = False
            hiliteNew = False

            # figure out what to hilite and what to unhilite
            if self.hiliteShape:
                if onShape is None or self.hiliteShape is not onShape:
                    unhiliteOld = True

            if onShape and onShape is not self.hiliteShape and onShape.shown:
                hiliteNew = True

            # if needed, hide the drag image so we can update the window
            if unhiliteOld or hiliteNew:
                self.dragImage.Hide()

            if unhiliteOld:
                dc = wx.ClientDC(self)
                self.hiliteShape.Draw(dc)
                self.hiliteShape = None

            if hiliteNew:
                dc = wx.ClientDC(self)
                self.hiliteShape = onShape
                self.hiliteShape.Draw(dc, wx.INVERT)

            # now move it and show it again if needed
            self.dragImage.Move(evt.GetPosition())
            if unhiliteOld or hiliteNew:
                self.dragImage.Show()

    # Left mouse button up.
    def OnLeftUp(self, evt):
        print "On left up!"

        if not self.dragImage or not self.dragShape:
            self.dragImage = None
            self.dragShape = None
            return

        # Hide the image, end dragging, and nuke out the drag image.
        self.dragImage.Hide()
        self.dragImage.EndDrag()
        self.dragImage = None

        if self.hiliteShape:
            self.RefreshRect(self.hiliteShape.GetRect())
            self.hiliteShape = None

        # reposition and draw the shape

        # Note by jmg 11/28/03 
        # Here's the original:
        #
        # self.dragShape.pos = self.dragShape.pos + evt.GetPosition() - self.dragStartPos
        #
        # So if there are any problems associated with this, use that as
        # a starting place in your investigation. I've tried to simulate the
        # wx.Point __add__ method here -- it won't work for tuples as we
        # have now from the various methods
        #
        # There must be a better way to do this :-)
        #

        self.dragShape.pos = (
            self.dragShape.pos[0] + evt.GetPosition()[0] - self.dragStartPos[0],
            self.dragShape.pos[1] + evt.GetPosition()[1] - self.dragStartPos[1]
            )

        self.dragShape.shown = True
        self.RefreshRect(self.dragShape.GetRect())
        self.dragShape = None

    # Fired whenever a paint event occurs
    def OnPaint(self, evt):
        print "On paint!"

        dc = wx.PaintDC(self)
        self.PrepareDC(dc)
        self.DrawShapes(dc)

    # Left mouse button is down.
    def OnLeftDown(self, evt):
        print "On left down!"

        # Did the mouse go down on one of our shapes?
        shape = self.FindShape(evt.GetPosition())

        # If a shape was 'hit', then set that as the shape we're going to
        # drag around. Get our start position. Dragging has not yet started.
        # That will happen once the mouse moves, OR the mouse is released.
        if shape:
            self.dragShape = shape
            self.dragStartPos = evt.GetPosition()

    # Go through our list of shapes and draw them in whatever place they are.
    def DrawShapes(self, dc):
        for shape in self.shapes:
            if shape.shown:
                shape.Draw(dc)

    # This is actually a sophisticated 'hit test', but in this
    # case we're also determining which shape, if any, was 'hit'.
    def FindShape(self, pt):
        for shape in self.shapes:
            if shape.HitTest(pt):
                return shape
        return None

    # Clears the background, then redraws it. If the DC is passed, then
    # we only do so in the area so designated. Otherwise, it's the whole thing.
    def OnEraseBackground(self, evt):
        dc = evt.GetDC()
        if not dc:
            dc = wx.ClientDC(self)
            rect = self.GetUpdateRegion().GetBox()
            dc.SetClippingRect(rect)
        self.TileBackground(dc)

    # tile the background bitmap
    def TileBackground(self, dc):
        sz = self.GetClientSize()
        w = self.bg_bmp.GetWidth()
        h = self.bg_bmp.GetHeight()

        x = 0

        while x < sz.width:
            y = 0

            while y < sz.height:
                dc.DrawBitmap(self.bg_bmp, x, y)
                y = y + h

            x = x + w

    # We're not doing anything here, but you might have reason to.
    # for example, if you were dragging something, you might elect to
    # 'drop it' when the cursor left the window.
    def OnLeaveWindow(self, evt):
        pass

class IdTimelinePanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, size = (400, 200))
        self.SetBackgroundColour((255, 0, 255))

        lbl1 = wx.StaticText(self, label="Position")
        lbl2 = wx.StaticText(self, label="Size")

        posPnlSzr = wx.BoxSizer(wx.VERTICAL)
        posPnlSzr.Add(lbl1, 1, wx.FIXED&wx.LEFT)
        posPnlSzr.Add(lbl2, 1, wx.FIXED&wx.LEFT)

        self.SetSizer(posPnlSzr)

        #wx.StaticText(self, -1, "This is the horizontal ID space for the timeline")
        self.SetAutoLayout(1)

class TimelinePanel(scrolled.ScrolledPanel):
    def __init__(self, parent):
        scrolled.ScrolledPanel.__init__(self, parent, -1, size = (300, 200))
        self.SetBackgroundColour((255, 0, 0))

        lbl12 = wx.StaticText(self, label="Position")
        lbl22 = wx.StaticText(self, label="Size")

        posPnlSzr2 = wx.BoxSizer(wx.VERTICAL)
        posPnlSzr2.Add(lbl12, 1, wx.GROW)
        posPnlSzr2.Add(lbl22, 1, wx.GROW)

        self.SetSizer(posPnlSzr2)

        #wx.StaticText(self, -1, "This is the horizontal scroll space for the timeline")
        self.SetAutoLayout(1)
        self.SetupScrolling(scroll_y = False)

class MechanismPanel(scrolled.ScrolledPanel):
    def __init__(self, parent):
        scrolled.ScrolledPanel.__init__(self, parent, -1, size = (400, 140))
        self.SetBackgroundColour((211, 211, 211))

        mechPnlSzr = wx.BoxSizer(wx.HORIZONTAL)

        os.chdir("./figures")
        for file in glob.glob("icon*.png"):
            print file
            imgIcon = wx.Image(file, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
            staticBitmap = wx.StaticBitmap(self, -1, imgIcon, (0, 0), (50, 50))
            shape = DragShape(staticBitmap.GetBitmap())
            shape.pos = (50, 50)
            shape.fullscreen = True
            parent.shapes.append(shape)
            #mechPnlSzr.Add(staticBitmap, 0, wx.FIXED, border = 20)

        self.SetSizer(mechPnlSzr)

        self.SetAutoLayout(1)
        self.SetupScrolling(scroll_y = False)

app = wx.App(False)
frame = MainWindow(None, "Trading Client")
app.MainLoop()

【问题讨论】:

【参考方案1】:

您的所有类都没有实现 wx.PyDropTarget 接口或包含对拖放所需的 wx.FileDataObject() 的引用。

我想你想看看演示中的 DragAndDrop 示例,而不仅仅是 DragImage。

【讨论】:

以上是关于在 wxPython 中跨面板拖放图像的主要内容,如果未能解决你的问题,请参考以下文章

wxPython 的机器人框架安装问题

如何获得带有 FancyText 且没有边框的 wxPython 框架 (wx.BORDER_NONE)?

在 WxPython 面板中嵌入 Seaborn 图

WPF 拖放 C# 也可以拖动图像

我们如何获得相对于窗口形式的位置?

wxpython中的派生面板类