1

我在使用 LC_VIRTUAL 时遇到 wx.ListCtrl 问题,无法找到正确的解决方法。

问题是如果 OnGetItemText() 花费太长时间来返回它的结果,控件的内容会闪烁。

到目前为止我尝试过的解决方案:

  • SetDoubleBuffered - 如果在 ListCtrl 本身或父控件上完成,它会导致内容根本不显示
  • 禁用背景擦除可修复闪烁,但会产生重绘问题,尤其是在调整控件大小时。
  • Freeze() 和 Thaw() 没有任何效果,因为控件已经处于重绘的中间。

到目前为止,我想出的唯一解决方案是在调用 Refresh() 之前缓存新数据。但是,在某些情况下,我可能需要显示 100,000 条或更多记录,因此提前缓存它们是行不通的。

这是我构建的一个示例来演示该问题,尽管您可能必须根据 CPU 速度调整计数:

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)'
4

2 回答 2

0

如果在面板上绘制所有内容并在面板上设置双缓冲,闪烁将被消除

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 的行为有点奇怪。

于 2017-09-05T15:27:15.243 回答
0

我在另一个项目中遇到了同样的问题,我终于想出了一个可行的解决方案。回想起来,我的解决方案正是 Mike Driscoll 上面的评论所暗示的。我将在此处提供更多详细信息,以防它对某人有所帮助。

诀窍是 ListCtrl 的 GetTopItem() 和 GetCountPerPage() 方法。通过这两个函数,您可以计算页面刷新时可见的索引范围。这些是准备刷新页面时唯一需要缓存的索引。

这是我如何实现它的简化示例。

get_row_count() 只是从表中执行“select 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 查询,并且消除了闪烁。

重要的是要注意,如果您将该数据用于其他事件,您希望将项目缓存存储在临时变量中,直到它被重建,否则您将遇到缓存不可用的情况,因为正在刷新。

于 2018-01-21T14:20:07.920 回答