wx(Python)小部件的GUI更新速度慢?

时间:2014-12-29 05:56:07

标签: python wxpython

考虑这个例子(在python2.7,Ubuntu 11.04上试过):

import wx
import wx.lib.agw.knobctrl as KC

# started from: http://wxpython.org/Phoenix/docs/html/lib.agw.knobctrl.html

class MyFrame(wx.Frame):

  def __init__(self, parent):

    wx.Frame.__init__(self, parent, -1, "KnobCtrl Demo")

    self.panel = wx.Panel(self)

    self.knob1 = KC.KnobCtrl(self, -1, size=(100, 100))
    self.knob1.SetTags(range(0, 151, 10))
    self.knob1.SetAngularRange(-45, 225)
    self.knob1.SetValue(45)

    # explicit sizes here - cannot figure out the expands ATM
    self.text_ctrl_1 = wx.TextCtrl(self, -1, "0", size=(50, -1))
    self.slider_1 = wx.Slider(self, -1, 0, -12, 12, style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS|wx.SL_INVERSE, size=(150, -1))
    self.text_ctrl_2 = wx.TextCtrl(self, -1, "0", size=(50, -1))

    main_sizer = wx.BoxSizer(wx.VERTICAL)
    main_sizer.Add(self.knob1, 0, wx.EXPAND | wx.ALL, 20)
    main_sizer.Add(self.text_ctrl_1, 0, wx.EXPAND, 20)
    main_sizer.Add(self.slider_1, 0, wx.EXPAND , 20)
    main_sizer.Add(self.text_ctrl_2, 0, wx.EXPAND, 20)

    self.panel.SetSizer(main_sizer)
    main_sizer.Layout()
    self.knob1.Bind(KC.EVT_KC_ANGLE_CHANGED, self.OnAngleChanged)
    self.slider_1.Bind(wx.EVT_SCROLL, self.OnSliderScroll)

  def OnAngleChanged(self, e):
    theknob = e.EventObject
    x = theknob._mousePosition.x
    y = theknob._mousePosition.y
    ang = theknob.GetAngleFromCoord(x, y)
    self.text_ctrl_1.SetValue("%.2f" % (ang))
    self.text_ctrl_1.Refresh() # no dice
  def OnSliderScroll(self, e):
    obj = e.GetEventObject()
    val = obj.GetValue()
    self.text_ctrl_2.SetValue(str(val))

# our normal wxApp-derived class, as usual

app = wx.App(0)

frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()

app.MainLoop()

结果是这样的:

test.png

问题是:如果您非常快地移动滑块,您会发现最下面的文本框也会更快地更新;但是如果你非常快地移动旋钮,它的文本框(在其下方)似乎会随着频率的降低而变化?!

为什么会这样;是否可以让旋钮的文本框的响应速度与滑块的响应速度一样快?

1 个答案:

答案 0 :(得分:0)

好吧,我认为我有一些工作,但我不是百分百肯定,所以一个更博学的答案将不胜感激。首先,请注意:

  • 我认为wx.Slider小部件是用C实现的;因此,Python“知道”有关其执行的任何事情的唯一时间是小部件广播事件
  • wx.lib.agw.knobctrl.KnobCtrl在Python中实现;因此,Python解释器“知道”(因为它必须运行它)小部件执行的每行代码。

所以,我做了什么,是我试图追踪执行,用:

python -m trace --trace --timing test.py > test.pylog

我能注意到的是:OnSliderScroll显然出现在日志中而没有被鼠标事件触发,而多个OnMouseEvents会出现在knobctrl.py(mouseovers)中,只有一些会出现触发SetTrackPosition(),最终调用OnAngleChanged()。但更重要的是,日志中有{em> ton _gdi_.DC_DrawLine!然后,查看knobctrl.py源代码,我意识到渐变实际上是在Python for循环中逐行绘制的:

def DrawDiagonalGradient(self, dc, size):
...
    for ii in xrange(0, maxsize, 2):
        currCol = (r1 + rf, g1 + gf, b1 + bf)                
        dc.SetPen(wx.Pen(currCol, 2))
        dc.DrawLine(0, ii+2, ii+2, 0)
...

...所以我想,这必须占用时间!所以,在下面的代码中做的是,子类是从KnobCtrl派生的,其中DrawDiagonalGradient()所以它使用普通填充而不是渐变,这样可以更快地运行。

因此,下面的代码使用相同的事件处理程序和相同的文本字段来比较原始变体和派生变体;您可以在https://vid.me/kM8V查看视频,如下所示:

test2.png

当原始旋钮转动时,你会注意到textctrl几乎没有变化(特别是,打印输出以预期的速度发出);但是当具有“普通”背景的派生旋钮被转动时(几乎与滑块一样快),它会相当适当地更新。我认为当重载方法中没有任何类型的绘制时,“普通”会更快,但是之后旋钮点的先前位置不会被删除。我的猜测是,原始旋钮梯度的绘制在分配的绘图时间范围内花费了太多时间,Python需要删除该帧的其他排队更新,这里特别是文本控件的更新。

请注意,旋钮同时发出KC.EVT_KC_ANGLE_CHANGING(除非处理程序中存在e.Skip(),否则不会刷新绘图)和KC.EVT_KC_ANGLE_CHANGED;但是,据我所知,他们总是互相跟随,所以低于CHANGED以下两者都使用。

当然,如果我误解了问题和解决方案,我很想知道......

import wx
import wx.lib.agw.knobctrl as KC

# started from: http://wxpython.org/Phoenix/docs/html/lib.agw.knobctrl.html

class KnobCtrlPlain(KC.KnobCtrl):
  def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
               size=wx.DefaultSize,
               agwStyle=KC.KC_BUFFERED_DC):
    super(KnobCtrlPlain, self).__init__(parent, id, pos, size, agwStyle)
  def DrawDiagonalGradient(self, dc, size):
    col1 = self._startcolour
    r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
    sizex, sizey = size
    # must have a filled draw here, to erase previous draws:
    dc.SetPen(wx.TRANSPARENT_PEN)
    dc.SetBrush(wx.Brush(col1, wx.SOLID))
    #~ dc.DrawCircle(self.Width/2, self.Height/2, sizex)
    dc.DrawRectangle(0, 0, sizex, sizey) # same effect as circle; prob. faster?


class MyFrame(wx.Frame):
  def __init__(self, parent):
    wx.Frame.__init__(self, parent, -1, "KnobCtrl DemoB")
    self.panel = wx.Panel(self)
    self.knob1 = KC.KnobCtrl(self.panel, -1, size=(100, 100))
    self.knob1.SetTags(range(0, 151, 10))
    self.knob1.SetAngularRange(-45, 225)
    self.knob1.SetValue(45)
    self.knob2 = KnobCtrlPlain(self.panel, -1, size=(100, 100))
    self.knob2.SetTags(range(0, 151, 10))
    self.knob2.SetAngularRange(-45, 225)
    self.knob2.SetValue(45)
    self.text_ctrl_1 = wx.TextCtrl(self.panel, -1, "0", size=(50, -1))
    self.slider_1 = wx.Slider(self.panel, -1, 0, -12, 12, style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS|wx.SL_INVERSE, size=(150, -1))
    self.text_ctrl_2 = wx.TextCtrl(self.panel, -1, "0", size=(50, -1))
    main_sizer = wx.BoxSizer(wx.VERTICAL)
    main_sizer.Add(self.knob1, 0, wx.EXPAND | wx.ALL, 20)
    main_sizer.Add(self.text_ctrl_1, 0, wx.EXPAND, 20)
    main_sizer.Add(self.knob2, 0, wx.EXPAND | wx.ALL, 20)
    main_sizer.Add(self.slider_1, 0, wx.EXPAND , 20)
    main_sizer.Add(self.text_ctrl_2, 0, wx.EXPAND, 20)
    self.panel.SetSizer(main_sizer)
    main_sizer.Layout()
    self.knob1.Bind(KC.EVT_KC_ANGLE_CHANGED, self.OnAngleChanged)
    self.knob2.Bind(KC.EVT_KC_ANGLE_CHANGED, self.OnAngleChanged)
    self.slider_1.Bind(wx.EVT_SCROLL, self.OnSliderScroll)
  def OnAngleChanged(self, e):
    theknob = e.EventObject
    x = theknob._mousePosition.x
    y = theknob._mousePosition.y
    ang = theknob.GetAngleFromCoord(x, y)
    strval = str("%.2f" % (ang))
    print("ac: " + strval)
    self.text_ctrl_1.SetValue(strval)
  def OnSliderScroll(self, e):
    obj = e.GetEventObject()
    val = obj.GetValue()
    strval = str(val)
    print("ss: " + strval)
    self.text_ctrl_2.SetValue(strval)

# our normal wxApp-derived class, as usual
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()