wx (Python) 小部件的 GUI 更新缓慢? [英] Slow GUI update of a wx (Python) widget?

查看:17
本文介绍了wx (Python) 小部件的 GUI 更新缓慢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Consider this example (tried on 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()

结果如下:

问题是:如果您非常快地移动滑块,您会注意到最底部的文本框更新也相当快;但是如果你非常快地移动旋钮,它的文本框(在它下面)似乎以大大降低的频率变化?!

The thing is: if you move the slider very fast, you will notice the bottommost text box updates also rather fast; but if you move the rotary knob very fast, it seems its text box (below it) changes with much reduced frequency ?!

这是为什么;有没有可能让旋钮的文本框的响应速度和滑块的一样快?

Why is this; and would it be possible to get the response speed of the rotary knob's text box to be as fast as slider's one?

推荐答案

好吧,我想我得到了一些工作,但我不是 100% 确定,所以更博学的答案将不胜感激.首先,请注意:

Ok, I think I got something working, but I'm not 100% sure, so a more erudite answer would be appreciated. First, note that:

  • wx.Slider 小部件(我认为)是用 C 实现的;因此,Python 唯一知道"有关其执行的任何信息的时间是小部件广播事件的时间
  • wx.lib.agw.knobctrl.KnobCtrl是用Python实现的;因此,Python 解释器知道"(因为它必须运行它)小部件执行的每一行代码.
  • The wx.Slider widget is (I think) implemented in C; so the only time Python "knows" anything about its execution is when the widget broadcasts an event
  • The wx.lib.agw.knobctrl.KnobCtrl is implemented in Python; thus the Python interpreter "knows" (as it has to run it) each line of code of the widget's execution.

所以,我所做的是尝试跟踪执行过程:

So, what I did, is I tried to trace the execution, with:

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

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

What I could notice is that: OnSliderScroll appeared in the log apparently without being triggered by a mouse event, while multiple OnMouseEvents would appear from knobctrl.py (the mouseovers), and only some would trigger SetTrackPosition() which eventually calls OnAngleChanged(). But even more importantly, there was a ton of _gdi_.DC_DrawLine in the log! Then, looking at the knobctrl.py source, I realised that the gradient is actually painted in a Python for loop, line-by-line:

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() 因此它使用普通填充而不是渐变,这很有效快得多.

... so I thought, this must be hogging the time! So, what is done in the code below, is that a subclass is derived from KnobCtrl, where DrawDiagonalGradient() so it uses a plain fill instead of a gradient, which works much faster.

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

So, the code below compares the original and the derived variant, using the same event handler and the same textfield; you can check out the video at https://vid.me/kM8V, which looks something like this:

当原始旋钮被转动时,你会注意到 textctrl 几乎没有变化(即使,值得注意的是,打印输出以预期的速度发出);但是当带有普通"背景的派生旋钮转动时(几乎和滑块一样快)更新得相当好.我认为当重载方法中没有任何类型的绘制时,普通"会更快,但是旋钮点的先前位置不会被擦除.我的猜测是,原始旋钮渐变的绘制在分配的绘制时间范围内占用了太多时间,因此 Python 需要删除该帧的其他排队更新,尤其是文本控件的更新.

You'll notice the textctrl barely changes when the original knob is turned (even if, notably, the printouts are emitted with the expected speed) ; but updates pretty decently when the derived knob with "plain" background is turned (nearly as fast as the slider). I think the "plain" goes even faster when there is no draw of any kind in the overloaded method, but then the previous positions of the knob dot are not erased. My guess is, the draws of the original knob's gradient take up so much time in the allocated drawing timeframe, that Python needs to drop other queued updates for that frame, here notably the update of the text control.

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

Note that the knob emits both KC.EVT_KC_ANGLE_CHANGING (which will not refresh the draw unless e.Skip() is present in the handler), and KC.EVT_KC_ANGLE_CHANGED; however, as far as I can see, they always follow each other, so below *CHANGED is used for both.

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

Of course, if I've misunderstood the problem and the solution, I'd love to know...

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

这篇关于wx (Python) 小部件的 GUI 更新缓慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆