geekdoc-python-zh/docs/pythonlibrary/how-to-use-wxpython-demo-co...

9.6 KiB
Raw Permalink Blame History

如何在演示之外使用 wxPython 演示代码

原文:https://www.blog.pythonlibrary.org/2018/01/23/how-to-use-wxpython-demo-code-outside-the-demo/

有时,有人会问他们如何在演示之外运行来自 wxPython 的演示代码。换句话说,他们想知道如何从演示中提取代码并在自己的程序中运行。我想我很久以前在 wxPython wiki 上写过这个主题,但是我想我也应该在这里写这个主题。


如何处理日志

我经常看到的第一个问题是,演示代码中充满了对某种日志的调用。它总是写入该日志,以帮助开发人员了解不同的事件是如何触发的,或者不同的方法是如何调用的。这一切都很好,但这使得从演示中复制代码变得很困难。让我们从 wx 中取出代码。ListBox 演示作为一个例子,看看我们是否可以让它在演示之外工作。下面是演示代码:


import wx

#----------------------------------------------------------------------
# BEGIN Demo Code
class FindPrefixListBox(wx.ListBox):
    def __init__(self, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize,
                 choices=[], style=0, validator=wx.DefaultValidator):
        wx.ListBox.__init__(self, parent, id, pos, size, choices, style, validator)
        self.typedText = ''
        self.log = parent.log
        self.Bind(wx.EVT_KEY_DOWN, self.OnKey)

    def FindPrefix(self, prefix):
        self.log.WriteText('Looking for prefix: %s\n' % prefix)

        if prefix:
            prefix = prefix.lower()
            length = len(prefix)

            # Changed in 2.5 because ListBox.Number() is no longer supported.
            # ListBox.GetCount() is now the appropriate way to go.
            for x in range(self.GetCount()):
                text = self.GetString(x)
                text = text.lower()

                if text[:length] == prefix:
                    self.log.WriteText('Prefix %s is found.\n' % prefix)
                    return x

        self.log.WriteText('Prefix %s is not found.\n' % prefix)
        return -1

    def OnKey(self, evt):
        key = evt.GetKeyCode()

        if key >= 32 and key <= 127:
            self.typedText = self.typedText + chr(key)
            item = self.FindPrefix(self.typedText)

            if item != -1:
                self.SetSelection(item)

        elif key == wx.WXK_BACK:   # backspace removes one character and backs up
            self.typedText = self.typedText[:-1]

            if not self.typedText:
                self.SetSelection(0)
            else:
                item = self.FindPrefix(self.typedText)

                if item != -1:
                    self.SetSelection(item)
        else:
            self.typedText = ''
            evt.Skip()

    def OnKeyDown(self, evt):
        pass

#---------------------------------------------------------------------------

class TestListBox(wx.Panel):
    def __init__(self, parent, log):
        self.log = log
        wx.Panel.__init__(self, parent, -1)

        sampleList = ['zero', 'one', 'two', 'three', 'four', 'five',
                      'six', 'seven', 'eight', 'nine', 'ten', 'eleven',
                      'twelve', 'thirteen', 'fourteen']

        wx.StaticText(self, -1, "This example uses the wx.ListBox control.", (45, 10))
        wx.StaticText(self, -1, "Select one:", (15, 50))
        self.lb1 = wx.ListBox(self, 60, (100, 50), (90, 120), sampleList, wx.LB_SINGLE)
        self.Bind(wx.EVT_LISTBOX, self.EvtListBox, self.lb1)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.EvtListBoxDClick, self.lb1)
        self.lb1.Bind(wx.EVT_RIGHT_UP, self.EvtRightButton)
        self.lb1.SetSelection(3)
        self.lb1.Append("with data", "This one has data");
        self.lb1.SetClientData(2, "This one has data");

        wx.StaticText(self, -1, "Select many:", (220, 50))
        self.lb2 = wx.ListBox(self, 70, (320, 50), (90, 120), sampleList, wx.LB_EXTENDED)
        self.Bind(wx.EVT_LISTBOX, self.EvtMultiListBox, self.lb2)
        self.lb2.Bind(wx.EVT_RIGHT_UP, self.EvtRightButton)
        self.lb2.SetSelection(0)

        sampleList = sampleList + ['test a', 'test aa', 'test aab',
                                   'test ab', 'test abc', 'test abcc',
                                   'test abcd' ]
        sampleList.sort()
        wx.StaticText(self, -1, "Find Prefix:", (15, 250))
        fp = FindPrefixListBox(self, -1, (100, 250), (90, 120), sampleList, wx.LB_SINGLE)
        fp.SetSelection(0)

    def EvtListBox(self, event):
        self.log.WriteText('EvtListBox: %s, %s, %s\n' %
                           (event.GetString(),
                            event.IsSelection(),
                            event.GetSelection()
                            # event.GetClientData()
                            ))

        lb = event.GetEventObject()
        # data = lb.GetClientData(lb.GetSelection())

        # if data is not None:
            # self.log.WriteText('\tdata: %s\n' % data)

    def EvtListBoxDClick(self, event):
        self.log.WriteText('EvtListBoxDClick: %s\n' % self.lb1.GetSelection())
        self.lb1.Delete(self.lb1.GetSelection())

    def EvtMultiListBox(self, event):
        self.log.WriteText('EvtMultiListBox: %s\n' % str(self.lb2.GetSelections()))

    def EvtRightButton(self, event):
        self.log.WriteText('EvtRightButton: %s\n' % event.GetPosition())

        if event.GetEventObject().GetId() == 70:
            selections = list(self.lb2.GetSelections())
            selections.reverse()

            for index in selections:
                self.lb2.Delete(index)
#----------------------------------------------------------------------
# END Demo Code
#----------------------------------------------------------------------

我不打算解释演示代码本身。相反,当我想尝试在演示之外运行它时,我将把重点放在这段代码出现的问题上。在演示的最后有一个 runTest 函数,我没有复制它,因为如果你在演示之外复制它,代码不会做任何事情。你看,演示代码有某种包装来使它工作。如果你想使用演示代码,你需要添加你自己的“包装器”。

这段代码呈现的主要问题是许多方法都调用了 self.log.WriteText 。您不能从代码中看出 log 对象是什么,但是您知道它有一个 WriteText 方法。在演示中您会注意到当其中一个方法触发时WriteText 调用似乎会写入演示底部的文本控件。所以日志必须是一个文本控件!

有许多不同的方法可以解决日志问题。以下是我最喜欢的三个:

  • 移除对 self.log.WriteText 的所有调用
  • 创建我自己的文本控件并将其传入
  • 用 WriteText 方法创建一个简单的类

我在很多场合都选择了第一种,因为这是一种简单的开始方式。但是对于教程来说,这有点无聊,所以我们将选择第三个选项,用 WriteText 方法创建一个类!将以下代码添加到包含上述代码的同一文件中:


#----------------------------------------------------------------------
# Start Your own code here           
class FakeLog:
    """
    The log in the demo is a text control, so just create a class
    with an overridden WriteText function
    """

    def WriteText(self, string):
        print(string)

# Create a frame that can wrap your demo code (works in most cases)

class MyFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, title='Listbox demo', 
                          size=(800,600))
        log = FakeLog()
        panel = TestListBox(self, log=log)

        self.Show()

if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

这里我们只是用 WriteText 方法创建了一个 FakeLog ,它接受一个字符串作为唯一的参数。该方法所做的就是将字符串打印到 stdout。然后我们创建 wx 的一个子类。框架,初始化我们的假日志和演示代码,并显示我们的框架。现在我们有了一个不在演示中的演示代码!如果你愿意,你可以在 Github 上获得完整的代码。


其他演示问题

还有一些其他的演示没有遵循与列表框演示完全相同的 API。例如如果你尝试使用我在上面为 wx 创建的类。按钮演示,你会发现它的 log 对象调用的是 write()方法而不是 WriteText()方法。在这种情况下,解决方案是显而易见的,因为我们只需要向我们的假日志记录类添加第二个方法:


class FakeLog:
    """
    The log in the demo is a text control, so just create a class
    with an overridden WriteText function
    """

    def WriteText(self, string):
        print(string)

    def write(self, string):
        print(string)

现在我们的演示运行代码更加灵活了。然而,当我让我的一个读者测试这段代码时,他们注意到了一个关于 wx 的问题。ListCtrl 演示。问题是它导入了一个名为“images”的模块。实际上有几个演示引用了这个模块。你只需要从演示中复制 images.py ,并把它放在你正在编写的脚本所在的位置,这样你就可以导入它了。

注意:我收到一份报告,说 wxPython 4 最新测试版中包含的 images.py 文件对他们不适用,他们不得不从旧版本的演示中获取一份副本。我自己没有遇到过这个问题,但请记住这一点。


包扎

现在,您应该有了让 wxPython 演示中的大多数演示在您自己的代码中工作所需的工具。去抓些代码来试试吧!编码快乐!