wx.ListCtrl мерцает, когда OnGetItemText занимает слишком много времени
У меня проблема с wx.ListCtrl с использованием LC_VIRTUAL, и я не могу найти правильный способ ее исправить.
Проблема в том, что содержимое элемента управления мерцает, если OnGetItemText() занимает слишком много времени, чтобы вернуть свои результаты.
Решения, которые я пробовал до сих пор:
- SetDoubleBuffered - если это делается либо на самом ListCtrl, либо на родительском элементе управления, это приводит к тому, что содержимое вообще не отображается
- отключение фонового стирания устраняет мерцание, но создает проблемы с перерисовкой, особенно при изменении размера элемента управления.
- Freeze () и Thaw() не имеют никакого эффекта, потому что элемент управления уже находится в середине перерисовки.
Единственное решение, которое я нашел, - это кэшировать новые данные перед вызовом Refresh(). Однако есть сценарии, в которых мне может понадобиться отобразить 100000 или более записей, поэтому их кэширование заранее не сработает.
Вот пример, который я построил, который демонстрирует проблему, хотя вам, возможно, придется скорректировать количество в зависимости от скорости вашего процессора:
from __future__ import print_function, unicode_literals
import wx
class MyListCtrl ( wx.ListCtrl ):
def OnGetItemText ( self, item, column ):
ar = []
for i in range ( 200000 ):
ar.append ( i )
return '{}'.format ( len ( ar ) )
class MyFrame ( wx.Frame ):
lc = None
timer = None
def __init__ ( self, *args, **kwargs ):
super ( MyFrame, self ).__init__ ( *args, **kwargs )
self.timer = wx.Timer ( self )
self.lc = MyListCtrl ( self, style=wx.LC_VIRTUAL|wx.LC_REPORT )
self.lc.InsertColumn ( 0, 'Count1', width=75 )
self.lc.InsertColumn ( 1, 'Count2', width=75 )
self.lc.InsertColumn ( 2, 'Count3', width=75 )
self.lc.InsertColumn ( 3, 'Count4', width=75 )
self.lc.SetItemCount ( 1 )
self.Bind ( wx.EVT_TIMER, self.on_timer, self.timer )
self.Bind ( wx.EVT_CLOSE, self.on_close )
self.timer.Start ( 1000 )
def on_timer ( self, event ):
self.lc.Refresh()
def on_close ( self, event ):
self.timer.Stop()
event.Skip()
if __name__=='__main__':
app = wx.App ( False )
frame = MyFrame ( None )
frame.Show()
app.MainLoop()
Я испытываю проблему в обеих моих рабочих средах:
Python 2.7.12 (v2.7.12:d33e0cf91556, Jun 27 2016, 15:19:22) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import wx
>>> wx.version()
'3.0.2.0 msw (classic)'
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import wx
>>> wx.version()
'3.0.3.dev2680+55dda48 msw (phoenix)'
2 ответа
Если вы рисуете все на панели и устанавливаете двойной буфер на панели, мерцание будет устранено
from __future__ import print_function, unicode_literals
import wx
class MyListCtrl ( wx.ListCtrl ):
def OnGetItemText ( self, item, column ):
ar = []
for i in range ( 200000 ):
ar.append ( i )
return '{}'.format ( len ( ar ) )
class MyFrame ( wx.Frame ):
lc = None
timer = None
def __init__ ( self, *args, **kwargs ):
super ( MyFrame, self ).__init__ ( *args, **kwargs )
self.timer = wx.Timer ( self )
self.panel = wx.Panel(self, -1)
self.panel.SetDoubleBuffered(True)
self.lc = MyListCtrl ( self.panel, style=wx.LC_VIRTUAL|wx.LC_REPORT )
self.lc.InsertColumn ( 0, 'Count1', width=75 )
self.lc.InsertColumn ( 1, 'Count2', width=75 )
self.lc.InsertColumn ( 2, 'Count3', width=75 )
self.lc.InsertColumn ( 3, 'Count4', width=75 )
self.lc.SetItemCount ( 1 )
self.Bind ( wx.EVT_TIMER, self.on_timer, self.timer )
self.Bind ( wx.EVT_CLOSE, self.on_close )
self.Show(True)
self.timer.Start ( 1000 )
def on_timer ( self, event ):
self.Freeze()
self.lc.Refresh()
self.Thaw()
def on_close ( self, event ):
self.timer.Stop()
event.Skip()
if __name__=='__main__':
app = wx.App ( False )
frame = MyFrame ( None )
frame.Show()
app.MainLoop()
Freeze() и Thaw() обычно не нужны, но в этом случае поведение lc немного странно.
Я столкнулся с той же проблемой в другом проекте и, наконец, нашел жизнеспособное решение. Оглядываясь назад, мое решение - именно то, что предлагает комментарий Майка Дрисколла выше. Я приведу немного больше подробностей, если это кому-то поможет.
Хитрость заключается в методах ListCtrl GetTopItem() и GetCountPerPage(). С помощью этих двух функций вы можете рассчитать диапазон индексов, которые будут видны при обновлении страницы. Это единственные индексы, которые необходимо кэшировать при подготовке к обновлению страницы.
Вот упрощенный пример того, как я это реализовал.
get_row_count () просто делает "выбор количества (*)" из таблицы
get_row() возвращает следующую запись от курсора, если курсор находится в правом смещении, в противном случае он выполняет новый запрос, чтобы переместить курсор в нужное место. По моему опыту, ListCtrl обновляет элементы в последовательном порядке, так что это работает очень хорошо.
python
def on_list_update ( self, event ):
self.on_begin_update() # notify subclasses that a refresh is coming
count = self.get_row_count() # how many records will be visible?
top = self.GetTopItem()
bottom = min ( top + self.GetCountPerPage(), count )
new_item_cache = {}
for item in range ( top, bottom ):
new_item_cache[item] = self.get_row ( item )
self.item_cache = new_item_cache
if count != self.GetItemCount():
self.SetItemCount ( count )
else:
self.RefreshItems ( 0, count-1 )
Приведенный выше шаблон обычно приводит к отправке только 2 SQL-запросов на сервер базы данных до начала обновления и устранения мерцания.
Важно отметить, что вы хотите сохранить кэш элементов во временной переменной до тех пор, пока он не будет перестроен, если вы используете эти данные для других событий, иначе вы столкнетесь с ситуациями, когда кэш недоступен из-за обновления.