2014-12-29 5 views
1

Рассмотрим этот пример (пробовал на python2.7, Ubuntu 11.04):Медленное обновление графического интерфейса виджета wx (Python)?

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

Дело в том: если вы переместите ползунок очень быстро , вы заметите, что самые последние текстовые окна обновляются довольно быстро; но если вы перемещаете вращающуюся ручку очень быстро, кажется, что ее текстовое поле (ниже этого) изменяется с гораздо меньшей частотой?!

Почему это; и можно ли получить скорость ответа в текстовом поле вращающейся ручки так же быстро, как и ползунок?

+2

Прежде всего, вы создали панель и назначен Sizer к нему, но виджеты, которыми управляет sizer, имеют кадр как родительский. Мне нужно было исправить это, прежде чем я смогу взаимодействовать с виджетами OSX, так что это может быть частью вашей основной проблемы. После этого изменения в OSX (по крайней мере) текстовый виджет обновляется со скоростью, которую я ожидал бы, пока ручка будет скручена. Какую версию wxPython вы используете? – RobinDunn

+0

Привет @RobinDunn, спасибо за комментарий! 'print (wx.VERSION_STRING)' говорит '2.8.11.0'; что касается панели/sizer, я в основном копировал ее из исходной ссылки (в коде), я не очень эффективен в 'wx' в противном случае. Должны ли виджеты иметь панель в качестве родителя? Я попытаюсь захватить видео о поведении, которое я получаю, и отправить его где-нибудь ... Приветствия! – sdaau

+0

@RobinDunn - удалось захватить короткое видео, но ниже в ответе лучше - я добавил распечатки из обработчика событий и (если я использую 'EVT_KC_ANGLE_CHANGED' и' e.Skip() '), то он будет видимым что распечатки на приличной скорости - но обновление текстового поля не является; как будто добавляется куча событий, забирая очередь событий GUI ... Еще раз спасибо - приветствия! – sdaau

ответ

0

Хорошо, я думаю, что у меня что-то работает, но я не уверен на 100%, поэтому более эрудированный ответ будет оценен. Во-первых, обратите внимание, что:

  • Виджет wx.Slider реализован в C; поэтому единственный раз, когда Python «знает» что-либо о его выполнении, - это когда виджет передает событие
  • wx.lib.agw.knobctrl.KnobCtrl реализован в Python; поэтому интерпретатор Python «знает» (как он должен его запускать) каждую строку кода выполнения виджета.

Итак, что я сделал, это я попытался проследить исполнение, с:

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

Что я мог заметить, что: OnSliderScroll появился в журнале, по-видимому, не будучи вызваны событиями мыши, в то время как с помощью knobctrl.py (накладки) появится несколько OnMouseEvents, и только некоторые вызовут SetTrackPosition(), который в конечном итоге вызывает OnAngleChanged(). Но что еще более важно, в журнале был т из _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() 
Смежные вопросы