在wxPython中在框架内绘制面板

6 投票
2 回答
5254 浏览
提问于 2025-04-20 19:12

下面,我在一个框架里有一个面板。为什么我不能在这个面板上画东西呢?我只看到一片白屏。如果我去掉面板,直接在框架上画,就可以了。希望能得到一些帮助。

import wx

class MyFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self,None,-1,'window',(200,200),(600,600))
        self.Center()
        self.panel=wx.Panel(self)
        self.panel.SetBackgroundColour('white')
        self.firstpoint=wx.Point(300,300)
        self.secondpoint=wx.Point(400,400)
        self.Bind(wx.EVT_PAINT,self.onPaint)


    def onPaint(self,event):
        dc=wx.PaintDC(self.panel)
        dc.DrawLine(self.firstpoint.x,self.firstpoint.y,
                    self.secondpoint.x,self.secondpoint.y)

2 个回答

1

我一直在寻找一个完整的示例,想要了解如何创建一个自定义的 wx Python 控件,这个控件是从 wx.Panel 继承而来的,并且能够在自己上面进行自定义绘图,但一直没找到。感谢这个问题(和其他一些问题),我终于搞出了一个最小的可工作示例——我决定把它分享出来,因为它展示了“在一个框架内的面板上绘图”;不过,与原问题的作者不同的是,这里是面板自己在绘图(而不是框架在面板上绘图)。

这段代码的效果大概是这样的:

test.png

... 基本上,当你调整窗口大小时,红色的矩形会被重新绘制。

注意代码中的注释,特别是关于在 OnSize 方法中需要调用 Refresh(),以避免出现渲染错误或闪烁的问题。

下面是最小工作示例的代码:

import wx

# tested on wxPython 2.8.11.0, Python 2.7.1+, Ubuntu 11.04
# http://stackoverflow.com/questions/2053268/side-effects-of-handling-evt-paint-event-in-wxpython
# http://stackoverflow.com/questions/25756896/drawing-to-panel-inside-of-frame-in-wxpython
# http://www.infinity77.net/pycon/tutorial/pyar/wxpython.html
# also, see: wx-2.8-gtk2-unicode/wx/lib/agw/buttonpanel.py

class MyPanel(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.Bind(wx.EVT_SIZE, self.OnSize)
    self.Bind(wx.EVT_PAINT, self.OnPaint)
  def OnSize(self, event):
    print("OnSize" +str(event))
    #self.SetClientRect(event.GetRect()) # no need
    self.Refresh() # MUST have this, else the rectangle gets rendered corruptly when resizing the window!
    event.Skip() # seems to reduce the ammount of OnSize and OnPaint events generated when resizing the window
  def OnPaint(self, event):
    #~ dc = wx.BufferedPaintDC(self) # works, somewhat
    dc = wx.PaintDC(self) # works
    print(dc)
    rect = self.GetClientRect()
    # "Set a red brush to draw a rectangle"
    dc.SetBrush(wx.RED_BRUSH)
    dc.DrawRectangle(10, 10, rect[2]-20, 50)
    #self.Refresh() # recurses here!


class MyFrame(wx.Frame):
  def __init__(self, parent):
    wx.Frame.__init__(self, parent, -1, "Custom Panel Demo")
    self.SetSize((300, 200))
    self.panel = MyPanel(self) #wx.Panel(self)
    self.panel.SetBackgroundColour(wx.Colour(10,10,10))
    self.panel.SetForegroundColour(wx.Colour(50,50,50))
    sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
    sizer_1.Add(self.panel, 1, wx.EXPAND | wx.ALL, 0)
    self.SetSizer(sizer_1)
    self.Layout()

app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
8

试着把事件绑定到面板上,而不是整个窗口:

self.panel.Bind(wx.EVT_PAINT, self.onPaint)

你的版本在我这(Windows)有点效果,但它一直在重新绘制面板,导致整个处理器都被占满了。

根据文档:注意,在绘制事件处理器中,应用程序必须始终创建一个 wxPaintDC 对象,即使你不使用它。否则,在 Windows 系统下,刷新这个窗口和其他窗口时会出问题。

在这里,你收到了整个窗口的绘制事件,但却使用了面板的 dc。

补充:这个 http://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind 解释得很清楚,为什么这样做不行:

self.Bind(wx.EVT_PAINT, self.onPaint, self.panel)

在这种情况下,onPaint 处理器根本不会被调用。

撰写回答