geekdoc-python-zh/docs/pythonlibrary/wxpython-wx-listctrl-tips-a...

23 KiB
Raw Permalink Blame History

wxPython: wx。ListCtrl 提示和技巧

原文:https://www.blog.pythonlibrary.org/2011/01/04/wxpython-wx-listctrl-tips-and-tricks/

去年,我们介绍了网格控制的一些技巧和窍门。在这篇文章中,我们将回顾 wx 的一些提示和技巧。处于“报告”模式时的 ListCtrl 小部件。看看下面的提示:

  • 如何创建一个简单的 ListCtrl
  • 如何对列表中的行进行排序
  • 如何使 ListCtrl 单元格可就地编辑
  • 将对象与 clistctrl 行相关联
  • 替换 ListCtrl 的行颜色

如何创建一个简单的 ListCtrl

listctrl_simple.png

简单的 clistctrl

列表控件是一个非常常见的小部件。在 Windows 中,您将在 Windows 资源管理器中看到列表控件。它有四种模式:图标、小图标、列表和报告。它们分别与 Windows 资源管理器中的图标、平铺、列表和详细信息视图大致匹配。我们将把重点放在报表模式下的 ListCtrl 上,因为这是大多数开发人员使用它的模式。下面是一个如何创建列表控件的简单示例:


import wx

########################################################################
class MyForm(wx.Frame):

    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")

        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        self.index = 0

        self.list_ctrl = wx.ListCtrl(panel, size=(-1,100),
                         style=wx.LC_REPORT
                         |wx.BORDER_SUNKEN
                         )
        self.list_ctrl.InsertColumn(0, 'Subject')
        self.list_ctrl.InsertColumn(1, 'Due')
        self.list_ctrl.InsertColumn(2, 'Location', width=125)

        btn = wx.Button(panel, label="Add Line")
        btn.Bind(wx.EVT_BUTTON, self.add_line)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

    #----------------------------------------------------------------------
    def add_line(self, event):
        line = "Line %s" % self.index
        self.list_ctrl.InsertStringItem(self.index, line)
        self.list_ctrl.SetStringItem(self.index, 1, "01/19/2010")
        self.list_ctrl.SetStringItem(self.index, 2, "USA")
        self.index += 1

#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

从上面的代码可以看出,创建 ListCtrl 实例非常容易。注意,我们使用 wx 将样式设置为报告模式。LC_REPORT 标志。为了添加列标题,我们调用 ListCtrl 的 InsertColumn 方法,并传递一个整数来告诉 ListCtrl 哪一列是哪一列,并传递一个字符串以方便用户。是的,这些列是从零开始的,所以第一列是数字 0第二列是数字 1依此类推。

下一个重要部分包含在按钮的事件处理程序中, add_line ,在这里我们学习如何向 ListCtrl 添加数据行。使用的典型方法是 InsertStringItem 方法。如果您还想在每一行中添加一个图像,那么您可以使用一个更复杂的方法,比如 InsertColumnInfoInsertImageStringItem 方法。您可以在 wxPython 演示中看到如何使用它们。在这篇文章中,我们坚持使用简单的东西。

无论如何,当你调用 InsertStringItem 时,你给它正确的行索引和一个字符串。您使用 SetStringItem 方法为该行的其他列设置数据。请注意SetStringItem 方法需要三个参数:行索引、列索引和一个字符串。最后,我们增加行索引,这样我们就不会覆盖任何内容。现在你可以走出去,做你自己的!让我们继续,看看如何对行进行排序!

如何对列表中的行进行排序

listctrl_sorting.png

可排序的 clistctrl

ListCtrl 小部件编写了一些额外的脚本,为小部件添加功能。这些脚本被称为 mixins。你可以在这里阅读它们。对于这个食谱我们将使用列排序器混合信息混合信息。下面的代码是一个 wxPython 演示示例的精简版本。


import wx
import wx.lib.mixins.listctrl as listmix

musicdata = {
0 : ("Bad English", "The Price Of Love", "Rock"),
1 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"),
2 : ("George Michael", "Praying For Time", "Rock"),
3 : ("Gloria Estefan", "Here We Are", "Rock"),
4 : ("Linda Ronstadt", "Don't Know Much", "Rock"),
5 : ("Michael Bolton", "How Am I Supposed To Live Without You", "Blues"),
6 : ("Paul Young", "Oh Girl", "Rock"),
}

########################################################################
class TestListCtrl(wx.ListCtrl):

    #----------------------------------------------------------------------
    def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=0):
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)

########################################################################
class TestListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin):

    #----------------------------------------------------------------------
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)

        self.list_ctrl = TestListCtrl(self, size=(-1,100),
                         style=wx.LC_REPORT
                         |wx.BORDER_SUNKEN
                         |wx.LC_SORT_ASCENDING
                         )
        self.list_ctrl.InsertColumn(0, "Artist")
        self.list_ctrl.InsertColumn(1, "Title", wx.LIST_FORMAT_RIGHT)
        self.list_ctrl.InsertColumn(2, "Genre")

        items = musicdata.items()
        index = 0
        for key, data in items:
            self.list_ctrl.InsertStringItem(index, data[0])
            self.list_ctrl.SetStringItem(index, 1, data[1])
            self.list_ctrl.SetStringItem(index, 2, data[2])
            self.list_ctrl.SetItemData(index, key)
            index += 1

        # Now that the list exists we can init the other base class,
        # see wx/lib/mixins/listctrl.py
        self.itemDataMap = musicdata
        listmix.ColumnSorterMixin.__init__(self, 3)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list_ctrl)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)

    #----------------------------------------------------------------------
    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
    def GetListCtrl(self):
        return self.list_ctrl

    #----------------------------------------------------------------------
    def OnColClick(self, event):
        print "column clicked"
        event.Skip()

########################################################################
class MyForm(wx.Frame):

    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")

        # Add a panel so it looks the correct on all platforms
        panel = TestListCtrlPanel(self)

#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

这段代码有点奇怪,因为我们继承了 wx 中的 mixin。基于面板的类而不是 wx。ListCtrl 类。只要你正确地重新排列代码,你可以用任何一种方式来做。无论如何,我们将聚焦于这个例子和前一个例子之间的主要区别。最重要的第一个区别是在我们插入列表控件数据的循环结构中。这里我们包含了列表控件的 SetItemData 方法,以包含允许排序发生的必要内部工作。正如您可能已经猜到的,这个方法将行索引与音乐数据字典的键相关联。

接下来我们实例化 ColumnSorterMixin 并告诉它列表控件中有多少列。我们可以让EVT _ 列表 _ 列 _ 点击绑定这个例子,因为它与行的实际排序无关,但是为了增加你的知识,它被留了下来。它所做的只是向您展示如何捕捉用户的列点击事件。代码的其余部分不言自明。如果您想了解这个 mixin 的要求,特别是当您的行中有图像时,请查看源代码中的相关部分(即 listctrl.py)。这不是很容易吗?让我们继续我们的旅程,并找出如何使单元格可编辑!

如何使 ListCtrl 单元格可就地编辑

listctrl_editable.png

可编辑的 clistctrl

有时,程序员会希望允许用户点击单元格并在适当的位置编辑它。这是一个轻量级版本的 wx.grid.Grid 控件。这里有一个例子:


import wx
import wx.lib.mixins.listctrl  as  listmix

########################################################################
class EditableListCtrl(wx.ListCtrl, listmix.TextEditMixin):
    ''' TextEditMixin allows any column to be edited. '''

    #----------------------------------------------------------------------
    def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=0):
        """Constructor"""
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
        listmix.TextEditMixin.__init__(self)

########################################################################
class MyPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        rows = [("Ford", "Taurus", "1996", "Blue"),
                ("Nissan", "370Z", "2010", "Green"),
                ("Porche", "911", "2009", "Red")
                ]
        self.list_ctrl = EditableListCtrl(self, style=wx.LC_REPORT)

        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")

        index = 0
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row[0])
            self.list_ctrl.SetStringItem(index, 1, row[1])
            self.list_ctrl.SetStringItem(index, 2, row[2])
            self.list_ctrl.SetStringItem(index, 3, row[3])
            index += 1

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY, "Editable List Control")
        panel = MyPanel(self)
        self.Show()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

在这个脚本中,我们将 TextEditMixin 放在 wx 中。ListCtrl 类而不是我们的 wx。面板这与前面的示例相反。mixin 本身承担了所有繁重的工作。同样,你必须检查 mixin 的源代码才能真正理解它是如何工作的。

将对象与 ListCtrl 行相关联

这个主题出现了很多次:如何将数据(即对象)与 ListCtrl 的行关联起来?好吧,我们将通过下面的代码找出具体的方法:


import wx

########################################################################
class Car(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, make, model, year, color="Blue"):
        """Constructor"""
        self.make = make
        self.model = model
        self.year = year
        self.color = color

########################################################################
class MyPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        rows = [Car("Ford", "Taurus", "1996"),
                Car("Nissan", "370Z", "2010"),
                Car("Porche", "911", "2009", "Red")
                ]

        self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
                                style=wx.LC_REPORT
                                |wx.BORDER_SUNKEN
                                )
        self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")

        index = 0
        self.myRowDict = {}
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row.make)
            self.list_ctrl.SetStringItem(index, 1, row.model)
            self.list_ctrl.SetStringItem(index, 2, row.year)
            self.list_ctrl.SetStringItem(index, 3, row.color)
            self.myRowDict[index] = row
            index += 1

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)

    #----------------------------------------------------------------------
    def onItemSelected(self, event):
        """"""
        currentItem = event.m_itemIndex
        car = self.myRowDict[currentItem]
        print car.make
        print car.model
        print car.color
        print car.year

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
        panel = MyPanel(self)
        self.Show()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

列表控件小部件实际上没有内置的方法来完成这一壮举。如果您想这样做,那么您会想要检查一下 ObjectListView 小部件,它包装了 ListCtrl 并赋予它更多的功能。与此同时,我们将花一分钟时间检查上面的代码。第一件只是一辆普通的类,有四个属性。然后在 MyPanel 类中,我们创建一个汽车对象列表,我们将使用它作为 ListCtrl 的数据。

为了将数据添加到 ListCtrl 中,我们使用一个 for 循环来遍历列表。我们还使用 Python 字典将每一行与一个汽车对象相关联。我们使用行的索引作为键,字典的值最终成为 Car 对象。这允许我们稍后在 onItemSelected 方法中访问所有 Car/row 对象的数据。我们去看看!

在 onItemSelected 中,我们用下面的小技巧获取行的索引: event.m_itemIndex 。然后,我们使用该值作为字典的键,这样我们就可以访问与该行相关联的 Car 对象。此时,我们只是打印出汽车对象的所有属性,但是您可以在这里做任何您想做的事情。这个基本思想可以很容易地扩展到使用来自 ListCtrl 数据的 SqlAlchemy 查询的结果集。希望你能大致了解。

现在,如果你像 Robin Dunn(wxPython 的创建者)一样密切关注那么你可能会注意到这段代码中一些非常愚蠢的逻辑错误。你找到他们了吗除非你对行进行排序删除或插入一行否则你看不到它。你现在明白了吗是的我愚蠢地将字典中的“unique”键基于行的位置如果发生任何事件行的位置就会改变。让我们来看一个更好的例子:


import wx

########################################################################
class Car(object):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, make, model, year, color="Blue"):
        """Constructor"""
        self.id = id(self)
        self.make = make
        self.model = model
        self.year = year
        self.color = color

########################################################################
class MyPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        rows = [Car("Ford", "Taurus", "1996"),
                Car("Nissan", "370Z", "2010"),
                Car("Porche", "911", "2009", "Red")
                ]

        self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
                                style=wx.LC_REPORT
                                |wx.BORDER_SUNKEN
                                )
        self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")

        index = 0
        self.myRowDict = {}
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row.make)
            self.list_ctrl.SetStringItem(index, 1, row.model)
            self.list_ctrl.SetStringItem(index, 2, row.year)
            self.list_ctrl.SetStringItem(index, 3, row.color)
            self.list_ctrl.SetItemData(index, row.id)
            self.myRowDict[row.id] = row
            index += 1

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)

    #----------------------------------------------------------------------
    def onItemSelected(self, event):
        """"""
        currentItem = event.m_itemIndex
        car = self.myRowDict[self.list_ctrl.GetItemData(currentItem)]
        print car.make
        print car.model
        print car.color
        print car.year

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
        panel = MyPanel(self)
        self.Show()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

在这个例子中,我们向 Car 类添加了一个新的属性,为每个实例创建一个惟一的 id这个实例是使用 Python 的便利的 id 内建的。然后在我们向列表控件添加数据的循环中,我们调用小部件的 SetItemData 方法,并给它行索引和汽车实例的唯一 id。现在行在哪里结束并不重要因为它已经附加了惟一的 id。最后我们必须修改 onItemSelected 来获得正确的对象。神奇的事情发生在这段代码中:


# this code was helpfully provided by Robin Dunn
car = self.myRowDict[self.list_ctrl.GetItemData(currentItem)]

很酷吧。我们的最后一个例子将涵盖如何交替行颜色,所以让我们看看!

替换 ListCtrl 的行颜色

listctrl_altcolors.png

具有交替行颜色的 ListCtrl

正如这一节的标题所暗示的,我们将看看如何改变 ListCtrl 的行的颜色。代码如下:


import wx
import wx.lib.mixins.listctrl  as  listmix

########################################################################
class MyPanel(wx.Panel):
    """"""

    #----------------------------------------------------------------------
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)

        rows = [("Ford", "Taurus", "1996", "Blue"),
                ("Nissan", "370Z", "2010", "Green"),
                ("Porche", "911", "2009", "Red")
                ]
        self.list_ctrl = wx.ListCtrl(self, style=wx.LC_REPORT)

        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")

        index = 0
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row[0])
            self.list_ctrl.SetStringItem(index, 1, row[1])
            self.list_ctrl.SetStringItem(index, 2, row[2])
            self.list_ctrl.SetStringItem(index, 3, row[3])
            if index % 2:
                self.list_ctrl.SetItemBackgroundColour(index, "white")
            else:
                self.list_ctrl.SetItemBackgroundColour(index, "yellow")
            index += 1

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, wx.ID_ANY,
                          "List Control w/ Alternate Colors")
        panel = MyPanel(self)
        self.Show()

#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

上面的代码将改变每一行的背景颜色。因此,您应该会看到黄色和白色的行。我们通过调用 ListCtrl 实例的 SetItemBackgroundColour 方法来实现这一点。如果你正在使用一个虚拟列表控件,那么你会想要覆盖 OnGetItemAttr 方法。要查看后一种方法的示例,请打开您的 wxPython 演示副本;里面有一个。

包扎

我们已经讨论了很多问题。你现在应该可以用你的 wx 做更多的事情了。当然,假设你是使用它的新手。欢迎在评论中提问或建议未来的食谱。我希望这对你有所帮助!

注意:所有例子都是在 Windows XP 上用 Python 2.5 和 wxPython 2.8.10.1 测试的。他们还在使用 Python 2.6 的 Windows 7 Professional 上进行了测试

附加阅读

源代码