PythonLibrary-博客中文翻译-六-
PythonLibrary 博客中文翻译(六)
wxPython 的“Book”控件(第 1 部分,共 2 部分)
原文:https://www.blog.pythonlibrary.org/2009/12/03/the-book-controls-of-wxpython-part-1-of-2/
如果您是 GUI 编程(尤其是 wxPython)的新手,您可能不知道什么是“book”控件。可能其他语言对此控件也有不同的称呼。在 wxPython 中,book 控件允许用户在各种面板之间切换。最常见的例子是带有标签界面的浏览器和系统选项对话框。本文将带您了解这些控件的创建和基本配置。
wxPython 目前内置了七个这样的控件,第八个内置在 agw AUI 小部件中。我将在本文中介绍以下小部件(虽然不一定按照这个顺序):AUI _ 笔记本、选择书、列表书、笔记本、工具书和树书。在第二篇文章中,我将介绍两个 agw book 控件:FlatNotebook 和嵌入在新 agw AUI 中的一个。请注意,这第一篇文章是一篇三页的文章。我个人觉得默认的 WordPress 分页不是很清晰。
wx。笔记本
我们将从最熟悉的 book 小部件 wx.Notebook 开始。下面的截图让您了解它应该是什么样子:
每个笔记本标签(或页面)将由一个 wx 组成。面板对象。我已经创建了三个不同的面板类,我们将用于选项卡。我从 wxPython 演示中为这些面板(和图书小部件)提取了一些代码,并在本教程中对它们进行了修改。让我们看一下其中的一个面板,让您了解一下我在做什么:
import wx
class TabPanel(wx.Panel):
"""
This will be the first notebook tab
"""
#----------------------------------------------------------------------
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txtOne, 0, wx.ALL, 5)
sizer.Add(txtTwo, 0, wx.ALL, 5)
self.SetSizer(sizer)
上面的代码将在一个垂直的 BoxSizer 中创建一个带有两个文本控件的面板。这就是它所能做的一切。现在让我们看看如何将这段代码与 wx.Notebook 结合使用。下面是一个简单的演示:
import images
import wx
import panelOne, panelTwo, panelThree
########################################################################
class NotebookDemo(wx.Notebook):
"""
Notebook class
"""
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Notebook.__init__(self, parent, id=wx.ID_ANY, style=
wx.BK_DEFAULT
#wx.BK_TOP
#wx.BK_BOTTOM
#wx.BK_LEFT
#wx.BK_RIGHT
)
# Create the first tab and add it to the notebook
tabOne = panelOne.TabPanel(self)
tabOne.SetBackgroundColour("Gray")
self.AddPage(tabOne, "TabOne")
# Show how to put an image on one of the notebook tabs,
# first make the image list:
il = wx.ImageList(16, 16)
idx1 = il.Add(images.Smiles.GetBitmap())
self.AssignImageList(il)
# now put an image on the first tab we just created:
self.SetPageImage(0, idx1)
# Create and add the second tab
tabTwo = panelTwo.TabPanel(self)
self.AddPage(tabTwo, "TabTwo")
# Create and add the third tab
self.AddPage(panelThree.TabPanel(self), "TabThree")
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging)
def OnPageChanged(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
def OnPageChanging(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"Notebook Tutorial",
size=(600,400)
)
panel = wx.Panel(self)
notebook = NotebookDemo(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = DemoFrame()
app.MainLoop()
让我们把它分解一下。首先,我创建了 wx 的一个实例。笔记本类,并将其命名为 NotebookDemo。在它的“init”中,我把一些样式注释掉了。这些样式告诉笔记本在哪里放置标签(例如,在顶部、左侧、右侧或底部)。默认是将它们放在顶部。您可以注释掉默认值,并取消注释其他行中的一行,以查看不同之处。
要创建第一个选项卡,我们需要做的事情如下:
# Create the first tab and add it to the notebook
tabOne = panelOne.TabPanel(self)
tabOne.SetBackgroundColour("Gray")
self.AddPage(tabOne, "TabOne")
实际上,我们甚至不需要设置背景颜色,但是我这样做是为了让文本控件更加突出。如果我们不设置颜色,我们可以把它变成这样的一行程序:
self.AddPage(panelOne.TabPanel(self), "TabOne")
AddPage()方法是向 notebook 小部件添加页面的主要方法。此方法具有以下参数:
AddPage(self,Page,text,select,imageId)
我只为选项卡添加页面和文本。如果您愿意,您可以将“True”作为第四个参数传递,并使该选项卡被选中。虽然第一个选项卡将被默认选中,但是这样做有点傻。第五个参数允许程序员向选项卡添加图像;然而,我们用下一段代码来做:
# Show how to put an image on one of the notebook tabs,
# first make the image list:
il = wx.ImageList(16, 16)
idx1 = il.Add(images.Smiles.GetBitmap())
self.AssignImageList(il)
# now put an image on the first tab we just created:
self.SetPageImage(0, idx1)
如您所见,我们首先创建一个 ImageList,并将图像设置为 16x16。然后,我使用 wxPython 演示中的“images”模块创建一个笑脸,并将其添加到列表中。接下来,我使用 AssignImageList()将列表分配给笔记本。要在其中一个选项卡上设置 ImageList 中的一个图像,可以调用 SetPageImage()并将选项卡索引作为第一个参数传递,将位图 ImageList 项实例作为第二个参数传递(即 idx1)。在这个例子中,我只向第一个选项卡添加了一个图像。
接下来的几行代码向笔记本添加了两个标签,然后绑定了两个事件:EVT _ 笔记本 _ 页面 _ 更改和 EVT _ 笔记本 _ 页面 _ 更改。当用户点击了不同于当前选择的标签时,EVT _ 笔记本 _ 页面 _ 改变被触发,当新标签被完全选择时结束。当下一个选项卡被完全选中时,EVT _ 笔记本 _ 页面 _ 已更改事件被触发。EVT _ 笔记本 _ 页面 _ 改变事件的一个方便的用法是,如果你需要用户在切换标签页之前做一些事情,可以否决这个事件。
文档是你的朋友。您将在那里找到许多方便的方法,如 GetPage、GetPageCount、GetPageImage、GetSelection、RemovePage 和 DeletePage 以及许多其他方法。
我需要为 wx 触及的最后一个主题。笔记本是如何嵌套标签的。在 wxPython 中嵌套制表符实际上非常容易。你所需要做的就是创建一个面板,上面有一个笔记本,然后把这个面板变成另一个笔记本的标签。这有点难以解释,所以我们来看一些代码:
import panelOne, panelTwo
import wx
class TabPanel(wx.Panel):
"""
This will be the first notebook tab
"""
#----------------------------------------------------------------------
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
# Create some nested tabs on the first tab
nestedNotebook = wx.Notebook(self, wx.ID_ANY)
nestedTabOne = panelOne.TabPanel(nestedNotebook)
nestedTabTwo = panelTwo.TabPanel(nestedNotebook)
nestedNotebook.AddPage(nestedTabOne, "NestedTabOne")
nestedNotebook.AddPage(nestedTabTwo, "NestedTabTwo")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(nestedNotebook, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
上面的代码只是创建了一个面板,上面有一个笔记本。我重新使用了这个子笔记本的一些面板。现在我们只需要将它插入另一个笔记本(反之亦然):
import images
import wx
import nestedPanelOne, panelTwo, panelThree
########################################################################
class NestedNotebookDemo(wx.Notebook):
"""
Notebook class
"""
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Notebook.__init__(self, parent, id=wx.ID_ANY, style=
wx.BK_DEFAULT
#wx.BK_TOP
#wx.BK_BOTTOM
#wx.BK_LEFT
#wx.BK_RIGHT
)
# Create the first tab and add it to the notebook
tabOne = nestedPanelOne.TabPanel(self)
self.AddPage(tabOne, "TabOne")
# Show how to put an image on one of the notebook tabs,
# first make the image list:
il = wx.ImageList(16, 16)
idx1 = il.Add(images.Smiles.GetBitmap())
self.AssignImageList(il)
# now put an image on the first tab we just created:
self.SetPageImage(0, idx1)
# Create and add the second tab
tabTwo = panelTwo.TabPanel(self)
self.AddPage(tabTwo, "TabTwo")
# Create and add the third tab
self.AddPage(panelThree.TabPanel(self), "TabThree")
这段代码与我们的第一段代码并没有太大的不同。唯一的区别是导入正确的面板并将其作为页面添加到笔记本中。现在我们将进入下一个图书控制。
wx。精选图书
wx。Choicebook 是笔记本和 wx 的融合。选择部件。这允许用户单击下拉控件来选择要查看的页面。Choicebook 也继承了 wx。BookCtrlBase,所以它有大部分与 wx 相同的方法。笔记本有。让我们来看一个快速演示:
import wx
import panelOne, panelTwo, panelThree
########################################################################
class ChoicebookDemo(wx.Choicebook):
"""
Choicebook class
"""
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Choicebook.__init__(self, parent, wx.ID_ANY)
# Create the first tab and add it to the notebook
tabOne = panelOne.TabPanel(self)
tabOne.SetBackgroundColour("Gray")
self.AddPage(tabOne, "Book One")
# Create and add the second tab
tabTwo = panelTwo.TabPanel(self)
self.AddPage(tabTwo, "Book Two")
# Create and add the third tab
self.AddPage(panelThree.TabPanel(self), "Book Three")
self.Bind(wx.EVT_CHOICEBOOK_PAGE_CHANGED, self.OnPageChanged)
self.Bind(wx.EVT_CHOICEBOOK_PAGE_CHANGING, self.OnPageChanging)
#----------------------------------------------------------------------
def OnPageChanged(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
#----------------------------------------------------------------------
def OnPageChanging(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"Choicebook Tutorial",
size=(600,400))
panel = wx.Panel(self)
notebook = ChoicebookDemo(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = DemoFrame()
app.MainLoop()
上面的代码与笔记本示例中的代码基本相同,但更简单。您只需使用 AddPage()方法向 book 控件添加一个新页面。其余的方法和我从文档中看到的差不多。请注意,Choicebook 确实有自己的一组专门命名的事件。如果您使用另一本书的事件名称,您会很快发现您的事件处理程序没有被触发。
wx。名单簿
Listbook 控件使用 ListCtrl 来代替 tab 来控制笔记本。在这种情况下,您实际上可以使用带标签的图片来更改标签。有点奇怪,但我觉得也挺酷的。与 Choicebook 一样,该控件继承自 BookCtrlBase,并具有相同的方法。主要的区别似乎在于列表本的外观及其独特的事件集。让我们快速浏览一下我的演示,看看如何创建一个!
import images
import wx
import panelOne, panelTwo, panelThree
########################################################################
class ListbookDemo(wx.Listbook):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Listbook.__init__(self, parent, wx.ID_ANY, style=
wx.BK_DEFAULT
#wx.BK_TOP
#wx.BK_BOTTOM
#wx.BK_LEFT
#wx.BK_RIGHT
)
# make an image list using the LBXX images
il = wx.ImageList(32, 32)
for x in range(3):
obj = getattr(images, 'LB%02d' % (x+1))
bmp = obj.GetBitmap()
il.Add(bmp)
self.AssignImageList(il)
pages = [(panelOne.TabPanel(self), "Panel One"),
(panelTwo.TabPanel(self), "Panel Two"),
(panelThree.TabPanel(self), "Panel Three")]
imID = 0
for page, label in pages:
self.AddPage(page, label, imageId=imID)
imID += 1
self.Bind(wx.EVT_LISTBOOK_PAGE_CHANGED, self.OnPageChanged)
self.Bind(wx.EVT_LISTBOOK_PAGE_CHANGING, self.OnPageChanging)
#----------------------------------------------------------------------
def OnPageChanged(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
#----------------------------------------------------------------------
def OnPageChanging(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"Listbook Tutorial",
size=(700,400)
)
panel = wx.Panel(self)
notebook = ListbookDemo(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = DemoFrame()
app.MainLoop()
就像 wx.Notebook 一样,Listbook 的“tab”控件可以设置为沿任意边运行。您也可以使用我们在开始时在 Notebook 示例中使用的相同方法来附加图像。我通过将面板放在一个列表中并遍历它来缩短代码,但是除此之外,关于这个小部件没有太多要写的。
wx。工具书
工具书是 wx。工具栏加一个 wx。笔记本,这意味着您使用带标签的位图按钮来控制您正在查看的笔记本的“标签”。正如您可能已经注意到的 Listbook 示例,我使用了 wxPython 演示的“images”模块作为它的图像,并且在我的示例代码中再次使用了它。
import images
import wx
import panelOne, panelTwo, panelThree
def getNextImageID(count):
imID = 0
while True:
yield imID
imID += 1
if imID == count:
imID = 0
########################################################################
class ToolbookDemo(wx.Toolbook):
"""
Toolbook class
"""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Toolbook.__init__(self, parent, wx.ID_ANY, style=
wx.BK_DEFAULT
#wx.BK_TOP
#wx.BK_BOTTOM
#wx.BK_LEFT
#wx.BK_RIGHT
)
# make an image list using the LBXX images
il = wx.ImageList(32, 32)
for x in range(3):
obj = getattr(images, 'LB%02d' % (x+1))
bmp = obj.GetBitmap()
il.Add(bmp)
self.AssignImageList(il)
imageIdGenerator = getNextImageID(il.GetImageCount())
pages = [(panelOne.TabPanel(self), "Panel One"),
(panelTwo.TabPanel(self), "Panel Two"),
(panelThree.TabPanel(self), "Panel Three")]
imID = 0
for page, label in pages:
self.AddPage(page, label, imageId=imageIdGenerator.next())
imID += 1
self.Bind(wx.EVT_TOOLBOOK_PAGE_CHANGED, self.OnPageChanged)
self.Bind(wx.EVT_TOOLBOOK_PAGE_CHANGING, self.OnPageChanging)
#----------------------------------------------------------------------
def OnPageChanged(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
#----------------------------------------------------------------------
def OnPageChanging(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"Toolbook Tutorial",
size=(700,400)
)
panel = wx.Panel(self)
notebook = ToolbookDemo(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = DemoFrame()
app.MainLoop()
Toolbook 跟随前面控件的脚步,因为它的血统来自于 wx.BookCtrlBase。这个小部件因其外观和感觉(尽管它看起来非常类似于 Listbook)以及 Toolbook 的独特事件而闻名。我使用了 wxPython 演示中一些有趣的代码来帮助将图像分配给工具栏的按钮,但这确实是唯一重要的区别。欲了解全部信息,请阅读该控制的文档!
wx。树书
Treebook 控件是 wx。TreeCtrl 和 wx.Notebook。我见过的大多数 tree ctrl 不使用图像,但是 wxPython 演示在 Treebook 中使用它们,所以我决定也使用它们。正如你从上面的截图中看到的,位图给了树书一种有趣的味道。让我们看看如何使这些控件之一!
import images
import wx
import panelOne, panelTwo, panelThree
def getNextImageID(count):
imID = 0
while True:
yield imID
imID += 1
if imID == count:
imID = 0
########################################################################
class TreebookDemo(wx.Treebook):
"""
Treebook class
"""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Treebook.__init__(self, parent, wx.ID_ANY, style=
wx.BK_DEFAULT
#wx.BK_TOP
#wx.BK_BOTTOM
#wx.BK_LEFT
#wx.BK_RIGHT
)
il = wx.ImageList(32, 32)
for x in range(6):
obj = getattr(images, 'LB%02d' % (x+1))
bmp = obj.GetBitmap()
il.Add(bmp)
self.AssignImageList(il)
imageIdGenerator = getNextImageID(il.GetImageCount())
pages = [(panelOne.TabPanel(self), "Panel One"),
(panelTwo.TabPanel(self), "Panel Two"),
(panelThree.TabPanel(self), "Panel Three")]
imID = 0
for page, label in pages:
self.AddPage(page, label, imageId=imageIdGenerator.next())
imID += 1
self.AddSubPage(page, 'a sub-page', imageId=imageIdGenerator.next())
self.Bind(wx.EVT_TREEBOOK_PAGE_CHANGED, self.OnPageChanged)
self.Bind(wx.EVT_TREEBOOK_PAGE_CHANGING, self.OnPageChanging)
# This is a workaround for a sizing bug on Mac...
wx.FutureCall(100, self.AdjustSize)
#----------------------------------------------------------------------
def AdjustSize(self):
#print self.GetTreeCtrl().GetBestSize()
self.GetTreeCtrl().InvalidateBestSize()
self.SendSizeEvent()
#----------------------------------------------------------------------
def OnPageChanged(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
#----------------------------------------------------------------------
def OnPageChanging(self, event):
old = event.GetOldSelection()
new = event.GetSelection()
sel = self.GetSelection()
print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel)
event.Skip()
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"Treebook Tutorial",
size=(700,400)
)
panel = wx.Panel(self)
notebook = TreebookDemo(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = DemoFrame()
app.MainLoop()
和 Toolbook 一样,我再次使用了 wxPython 演示中的一些代码来创建这个演示,并重用了一些我自己的“框架”代码。除了 Treebook 的专门化事件之外,您还应该注意到它有一个 AddSubPage 方法,该方法向树中添加一个子节点,这又向笔记本中添加了另一个页面。还有其他几个只有这个控件才有的方法:CollapseNode、ExpandNode、GetPageParent、GetTreeCtrl、InsertSubPage 和 IsNodeExpanded。我认为它们是不言自明的,但是不要害怕阅读文档。
wx.AuiNotebook
本文的最后一个控件来自 wx.aui 名称空间: AuiNotebook 。这是本文中唯一一个没有从通用 BookCtrlBase 继承的 notebook 控件。一定要研究这个控件的 API,以充分理解如何使用它,因为 AuiNotebook 有几个方法与我们到目前为止看到的其他控件不同。
AuiNotebook 允许用户重新排列标签,将标签拖到它自己的区域(正如你从上面的截图中看到的),甚至将标签从一个 AuiNotebook 拖到另一个。以下代码片段将允许您完成所有这些事情:
import wx
import wx.aui
import panelOne, panelTwo, panelThree
class DemoPanel(wx.Panel):
"""
This will be the first notebook tab
"""
#----------------------------------------------------------------------
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
# create the AuiNotebook instance
nb = wx.aui.AuiNotebook(self)
# add some pages to the notebook
pages = [(panelOne.TabPanel(nb), "Tab 1"),
(panelTwo.TabPanel(nb), "Tab 2"),
(panelThree.TabPanel(nb), "Tab 3")]
for page, label in pages:
nb.AddPage(page, label)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(nb, 1, wx.EXPAND)
self.SetSizer(sizer)
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"AUI-Notebook Tutorial",
size=(600,400))
panel = DemoPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = DemoFrame()
app.MainLoop()
对于这个简单的例子,我只是通过实例化 wx.aui.AuiNotebook(self)并传入 wx.Panel 来创建 AuiNotebook 的一个实例。还要注意的是,你可以通过点击单独的“x”来关闭任何标签。现在你知道如何制作你自己的笔记本了!
包扎
在本文的第二部分,我们将发现 AGW 笔记本的许多有趣的特性。我将它们排除在本文之外的主要原因之一是因为它们是纯 python 模块,并且有许多不在这组小部件中的额外功能。所有这些代码都在 Windows XP 和 Vista 上使用 wxPython 2.8.10.1(unicode)、Python 2.5 进行了测试。所有的例子都可以在 Mac 和 Linux 上很好的运行,但是请在评论中告诉我它们的运行效果!
进一步阅读
下载
PySimpleGUI 的演示
原文:https://www.blog.pythonlibrary.org/2019/10/31/the-demos-for-pysimplegui/
项目包含了许多有趣的 T2 演示,你可以用它们来学习如何使用 PySimpleGUI。据我所知,演示涵盖了所有基本的小部件,还涵盖了推荐的软件包设计模式。此外,还有几个游戏和其他小应用程序,如 Pong 和 Snake 游戏的版本。
在本文中,您将看到这个项目中的一个小样本演示,这将让您对使用 PySimpleGUI 可以做些什么有所了解。
查看可用的小部件
PySimpleGUI 有一个很好的小演示,名为 Demo_All_Widgets.py ,演示了 PySimpleGUI 当前支持的几乎所有小部件。PySimpleGUI 已经包装了 Tkinter 的所有核心部件,但没有包装 ttk 部件。
这是运行演示时的样子:
让我们快速看一下这个演示的代码:
#!/usr/bin/env python
'''
Example of (almost) all widgets, that you can use in PySimpleGUI.
'''
import PySimpleGUI as sg
sg.change_look_and_feel('GreenTan')
# ------ Menu Definition ------ #
menu_def = [['&File', ['&Open', '&Save', 'E&xit', 'Properties']],
['&Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ],
['&Help', '&About...'], ]
# ------ Column Definition ------ #
column1 = [[sg.Text('Column 1', background_color='lightblue', justification='center', size=(10, 1))],
[sg.Spin(values=('Spin Box 1', '2', '3'),
initial_value='Spin Box 1')],
[sg.Spin(values=('Spin Box 1', '2', '3'),
initial_value='Spin Box 2')],
[sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]]
layout = [
[sg.Menu(menu_def, tearoff=True)],
[sg.Text('(Almost) All widgets in one Window!', size=(
30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)],
[sg.Text('Here is some text.... and a place to enter text')],
[sg.InputText('This is my text')],
[sg.Frame(layout=[
[sg.CBox('Checkbox', size=(10, 1)),
sg.CBox('My second checkbox!', default=True)],
[sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10, 1)),
sg.Radio('My second Radio!', "RADIO1")]], title='Options',
title_color='red',
relief=sg.RELIEF_SUNKEN,
tooltip='Use these to set flags')],
[sg.MLine(default_text='This is the default Text should you decide not to type anything', size=(35, 3)),
sg.MLine(default_text='A second multi-line', size=(35, 3))],
[sg.Combo(('Combobox 1', 'Combobox 2'), size=(20, 1)),
sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)],
[sg.OptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))],
[sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)),
sg.Frame('Labelled Group', [[
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25, tick_interval=25),
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75),
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10),
sg.Col(column1, background_color='lightblue')]])
],
[sg.Text('_' * 80)],
[sg.Text('Choose A Folder', size=(35, 1))],
[sg.Text('Your Folder', size=(15, 1), justification='right'),
sg.InputText('Default Folder'), sg.FolderBrowse()],
[sg.Submit(tooltip='Click to submit this form'), sg.Cancel()]]
window = sg.Window('Everything bagel', layout,
default_element_size=(40, 1), grab_anywhere=False)
event, values = window.read()
sg.popup('Title',
'The results of the window.',
'The button clicked was "{}"'.format(event),
'The values are', values)
PySimpleGUI 通过使用 Python 列表来布局它们的小部件。您还可以看到,这个演示也使用列表来生成菜单。然后创建一个窗口对象,并传入布局,这是您的元素列表或小部件列表。
让我们看看你还能做什么!
使用 PySimpleGUI 绘图
PySimpleGUI 还支持创建图形。一个这样的例子可以在Demo _ Graph _ Element _ Sine _ wave . py中找到。这个演示向开发者展示了如何使用 Graph 小部件。
这是运行演示时的样子:
下面是代码的样子:
import PySimpleGUI as sg
import math
# Yet another usage of Graph element.
SIZE_X = 200
SIZE_Y = 100
NUMBER_MARKER_FREQUENCY = 25
def draw_axis():
graph.draw_line((-SIZE_X, 0), (SIZE_X, 0)) # axis lines
graph.draw_line((0, -SIZE_Y), (0, SIZE_Y))
for x in range(-SIZE_X, SIZE_X+1, NUMBER_MARKER_FREQUENCY):
graph.draw_line((x, -3), (x, 3)) # tick marks
if x != 0:
# numeric labels
graph.draw_text(str(x), (x, -10), color='green')
for y in range(-SIZE_Y, SIZE_Y+1, NUMBER_MARKER_FREQUENCY):
graph.draw_line((-3, y), (3, y))
if y != 0:
graph.draw_text(str(y), (-10, y), color='blue')
# Create the graph that will be put into the window
graph = sg.Graph(canvas_size=(400, 400),
graph_bottom_left=(-(SIZE_X+5), -(SIZE_Y+5)),
graph_top_right=(SIZE_X+5, SIZE_Y+5),
background_color='white',
key='graph')
# Window layout
layout = [[sg.Text('Example of Using Math with a Graph', justification='center', size=(50, 1), relief=sg.RELIEF_SUNKEN)],
[graph],
[sg.Text('y = sin(x / x2 * x1)', font='COURIER 18')],
[sg.Text('x1'), sg.Slider((0, 200), orientation='h',
enable_events=True, key='-SLIDER-')],
[sg.Text('x2'), sg.Slider((1, 200), orientation='h', enable_events=True, key='-SLIDER2-')]]
window = sg.Window('Graph of Sine Function', layout)
while True:
event, values = window.read()
if event is None:
break
graph.erase()
draw_axis()
prev_x = prev_y = None
for x in range(-SIZE_X, SIZE_X):
y = math.sin(x/int(values['-SLIDER2-']))*int(values['-SLIDER-'])
if prev_x is not None:
graph.draw_line((prev_x, prev_y), (x, y), color='red')
prev_x, prev_y = x, y
window.close()
为了使图形正确工作,您需要擦除图形,并在上面的 while 循环中重新绘制。稍微摆弄一下代码,看看能做些什么。demo 文件夹中还有几个与图形相关的演示,您也应该看看。
PySimpleGUI 还支持 matplotlib 集成。一个有趣的游戏是Demo _ Matplotlib _ animated . py。
当我运行它时,演示结果如下:
现在让我们来看看另一个演示!
用 PySimpleGUI 创建 Pong
正如我在本文前面提到的,您也可以使用 PySimpleGUI 非常容易地创建 Pong 游戏。可以查看 Demo_Pong.py 了解全部详情。
以下是运行该代码时所创建的内容:
这个游戏的代码有点长,但是不难理解。在编写的时候,这个游戏是在一个模块中使用 183 行代码编写的。
包扎
PySimpleGUI 的演示文件夹里有 150+个演示。我确实发现了一些由于使用特定于操作系统的代码而无法在 Linux 上工作的问题。然而,大多数例子似乎是可行的,它们是一个很好的方式来看看你可以用这个项目做什么。查看它们,了解如何将 PySimpleGUI 用于您自己的项目或演示。
相关阅读
- 简要介绍 PySimpleGUI
- PySimpleGUI 文档
- 其他简单的 PySimpleGUI 应用
wxPython 的对话框(第 1 部分,共 2 部分)
原文:https://www.blog.pythonlibrary.org/2010/06/26/the-dialogs-of-wxpython-part-1-of-2/
对话框是用户界面设计不可或缺的一部分。我们一直在使用它们。我们到处都能找到各种形状和大小的对话框。在本文中,我们将介绍以下对话框类型:
- wx.BusyInfo
- wx(地名)。ColourDialog(放置对话框)
- 立方结构对话方块(AGW)
- wx。DirDialog 和 MultiDirDialog (AGW)
- wx . file 对话方块
- wx 字体对话框
- wx.MessageDialog
这是很多对话框,但是在 wxPython 演示中还有八个。我们下次会讲到这些。现在,让我们来看看这个列表!
wx。Busy info——让用户知道你很忙
BusyInfo 对话框并不广为人知。出于某种原因,它甚至没有在 wxPython 演示应用程序中演示。为了让你看得开心,我们现在就制作一个。
import time
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"BusyDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
busyBtn = wx.Button(panel, label="Show Busy Dialog")
busyBtn.Bind(wx.EVT_BUTTON, self.onBusy)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(busyBtn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onBusy(self, event):
self.Hide()
msg = "Please wait while we process your request..."
busyDlg = wx.BusyInfo(msg)
time.sleep(5)
busyDlg = None
self.Show()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
幸运的是, BusyInfo 对话框也是最容易创建的对话框之一。你需要做的就是用一个字符串实例化它。关闭该对话框的常用方法不是销毁它,而是将实例设置为 None。这有点奇怪,但它工作得很好。如果需要的话,你也可以销毁它,但是我从来不需要也没有见过其他人这样做的例子。
用 wx 选择颜色。ColourDialog 和 CubeColourDialog
在 wxPython 发行版中有两个颜色选择器对话框。我们要看的第一个是原生对话框,wx.ColourDialog。Windows XP 上的 ColourDialog 看起来像:
对于这个例子,我们将创建一个简单的双按钮表单,可以打开两种类型的颜色对话框。让我们来看看:
import wx
import wx.lib.agw.cubecolourdialog as CCD
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"File and Folder Dialogs Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
self.colourData = None
colorDlgBtn = wx.Button(panel, label="Open ColorDialog")
colorDlgBtn.Bind(wx.EVT_BUTTON, self.onColorDlg)
colorCubeBtn = wx.Button(panel, label="Open ColorCubeDialog")
colorCubeBtn.Bind(wx.EVT_BUTTON, self.onCubeColorDialog)
# put the buttons in a sizer
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(colorDlgBtn, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(colorCubeBtn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onColorDlg(self, event):
"""
This is mostly from the wxPython Demo!
"""
dlg = wx.ColourDialog(self)
# Ensure the full colour dialog is displayed,
# not the abbreviated version.
dlg.GetColourData().SetChooseFull(True)
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetColourData()
print 'You selected: %s\n' % str(data.GetColour().Get())
dlg.Destroy()
#----------------------------------------------------------------------
def onCubeColorDialog(self, event):
"""
This is mostly from the wxPython Demo!
"""
self.colourData.SetColour(self.GetBackgroundColour())
dlg = CCD.CubeColourDialog(self, self.colourData)
if dlg.ShowModal() == wx.ID_OK:
# If the user selected OK, then the dialog's wx.ColourData will
# contain valid information. Fetch the data ...
self.colourData = dlg.GetColourData()
h, s, v, a = dlg.GetHSVAColour()
# ... then do something with it. The actual colour data will be
# returned as a three-tuple (r, g, b) in this particular case.
colour = self.colourData.GetColour()
self.SetBackgroundColour(self.colourData.GetColour())
self.Refresh()
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
如你所见,wx。ColourDialog 非常容易实例化(参见 onColorDlg 方法)。你需要做的就是调用对话框。wxPython 演示还包含了一行代码,展示了如何显示对话框的全彩版本,所以我们在这个例子中包含了这一行代码(和注释): dlg。GetColourData()。SetChooseFull(True)。最后,我们调用对话框的 ShowModal 方法来显示对话框。为了获得用户的颜色选择,我们遵循两步过程。首先,我们使用数据= dlg 获取所有颜色数据。GetColourData() 。然后为了获得特定的颜色,我们调用数据对象的 GetColour()。Get() 方法。
现在让我们看看 Windows XP 上的立方体颜色对话框是什么样子的:
注意:如果您使用的不是 wxPython 2.8.11.0 或这个小部件的 SVN 版本,您可能会收到一个与设置 alpha 相关的错误。
CubeColourDialog 是用纯 python 编写的,而不是用 SWIG 包装的 C++小部件。这使得 CubeColourDialog 更容易编辑和增强。让我们看看上面使用的 onCubeColorDialog 方法的代码:
def onCubeColorDialog(self, event):
"""
This is mostly from the wxPython Demo!
"""
self.colourData.SetColour(self.GetBackgroundColour())
dlg = CCD.CubeColourDialog(self, self.colourData)
if dlg.ShowModal() == wx.ID_OK:
# If the user selected OK, then the dialog's wx.ColourData will
# contain valid information. Fetch the data ...
self.colourData = dlg.GetColourData()
h, s, v, a = dlg.GetHSVAColour()
# ... then do something with it. The actual colour data will be
# returned as a three-tuple (r, g, b) in this particular case.
colour = self.colourData.GetColour()
self.SetBackgroundColour(self.colourData.GetColour())
self.Refresh()
dlg.Destroy()
首先,我们将 colourData 变量设置为父小部件的背景色。我不太清楚为什么我们要这样做,但我认为这是因为我们希望对话框匹配其父对话框的颜色。接下来创建对话框的一个实例并显示它。如果用户按下 OK 按钮,我们就会得到他们选择的颜色。注意,我们可以通过调用对话框的 GetHSVAColour 方法来获得所选颜色的色调、饱和度、亮度和 alpha 分量。在这个例子中,我们实际上并不使用这些信息,但是在您的应用程序中可能会很方便。要真正选择颜色,我们只需从更新后的 colourData 实例中使用名副其实的 GetColour()方法。最后,我们将应用程序的背景设置为新选择的颜色。很整洁,是吧?
还有另一个名为 PyColourChooser 的纯 python 颜色选择器。然而,它不是一个独立的对话框,所以我们不会在这里讨论它。
用 wx 打开文件和文件夹。文件对话框,wx。DirDialog 和 MultiDirDialog (AGW)
用 wxPython 打开文件和文件夹轻而易举!它包括本机对话框的包装器,还包括同时选择多个文件夹(即目录)的纯 python 实现。让我们先看一下示例代码,然后我们将讨论每个对话框的细节。
import os
import wx
import wx.lib.agw.multidirdialog as MDD
wildcard = "Python source (*.py)|*.py|" \
"All files (*.*)|*.*"
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"File and Folder Dialogs Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
self.currentDirectory = os.getcwd()
# create the buttons and bindings
openFileDlgBtn = wx.Button(panel, label="Show OPEN FileDialog")
openFileDlgBtn.Bind(wx.EVT_BUTTON, self.onOpenFile)
saveFileDlgBtn = wx.Button(panel, label="Show SAVE FileDialog")
saveFileDlgBtn.Bind(wx.EVT_BUTTON, self.onSaveFile)
dirDlgBtn = wx.Button(panel, label="Show DirDialog")
dirDlgBtn.Bind(wx.EVT_BUTTON, self.onDir)
multiDirDlgBtn = wx.Button(panel, label="Show MultiDirDialog")
multiDirDlgBtn.Bind(wx.EVT_BUTTON, self.onMultiDir)
# put the buttons in a sizer
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(openFileDlgBtn, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(saveFileDlgBtn, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(dirDlgBtn, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(multiDirDlgBtn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onDir(self, event):
"""
Show the DirDialog and print the user's choice to stdout
"""
dlg = wx.DirDialog(self, "Choose a directory:",
style=wx.DD_DEFAULT_STYLE
#| wx.DD_DIR_MUST_EXIST
#| wx.DD_CHANGE_DIR
)
if dlg.ShowModal() == wx.ID_OK:
print "You chose %s" % dlg.GetPath()
dlg.Destroy()
#----------------------------------------------------------------------
def onMultiDir(self, event):
"""
Create and show the MultiDirDialog
"""
dlg = MDD.MultiDirDialog(self, title="Choose a directory:",
defaultPath=self.currentDirectory,
agwStyle=0)
if dlg.ShowModal() == wx.ID_OK:
paths = dlg.GetPaths()
print "You chose the following file(s):"
for path in paths:
print path
dlg.Destroy()
#----------------------------------------------------------------------
def onOpenFile(self, event):
"""
Create and show the Open FileDialog
"""
dlg = wx.FileDialog(
self, message="Choose a file",
defaultDir=self.currentDirectory,
defaultFile="",
wildcard=wildcard,
style=wx.FD_OPEN | wx.FD_MULTIPLE | wx.FD_CHANGE_DIR
)
if dlg.ShowModal() == wx.ID_OK:
paths = dlg.GetPaths()
print "You chose the following file(s):"
for path in paths:
print path
dlg.Destroy()
#----------------------------------------------------------------------
def onSaveFile(self, event):
"""
Create and show the Save FileDialog
"""
dlg = wx.FileDialog(
self, message="Save file as ...",
defaultDir=self.currentDirectory,
defaultFile="", wildcard=wildcard, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
print "You chose the following filename: %s" % path
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
打开和保存文件对话框
wx。FileDialog 有两个版本:打开和保存文件版本。上面是开放版本,下面是保存版本:
让我们来看看每一项的代码:
def onOpenFile(self, event):
"""
Create and show the Open FileDialog
"""
dlg = wx.FileDialog(
self, message="Choose a file",
defaultDir=self.currentDirectory,
defaultFile="",
wildcard=wildcard,
style=wx.FD_OPEN | wx.FD_MULTIPLE | wx.FD_CHANGE_DIR
)
if dlg.ShowModal() == wx.ID_OK:
paths = dlg.GetPaths()
print "You chose the following file(s):"
for path in paths:
print path
dlg.Destroy()
#----------------------------------------------------------------------
def onSaveFile(self, event):
"""
Create and show the Save FileDialog
"""
dlg = wx.FileDialog(
self, message="Save file as ...",
defaultDir=self.currentDirectory,
defaultFile="", wildcard=wildcard, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
print "You chose the following filename: %s" % path
dlg.Destroy()
方法显示了如何创建开放版本。您可以设置对话框的标题,对话框将在哪个目录下打开,它将显示哪些文件,选择的默认文件,您是否可以选择多个文件(wx。多个标志)以及是否可以更改目录(通过 CHANGE_DIR 标志)。在这个例子中,我们使用对话框的 GetPaths 方法获取用户选择的一个或多个路径,然后将这些选择打印到 stdout。
对话框的保存版本几乎相同。唯一不同的是我们把样式改成了 wx.SAVE。
wx。目录对话框
目录对话框比文件对话框更容易实例化!下面是简单的代码:
#----------------------------------------------------------------------
def onDir(self, event):
"""
Show the DirDialog and print the user's choice to stdout
"""
dlg = wx.DirDialog(self, "Choose a directory:",
style=wx.DD_DEFAULT_STYLE
#| wx.DD_DIR_MUST_EXIST
#| wx.DD_CHANGE_DIR
)
if dlg.ShowModal() == wx.ID_OK:
print "You chose %s" % dlg.GetPath()
dlg.Destroy()
在这个对话框中,我们只能更改几个项目:标题、目录是否必须已经存在以及用户是否可以更改目录。您还可以通过 defaultPath 参数设置它启动的默认目录。
多重对话(AGW)
来自 AGW 库的 MultiDirDialog 是 wx 的纯 python 实现。DirDialog,但是有更多的特性。例如,它允许用户一次选择多个目录。下面是如何使用它的一个简单示例:
def onMultiDir(self, event):
"""
Create and show the MultiDirDialog
"""
dlg = MDD.MultiDirDialog(self, title="Choose a directory:",
defaultPath=self.currentDirectory,
agwStyle=0)
if dlg.ShowModal() == wx.ID_OK:
paths = dlg.GetPaths()
print "You chose the following file(s):"
for path in paths:
print path
dlg.Destroy()
虽然这段代码没有显示出来,但是您也可以设置是否允许用户创建一个新目录。除此之外,还有特殊的 agwStyle 标志,这个对话框没有太大的不同。尽管如此,因为它是用 python 编写的,所以使用起来很方便,而且你可以比 wx 更容易地增强它。目录对话框版本。
wx 字体对话框
字体对话框给予用户选择字体的能力。对于这个例子,我们将使用 wxPython 演示本身的代码的稍微修改的版本:
import wx
from wx.lib import stattext
#---------------------------------------------------------------------------
class TestPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
btn = wx.Button(self, -1, "Select Font")
self.Bind(wx.EVT_BUTTON, self.OnSelectFont, btn)
self.sampleText = stattext.GenStaticText(self, -1, "Sample Text")
self.sampleText.SetBackgroundColour(wx.WHITE)
self.curFont = self.sampleText.GetFont()
self.curClr = wx.BLACK
fgs = wx.FlexGridSizer(cols=2, vgap=5, hgap=5)
fgs.AddGrowableCol(1)
fgs.AddGrowableRow(0)
fgs.Add(btn)
fgs.Add(self.sampleText, 0, wx.ADJUST_MINSIZE|wx.GROW)
fgs.Add((15,15)); fgs.Add((15,15)) # an empty row
fgs.Add(wx.StaticText(self, -1, "PointSize:"))
self.ps = wx.StaticText(self, -1, "")
font = self.ps.GetFont()
font.SetWeight(wx.BOLD)
self.ps.SetFont(font)
fgs.Add(self.ps, 0, wx.ADJUST_MINSIZE)
fgs.Add(wx.StaticText(self, -1, "Family:"))
self.family = wx.StaticText(self, -1, "")
self.family.SetFont(font)
fgs.Add(self.family, 0, wx.ADJUST_MINSIZE)
fgs.Add(wx.StaticText(self, -1, "Style:"))
self.style = wx.StaticText(self, -1, "")
self.style.SetFont(font)
fgs.Add(self.style, 0, wx.ADJUST_MINSIZE)
fgs.Add(wx.StaticText(self, -1, "Weight:"))
self.weight = wx.StaticText(self, -1, "")
self.weight.SetFont(font)
fgs.Add(self.weight, 0, wx.ADJUST_MINSIZE)
fgs.Add(wx.StaticText(self, -1, "Face:"))
self.face = wx.StaticText(self, -1, "")
self.face.SetFont(font)
fgs.Add(self.face, 0, wx.ADJUST_MINSIZE)
fgs.Add((15,15)); fgs.Add((15,15)) # an empty row
fgs.Add(wx.StaticText(self, -1, "wx.NativeFontInfo:"))
self.nfi = wx.StaticText(self, -1, "")
self.nfi.SetFont(font)
fgs.Add(self.nfi, 0, wx.ADJUST_MINSIZE)
# give it some border space
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(fgs, 0, wx.GROW|wx.ADJUST_MINSIZE|wx.ALL, 25)
self.SetSizer(sizer)
self.UpdateUI()
def UpdateUI(self):
self.sampleText.SetFont(self.curFont)
self.sampleText.SetForegroundColour(self.curClr)
self.ps.SetLabel(str(self.curFont.GetPointSize()))
self.family.SetLabel(self.curFont.GetFamilyString())
self.style.SetLabel(self.curFont.GetStyleString())
self.weight.SetLabel(self.curFont.GetWeightString())
self.face.SetLabel(self.curFont.GetFaceName())
self.nfi.SetLabel(self.curFont.GetNativeFontInfo().ToString())
self.Layout()
def OnSelectFont(self, evt):
data = wx.FontData()
data.EnableEffects(True)
data.SetColour(self.curClr) # set colour
data.SetInitialFont(self.curFont)
dlg = wx.FontDialog(self, data)
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetFontData()
font = data.GetChosenFont()
colour = data.GetColour()
self.curFont = font
self.curClr = colour
self.UpdateUI()
# Don't destroy the dialog until you get everything you need from the
# dialog!
dlg.Destroy()
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"wx.FontDialog Tutorial")
panel = TestPanel(self)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
在这个例子中,我们在 OnSelectFont 方法中创建和显示了对话框。出于我不完全理解的原因,我们立即创建了一个字体数据对象,并为它设置了各种属性。然后,如果用户选择了一种字体并点击了 OK 按钮,我们会转储这些设置并根据用户的选择创建新的设置。我们从用户选择的字体中提取字体和颜色数据,并设置一些类属性。接下来我们调用 UpdateUI 方法。这将更新我们的 GenStaticText 来显示选择的字体。请随意查看在 UpdateUI 方法中是如何实现的。
wx.MessageDialog
wx。MessageDialog 用于向用户提供某种消息或询问一个简单的问题。以下是一些截图示例:
现在让我们快速看一下如何创建这些对话框!
# http://www.wxpython.org/docs/api/wx.MessageDialog-class.html
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"MessageDialog Tutorial")
self.panel = wx.Panel(self, wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
buttons = [("Exclamation", self.onExclamation),
("Information", self.onInfo),
("Question", self.onQuestion)
]
for label, handler in buttons:
self.createAndLayoutButtons(label, handler, sizer)
self.panel.SetSizer(sizer)
#----------------------------------------------------------------------
def createAndLayoutButtons(self, label, handler, sizer):
""""""
button = wx.Button(self.panel, label=label)
button.Bind(wx.EVT_BUTTON, handler)
sizer.Add(button, 0, wx.ALL|wx.CENTER, 5)
#----------------------------------------------------------------------
def onExclamation(self, event):
""""""
msg = "You have an encountered an unknown error. Would you like to continue?"
self.showMessageDlg(msg, "ERROR",
wx.YES_NO|wx.YES_DEFAULT|wx.ICON_EXCLAMATION)
#----------------------------------------------------------------------
def onInfo(self, event):
"""
This method is fired when its corresponding button is pressed
"""
self.showMessageDlg("This is for informational purposes only!",
"Information", wx.OK|wx.ICON_INFORMATION)
#----------------------------------------------------------------------
def onQuestion(self, event):
""""""
self.showMessageDlg("Why did you push me?", "Question",
wx.OK|wx.CANCEL|wx.ICON_QUESTION)
#----------------------------------------------------------------------
def showMessageDlg(self, msg, title, style):
""""""
dlg = wx.MessageDialog(parent=None, message=msg,
caption=title, style=style)
dlg.ShowModal()
dlg.Destroy()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
如果你看上面的例子,你会很快注意到我们可以只使用样式标志来改变 MessageDialog 的外观:
- wx。YES_NO 为对话框提供“是”和“否”按钮
- wx。“确定”只给了我们一个“确定”按钮
- wx。好|wx。取消创建“确定”和“取消”按钮
- wx。ICON _ 感叹号在对话框中放置一个黄色的三角形图标
- wx。ICON_INFORMATION 创建一个圆形蓝色图标
- wx。ICON_QUESTION 给我们一个问号图标
在文档中还列出了几个我们可以使用的标志,但它们大多是多余的。不过,请随意阅读它们。
包扎
在这篇文章中,我们已经讨论了很多内容。现在,您已经知道如何创建 wxPython 中提供的大约一半标准对话框。我们将在本文的第二部分看另一半。同时,我希望你已经发现这是有启发性的。欢迎在评论中提问!
注意:此代码在以下位置进行了测试:
- 视窗 XP 专业版,wxPython 2.8.10.1/2 . 8 . 11 . 0,Python 2.5
- Windows 7 家庭版,wxPython 2.8.10.1,Python 2.6
如果你想看这篇文章的第二部分,点击这里
附加阅读
- wx。BusyInfo 文档或者查看维基页面
- wx。颜色对话框文档
- CubeColourDialog (AGW) 文档(另见加瓦纳的文档)
- PyColourChooser 文档
- wx。导演对话和多导演对话 ( AGW )
- wx。文件对话框文档
- wx。字体对话框文档
- wx。消息对话框文档
下载
wxPython 的对话框(第 2 部分,共 2 部分)
原文:https://www.blog.pythonlibrary.org/2010/07/10/the-dialogs-of-wxpython-part-2-of-2/
在本系列的后半部分,我发现对话框比我原来想象的还要多。虽然将它分成三部分可能是个好主意,但我们将坚持只分成两部分。在本文中,我们将讨论以下对话:
- 通用消息对话框(AGW)
- 图像对话框
- wx。多选对话框
- wx.PageSetupDialog
- wx。打印对话框
- wx。进度对话框
- PyBusyInfo (AGW)
- PyProgress (AGW)
- ScrolledMessageDialog
- wx。单选对话框
- wx 文本交互组件
对于门外汉来说,还有一个 AboutBox 对话框,这里没有,原因很简单,它已经在本系列之外的博客中讨论过了。你自己去看看。只是想澄清一下为什么 wx?对话框不在这里:本系列只讨论预构建的对话框。wx。对话框小部件非常适合创建你自己的自定义对话框。需要注意的最后一点是,这里的示例代码是从 wxPython 演示中提取出来的,并重新用于本文。
现在,继续表演!
GenericMessageDialog
GenericMessageDialog 是 Andrea Gavana 创建的 AGW 通用控件库的一部分。它比 MessageDialog 本身给了我们更多的选择,因为 GenericMessageDialog 是用纯 python 编写的,因此更容易被黑客攻击。让我们看看如何在屏幕截图中创建对话框:
import images
import wx
import wx.lib.agw.genericmessagedialog as GMD
_msg = "This is the about dialog of GenericMessageDialog demo.\n\n" + \
"Author: Andrea Gavana @ 07 Oct 2008\n\n" + \
"Please report any bugs/requests of improvements\n" + \
"to me at the following addresses:\n\n" + \
"andrea.gavana@gmail.com\n" + "gavana@kpo.kz\n\n" + \
"Welcome to wxPython " + wx.VERSION_STRING + "!!"
########################################################################
class MyForm(wx.Frame):
"""
Based on Andrea Gavana's demo from the wxPython Demo
"""
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Generic Message Dialog Tutorial",
size=(575,225))
self.mainPanel = wx.Panel(self)
self.buttonSizer_staticbox = wx.StaticBox(self.mainPanel, -1, "Buttons Styles")
self.ok = wx.CheckBox(self.mainPanel, -1, "wx.OK")
self.yes_no = wx.CheckBox(self.mainPanel, -1, "wx.YES_NO")
self.cancel = wx.CheckBox(self.mainPanel, -1, "wx.CANCEL")
self.yes = wx.CheckBox(self.mainPanel, -1, "wx.YES")
self.no = wx.CheckBox(self.mainPanel, -1, "wx.NO")
self.no_default = wx.CheckBox(self.mainPanel, -1, "wx.NO_DEFAULT")
self.help = wx.CheckBox(self.mainPanel, -1, "wx.HELP")
self.dialogStyles = wx.RadioBox(self.mainPanel, -1, "Dialog Styles",
choices=["wx.ICON_INFORMATION", "wx.ICON_WARNING",
"wx.ICON_EXCLAMATION", "wx.ICON_ERROR",
"wx.ICON_QUESTION"],
majorDimension=5, style=wx.RA_SPECIFY_ROWS)
self.showDialog = wx.Button(self.mainPanel, -1, "Show GenericMessageDialog")
self.SetProperties()
self.DoLayout()
self.Bind(wx.EVT_BUTTON, self.OnShowDialog, self.showDialog)
self.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
def SetProperties(self):
self.ok.SetValue(1)
self.dialogStyles.SetSelection(0)
self.showDialog.SetDefault()
def DoLayout(self):
frameSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
buttonSizer = wx.StaticBoxSizer(self.buttonSizer_staticbox, wx.VERTICAL)
buttonSizer.Add(self.ok, 0, wx.LEFT|wx.RIGHT|wx.TOP, 5)
buttonSizer.Add((0, 2), 0, 0, 0)
buttonSizer.Add(self.yes_no, 0, wx.LEFT|wx.RIGHT, 5)
buttonSizer.Add((0, 2), 0, 0, 0)
buttonSizer.Add(self.cancel, 0, wx.LEFT|wx.RIGHT, 5)
buttonSizer.Add((0, 2), 0, 0, 0)
buttonSizer.Add(self.yes, 0, wx.LEFT|wx.RIGHT, 5)
buttonSizer.Add((0, 2), 0, 0, 0)
buttonSizer.Add(self.no, 0, wx.LEFT|wx.RIGHT, 5)
buttonSizer.Add((0, 2), 0, 0, 0)
buttonSizer.Add(self.no_default, 0, wx.LEFT|wx.RIGHT, 5)
buttonSizer.Add((0, 2), 0, 0, 0)
buttonSizer.Add(self.help, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM, 5)
mainSizer.Add(buttonSizer, 0, wx.ALL, 5)
mainSizer.Add(self.dialogStyles, 0, wx.ALL, 5)
mainSizer.Add((10, 0), 0, 0, 0)
mainSizer.Add(self.showDialog, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 10)
mainSizer.Add((10, 0), 0, 0, 0)
self.mainPanel.SetSizer(mainSizer)
frameSizer.Add(self.mainPanel, 1, wx.EXPAND)
self.SetSizer(frameSizer)
frameSizer.Layout()
def OnCheckBox(self, event):
obj = event.GetEventObject()
widgets = [self.yes, self.yes_no, self.no, self.no_default]
if not event.IsChecked():
return
if obj == self.ok:
for checks in widgets:
checks.SetValue(0)
elif obj in widgets:
self.ok.SetValue(0)
def OnShowDialog(self, event):
btnStyle = 0
for child in self.mainPanel.GetChildren():
if isinstance(child, wx.CheckBox):
if child.GetValue():
btnStyle |= eval(child.GetLabel())
dlgStyle = eval(self.dialogStyles.GetStringSelection())
dlg = GMD.GenericMessageDialog(self, _msg,
"A Nice Message Box",
btnStyle | dlgStyle)
dlg.SetIcon(images.Mondrian.GetIcon())
dlg.ShowModal()
dlg.Destroy()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
在任何示例中,首先要寻找的是文件顶部导入的内容。在本例中,我们使用 wxPython 演示中的 images.py 文件来提供本例中的一些图像。我们还从wx . lib . agw . genericmessagedialog中导入我们需要的对话框。我们可以跳过框架的初始化代码,因为我们想把重点放在对话框上。我们看到的第一个对话框相关的方法是设置属性。它在对话框中设置了一些默认选项,如选中“ok”复选框,选择第一个单选按钮,并将焦点放在将打开通用对话框的按钮上。前两个将告诉我们的对话框创建方法 OnShowDialog ,我们想要什么样的对话框。因此,我们要看的下一个方法是 OnShowDialog 方法。
在这里,我们发现 GenericMessageDialog 采用了与普通 MessageDialog 几乎相同的值,即父项、消息、标题和一些样式。如果适用,您还可以设置其大小、位置和 agwstyle。我们还可以通过调用 SetIcon 向对话框添加图标。你有它!一个通用的消息对话框!
使用图像对话框查看图像
ImageDialog 是一个方便的小对话框,可以用来预览各种图像格式。这个对话框是另一个通用对话框,但它不是 AGW 库的一部分。ImageDialog 非常方便,非常容易创建。有多简单?我们来看看下面的代码,自己看看吧!
import os
import wx
import wx.lib.imagebrowser as ib
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"ImageDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a ImageDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo by the same name
"""
# get current working directory
dir = os.getcwd()
# set the initial directory for the demo bitmaps
initial_dir = os.path.join(dir, 'bitmaps')
# open the image browser dialog
dlg = ib.ImageDialog(self, initial_dir)
dlg.Centre()
if dlg.ShowModal() == wx.ID_OK:
# show the selected file
print "You Selected File: " + dlg.GetFile()
else:
print "You pressed Cancel"
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
如果你查看一下 onButton 方法,你可以看到我们是如何初始化这个对话框的。基本上,你只需要给控件提供一个父控件和一个装满图片的目录,然后显示对话框。就是这样!我没告诉你这很容易吗?
用 wx 做选择。多选对话框
如果你需要向用户询问一条或多条简单的信息,你会怎么做?你可以创建自己的控件,也可以选择 wx。多选对话框。多选择对话框使您能够提供一个允许用户选择多个选项的对话框。是的,这很明显...如此这般地进行编码!
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"MultiChoiceDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a wx.MultiChoiceDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo - opens the MultiChoiceDialog
and prints the user's selection(s) to stdout
"""
lst = ["Python", "Ruby", "Perl", "PHP"]
dlg = wx.MultiChoiceDialog( self,
"Pick some programming languages",
"wx.MultiChoiceDialog", lst)
if (dlg.ShowModal() == wx.ID_OK):
selections = dlg.GetSelections()
strings = [lst[x] for x in selections]
print "You chose:" + str(strings)
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
多选择对话框和大多数对话框一样。它接受父标签、描述性标签、窗口标题和选项列表。要获得用户的选择,你需要做的就是调用对话框的 GetSelections 方法。就是这样!
wx.PageSetupDialog
wx。PageSetupDialog 用于设置打印页面的一些参数,或者只是设置页面进行编辑。您可以使用它来调整纸张大小、页边距以及页面应该是横向还是纵向。下面是设置的方法:
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"PageSetupDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a PageSetupDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo - sets some default values for the
PageSetupDialog, then opens it. If the user presses OK, the
user's choices are extracted and printed to stdout
"""
data = wx.PageSetupDialogData()
data.SetMarginTopLeft( (15, 15) )
data.SetMarginBottomRight( (15, 15) )
#data.SetDefaultMinMargins(True)
data.SetPaperId(wx.PAPER_LETTER)
dlg = wx.PageSetupDialog(self, data)
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetPageSetupData()
tl = data.GetMarginTopLeft()
br = data.GetMarginBottomRight()
print 'Margins are: %s %s\n' % (str(tl), str(br))
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
让我们看看 onButton 方法。首先,我们需要为对话框设置一些默认值,这就是前四行的目的。接下来我们需要获得用户的选择,所以在条件 IF 语句中我们看到如何访问对话框中的一些设置,例如 GetMarginTopLeft 或 GetMarginBottomRight 。您可以使用其他 getters 来获取其他信息,如 GetPaperSize 。注意,要访问这些方法,首先必须创建一个基于 GetPageSetupData 的对象。
wx。打印对话框
wx。PrintDialog 允许用户选择打印到哪台打印机,并为用户提供正常的选项,如果用户试图打印某样东西,就会看到这些选项。这是一个围绕平台的本地打印对话框的方便的包装器。你可以这样做:
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"PrintDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a wx.PrintDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, evt):
"""
Based on the wxPython demo - Sets up a few defaults for the dialog
before showing it modally.
"""
data = wx.PrintDialogData()
data.EnableSelection(True)
data.EnablePrintToFile(True)
data.EnablePageNumbers(True)
data.SetMinPage(1)
data.SetMaxPage(5)
data.SetAllPages(True)
dlg = wx.PrintDialog(self, data)
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetPrintDialogData()
print 'GetAllPages: %d\n' % data.GetAllPages()
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
与 PageSetupDialog 非常相似,我们也需要为这个对话框设置一些默认值。在这里,我们设置最小和最大页数,启用打印到文件,等等。当您获取用户的选择时,您可能希望保存它们并使用该信息实例化对话框,这样他们就不必在想要再次打印时重新定制他们的打印作业。总之,为了实际获得用户的选择,我们调用对话框的 GetPrintDialogData 方法,然后使用结果来查询其他部分。我们的例子只是展示了如何获取所有要打印的页面。您可以通过执行以下操作来深入查看更多信息:
pd = data.GetPrintData()
>>> pd.Duplex
0
>>> pd.GetFilename()
u''
>>> pd.GetOrientation()
1
>>> pd.GetPaperSize()
wx.Size(210, 297)
>>> pd.GetPrintMode()
您将不得不参考 wxPython、wxWidgets 甚至您的平台的文档来弄清楚一些结果的含义。
wx。进度对话框
wx。ProgressDialog 可以用来向用户显示下载进行到什么程度,或者向用户提供关于其他长时间运行过程的反馈。你也可以使用 wx。Gauge 或 PyProgress (AGW)向用户提供类似的信息。我们很快就会看到这一点,但首先我们要看看如何创建 wx。进度对话框:
# progressDialog.py
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"ProgressDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a ProgressDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, evt):
"""
Copied from the wxPython demo
"""
max = 80
dlg = wx.ProgressDialog("Progress dialog example",
"An informative message",
maximum = max,
parent=self,
style = wx.PD_CAN_ABORT
| wx.PD_APP_MODAL
| wx.PD_ELAPSED_TIME
#| wx.PD_ESTIMATED_TIME
| wx.PD_REMAINING_TIME
)
keepGoing = True
count = 0
while keepGoing and count < max:
count += 1
wx.MilliSleep(250)
if count >= max / 2:
(keepGoing, skip) = dlg.Update(count, "Half-time!")
else:
(keepGoing, skip) = dlg.Update(count)
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
如您所见,ProgressDialog 接受以下参数:窗口标题、消息、运行时间、父元素和各种样式。我们添加了 wx。PD_CAN_ABORT 样式允许我们中止对话框。其余的风格和方法都是不言自明的。
PyBusyInfo (AGW)
在上一篇文章中,我们介绍了 BusyDlg。嗯,Andrea Gavana 写了一个纯 python 版本的几乎未知的对话框,让我们能够添加图像!由于它是 python,我们可以很容易地向它添加其他特性。下面是创建一个的方法:
import images
import wx
import wx.lib.agw.pybusyinfo as PBI
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"PyBusyInfo Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Test PyBusyInfo")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo by the same name
"""
event.Skip()
message = "Please wait 5 seconds, working..."
busy = PBI.PyBusyInfo(message, parent=None, title="Really Busy",
icon=images.Smiles.GetBitmap())
wx.Yield()
for indx in xrange(5):
wx.MilliSleep(1000)
del busy
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
注意到 PyBusyInfo 也有一个古怪的方法来破坏对话框。在这种情况下,我们称 python 的内置为“del”。希望 Gavana 先生会给它添加一个 Destroy()方法,这样对话框就可以用标准的方式销毁了。还要注意我们是如何添加图标的,只需将图标参数传入即可。
PyProgress (AGW)
勤劳的安德里亚·加瓦纳为我们带来了对话。是的,它是另一个 AGW 库小部件,这意味着它也是纯 python。它看起来和 wx.ProgressDialog 有点不同。希望你也会喜欢它的美学。让我们来看看如何制作一个我们自己的:
import images
import wx
import wx.lib.agw.pyprogress as PP
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"PyBusyInfo Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Test PyBusyInfo")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo by the same name
"""
event.Skip()
style = wx.PD_APP_MODAL|wx.PD_ELAPSED_TIME|wx.PD_CAN_ABORT
dlg = PP.PyProgress(None, -1, "PyProgress Example",
"An Informative Message",
style=style)
max = 400
keepGoing = True
count = 0
while keepGoing and count < max:
count += 1
wx.MilliSleep(30)
if count >= max / 2:
keepGoing = dlg.UpdatePulse("Half-time!")
else:
keepGoing = dlg.UpdatePulse()
dlg.Destroy()
wx.SafeYield()
wx.GetApp().GetTopWindow().Raise()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
上面的代码非常像我们到目前为止看到的所有其他对话框。只是为了好玩,我会让你弄清楚这个是如何工作的。
ScrolledMessageDialog
ScrolledMessageDialog 是少数几个不是由 Andrea Gavana 编写的通用对话框之一。它也是最容易创建和使用的工具之一。不相信我?好吧,看看下面的代码,看看你错得有多离谱!
import wx
import wx.lib.dialogs
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"ScrolledMessageDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a ScrolledMessageDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo by the same name
"""
f = open("imageDialog.py", "r")
msg = f.read()
f.close()
dlg = wx.lib.dialogs.ScrolledMessageDialog(self, msg, "message test")
dlg.ShowModal()
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
正如您可以清楚地看到的,您所需要的只是一些要传递给对话框的文本。在本例中,我们读取了另一个 python 文件,并将其传递给对话框。我希望你已经注意到我们为这些通用对话框所做的特殊导入。为此,我们必须导入 wx.lib.dialogs 来访问 ScrolledMessageDialog 。
wx。单选对话框
有时你想给用户一个项目列表,但只允许他选择一个。您可以使用自定义的 wx。或者你可以使用 wx。SingleChoiceDialog 。如果你没猜错,我们将选择后者...
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"SingleChoiceDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a SingleChoiceDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo by the same name
"""
dlg = wx.SingleChoiceDialog(
self, "What's your favorite langauge?", 'The Caption',
["C++", "VB", "Python", "Perl", "Ruby", "FoxPro"],
wx.CHOICEDLG_STYLE
)
if dlg.ShowModal() == wx.ID_OK:
print 'You selected: %s\n' % dlg.GetStringSelection()
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
这里的 SingleChoiceDialog 接受了大多数与其他对话框相同的参数。请注意,我们需要传入一个列表来创建选择列表和 wx。CHOICEDLG_STYLE 样式使对话框正常工作。除此之外,我们去过那里,做过那件事。
wx 文本交互组件
wx。TextEntryDialog 给了我们允许用户告诉我们他想要什么的能力。是的,这很可怕,但有时你不得不这么做。您可以使用这个对话框从您尊敬的用户那里获得非特定的评论或反馈。你可以这样做:
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"TextEntryDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
b = wx.Button(panel, label="Create and Show a TextEntryDialog")
b.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(b, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Based on the wxPython demo by the same name
"""
dlg = wx.TextEntryDialog(
self, 'What is your favorite programming language?',
'Eh??', 'Python')
dlg.SetValue("Python is the best!")
if dlg.ShowModal() == wx.ID_OK:
print "you accepted!"
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
这里我们看到对话框的参数包括:parent、message、caption、defaultValue。当然,当我们调用对话框的 SetValue() 方法时,我们会覆盖 defaultValue。代码的其余部分应该看起来很熟悉。
包扎
如果你能走到这一步,恭喜你!你刚刚艰难地读完了这个网站上有史以来最长的帖子之一!您现在应该是 wxPython 对话框的专家了。如果你想要更多的信息,请随意阅读下面的参考资料,继续你的学习。走出去,开始使用这些对话,以取得良好的效果!
注意:在撰写本文时,除了 wxPython 演示、Wingware 的 IDE 和几页文档之外,我没有使用任何东西。
附加阅读
- GenericMessageDialog - 官方文档, Gavana 的文档,来自 wxPython Wiki 的同名文件
- ImageDialog - 官方文档,部分 java 网站
- wx。多选对话框- 官方文档
- wx。PageSetupDialog - 官方文档,Andrea Gavana 的文档
- wx。打印对话- 官方文档,打印维基页面
- wx。进度对话框- 官方文档
- PyBusyInfo - Andrea Gavana's Docs
- 官方文件,安德里亚·加瓦那的文件
- ScrolledMessageDialog - 官方文档,维基示例
- wx。SingleChoiceDialog - 官方文档
- wx。TextEntryDialog - 官方文档
更多阅读
下载
注意:此代码在以下位置进行了测试:
- 视窗 XP 专业版,wxPython 2.8.10.1/2 . 8 . 11 . 0,Python 2.5
- Windows 7 家庭版,wxPython 2.8.10.1,Python 2.6
DZone 基本核心 Python 备忘单
原文:https://www.blog.pythonlibrary.org/2013/10/28/the-dzone-essential-core-python-cheat-sheet/
几个月前, Dzone 联系我,让我和他们一起完成另一个作者开始的 Python 备忘单。大约两周前,我发现另一位作者是内奥米·塞德。无论如何,经过 DZone 方面的大量审查,他们终于发布了成品。你可以在这里查看
我认为尝试延续别人的想法是非常有趣的,尽管有时也会有点令人沮丧。将来,我想我会喜欢和其他作家一起合作完成一件作品。不过最后,我认为这个项目结果很好,DZone 的人让它看起来非常好。
希望新的 Python 开发人员(也可能是老的)会发现它对学习 Python 很有帮助。
Functools 模块(PyCharm 网络研讨会)
原文:https://www.blog.pythonlibrary.org/2022/05/26/the-functools-module-pycharm-webinar/
你准备好启动你的 Python 技能了吗?Python 编程语言的标准库中有 200 多个模块。在本教程中,您将了解 functools,这是一个专门为处理或返回其他模块而定制的模块。您将了解如何有效地使用 functool decorators、缓存、函数重载等等!
https://www.youtube.com/embed/t4zln38ABsE?feature=oembed
一定要看看我的 YouTube 频道
独立 Python 盛会图书包
原文:https://www.blog.pythonlibrary.org/2021/10/01/the-indie-python-extravaganza-book-bundle/
我正和我的一些独立内容创作者伙伴一起为 2021 年 10 月的月创建一个免费 Python 书籍包!
独立 Python 盛会!
这些书将帮助您一页一页地提高 Python 编程语言的知识。加入四个独立作者的旅程,从 Python 的基础到生产就绪系统的结构,浏览该语言的核心特性,一些中间项目和深入研究正则表达式。
在这个包里,Mike 将用 Python 101 教你 Python 的基础知识。Sundeep 将率先帮助你通过实践 Python 项目将你的知识付诸实践。在 Rodrigo 的Pydon ts书中,学习在用他写 Python 程序时不要做什么!如果你需要学习正则表达式,Sundeep 用他的 Python re(gex)再次为你撑腰? book,当你准备好开始生产代码时,你会有 Python 中的 Clean Architectures 来帮助你!
该套餐仅在 2021 年10 月日免费使用。
仅在 Leanpub 上提供!
以下是关于该捆绑包中图书的更多信息:
Python 101
第二版
作者:迈克·德里斯科尔
了解如何编写 Python 以及其他内容。您不仅将学习 Python 的语法,还将创建原型应用程序和二进制文件,并与家人和朋友分享。
练习 Python 项目
受现实世界用例启发的初级到中级项目
作者:桑迪普·阿加瓦尔
知道 Python 基础但不知道下一步该怎么做?通过受真实世界启发的 Python 项目,在您的编程之旅中迈出下一步。
Pydon ts
编写优雅的 Python 代码
罗德里戈·吉罗将
Python 有如此多的库,以至于人们经常忘记花时间去了解 Python 提供的所有真正有趣和有用的特性。
Pydon 'ts 教你 Python 的这些核心特性,用大量的代码示例向你展示如何在真实世界的真实代码中使用这些特性。
想掌握 Python?从这里开始????。
Python re(gex)?
文本处理的神奇工具
作者:桑迪普·阿加瓦尔
脚本和自动化任务通常需要从输入数据中提取特定的文本部分,或者将它们从一种格式修改为另一种格式。
这本书将帮助你学习 Python 正则表达式,一种满足各种文本处理需求的迷你编程语言。
Python 中的干净架构
更好的软件设计的实用方法
作者莱昂纳多·乔尔达尼
什么是好的软件架构?为什么我们要费心构造代码并花时间测试它呢?如果你喜欢花几个小时调试你的程序,或者在办公室呆到很晚,从生产中的错误部署中恢复过来,这本书绝对不适合你!
新的 Python 证书
原文:https://www.blog.pythonlibrary.org/2010/12/14/the-new-python-certificate/
今天,我收到了一封来自奥莱利理工学院的邮件,宣传他们新的“Python 编程证书”。这似乎是由史蒂夫·霍尔登创建的一套在线课程,史蒂夫·霍尔登是 Python 软件基金会现任主席和霍尔登网站的所有者。
以下是他们的网站对这四门课程的介绍:
第一门课程介绍 Python 语言,第二门课程结束时,你已经创建了图形用户界面,访问了关系数据库并分析了电子邮件。第三堂课通过解释口译员“在引擎盖下”的一些秘密来增加你对语言的掌握。第四部分是完整的体验,为您提供 Python 的整体知识,这将使您准备好继续您的编程生涯,对您掌握 Python 充满信心。
这里有四个课程名称:
- Python 1:Python 入门
- Python 2:从 Python 中获得更多
- Python 3:Python 环境
- Python 4:高级 Python
该证书实际上是由伊利诺伊大学香槟分校颁发的。我不知道是否有人能以优惠的价格把 CLEP 从任何一个班级中除名。无论如何,这是给所有希望获得“认证”的有抱负的 Python 专业人员的参考信息。
据我所知,这是第一个也是唯一一个 Python 认证项目。我记得,Steve Holden 几年前在现已停刊的 Python 杂志的专栏中提到人们想要 Python 证书。
新的 sh 包——子流程包装器
原文:https://www.blog.pythonlibrary.org/2014/08/07/the-new-sh-package-a-subprocess-wrapper/
前几天偶然看到一篇文章讲的是 pbs 包的一个分叉叫 sh 。这些包是 Python 的子流程模块的包装器。基本上,sh 允许您直接从 Python 导入和使用 shell 命令。本文将通过几个例子向您展示如何使用这个有趣的小库。
注意,在撰写本文时,sh 包只支持 Linux 和 Mac。如果您需要 Windows 支持,那么您应该尝试 pbs 项目。
入门指南
要开始使用 sh 包,您需要安装它。最简单的方法是使用 pip:
pip install sh
现在您已经安装了它,我们准备开始学习了!
使用 sh
要使用 sh,您只需导入想要使用的命令。让我们在 Python 的解释器中用几个简单的例子来尝试一下:
>>> from sh import ls
>>> ls("/home/mdriscoll")
Downloads Music Public
Desktop err.log nohup.out Pictures Templates
Documents keyed.kdbx PDF Settings Videos
>>> import sh
>>> sh.firefox("https://www.blog.pythonlibrary.org/")
>>> sh.ping("www.yahoo.com", c=4)
PING ds-eu-fp3.wa1.b.yahoo.com (46.228.47.115) 56(84) bytes of data.
64 bytes from ir1.fp.vip.ir2.yahoo.com (46.228.47.115): icmp_seq=1 ttl=50 time=144 ms
64 bytes from ir1.fp.vip.ir2.yahoo.com (46.228.47.115): icmp_seq=2 ttl=50 time=121 ms
64 bytes from ir1.fp.vip.ir2.yahoo.com (46.228.47.115): icmp_seq=3 ttl=50 time=119 ms
64 bytes from ir1.fp.vip.ir2.yahoo.com (46.228.47.115): icmp_seq=4 ttl=50 time=122 ms
--- ds-eu-fp3.wa1.b.yahoo.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 119.726/126.862/144.177/10.036 ms
上面的例子演示了几个不同的概念。首先,您可以从 sh 包中导入命令名。在本例中,我们导入了 ls 命令,并对我的主文件夹运行它。接下来,我们导入 sh 模块,并使用它打开 Firefox 浏览器到特定的 web 页面。最后我们打电话给平。您会注意到,如果命令接受命令行参数,您不会将它们包含在传递给命令的字符串中。相反,您将它们变成 Python 风格的参数。在这种情况下,“-c 4”变成了“c=4”,这告诉 ping 命令只 ping 4 次。
如果您想运行一个长时间运行的流程,sh 项目支持通过 _bg=True 参数将其放在后台。
还可以通过一些特殊的关键字参数来重定向 stdout 和 stderr:_ out和 _err 。您需要向这些参数传递一个文件或类似文件的对象,以使它们正常工作。
包扎
该项目的文档有更多的信息和额外的例子,值得你花时间细读。它告诉你如何完成子命令,获得退出代码,管道,子命令等等。
wxPython 的新快捷编辑器
原文:https://www.blog.pythonlibrary.org/2012/06/23/the-new-shortcuteditor-for-wxpython/
我本打算在读到它的一周内写一篇文章,但后来事情变得很忙,我就忘了。总之,wxPython 工具包中有一个新的小部件,叫做 ShortcutEditor。你可以在 wxPython 邮件列表上读到它。除非你经常从 SVN 出差,否则你可能需要更新本地驱动器上的 agw lib 文件夹。它也可能包含在最新的凤凰版本中,但我不确定。如果您想检查它,请在命令行上执行以下操作(假设您已经安装了 svn)
svn checkout http://svn.wxwidgets.org/svn/wx/wxPython/3rdParty/AGW/agw/
这将在你当前的目录下创建一个“agw”文件夹。您会希望将它复制到 wxPython 安装中的文件之上。在我的机器上,它位于以下位置:C:\ python 27 \ Lib \ site-packages \ wx-2 . 9 . 3-MSW \ wx \ Lib源代码中包含了一个简单的演示应用程序。我在这里复制它,所以你也可以试着运行它:
import wx
import wx.lib.agw.shortcuteditor as SE
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "ShortcutEditor Demo")
bar = wx.MenuBar()
menu = wx.Menu()
menu.Append(101, "&Mercury", "This the text in the Statusbar")
menu.Append(102, "&Venus", "")
menu.Append(103, "&Earth", "You may select Earth too")
menu.AppendSeparator()
menu.Append(104, "&Close", "Close this frame")
bar.Append(menu, 'File')
self.SetMenuBar(bar)
dlg = SE.ShortcutEditor(self)
dlg.FromMenuBar(self)
if dlg.ShowModal() == wx.ID_OK:
# Changes accepted, send back the new shortcuts to the TLW wx.MenuBar
dlg.ToMenuBar(self)
dlg.Destroy()
# our normal wxApp-derived class, as usual
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
如果您运行上面的代码,您应该会在屏幕上看到类似这样的内容:
然后就可以开始换快捷键了。我觉得这很酷。根据该文件的内部文档,它可以更改菜单快捷键和加速器表。你应该找个时间试一试。我认为这将是一个非常有用的工具,可以添加到我自己的脚本中。
国家安全局有一个初级 Python 课程
原文:https://www.blog.pythonlibrary.org/2020/02/11/the-nsa-has-a-beginner-python-course/
根据 ZDNet 的消息,美国国家安全局(NSA)最近根据信息自由法案(FOIA)的要求,为初学者发布了免费的 Python 编程课程。最初提出请求的软件开发人员克里斯·斯文森已经上传了将近 400 页的资料到数字海洋空间。
您可以在此直接访问课程 PDF。有趣的是,这份文件提到了几本 No Starch 出版社最受欢迎的书,比如《用 Python 自动化枯燥的东西》和《Python 速成教程》。
这份文件有点枯燥,但是看看美国政府是如何教 Python 的很有意思。
Kushal Das 花了比我更多的时间来挖掘资料,并在他的博客上发布了他的发现。我个人觉得有趣的是,他们使用的是 Anaconda 发行版,而不是 CPython 发行版。
不管怎样,如果你有时间,你应该去看看。
Packt 5 美元电子书/视频销售
原文:https://www.blog.pythonlibrary.org/2020/12/15/the-packt-5-ebook-video-sale/
从 2020 年 12 月 15 日到 2021 年 1 月 13 日,Packt Publishing 将以 5 美元的价格出售他们所有的电子书和视频。如果你想以便宜的价格得到一些新的 Python 书籍,这是一个很好的时机。****
****我的书 Python 访谈,包含在此次销售中。
以下是他们藏书中的一些亮点:
Packt Python 简易包 2019
原文:https://www.blog.pythonlibrary.org/2018/12/31/the-packt-python-humble-bundle-2019/
Packt Publishing 正在与 Humble Bundle 合作,于 2019 年推出 Python 捆绑包。
你可以在这里查看完整的头衔列表:https://www.humblebundle.com/books/python-packt-2019-books
其中一本是我自己的 Python 访谈书
这一捆书里还有不少其他看起来很有趣的书。我知道史蒂文·洛特和达斯丁·菲利普斯的书最有可能是好书。
这个包的另一个好处是,你可以通过获得一堆有趣的 Python 书籍来支持 Python 软件基金会!
计算机视觉的视频搜索大师 Kickstarter
我实际上从未听说过这个人,但是这个在 pyimagesearch 博客背后的家伙已经为计算机视觉订阅课程创建了一个 Kickstarter 。他的名字是 Adrian Rosebrock,他的 Kickstarter 是在 25 分钟获得资助的!他的课程涵盖了计算机视觉的许多不同主题,听起来非常有趣。你绝对应该去看看,尤其是如果你在这个领域。
Python 101 Screencast Kickstarter 即将发布
我目前正在进行另一个 Kickstarter 活动,将我的书 Python 101 变成一个视频系列。我计划在 3 月 25 日星期三启动这个项目。在我的书中有 44 个章节,所以我将创建至少 44 个截屏视频。以下是本书目前涵盖的内容以及视频系列中也将涵盖的内容:
第一部分:学习基础知识
第 1 章-空闲
第 2 章-字符串
第 3 章-列表、元组和字典
第 4 章-条件语句
第 5 章-循环
第 6 章- Python 理解
第 7 章-异常处理
第 8 章-使用文件
第 9 章-导入
第 10 章-函数
第 11 章-类
第二部分:向图书馆学习
第 12 章-自省
第 13 章 csv 模块
第 14 章 ConfigParser 模块
第 15 章-日志模块
第 16 章-操作系统模块
第 17 章-电子邮件和 smtplib 模块
第 18 章 sqlite 模块
第 19 章-子流程模块
第 20 章-系统模块
第三部分-中间零碎部分
第 24 章 Python 调试器第 25 章——装饰器
第 26 章——lambda
第 27 章——代码剖析
第 28 章——测试简介
第四部分-提示、技巧和教程
第 29 章-安装模块
第 30 章- ConfigObj
第 31 章-用 lxml 解析 xml
第 32 章- Python 代码分析
第 33 章-请求包
第 34 章- SQLAlchemy
第 35 章- virtualenv
第五部分-包装和配送
第 36 章-创建模块和包
第 37 章-如何将代码添加到 PyPI
第 38 章- Python 蛋
第 39 章-Python 轮子
第 40 章- py2exe
第 41 章- bbfreeze
第 42 章- cx_Freeze
第 43 章- PyInstaller
第 44 章-创建安装程序
当我继续下去的时候,我可能会抛出其他相关主题的视频。我期待收到我的读者和支持者的来信。这将是一个爆炸,以找出如何让所有这些信息到你的手中!
Python 101 截屏 Kickstarter 现已上线!
原文:https://www.blog.pythonlibrary.org/2015/03/25/the-python-101-screencast-kickstarter-is-now-live/
我最近的项目是把我的书《Python 101》改编成电影。我已经启动了一个 Kickstarter 来筹集资金来帮助这一努力。你可以在这里查看:
https://www . kickstarter . com/projects/34257246/the-python-101-screencast
基本的想法是把这本书的每一章都变成一个截屏。目前有 44 个章节将被制作成迷你视频。我已经意识到我可以在一个截屏中添加许多其他项目,这些项目展示起来比写起来容易,所以肯定会有额外的内容。我希望你能和我一起参与这个项目。
谢谢,迈克
Python 装饰者视频教程
原文:https://www.blog.pythonlibrary.org/2022/09/01/the-python-decorators-video-tutorial/
了解 Python decorators 如何与 Mike Driscoll 合作
你将学到什么:
- 函数如何工作
- 功能自省
- 关闭
- 创建装饰器
- 堆叠装饰者
- 有争论的装饰者
- 班级装饰者
- 装饰课
- 实用装修工
- Python 标准库中的装饰者
https://www.youtube.com/embed/AhyHuxCunpk?feature=oembed
Python Lambda
原文:https://www.blog.pythonlibrary.org/2010/07/19/the-python-lambda/
当我第一次开始学习 Python 时,最让我困惑的概念之一是 lambda 语句。我敢肯定其他新程序员也会被它弄糊涂,你们中的一些人可能想知道我在说什么。所以,本着教育的精神,让我们来个突击测验:
什么是λ?
A.希腊字母中的第 11 个字母
B .颅骨矢状缝和人字缝交界处的颅骨测量点
C .可以让它将用户的想法变为现实的手臂奴隶机甲中的驾驶员
D .一系列日本火箭的名称
E .匿名(未绑定)功能
如果你猜对了以上所有的问题,你就答对了!当然,在这篇文章的上下文中,“E”确实是正确的答案。Python lambda 语句是一个匿名或未绑定的函数,而且是一个非常有限的函数。让我们看几个典型的例子,看看我们是否能找到它的用例。
人们通常看到的讲授 lambda 的典型例子是某种无聊的加倍函数。恰恰相反,我们的简单例子将显示如何找到平方根。首先我们将展示一个普通函数,然后是 lambda 等价函数:
import math
#----------------------------------------------------------------------
def sqroot(x):
"""
Finds the square root of the number passed in
"""
return math.sqrt(x)
square_rt = lambda x: math.sqrt(x)
如果您尝试这些函数中的每一个,您将得到一个 float。这里有几个例子:
`>>> sqroot(49)
7.0
square_rt(64)
8.0`
很圆滑,对吧?但是在现实生活中,我们实际上在哪里使用λ呢?也许是计算器程序?好吧,那是可行的,但是对于 Python 的内置来说,这是一个非常有限的应用!lambda 示例经常应用的 Python 的主要部分之一是 Tkinter 回调。我们将对此进行研究,但是我们也将获取这些信息,并使用 wxPython 进行尝试,看看是否也能在那里工作。
Tkinter + lambda
我们将从 Tkinter 开始,因为它包含在标准 Python 包中。这是一个非常简单的脚本,有三个按钮,其中两个使用 lambda 绑定到它们的事件处理程序:
import Tkinter as tk
########################################################################
class App:
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
frame = tk.Frame(parent)
frame.pack()
btn22 = tk.Button(frame, text="22", command=lambda: self.printNum(22))
btn22.pack(side=tk.LEFT)
btn44 = tk.Button(frame, text="44", command=lambda: self.printNum(44))
btn44.pack(side=tk.LEFT)
quitBtn = tk.Button(frame, text="QUIT", fg="red", command=frame.quit)
quitBtn.pack(side=tk.LEFT)
#----------------------------------------------------------------------
def printNum(self, num):
""""""
print "You pressed the %s button" % num
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
root.mainloop()
注意 btn22 和 btn44 变量。这是行动的地方。我们创建了一个传统知识。按钮实例并绑定到我们的 printNum 方法。λ被分配给按钮的命令参数。这意味着我们正在为这个命令创建一个一次性的函数,就像在退出按钮中我们调用框架的退出方法一样。这里的区别在于,这个特定的 lambda 是一个调用另一个方法并向后者传递一个整数的方法。在 printNum 方法中,我们通过使用从 lambda 函数传递给它的信息,将哪个按钮被按下打印到 stdout。你都明白了吗?如果是这样,我们可以继续...如果没有,根据需要反复阅读这一段,直到信息被理解或者你发疯,无论哪一个先出现。
wxPython+λ
我们的 wxPython 示例与 Tkinter 示例非常相似,只是更详细一点:
import wx
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"wx lambda tutorial",
size=(600,400)
)
panel = wx.Panel(self)
button8 = wx.Button(panel, label="8")
button8.Bind(wx.EVT_BUTTON, lambda evt, name=button8.GetLabel(): self.onButton(evt, name))
button10 = wx.Button(panel, label="10")
button10.Bind(wx.EVT_BUTTON, lambda evt, name=button10.GetLabel(): self.onButton(evt, name))
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(button8, 0, wx.ALL, 5)
sizer.Add(button10, 0, wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event, buttonLabel):
""""""
print "You pressed the %s button!" % buttonLabel
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = DemoFrame().Show()
app.MainLoop()
在这种情况下,我们用 lambda 语句创建一个双参数匿名函数。第一个参数是 evt ,第二个是按钮的标签。这些被传递给 onButton 事件处理程序,当用户点击两个按钮中的一个时,这个事件处理程序被调用。此示例还将按钮的标签打印到 stdout。
包扎
lambda 语句也用于各种其他项目。如果你在谷歌上搜索一个 Python 项目名和 lambda,你可以找到很多活代码。例如,如果您搜索“django lambda”,您会发现 django 有一个利用 lambdas 的 modelformset 工厂。SqlAlchemy 的 Elixir 插件也使用 lambdas。睁大你的眼睛,你会惊奇地发现有多少次你会偶然发现这个方便的小函数生成器。
进一步阅读
- 深入 Python: 使用 lambda 函数
- Effbot Tkinter lambda 示例
- wxPython Wiki: 向回调传递参数
Python 论文时事通讯
原文:https://www.blog.pythonlibrary.org/2023/01/02/the-python-papers-newsletter/
几个月来,我一直在考虑创办一份 Python 时事通讯的想法。我决定硬着头皮在 2023 年试一试。
我的时事通讯叫做Python 论文。
我将涉及许多不同的 Python 主题:
- Python GUIs
- 用 Python 进行图像处理
- 初级 Python 主题到高级主题
- Excel 和 Python
- 我对各种套餐的了解,包括熊猫和姜戈
- 还有更多!
- 我还计划涵盖非 Python 主题。我喜欢写作,但是我想写的一些东西并不真正适合这里的 Mouse Vs Python。我将能够在时事通讯中更多地谈论我的非编程爱好以及一些通用工程主题。
这里有几个例子:
- 工程最佳实践
- Python pro 提示
- Python 测验
- 面试技巧
- 我的内容创作理念
- 对于我的内容,什么有效,什么无效
- 不管我还在想什么
我希望你能加入我的新冒险。Python 论文中的一些主题肯定也会泄露给鼠标 Vs Python。如果我能帮助它,这个网站将不会很快消亡!
Python 问答书 Kickstarter
原文:https://www.blog.pythonlibrary.org/2022/10/10/the-python-quiz-book-kickstarter/
Quizzes are a fun way to test your knowledge of something. They make you think. The Python Quiz Book is all about brain teasers for the Python programming language. The Python Quiz Book isn't made to teach you Python. It's there to help you test your knowledge or teach you something new about the language.The Python Quiz Book will contain over 100 quizzes! The book will also have a companion course on Teach Me Python that you can get access to if you pledge for the appropriate level.The goal of the book is to help you be a better engineer. Some of the quizzes will be esoteric or demonstrate bad programming practices. You can learn what not to do in your own code. You will also learn neat tips and tricks about Python that you can share with your friends and coworkers.You can support my latest Python book on Kickstarter today. Or download some sample chapters to check out the book before you commit to purchasing.
标志的故事
原文:https://www.blog.pythonlibrary.org/2011/01/15/the-story-of-the-logos/
去年九月,我觉得为我的读者举办一场比赛会很酷,在比赛中,我会让他们为我的博客设计一些很棒的标志,以换取一些奖品。如果你愿意,你可以在这里阅读全部内容。唉,我没有收到任何设计。我确实从人们那里得到了一些积极的评价,也有一两条评论说我太小气了。无论如何,在那次彻底失败后,我决定问我的一些艺术家朋友是否有兴趣做这件事,但他们也拒绝了。这个世界也将会怎样?这些人人都在谈论的“饥饿”艺术家在哪里?
(注意:这篇文章会有很多图片,所以加载时间可能会比平时长。)
一点背景信息
幸运的是,我哥哥认识一个自称为“杀戮者”的人(不知道他是否在挨饿),他是在“T2 G-Fest”上认识的,这是一个专门为哥斯拉和其他东邦和怪兽制作的“节日”。你可以看到他的一些关于越轨艺术的作品。无论如何,他和他的妻子一起经营生意,他们实际上并没有比我的比赛花费更多。让我们来看看标志在设计阶段是如何变化的。
概念艺术
当我委托制作这些作品时,我告诉艺术家,我想要某种机甲标志和某种类似纳尼亚的老鼠与蛇搏斗的东西。我想我可能也跟他说过我的蛇头想法,但我不太记得了。不管怎样,这是第一组草图:
我有点喜欢上面的,但不认为它作为一个标志会很好。下面这个也一样。
我喜欢这个。蛇吃自己有点恶心,但我喜欢这个概念。我可能会在某个时候委托这个。
这可能是我最喜欢的一个了。我喜欢那只老鼠近乎蒸汽朋克的样子。你会看到最终的结果更加完美,但我有点希望我的鼠标看起来更像原来的样子。
当我第一次看到它时,我不确定下面的图片是怎么回事,但一旦我意识到它是什么,我真的很喜欢它。
过了一段时间,我终于明白了纳尼亚老鼠的概念:
这位艺术家独自想出了下面两条:
我喜欢上面的那个,所以它仍然在我的“可能”清单上。你怎么想呢?
这是一个标志概念,艺术家将蛇和老鼠混合到标志中。我觉得这很有趣,我可能应该让他多做几个例子,但我决定只做黑鱼,纳尼亚和机甲。
打磨概念艺术
既然我已经做出了选择,下一步就是让插图画家润色它们并给它们上色。让我们来看看:
正如你所看到的,这个机器人鼠标有轮子而不是腿,最后看起来有点“卡通化”。尽管如此,我还是喜欢它。
我觉得蛇头用对比色好看多了。很容易看出他在用电脑鼠标吃零食。我想他一定有无线鼠标。
这看起来很酷。这只老鼠看起来有点像《纳尼亚传奇》中的雷佩契普传,又有点像微型火枪手。
最终结果
在批准了上面的图片后,插画师把它们交给了他的伴侣和妻子。她拿起它们,打磨了一些,她还创造了标志的文字部分。你准备好检查他们了吗?如果有,开始滚动!
蛇头看起来更锋利,它的眼睛看起来有点呆滞。令人毛骨悚然,但仍然整洁。它的尖牙也长了一点。不过,我不确定他的鼻洞怎么了。
纳尼亚的画面变得不那么详细了,老鼠换了帽子,把头发染成了灰色,但看起来基本没变。
这张显示的是机甲版加 logo。我不确定她是如何匹配官方 Python logo 字体的(我也没有要求她这么做),但我觉得结果相当不错。
包扎
现在你知道我的商标是怎么来的了。我制作它们是为了让我的博客看起来更酷,同时我也可以制作一些有趣的商品,希望有人会喜欢购买它们并支持我的博客。我用 Zazzle 创建了我的商店。我只得到销售的 10%的佣金,所以我怀疑我能赚多少。即使除了我自己没有人买任何东西,我仍然认为它是值得的。我喜欢我得到的,我希望你也是。
2014 年十大文章
原文:https://www.blog.pythonlibrary.org/2015/01/01/the-top-10-articles-of-2014/
新年快乐过去的一年非常美好,我期待着 2015 年的到来。我写这个博客已经有一段时间了,今天是时候回顾一下 2014 年阅读量最大的文章了。我们来看看吧!
- 一个简单的逐步报告实验室教程 (16,091 次页面浏览)
- Python: 一个简单的循序渐进的 SQLite 教程(11373 次页面浏览)
- Python 和 Microsoft Office 使用 PyWin32 (7,201 次页面浏览)
- Reportlab 表格-使用 Python 在 pdf 中创建表格 (6,356 次页面浏览)
- wxPython - 创建一个简单的媒体播放器(5851 次页面浏览)
- Python 101: 如何下载一个文件(5723 次页面浏览)
- wxPython - wx。ListCtrl 提示和技巧(5655 次页面浏览)
- Python 101: 如何打开文件或程序(5373 次页面浏览)
- 一个 PyInstaller 教程(5010 次页面浏览)
- PyPdf 2-PyPdf 的新分支(4959 次页面浏览)
这一年总共有 493,339 次页面浏览。
如果你回顾过去几个十大清单,你会注意到这个清单中的项目已经彻底改变了位置。你可能还会注意到,今年每篇文章的浏览量要低得多。我觉得那很有趣。以下是过去几年的链接,便于参考:
遗憾的是,我 2014 年的文章今年都没有进入前十名。因此,我将列出去年的五大文章,让你快速领略一下 2014 年你错过了什么:
- Python 101:Python 调试器简介(4480 次页面浏览)
- Python 并发:线程介绍(4366 次页面浏览)
- Python 201: 装饰者(3688 次页面浏览)
- Python 201: 属性(3567 次页面浏览)
- Python: 如何创建旋转日志(3323 次页面浏览)
感谢我所有的读者!我期待着今年与你分享更多的文章。
终极程序员超级堆栈包
原文:https://www.blog.pythonlibrary.org/2018/11/06/the-ultimate-programmer-super-stack-bundle/
最近,我有机会拿到了我的第二本书, Python 201:中级 Python 加入了其他一些有趣的编程书籍中。
它被称为 终极程序员超级堆栈 ,它是一个由 25 个以上的优质电子课程、畅销电子书和奖金资源组成的手工精选集合,将帮助新程序员:
- 了解当今最流行(也最赚钱)的各种语言和框架,包括从 Python、JavaScript 和 Ruby 到 HTML、CSS 和 Kotlin 等等
- 了解如何从头开始构建 API、网站以及 iOS 和 Android 应用程序
- 揭示“软件行业”(计算机程序如何工作,计算机程序员如何思考,以及如何开始你自己的计算机编程业务)
- 掌握成为“完整的程序员”所需的软技能(相信我,这将对你的职业生涯产生巨大影响)
还有更多。
以下是您可以在堆栈中找到的几个亮点:
- Dan Bader 的《Python 技巧:令人敬畏的 Python 特性自助餐》(零售价:29.00 美元)。Dan 是 Realpython.com 的创始人,他的文章、视频和培训已经覆盖了全球超过一百万的开发者。这是他最畅销的书籍之一,无论您是 Python 的新手,还是希望掌握这门技术并成为认证的 Python 爱好者,这都是一个很好的起点。
- 菲尔·斯特金的《构建你不会讨厌的 API》(T1)(零售价:26.99 美元)。Phil 是一名 API 设计师和系统架构师,目前正在帮助 WeWorK 扩展他们的 API,以处理更多的流量,更好地抵抗变化,并且不会像多米诺骨牌一样在其中一个人遇到困难时倒下。Phil 被认为是 API 方面的领先专家之一,这本书就像是深入他的大脑。
- Grant Klimaytys 出版的《前 1%开发者- iOS 版》(零售价:197.00 美元)。Grant 是 Learn App Development 的创始人,他曾指导全球超过 120,000 名学生如何成为专业的应用程序开发人员。在这个高级课程中,您将从头开始学习如何为 iPhone 编写代码,了解软件创建的基础知识(适用于任何语言),甚至创建自己的应用程序,开始在 App Store 上赚取被动收入(赢家赢家,鸡肉晚餐!)
点击这里查看
wxPython 演示
原文:https://www.blog.pythonlibrary.org/2010/10/15/the-wxpython-demo/
wxPython 项目是最流行和最容易使用的 Python GUI 工具包之一。它使用 SWIG 包装了流行的 C++ wxWidgets 项目。这也是我所知道的唯一一个尽可能在每个平台上使用本机小部件的跨平台工具包(无论是好是坏)。有些人喜欢说 PyQt 也是这样做的,但是据我所知,PyQt 实际上绘制了所有的控件来模仿本地控件,它实际上并没有使用它们。无论如何,这是无关紧要的。主题是 wxPython 演示。有很多人甚至不知道这个演示的存在。
wxPython 演示是从 wxPython 的主网站额外下载的。它有一个 wxPython 包含的几乎所有小部件的可运行示例。在本文中,我们将快速看一下这个演示可以做什么。
介绍 wxPython 演示!
如果你去 wxPython 网站,点击左边的下载链接,你会在这里找到。只需根据您当前使用的平台(即 Windows、Mac 或 Linux)在相应的“文档、演示、示例等”下下载相应的文件部分。在 Linux 上,包管理器有时也会列出它。安装完成后,双击快捷方式(在 Windows 上)或“\wxPython2.8 Docs and Demos\demo”文件夹中的 demo.py 文件运行演示。你应该会得到类似上面的图片(点击它看得更清楚)。
使用演示
要使用该演示,您可以通过展开左侧的树,然后单击各个项目来浏览小部件。或者,您可以使用左下角的搜索框来过滤演示,从而搜索项目。一旦你选择了一个演示,你会看到一个笔记本,右边有三个标签。标签将被标记为小部件名称和概述、演示代码,然后是演示本身。您可以修改演示的代码,保存修改并在演示中运行它们。然后恢复到原始状态。真的很酷!
wxPython 演示还有其他工具!
wxPython 演示在帮助菜单中有几个其他很酷的工具。第一个是 PyShell,这是一个有趣的小 Python shell,您可以像空闲时一样使用它。然后是小部件检查工具。对于调试应用程序来说,这是一个非常方便的工具。它可以突出显示 sizer 和 widget,这可以告诉您 widget 是否被放错了 sizer 中,或者为什么您的 GUI 看起来如此时髦。wxPython 的创建者 Robin Dunn 创建了它,他经常使用它来调试 wxPython 邮件列表中的代码或粘贴在 wxPython IRC 频道中的代码。
包扎
无论如何,您可以通过使用 wxPython 演示来学习如何使用大多数小部件。当我想学习一个小工具或者回答别人关于小工具的问题时,我通常会去那里。我希望这对您学习 wxPython 有所帮助。
进一步阅读
这个博客有一个 Twitter 账户
原文:https://www.blog.pythonlibrary.org/2010/10/15/this-blog-gets-a-twitter-account/
仅供喜欢这个博客的人参考。我最近开通了一个鼠标对 Python 的 Twitter 账户,以便更容易地关注这个博客的更新。您可以在这里找到它:
http://twitter.com/mousevspython
我正在试验可以自动发布到 Twitter 的 WordPress 插件,但是还没有找到任何一个能这么好工作的插件。如果你有什么好的建议,请告诉我。谢谢!
数千篇科学论文可能因误解 Python 而无效
最近发现,数千篇科学文章的结论可能是无效的,因为科学家不理解 Python 的 glob.glob() 不返回排序结果。
这是由 Vice 、 Slashdot 报道的,在 Reddit 上也有一场有趣的讨论。
有些人认为这是 Python 中的一个小故障,但是 glob 从未保证返回的结果是经过排序的。和往常一样,我建议仔细阅读文档,以充分理解代码的作用。如果你能围绕你的代码写测试也是一个好主意。Python 包含了一个 unittest 模块,使得这变得更加容易。
如何显示/隐藏一个窗口
原文:https://www.blog.pythonlibrary.org/2012/07/26/tkinter-how-to-show-hide-a-window/
今天我们就来看看 Tkinter!我很好奇一个人如何隐藏一个帧,然后使用 Tkinter 重新显示它,我不断发现一些线程(比如这个 one )讨论了如何使用 withdraw()和 deiconify(),但并没有真正提供任何可用的代码。在 wxPython 中,我使用 pubsub 做了这类事情。我们将讨论如何隐藏和显示根框架的三个不同版本。
我的第一个例子
Tkinter 的很多示例代码都不是非常面向对象。我的意思是,我看到的代码不在类中。但我发现 GUI 代码在一个类中更容易理解。无论如何,这就是我如何结束创作我的第一个例子:
import Tkinter as Tk
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Main frame")
self.frame = Tk.Frame(parent)
self.frame.pack()
btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
btn.pack()
#----------------------------------------------------------------------
def hide(self):
""""""
self.root.withdraw()
#----------------------------------------------------------------------
def openFrame(self):
""""""
self.hide()
otherFrame = Tk.Toplevel()
otherFrame.geometry("400x300")
otherFrame.title("otherFrame")
handler = lambda: self.onCloseOtherFrame(otherFrame)
btn = Tk.Button(otherFrame, text="Close", command=handler)
btn.pack()
#----------------------------------------------------------------------
def onCloseOtherFrame(self, otherFrame):
""""""
otherFrame.destroy()
self.show()
#----------------------------------------------------------------------
def show(self):
""""""
self.root.update()
self.root.deiconify()
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tk.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
让我们花点时间来分解一下。我们有一个简单的类,其中我们传入了一个“根”对象(Tk。Tk())作为顶级父级。这又被用作 Tk.Frame 的父帧。 pack() 命令是 Tkinter 附带的几何管理器之一。它允许你将部件“打包”成列或行,并且有不同的选项,如填充、扩展和侧边。接下来,我们创建一个 Tk。扣上扣子,打包。如果你不调用 pack(或者其他的几何管理器),那么你的小部件根本不会出现。在按钮实例化过程中,我们传递给它一个父节点、一个标签字符串和一个单击按钮时要运行的命令。
当用户点击按钮时,我们创建另一个顶层窗口,并给它一个不同的标题,大小和关闭按钮。我们使用 lambda 匿名方法来创建回调,因为我们需要将 otherFrame 实例传递给处理程序,以便我们可以关闭它。我们也可以只创建 otherFrame 作为类属性(即 self.otherFrame)并跳过 lambda,但是如果你用 Tkinter 做了很多,那么你真的需要习惯看到那种回调设置。当关闭按钮被调用时,它销毁另一帧并调用显示方法,显示原始帧。一些例子表明,在调用deiconfig()方法之前,您需要调用 update() 方法,然而,如果您注释掉 update()调用,您会发现它工作得很好。至少在使用 Python 2.6 的 Windows 7 上是这样的。
现在让我们试着把第二个框架分割成它自己的类!
将第二帧分成一个类
将第二个框架放入它自己的类中可以促进代码重用和更好地组织代码,尤其是如果第二个框架非常复杂的话。有一种方法可以做到:
import Tkinter as Tk
########################################################################
class OtherFrame(Tk.Toplevel):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
Tk.Toplevel.__init__(self)
self.geometry("400x300")
self.title("otherFrame")
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Main frame")
self.frame = Tk.Frame(parent)
self.frame.pack()
btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
btn.pack()
#----------------------------------------------------------------------
def hide(self):
""""""
self.root.withdraw()
#----------------------------------------------------------------------
def openFrame(self):
""""""
self.hide()
subFrame = OtherFrame()
handler = lambda: self.onCloseOtherFrame(subFrame)
btn = Tk.Button(subFrame, text="Close", command=handler)
btn.pack()
#----------------------------------------------------------------------
def onCloseOtherFrame(self, otherFrame):
""""""
otherFrame.destroy()
self.show()
#----------------------------------------------------------------------
def show(self):
""""""
self.root.update()
self.root.deiconify()
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tk.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
这和代码的第一个版本基本相同。在第二个框架的类中创建第二个框架的按钮确实很好,但是如果我们这样做,那么就很难告诉原始框架去伪存真。还是为了完整起见,让我们看看它是什么样子:
import Tkinter as Tk
########################################################################
class OtherFrame(Tk.Toplevel):
""""""
#----------------------------------------------------------------------
def __init__(self, original):
"""Constructor"""
self.original_frame = original
Tk.Toplevel.__init__(self)
self.geometry("400x300")
self.title("otherFrame")
btn = Tk.Button(self, text="Close", command=self.onClose)
btn.pack()
#----------------------------------------------------------------------
def onClose(self):
""""""
self.destroy()
self.original_frame.show()
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Main frame")
self.frame = Tk.Frame(parent)
self.frame.pack()
btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
btn.pack()
#----------------------------------------------------------------------
def hide(self):
""""""
self.root.withdraw()
#----------------------------------------------------------------------
def openFrame(self):
""""""
self.hide()
subFrame = OtherFrame(self)
#----------------------------------------------------------------------
def show(self):
""""""
self.root.update()
self.root.deiconify()
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tk.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
注意,在这个版本中,我们必须将 MyApp 类的实例传递给另一个框架,这样我们就可以调用它的 show 方法。您还可以看到,我们不再需要 lambda 函数,因为我们不再需要将另一个 frame 实例传递给处理程序。这让事情变得简单了。这仍然是一种脆弱的做事方式。为什么?如果你决定改变主框架的显示方法为 showFrame 或者其他什么,那么你必须记得在另一个类中也改变它,否则它会中断。如果将实例传递给多个类,这很快就会变得乏味。幸运的是,有一个简单的解决方案,它叫做 pubsub!
使用 pubsub 在两个窗口之间进行通信
你需要去 pubsub 的网站安装这个包,因为它不包含在 Python 中。它包含在 wxPython 中,尽管我不认为您可以在 wxPython 之外轻松使用该版本。无论如何,一旦你有了它,你就可以按照这个代码:
from pubsub import pub
import Tkinter as Tk
########################################################################
class OtherFrame(Tk.Toplevel):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
Tk.Toplevel.__init__(self)
self.geometry("400x300")
self.title("otherFrame")
# create the button
btn = Tk.Button(self, text="Close", command=self.onClose)
btn.pack()
#----------------------------------------------------------------------
def onClose(self):
"""
closes the frame and sends a message to the main frame
"""
self.destroy()
pub.sendMessage("otherFrameClosed", arg1="data")
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Main frame")
self.frame = Tk.Frame(parent)
self.frame.pack()
btn = Tk.Button(self.frame, text="Open Frame", command=self.openFrame)
btn.pack()
pub.subscribe(self.listener, "otherFrameClosed")
#----------------------------------------------------------------------
def listener(self, arg1, arg2=None):
"""
pubsub listener - opens main frame when otherFrame closes
"""
self.show()
#----------------------------------------------------------------------
def hide(self):
"""
hides main frame
"""
self.root.withdraw()
#----------------------------------------------------------------------
def openFrame(self):
"""
opens other frame and hides main frame
"""
self.hide()
subFrame = OtherFrame()
#----------------------------------------------------------------------
def show(self):
"""
shows main frame
"""
self.root.update()
self.root.deiconify()
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tk.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
正如所料,这段代码只是集成了 pubsub 的内容。我们在主框架中创建一个监听器方法,并通过调用
pub.subscribe(self.listener, "otherFrameClosed")
“签名”是 otherFrameClosed 。因此,如果我们发布一个带有该签名的消息,那么主框架和任何其他订阅了该签名的类都将调用它们各自的方法。在另一个框架中,我们在 close 方法的末尾添加了一个 pub.sendMessage 调用,在这里我们发布到前面提到的签名,并传递一个伪参数。你不必这样做,但我认为如果你知道如何在班级之间传递信息会更好。您几乎可以传递任何想要传递的 Python 对象/类型。
包扎
现在您对 Tkinter 和它的一些顶级方法有了一些了解。您可以让您的框架消失,并再次出现在命令!你也尝到了 pubsub 的威力。用这些新知识去编码吧!
额外资源
- 来自 effbot 的顶级信息
- zetcode 的 Tkinter 教程
- Python lambda
- 弗雷德里克·伦德简介为 Tkinter
下载源代码
Tkinter -重定向标准输出/标准错误
原文:https://www.blog.pythonlibrary.org/2014/07/14/tkinter-redirecting-stdout-stderr/
重定向 stdout 似乎是 wxPython 用户组中非常常见的请求,所以我决定看看用 Tkinter 做这件事有多简单。重定向 stdout 或 stderr 的典型用例是,您正在调用某个其他进程(如 ping 或 tracert ),并且您希望捕获它在做什么,以便将其放入您的 UI。通常你可以只使用 Python 的子流程模块,调用它的 communicate() 方法来访问数据。然后你可以把它打印到 stdout,它会神奇地出现在你选择的 UI 小部件中。
我们完成的用户界面将如下所示:
让我们来看看如何使用 Tkinter:
import ScrolledText
import sys
import tkFileDialog
import Tkinter
########################################################################
class RedirectText(object):
""""""
#----------------------------------------------------------------------
def __init__(self, text_ctrl):
"""Constructor"""
self.output = text_ctrl
#----------------------------------------------------------------------
def write(self, string):
""""""
self.output.insert(Tkinter.END, string)
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Redirect")
self.frame = Tkinter.Frame(parent)
self.frame.pack()
self.text = ScrolledText.ScrolledText(self.frame)
self.text.pack()
# redirect stdout
redir = RedirectText(self.text)
sys.stdout = redir
btn = Tkinter.Button(self.frame, text="Open file", command=self.open_file)
btn.pack()
#----------------------------------------------------------------------
def open_file(self):
"""
Open a file, read it line-by-line and print out each line to
the text control widget
"""
options = {}
options['defaultextension'] = '.txt'
options['filetypes'] = [('all files', '.*'), ('text files', '.txt')]
options['initialdir'] = '/home'
options['parent'] = self.root
options['title'] = "Open a file"
with tkFileDialog.askopenfile(mode='r', **options) as f_handle:
for line in f_handle:
print line
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tkinter.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
那是相当多的代码。让我们花一两分钟来分解它。首先,我们导入了 ScrolledText ,这是一个包含滚动条的文本控件。我们还导入了 tkFileDialog ,它让我们能够打开一个文件。为了使这个例子非常简单,我们将使用对话框打开一个文本文件,并逐行打印到 stdout。
我们看到的第一个类叫做 RedirectText 。它将一个文本控件小部件作为其参数。我们创建了一个 write 方法,将一个字符串附加到小部件的当前值。然后在 MyApp 类中,我们创建所有必要的小部件并将 stdout 重定向到我们的 RedirectText 类。我们还将按钮绑定到我们的 open_file 方法。这是行动发生的地方!
这里我们创建文件对话框并打开一个文件。然后我们逐行读取文件,并将其打印到 stdout。如果您尝试该示例,您将看到 ScrolledText 小部件中出现的每一行文本。
包扎
正如您所看到的,重定向 stdout / stderr 非常容易。我希望你会发现这种技术有许多巧妙的用途。快乐编码!
相关阅读
- wxPython – Redirecting stdout / stderr
2019 年十大阅读量最高的鼠标与 Python 文章
原文:https://www.blog.pythonlibrary.org/2020/01/07/top-10-most-read-mouse-vs-python-articles-of-2019/
2019 年对我的博客来说是个好年景。虽然我们最终没有获得很多新读者,但我们确实得到了一点点提升。对这个网站上的书籍也有了更多的兴趣。
对于 2019 年,这些是最常阅读的十大:
-
1 - 使用 PyFPDF 和 Python 创建 PDFs】
-
2 - 使用 Python 从 pdf 导出数据
-
3 - 使用 Python 和 xlrd 读取 Excel 电子表格
-
4 - 在 Python 中确定列表中的所有元素是否都相同
-
5 - 简单的分步报告实验室教程
-
6 - 如何在 Python 中获得类属性列表
-
7 - Python:用乌龟画圆
-
8 - 如何将 Jupyter 笔记本导出为其他格式
-
9 - Python 201:多重处理教程
-
10 - Python 3:枚举介绍
注意,这些文章实际上都不是 2019 年写的。其中一半是在 2018 年写的,其中一个可以追溯到 2010 年。有趣的是,我 2019 年最受欢迎的文章是关于使用 Python 拍摄黑洞的照片。那篇文章排在第 28 位。
对于 2020 年,我将努力创作新的内容和教程,这对您的 Python 之旅很有用。与此同时,我希望你会喜欢阅读档案,而我工作在一些新的!
2013 年十大文章
原文:https://www.blog.pythonlibrary.org/2014/01/01/top-ten-articles-2013/
新年快乐现在是 2013 年末 2014 年初,是时候看看今年有哪些文章进入了前十榜单了。我在 12 月 31 日进行了统计,得出了以下列表:
- Python: 一个简单的一步一步的 SQLite 教程(37658 次页面浏览)
- Python 101: 如何打开文件或程序(27553 次页面浏览)
- 一个简单的逐步报告实验室教程 (18,763 次页面浏览)
- 如何用 Python 发送电子邮件(16922 次页面浏览)
- Python 101: 日志简介(13587 次页面浏览)
- Python 101: 如何下载一个文件(12183 次页面浏览)
- Python: 用 minidom 解析 XML(12,063 次页面浏览)
- Python 和 Microsoft Office 使用 PyWin32 (11,424 次页面浏览)
- 一个简单的 SqlAlchemy 0.7 / 0.8 教程(11398 次页面浏览)
- Python 201: 创建模块和包(11005 次页面浏览)
我发现有趣的是,2013 年的文章没有一篇受欢迎到足以进入前十名。另一方面,自去年以来,各种文章的排名变化非常有趣。例如,SQLite 教程从第 8 名跃升至第 1 名,而去年的第一名则降至第 2 名。您可以点击下面相应的链接,浏览各种十大列表:
我想我的读者可能也有兴趣知道 2013 年的哪些文章很受欢迎,所以这里是 2013 年写的前 5 篇文章:
- python 101 "元素树 XML 解析简介(4730 次页面浏览)
- 如何在 Python 中获得类属性列表(4140 次页面浏览)
- PySide: 标准对话框和消息框(3141 次页面浏览)
- 用 Python 连接到 Dropbox(2,951 次页面浏览)
- 电子书评论:真正的 Python(2438 次浏览量)
特别感谢我的忠实读者和我所有的新读者。让我们让 2014 年更加美好!
2010 年十大文章
原文:https://www.blog.pythonlibrary.org/2010/12/30/top-ten-articles-of-2010/
本周很多网站都在做年终回顾,所以我想你可能会很有兴趣知道这个博客上的哪些文章是今年最受欢迎的。下面你会找到每篇文章的链接,以及我从谷歌分析获得的页面浏览量:
- 一个简单的逐步报告实验室教程,9709 次页面浏览,发布于 2010 年 3 月 8 日
- 另一个循序渐进的 SqlAlchemy 教程第一部分,7746 次页面浏览,发布于 2010 年 2 月 3 日
- 另一个循序渐进的 SqlAlchemy 教程第 2 部分,4858 次页面浏览,发布于 2010 年 2 月 3 日
- 使用 Python 和 pyPdf 操作 Pdf ,4511 次页面浏览,发布于 2010 年 5 月 15 日
- Python 101:内省,4473 次页面浏览,发布于 2010 年 10 月 14 日
- wxPython:网格提示和技巧,3476 次页面浏览,发布于 2010 年 4 月 4 日
- wxPython:创建一个简单的 MP3 播放器,3401 次页面浏览,发布于 2010 年 4 月 20 日
- Python 和 Microsoft Office -使用 PyWin32 ,3323 次页面浏览,发布于 2010 年 7 月 16 日
- wxPython 和 Threads ,3183 次页面浏览,发布于 2010 年 5 月 22 日
看起来 SqlAlchemy 和 Reportlab 是非常流行的话题。你认为我应该写一些关于这些很酷的软件包的文章吗?如你所见,wxPython 三次进入前十!关于 wxPython 接下来我该写什么?
在即将到来的这一年,我计划写一些其他的 GUI 工具包。你认为我应该先做哪一个?Tkinter,PySide,PyGUI 还是别的?您认为我应该涵盖哪些包或标准库?请随时通过下面的评论或我的联系方式(顶部的链接)让我知道。我期待着又一年的 Python 修补和写作,我希望你也是!感谢您今年的读者和鼓励!
2011 年十大文章
原文:https://www.blog.pythonlibrary.org/2011/12/31/top-ten-articles-of-2011/
大家都喜欢回顾文章。我不知道为什么,但名单就是吸引人。去年,我的十大榜单相当受欢迎,所以今年我打算再做一次。今年,我的访问量达到了 247,901 次,页面浏览量为 345,452 次,而去年的访问量为 137,727 次,页面浏览量为 213,814 次。
2010 年 8 月 3 日发布,页面浏览量为 16,378 次
- 如何使用 Python 发送页面浏览量达到 11,459 的电子邮件,发布于 2010 年 5 月 14 日
- CX _ Freeze 教程构建二进制序列!2010 年 8 月 12 日发布,页面浏览量为 9,735 次
- Python 和 Microsoft Office 使用 PyWin32 ,页面浏览量为 9,336,发布于 2010 年 7 月 16 日
- 另一个循序渐进的 SqlAlchemy 教程(第 1 部分,共 2 部分),7990 次页面浏览,发布于 2010 年 2 月 3 日
- Python:用 minidom 解析 XML7900 次页面浏览,发布于 2010 年 11 月 12 日
- 使用 Python 操作 Pdf 和 pyPdf 有 7,304 次页面浏览,发布于 2010 年 5 月 15 日
- wxPython: wx。ListCtrl 提示和技巧,7265 次页面浏览,发布于 2011 年 1 月 4 日
- Reportlab 表格-使用 Python 创建 pdf 格式的表格,页面浏览量为 6,634,发布于 2010 年 9 月 21 日
- Python 101:如何打开一个有 6440 页面浏览量的文件或程序,发布于 2010 年 9 月 4 日
上一次,我想在 2011 年写一些关于其他 GUI 工具包的文章,但是我从来没有真正接触过。反而写了很多 wxPython 的文章。从上面的列表中你可以看出,他们并不是非常受欢迎,只有一个人进入了前十名。也许今年我会展开一点,实际上看看其他一些 GUI 工具包。我还计划写更多关于连续两年进入我的十大列表的主题,如 reportlab 和 SqlAlchemy。如果你能想到关于这三个话题的任何你想知道更多的东西,请在评论中自由提问,我会考虑写下来。
我希望您已经准备好迎接 Python 编程的又一个辉煌之年。我知道我是!
2012 年十大文章
原文:https://www.blog.pythonlibrary.org/2013/01/10/top-ten-articles-of-2012/
像过去一样,我忘了在年初完成这篇文章。我希望你能从这篇迟来的帖子中找到一些有趣的阅读材料。我也希望我的读者度过了美好的假期,并期待着即将到来的一年!
注意:我使用谷歌分析来帮助我找出哪些文章做得好,并获得许多其他有趣的信息。这就是这些信息的来源。
- Python 101: 如何打开文件或程序(32631 次页面浏览)
- 如何用 Python 发送电子邮件(25573 次页面浏览)
- Python: 用 minidom 解析 XML(23,813 次页面浏览)
- 一个简单的逐步报告实验室教程 (22,260 次页面浏览)
- Python 101: 日志简介(16171 次页面浏览)
- Python 和 Microsoft Office 使用 PyWin32 (14,084 次页面浏览)
- CX _ Freeze 教程构建二进制序列!(12 724 次页面浏览)
- Python: 一个简单的逐步 SQLite 教程(11,834 次页面浏览)
- 另一个循序渐进的 SqlAlchemy 教程(第 1 部分,共 2 部分)(10,207 次页面浏览)
- wxPython: wx。ListCtrl 提示和技巧 (10,142 次页面浏览)
我总是觉得有趣的是,过去的文章仍然有如此多的点击量。其中七篇是 2010 年的,一篇是 2011 年的,只有两篇是 2012 年的。希望我今年写的文章能像往年一样经久不衰。一如既往,我欢迎关于写什么或评论 Python 书籍的建议。我期待今年收到你的来信!
往昔十大榜单
2016 年十大文章
原文:https://www.blog.pythonlibrary.org/2017/01/31/top-ten-articles-of-2016/
我尽量每年写一篇前一年的回顾,一般是新年第一天。然而,由于大量的项目和家庭医疗问题,我最终把它推到了今天。2016 年非常激动人心。我写过两本书。第一本书是我的第一本书的续集, Python 101 ,我将其命名为 Python 201:中级 Python 。然后在 2016 年 12 月底,我发布了我的第三本书, wxPython Cookbook ,这是我迄今为止最长的一本书。
博客也做得很好。2016 年,我们的浏览量达到了 957,763 次。这几乎是一百万的页面浏览量,这是相当令人兴奋的!让我们来看看今年排名前十的文章是什么:
- 一个简单的逐步报告实验室教程 - 31,145 次页面浏览
- 使用 Python 和 xlrd 阅读 Excel 电子表格 - 29,860 次浏览量
- Python 101:如何打开一个文件或程序
- Python 101:日志简介 - 23,211 次浏览量
- Python:用乌龟画画-20280 浏览量
- Python:一个简单的循序渐进的 SQLite 教程 - 20,162 浏览量
- Python:如何创建旋转日志-17751 浏览量
- Python 101:如何下载一个文件-17049 浏览量
- Python 101:读写 CSV 文件-14912 次页面浏览
- Reportlab:用 Python 创建 pdf 格式的表格 - 14,211 次页面浏览
我的介绍性 Reportlab 教程继续在列表中占据主导地位,第二个 Reportlab 教程在列表的最底部。还有两篇与日志记录相关的文章,所以用 Python 做这件事一定很有意思。对我来说,最大的收获是 40%的列表是由我的 Python 101 系列组成的,而其余的主要是 Python 或第三方库中模块的介绍性部分(第 10 项除外)。
人们对介绍性文章和 Reportlab 仍然很感兴趣。结合我从读者那里获得的反馈,这有助于我确定 2017 年的重点。除了我不断遇到的许多很酷的第三方模块之外,我希望继续编写更多关于 Python 标准库的有用教程。我还打算今年开始写至少一本书,希望我的读者会喜欢。
我期待着看到 2017 年还有什么可以提供的。我希望你能和我一起发现这一年!
2017 年十大 Python 文章
原文:https://www.blog.pythonlibrary.org/2018/01/05/top-ten-python-articles-of-2017/
就本博客的读者人数而言,2017 年是重要的一年。最终有 1,027,600 个会话、704,991 个用户和 1,233,805 次页面浏览。这些数字几乎是 2015 年的两倍,也是对 2016 年的巨大推动。不出所料,读者最多的国家是美国。在过去的几年里,印度是我的第二大读者群,英国和德国分列第三和第四位。
以下是该网站 2017 年最受欢迎的文章:
- 使用 Python 和 xlrd 阅读 Excel 电子表格 - 46,046 次页面浏览
- 一个简单的逐步报告实验室教程 - 41,008 次页面浏览
- Python 201: 多处理 Tutoria l - 35,493 页面浏览量
- Python - 用乌龟画画-31298 次浏览量
- python 3-asyncio 简介 - 30,909 次浏览量
- Python 101 - 如何打开文件或程序 - 28,145 次浏览量
- Python 101 - 日志简介 - 26,731 次浏览量
- Python - 如何创建旋转日志 - 23,027 次浏览量
- pyfpdf 简介 - 20,995 次浏览量
- Python 101 - 读写 csv 文件 - 20,840 次浏览量
这些文章都不是 2017 年发表的。事实上,上面列出的文章中,只有两篇是 2016 年的。其他的都是 2014 年或者更早的时候发表的。第一篇文章是关于使用 Microsoft Excel 的。其他受欢迎的主题似乎是与并发相关的主题,用 Python 创建 pdf 和创建日志。
2016 年,我设法写了两本书。我希望能在 2017 年完成一本书,但这并没有发生。相反,我最终让出版社以他们的名字重新出版 wxPython 食谱,命名为 wxPython 食谱书。我确实计划下个月通过 Packt Publishing 发行另一本书,尽管这不是一本真正的编程书。然而,我也计划今年至少写一本与 Python 相关的书,并出版发行。
无论如何,我很期待看到 2018 年的精彩。我希望你今年也能加入我的旅程。编码快乐!
TurboGears 2:在 Windows 上设置
原文:https://www.blog.pythonlibrary.org/2011/08/14/turbogears-2-setting-up-on-windows/
TurboGears 是几个可用的 Python web 框架之一。目前最受欢迎的是姜戈。在我工作的地方,我们选择了 TurboGears,因为它集成了支持组合键的 SQLAlchemy。那时,Django 不支持这个特性,我也不确定它是否支持。总之,我几乎完全在 Windows 平台上开发,并且发现 TurboGears 关于这个主题的文档有点令人困惑。所以我是这样做的。
注意:在本教程中我们将使用 turbo gears 2.1
入门指南
我们首先要确保 Python 设置正确。进入开始- >运行打开命令窗口,然后输入“cmd”并按下回车。尝试在那里键入“python”(不带引号),然后按回车键。如果你看到了 Python 外壳,那么我们就成功了一半。如果没有,那么我们需要修改一些设置。
修改您的路径
在 Windows 上,我发现通过将我的 Python 路径和脚本文件夹的路径添加到我的路径中来修改我的路径设置很有帮助。你可以这样做:
-
Right-Click "My Computer" and choose Properties (Windows XP) or you may have to go digging in Control Panel on Windows 7 and do a "Show All Control Panel Items" from the path bar and look for System. You should see something like this:
-
Go to the Advanced tab and press the Environmental Variable button to see something like the following:
-
这里你会看到两种类型的变量。底部是系统变量。向下滚动到标有路径的那一行,并在最后添加下面两行:;c:\ python 26;C:\Python26\Scripts(根据您的 Python 版本及其位置进行必要的调整)请注意,每个条目都由分号分隔,因此请确保您也这样做,否则将不起作用!
**### 安装先决条件
完成后,我们需要确保您已经安装了 SetupTools。转到 Python 包索引并下载如果你知道你还没有它,因为 SetupTools 将安装一个叫做 easy_install 的小脚本,这将使安装 TurboGears 变得轻而易举。只需滚动到页面的末尾,选择与您的 Python 版本相对应的 exe。你不能安装一个鸡蛋,除非你已经安装了这个,所以我不知道为什么他们有鸡蛋在那里开始。
完成后,打开一个新的命令窗口,尝试输入以下命令:easy_install。您应该会收到类似这样的错误:“错误:没有指定 URL、文件名或要求(参见- help)”。如果是这样,那么您已经安装了它,并且在您的路径上。干得好!
TurboGears 人员建议安装 virtualenv,以帮助您尽可能保持 Python 主安装(PyPI)的原始状态。当您需要试验不同版本的软件,并且不希望中断当前项目时,它也会有所帮助。这是如何工作的?嗯,virtualenv 在你的机器上为你创建了一个小的虚拟工作空间,在那里它放了一个 Python 的副本,并把所有的软件安装到那个虚拟的地方,供你使用。这是一个开发沙盒!既然您现在有了 easy_install,我们将使用它来安装。在命令窗口中,键入:easy_install virtualenv
这将导致 easy_install 出现在互联网上,并试图从 PyPI 下载和安装 virtualenv。它应该是这样的:
如果成功完成,那么我们就可以为我们的 TurboGears 安装创建一个虚拟环境了。将目录更改到您希望执行此操作的位置。在 Windows 7 上,如果你试图在你的文档或类似的收藏文件夹之外这样做,你可能会有安全问题,所以你应该坚持使用其中之一。对于本教程,我们将在我的文档中创建它。
一旦你在那里,输入如下:virtualenv - no-site-packages 示例
这将创建一个标记为“example”的文件夹,其中包含几个文件夹。您应该会在屏幕上看到以下内容:
- no-site-packages 命令将阻止虚拟环境从系统 Python 的 site-packages 文件夹中继承任何东西。现在,在命令窗口中将目录切换到您的示例文件夹中,并键入以下内容:Scripts\activate.bat(参见下面的屏幕截图)
安装和设置涡轮齿轮
现在你的虚拟环境是活跃的,并准备摇滚。您可以分辨出来,因为现在每一行都以您所在的文件夹的名称为前缀,在本例中是“example”。此时您可以安装 Turbogears,因为您的虚拟环境也安装了设置工具。但是,这次安装有点不同,因为我们将指定一个特定的 URL。键入以下内容(您可能想查看 TurboGears 网站,看看自撰写本文以来 URL 是否已更新):
easy_install.exe -i http://www.turbogears.org/2.1/downloads/current/index tg.devtools
根据你的电脑速度和你的连接,这将需要一段时间。TurboGears 是大量第三方 Python 包的混搭。你可以看着它安装,或者在等待的时候去喝一杯或者打一个电话。一旦完成,我们就快完成了。
注意:所有的包应该被安装到你的虚拟空间的 lib 文件夹中,而不是你默认的 Python 文件夹中。如果是后一种情况,你很可能没有激活虚拟环境
**### 创建 TurboGears 工作空间
我们之前安装的一个包是 Python Paste。TurboGears 使用这个包来引导一个新的 TurboGears 应用程序。因此,在您激活的虚拟环境中,键入以下命令:
paster quickstart example
注意:如果“example”文件夹的路径中有一个空格,您将收到一个需要安装 PyWin32 库的错误消息。非常讨厌。
当你运行这个时,你需要回答 3 个问题。首先是给项目取什么名字。默认名称与文件夹名称相同。请注意,项目名称必须是小写的。然后它会问你是否愿意使用樱井真子模板。它没有说的是,如果你选择否(这是默认的),你将得到石根模板。这取决于你。据我所见,樱井真子看起来和感觉起来更“蟒蛇”,所以我会推荐它,除非你已经知道石根或类似石根的地方。最后一个问题是您是否需要身份验证,这将为您提供一个简单的管理界面,您可以登录到该界面,默认设置是“yes”。您可以尝试这些方法,或者只使用默认方法。
下一步是将目录更改为新的项目目录,然后在命令行中键入以下内容:
python setup.py develop
这将把您的项目添加到虚拟环境中,并下载一些可能对您的开发有用的包。我真的不知道这些对你有什么帮助,但是根据 TurboGear 的文档,这似乎是一种需求。
倒数第二步是在命令行窗口中输入以下内容:
paster setup-app development.ini
这将启动您的数据库,它基本上只是为您同意的安全性/身份验证创建和填充一些默认信息。如果您没有安装那些东西或者不想运行这一步,您不必安装。但是,除非您登录,否则您将无法登录。
最后,您需要键入以下内容:
paster serve development.ini
现在你将拥有一个运行在 http://localhost:8080/(或 http://127.0.0.1:8080) 上的全功能网站。如果您打开浏览器并导航到该页面,您应该会看到如下内容:
包扎
现在您应该知道如何安装和设置 TurboGears web 应用程序。做了那么多工作后,前途无量。你需要开始阅读文档,添加一点 javascript 和一点 CSS,你很快就会有一个很酷的网站。祝你好运,玩得开心!****
PyPI 上发现两个新的域名仿冒库
原文:https://www.blog.pythonlibrary.org/2019/12/04/two-new-typosquatting-libraries-found-on-pypi/
根据 ZDNet ,在 Python 打包索引(PyPI)上发现了两个新的恶意包,旨在窃取 GPG 和 SSH 密钥。这些包被命名为 python3-dateutil 和 jeIlyfish ,其中第一个“L”实际上是一个 I。这两个库分别模仿了 dateutil 和水母包。
假冒的 python3-dateutil 将导入假冒的 jeIlyfish 库,该库包含试图窃取 GPG 和 SSH 密钥的恶意代码。虽然这两个库都已经从 PyPI 中删除了,但这只是提醒您要始终确保安装正确的包。
要了解完整的细节,请查看 ZDNet 的文章,因为它分析了这些库是如何工作的。
相关阅读
- 发现针对 Linux 的新恶意 Python 库
- 在 Python 包索引(PyPI) 上发现恶意库
Python 中的类型检查
原文:https://www.blog.pythonlibrary.org/2020/04/15/type-checking-in-python/
类型检查或提示是 Python 3.5 中新增的一个新特性。类型提示也被称为类型注释。类型提示是向函数和变量声明中添加特殊的语法,告诉开发人员参数或变量是什么类型。
Python 不强制类型提示。因此,在 Python 中仍然可以随意更改类型。然而,一些集成开发环境,如 PyCharm,支持类型提示,并会突出显示输入错误。你也可以使用一个叫做 Mypy 的工具来帮你检查你的打字情况。在本文的后面,您将了解到关于该工具的更多信息。
您将了解以下内容:
- 类型提示的利与弊
- 内置类型提示/变量注释
- 集合类型提示
- 可以是
None
的暗示值 - 类型提示功能
- 当事情变得复杂时该怎么办
- 班级
- 装修工
- 错认假频伪信号
- 其他类型提示
- 键入注释
- 静态类型检查
我们开始吧!
类型提示的利与弊
谈到 Python 中的类型提示,有几件事情需要提前了解。让我们先来看看类型提示的优点:
- 除了文档字符串之外,类型提示也是记录代码的好方法
- 类型提示可以让 ide 和 linters 给出更好的反馈和更好的自动完成
- 添加类型提示迫使您考虑类型,这可能有助于您在设计应用程序时做出正确的决策。
添加类型提示并不都是彩虹和玫瑰。有一些缺点:
- 代码更加冗长,也更难编写
- 类型提示增加了开发时间
- 类型提示仅在 Python 3.5+中有效。在那之前,你必须使用类型注释
- 类型提示在使用它的代码中会有一个较小的启动时间损失,特别是如果您导入了
typing
模块。
那么什么时候应该使用类型提示呢?以下是一些例子:
- 如果您计划编写简短的代码片段或一次性脚本,则不需要包含类型提示。
- 初学者在学习 Python 时也不需要添加类型提示
- 如果您正在设计一个供其他开发人员使用的库,添加类型提示可能是一个好主意
- 大型 Python 项目(即数千行代码)也可以从类型提示中受益
- 如果你要写单元测试,一些核心开发人员建议添加类型提示
在 Python 中,类型提示是一个颇有争议的话题。您不需要一直使用它,但是在某些情况下,类型提示会有所帮助。
让我们用这篇文章的剩余部分来学习如何使用类型提示!
内置类型提示/变量注释
您可以使用下列内置类型添加类型提示:
int
float
bool
str
bytes
这些既可以用在函数中,也可以用在变量注释中。变量注释的概念是在 3.6 版本中添加到 Python 语言中的。变量批注允许您向变量添加类型提示。
以下是一些例子:
x: int # a variable named x without initialization
y: float = 1.0 # a float variable, initialized to 1.0
z: bool = False
a: str = 'Hello type hinting'
您可以在根本不初始化变量的情况下向变量添加类型提示,第一行代码就是这种情况。另外 3 行代码展示了如何注释每个变量并适当地初始化它们。
接下来让我们看看如何为集合添加类型提示!
序列类型提示
在 Python 中,集合是一组项目。常见的集合或序列有list
、dict
、tuple
和set
。但是,您不能使用这些内置类型来注释变量。相反,你必须使用typing
模块。
让我们看几个例子:
>>> from typing import List
>>> names: List[str] = ['Mike']
>>> names
['Mike']
这里您创建了一个只有一个str
的list
。这表明您正在创建一个字符串的list
。如果您知道列表总是相同的大小,您可以在列表中指定每个项目的类型:
>>> from typing import List
>>> names: List[str, str] = ['Mike', 'James']
提示元组非常相似:
>>> from typing import Tuple
>>> s: Tuple[int, float, str] = (5, 3.14, 'hello')
字典略有不同,因为您应该提示键和值的类型是:
>>> from typing import Dict
>>> d: Dict[str, int] = {'one': 1}
如果您知道集合的大小可变,您可以使用省略号:
>>> from typing import Tuple
>>> t: Tuple[int, ...] = (4, 5, 6)
现在让我们来学习如果一个项目是类型None
该怎么做!
暗示值可能是零
有时一个值需要被初始化为None
,但是当它被稍后设置时,你希望它是别的值。
为此,您可以使用Optional
:
>>> from typing import Optional
>>> result: Optional[str] = my_function()
另一方面,如果值永远不能是None
,那么您应该在代码中添加一个assert
:
>>> assert result is not None
接下来让我们看看如何注释函数!
类型提示功能
类型提示函数类似于类型提示变量。主要的区别是你也可以给函数添加一个返回类型。
让我们来看一个例子:
def adder(x: int, y: int) -> None:
print(f'The total of {x} + {y} = {x+y}')
这个例子向您展示了adder()
有两个参数,x
和y
,它们都应该是整数。返回类型是None
,您可以在结束括号之后冒号之前使用->
来指定。
假设您想将adder()
函数赋给一个变量。您可以像这样将变量注释为Callable
:
from typing import Callable
def adder(x: int, y: int) -> None:
print(f'The total of {x} + {y} = {x+y}')
a: Callable[[int, int], None] = adder
Callable
接受函数的参数列表。它还允许您指定返回类型。
让我们再看一个例子,在这个例子中,你可以传递更复杂的参数:
from typing import Tuple, Optional
def some_func(x: int, y: Tuple[str, str],
z: Optional[float]: = None): -> Optional[str]:
if x > 10:
return None
return 'You called some_func'
对于这个例子,您创建了接受 3 个参数的some_func()
:
- 一个
int
- 两个项目的字符串
- 默认为
None
的可选float
注意,当你在函数中使用默认值时,在使用类型提示时,你应该在等号前后加一个空格。
它也返回None
或一个字符串。
让我们继续前进,发现在更复杂的情况下该怎么做!
当事情变得复杂时该怎么办
你已经学会了当一个值可以是None
时该怎么做,但是当事情变得复杂时你还能做什么?例如,如果传入的参数可以是多种不同的类型,您会怎么做?
对于特定用例,您可以使用Union
:
>>> from typing import Union
>>> z: Union[str, int]
这个类型提示的意思是,变量z
可以是字符串,也可以是整数。
也有函数接受一个对象的情况。如果该对象可以是几个不同对象中的一个,那么您可以使用Any
。
x: Any = some_function()
小心使用Any
,因为你不能真正说出你要返回的是什么。由于它可以是“任何”类型,这就像用一个简单的except
来捕捉所有异常。当你使用Any
的时候,你不知道你在捕捉什么异常,你也不知道你在暗示什么类型。
班级
如果你已经写了一个class
,你也可以为它创建一个注释。
>>> class Test:
... pass
...
>>> t: Test = Test()
如果您在函数或方法之间传递类的实例,这将非常有用。
装修工
装修工是一种特殊的野兽。它们是接受其他函数并修改它们的函数。在这本书的后面你会学到关于装饰者的知识。
给装饰者添加类型提示有点难看。
让我们来看看:
>>> from typing import Any, Callable, TypeVar, cast
>>> F = TypeVar('F', bound=Callable[..., Any])
>>> def my_decorator(func: F) -> F:
def wrapper(*args, **kwds):
print("Calling", func)
return func(*args, **kwds)
return cast(F, wrapper)
TypeVar
是一种指定自定义类型的方式。您正在创建一个定制的Callable
类型,它可以接受任意数量的参数并返回Any
。然后创建一个装饰器,并添加新类型作为第一个参数的类型提示以及返回类型。
仅静态代码检查器实用程序 Mypy 使用了cast
函数。它用于将值强制转换为指定的类型。在这种情况下,您将把wrapper
函数转换为类型F
。
错认假频伪信号
您可以为类型创建新名称。例如,让我们将List
类型重命名为Vector
:
>>> from typing import List
>>> Vector = List[int]
>>> def some_function(a: Vector) -> None:
... print(a)
现在Vector
和List
指的是同一类型的提示。混淆类型提示对于复杂类型很有用。
typing
文档中有一个很好的例子,复制如下:
from typing import Dict, Tuple
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]
这段代码允许您将类型嵌套在其他类型中,同时仍然能够编写适当的类型提示。
其他类型提示
您还可以使用其他几种类型提示。例如,有一些通用的可变类型,比如MutableMapping
,您可以将它们用于定制的可变字典。
还有一种ContextManager
类型可以用于上下文管理器。
查看所有各种类型的所有细节的完整文档:
键入注释
Python 2.7 开发于 2020 年 1 月 1 日结束。然而,在未来的几年里,人们将不得不使用许多遗留的 Python 2 代码。Python 2 中从未添加类型提示。但是您可以使用类似的语法作为注释。
这里有一个例子:
def some_function(a):
# type: str -> None
print(a)
要实现这一点,您需要让注释以type:
开头。这一行必须位于它所提示的代码的同一行或下一行。如果函数有多个参数,那么可以用逗号分隔提示:
def some_function(a, b, c):
# type: (str, int, int) -> None
print(a)
一些 Python IDEs 可能支持 docstring 中的类型提示。例如,PyCharm 允许您执行以下操作:
def some_function(a, b):
"""
@type a: int
@type b: float
"""
Mypy 将处理其他评论,但不会处理这些评论。如果您使用 PyCharm,您可以使用任何一种类型提示。
如果你的公司想使用类型提示,你应该提倡升级到 Python 3 来充分利用它。
静态类型检查
你已经多次看到有人提到我的 py。你可以在这里阅读所有相关内容:
如果您想在自己的代码上运行 Mypy,您需要使用pip
来安装它:
$ pip install mypy
一旦安装了mypy
,就可以像这样运行这个工具:
$ mypy my_program.py
Mypy 将针对您的代码运行,并打印出它发现的任何类型错误。当 Mypy 运行时,它不运行您的代码。这很像棉绒的工作原理。linter 是一个静态检查代码错误的工具。
如果你的程序中没有类型提示,Mypy 将不会打印任何错误报告。
让我们编写一个错误类型的提示函数,并将其保存到一个名为bad_type_hinting.py
的文件中:
# bad_type_hinting.py
def my_function(a: str, b: str) -> None:
return a.keys() + b.keys()
现在您有了一些代码,您可以对它运行 Mypy:
$ mypy bad_type_hinting.py
bad_type_hinting.py:4: error: "str" has no attribute "keys"
Found 1 error in 1 file (checked 1 source file)
这个输出告诉您第 4 行有一个问题。字符串没有keys()
属性。
让我们更新代码,删除对不存在的keys()
方法的调用。您可以将这些更改保存到名为bad_type_hinting2.py
的新文件中:
# bad_type_hinting2.py
def my_function(a: str, b: str) -> None:
return a + b
现在,您应该针对您的更改运行 Mypy,看看您是否修复了它:
$ mypy bad_type_hinting2.py
bad_type_hinting2.py:4: error: No return value expected
Found 1 error in 1 file (checked 1 source file)
哎呦!仍然有一个错误。这一次你知道你不期望这个函数返回任何东西。您可以修改代码,使其不返回任何内容,或者您可以修改类型提示,使其返回一个str
。
您应该尝试后一种方法,并将下面的代码保存到good_type_hinting.py
:
# good_type_hinting.py
def my_function(a: str, b: str) -> str:
return a + b
现在对这个新文件运行 Mypy:
$ mypy good_type_hinting.py
Success: no issues found in 1 source file
这一次你的代码没有问题!
您可以针对多个文件甚至整个文件夹运行 Mypy。如果您致力于在代码中使用类型提示,那么您应该经常在代码中运行 Mypy,以确保您的代码没有错误。
包扎
您现在知道了什么是类型提示或注释,以及如何操作。事实上,您已经学习了有效进行类型提示所需的所有基础知识。
在本文中,您了解了:
- 类型提示的利与弊
- 内置类型提示/变量注释
- 集合类型提示
- 可以是
None
的暗示值 - 类型提示功能
- 当事情变得复杂时该怎么办
- 班级
- 装修工
- 错认假频伪信号
- 其他类型提示
- 键入注释
- 静态类型检查
如果遇到困难,您应该查看以下资源寻求帮助:
- 来自 Mypy 的类型提示 Cheatsheet
- 排版
- Python 的类型模块的文档
Python 中不需要类型提示。您可以编写所有代码,而无需在代码中添加任何注释。但是类型提示很好理解,并且可能证明在您的工具箱中有它很方便。
理解 Python 中的回溯
原文:https://www.blog.pythonlibrary.org/2018/07/24/understanding-tracebacks-in-python/
当你第一次开始学习如何编程时,你首先想知道的事情之一是错误信息意味着什么。在 Python 中,错误消息通常被称为回溯。以下是一些常见的回溯错误:
- 句法误差
- ImportError or ModuleNotFoundError
- 属性错误
- NameError
当你得到一个错误时,通常建议你回溯(即回溯)。所以从回溯的底部开始,倒着读。
让我们看几个 Python 中回溯的简单例子。
句法误差
一个非常常见的错误(或异常)是语法错误。当程序员在写出代码时出错时,就会发生语法错误。例如,他们可能会忘记关闭左括号,或者偶然在字符串周围使用引号。让我们来看一个我在空闲时运行的例子:
>>> print('This is a test)
SyntaxError: EOL while scanning string literal
在这里,我们试图打印出一个字符串,我们收到一个语法错误。它告诉我们,错误与它没有找到行尾(EOL)有关。在这种情况下,我们没有用单引号结束字符串。
让我们看看另一个会引发语法错误的例子:
def func
return 1
当您从命令行运行此代码时,您将收到以下消息:
File "syn.py", line 1
def func
^
SyntaxError: invalid syntax
这里 SyntaxError 说我们使用了“无效语法”。然后 Python 使用一个箭头(^)来指出我们弄乱语法的确切位置,这很有帮助。最后,我们了解到我们需要查看的代码行在“第 1 行”。利用所有这些事实,我们可以很快发现我们忘记了在函数定义的末尾添加一对括号和一个冒号。
导入错误
我看到的另一个常见错误是 ImportError ,即使是有经验的开发人员也是如此。每当 Python 找不到您试图导入的模块时,您就会看到这个错误。这里有一个例子:
>>> import some
Traceback (most recent call last):
File "", line 1, in <module>ImportError: No module named some
这里我们了解到 Python 无法找到“some”模块。注意,在 Python 3 中,您可能会得到一个 ModuleNotFoundError 错误,而不是 ImportError。ModuleNotFoundError 只是 ImportError 的一个子类,实际上意思相同。不管您最终看到哪个异常,您看到这个错误的原因是因为 Python 找不到模块或包。这实际上意味着模块要么安装不正确,要么根本没有安装。大多数时候,你只需要弄清楚这个模块是哪个包的一部分,然后用 pip 或 conda 安装它。
属性错误
这个 AttributeError 真的很容易被意外击中,尤其是如果你的 IDE 中没有代码完成的话。当您尝试调用一个不存在的属性时,会出现此错误:
>>> my_string = 'Python'
>>> my_string.up()
Traceback (most recent call last):
File "", line 1, in <module>my_string.up()
AttributeError: 'str' object has no attribute 'up'
在这里,我试图使用一个不存在的字符串方法“up ”,而我应该调用“upper”。基本上,这个问题的解决方案是阅读手册或检查数据类型,并确保调用手边对象的正确属性。
NameError
当找不到本地或全局名称时,出现名称错误。如果你是编程新手,这种解释似乎很模糊。这是什么意思?在这种情况下,这意味着你试图与一个变量或者一个还没有定义的对象进行交互。让我们假设您打开一个 Python 解释器,并键入以下内容:
>>> print(var)
Traceback (most recent call last):
File "", line 1, in <module>print(var)
NameError: name 'var' is not defined
这里你发现‘var’并没有定义。这很容易解决,因为我们需要做的就是将“var”设置为某个值。让我们来看看:
>>> var = 'Python'
>>> print(var)
Python
看到这有多简单了吗?
包扎
在 Python 中你会看到很多错误,知道如何诊断这些错误的原因对于调试非常有用。很快,它将成为你的第二天性,你将能够只看一眼追溯,就知道到底发生了什么。Python 中还有许多其他内置异常,记录在他们的网站上,我鼓励你熟悉它们,这样你就知道它们的意思了。大多数时候,这应该是显而易见的。
相关阅读
- 关于错误和异常的 Python 文档
- Python 中的内置异常
- 回溯模块
- 在 Python 中处理异常
使用 Python 和 Pint 包进行单位转换
原文:https://www.blog.pythonlibrary.org/2021/09/01/unit-conversion-pint/
你需要经常测量吗?从一种计量单位转换到另一种计量单位怎么样?有一个名为 Pint 的 Python 包,使得处理数量变得容易。Pint 允许你在数值和数量之间进行算术运算。你可以在 Pint 的 GitHub 项目中看到许多不同的单元类型。
让我们从学习如何安装 Pint 开始!
装置
您可以像这样使用 pip 安装 Pint :
python3 -m pip install pint
如果您是一个 conda 用户,那么您可能希望使用以下命令:
conda install -c conda-forge pint
现在你已经安装了 Pint,你可以开始学习如何使用它了!
品脱入门
Pint 最酷的特性之一是你可以用它从一种单位类型转换到另一种。例如,您可能想将某种英制单位转换为公制单位。
一个常见的用例是将英里转换为公里。打开您的 Python REPL(或空闲),尝试以下代码:
>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
>>> distance = 5 * ureg.mile
>>> distance
<Quantity(5, 'mile')>
>>> distance.to("kilometer")
<Quantity(8.04672, 'kilometer')>
这里你创建一个名为距离的数量对象。您将其值设置为 5 英里。然后,要将其转换为千米,您可以调用距离的 to() 方法,并传入您想要的新数量名称。结果是 5 英里换算成 8.04672 公里。
您也可以在同一系统内将数量转换为不同的单位类型。例如,如果需要,您可以将千米转换为厘米:
>>> from pint import UnitRegistry
>>> ureg = UnitRegistry()
>>> distance_in_km = 5 * ureg.kilometer
>>> distance_in_km
<Quantity(5, 'kilometer')>
>>> distance_in_cm = distance_in_km.to("centimeter")
>>> distance_in_cm
<Quantity(500000.0, 'centimeter')>
Pint 解析字符串
Pint 的一个很酷的特性是你可以用字符串来指定数量。这意味着你可以这样做:
>>> my_quantity = ureg.Quantity
>>> my_quantity(2.54, 'centimeter')
<Quantity(2.54, 'centimeter')>
或者你可以简化它,甚至更简单:
>>> my_quantity = ureg.Quantity
>>> my_quantity('2.54in')
<Quantity(2.54, 'inch')>
现在您已经在不同的单位类型之间进行了转换,您已经准备好学习使用 Pint 进行字符串格式化了。
对 Pint 使用字符串格式
Pint 支持使用 Python 的进行格式化。format() 并通过使用 f 字符串。这里有一个来自品脱教程的例子:
>>> ureg = ureg.Quantity
>>> accel = 1.3 * ureg['meter/second**2']
>>> print(f'The str is {accel}')
The str is 1.3 meter / second ** 2
当对 f 字符串求值时,Quantity 对象被转换成更易于阅读的格式。
Pint 通过扩展 Python 的格式化功能走得更远。以下是他们定制的“漂亮印刷品”的一个例子:
>>> ureg = ureg.Quantity
>>> accel = 1.3 * ureg['meter/second**2']
>>> # Pretty print
>>> 'The pretty representation is {:P}'.format(accel)
'The pretty representation is 1.3 meter/second²'
Pint 还支持 Jupyter 笔记本的 LaTeX 和 HTML 定制打印。
包扎
Pint 是一个非常好的 Python 包。虽然本教程没有涉及到它,Pint 允许您设置您的区域设置,以便单元名称与您的语言相匹配。如果您经常处理需要在单位类型之间进行转换的量(如厘米到毫米或英寸到厘米),这可能正是您需要的,可以使您的编码工作更容易。
更简洁的 Python 包
想了解其他优秀的第三方 Python 包吗?查看以下文章:
为远程服务使用 Asyncio 和批处理 API
原文:https://www.blog.pythonlibrary.org/2022/09/20/using-asyncio-and-batch-apis/
批处理 API 简介
在现代 Python 应用程序中,使用 REST 或其他基于 web 的技术访问远程 API 是很常见的。批处理 API 能够通过一次调用处理多个请求。您可以使用批处理 API 来减少对远程服务的网络调用次数。当您必须对一个远程服务进行大量调用,而这些调用可以被批量处理成一个请求时,这是非常理想的。
假设您有一个 REST API,它返回股票的当前价格。使用一个简单的 API 获取一个股票标识符并返回当前价格,如果您需要获得一千只股票的价格,您将需要进行一千次 API 调用。提供相同功能的批处理 API 将在请求中获取一组股票标识符,并返回所有请求的标识符的当前价格。使用 batch API,您将能够在一个请求中获取您需要的所有价格。这减少了网络开销,从而减少了应用程序的延迟。它还可能减少远程服务器上的负载。
在本文中,您将学习如何使用批处理模式和 Python 的 asyncio 包将许多单独的函数调用批处理成更少数量的请求。
动机:Excel 中的异步 Python 函数
这篇文章来自于一个使用 Python Excel 插件 PyXLL 的用户,他问了一个关于如何使用批处理 API 来简化 Excel 电子表格的问题。
PyXLL 将 Python 嵌入到 Excel 中,它使能够在 Excel 电子表格中直接调用 Python 函数。每次单元使用 Python 函数进行计算时,它都会调用该 Python 函数。在本例中,这个函数是一个异步函数,它向 REST 服务器发出请求。
一个有数千个单元格向 REST API 发出单独请求的工作表花费的时间太长。解决方案是使用批处理模式!
背景:异步和并发
当向远程服务器发出多个请求时,您通常不希望发送一个请求并在发送下一个请求之前等待响应。通常并行(同时)发送多个请求并等待所有响应要快得多。您可以在 Python 中使用多线程或异步编程来实现这一点。本节概述了多线程和异步编程。你也会看到为什么你会选择一个而不是另一个。
多线程操作
多线程是一种并发执行多个任务的方法。在线程模型中,您启动多个线程,每个线程同时执行其代码。如果您的问题是 CPU 受限的,使用多线程将它分解成并行运行的任务会有所帮助。当一个程序的主要性能瓶颈是 CPU 处理时间时,就说这个程序是 CPU 受限的。
对于 Python 的线程化来说,有一些微妙之处你不会在本文中深入探讨,但是理论上,这就是它的基本工作原理!
计算机的操作系统管理所有的线程,并确保每个线程都得到一份 CPU 时间。这增加了复杂性,因为每次上下文切换花费的时间可以用来做其他事情。这种复杂性随着线程数量的增加而增加。当瓶颈在等待 IO(例如,网络请求)时,为每个请求运行多个线程并等待网络响应是不理想的。它也不能扩展到成千上万的请求。这就是异步编程的用武之地。
用 asyncio 进行异步编程
Python 中的异步编程是一种不同的并发模型,它不使用多线程。相反,所有事情都在一个线程上运行,Python 管理活动任务之间的切换。它非常适合使用大量网络请求或其他 IO 绑定任务(如磁盘或数据库访问)的程序。
事件循环管理一组正在运行的任务。当一个任务等待网络请求完成时,它就“等待”了。当任务等待事件时,循环可以安排其他任务运行。这允许另一个任务发送另一个网络请求,然后等待,允许另一个任务运行等等。当网络请求就绪时,事件循环可以恢复任务。这使得我们可以同时处理多个请求,而不需要为每个请求增加一个线程的开销。
Python 关键字“ async ”将一个函数指定为要在事件循环上运行的异步函数。关键字“ await 让位于事件循环,并等待另一个异步函数或任务完成。Python 包“ asyncio ”提供了管理异步任务所需的原语。
批处理 API 的优势
上面您已经了解到您可以同时发出多个请求。这比在发送下一个请求之前等待每个请求返回要快得多。如果可以同时发送所有需要的请求,为什么还需要批处理 API 呢?
发送多个请求比发送单个请求需要更多的网络流量。如果您可以使用单个请求来请求您需要的所有数据,从数据传输的角度来看,这样会更有效。
它还可以有其他好处。例如,如果远程服务器可以通过一次获取所有内容来减少它需要做的工作量,那么它为一个批处理请求提供服务所需的时间实际上会少于为同等的单个请求提供服务所需的总时间。
Python 中的批处理模式
现在您已经了解了什么是批处理 API,并且您可以使用 Python 中的异步编程并发地发出多个请求,那么什么是批处理模式,为什么您需要它?
简而言之,批处理模式将多个请求收集到一个请求中,并一次性分派这个请求。在本文的其余部分,您将看到如何使用它将使用大量单个请求的实现转变为一个批量请求的实现,从而减少对远程服务器的调用。
示例:获取街道地址的位置
您将使用获取街道地址的位置作为示例。为此,你可以使用来自 https://www.geoapify.com/的 REST API。有一个免费层可以注册测试,它支持批量获取多个位置。要使用下面的代码,您需要注册并获得一个 API 密钥。
这是第一次尝试用一些代码来获取一些街道地址的位置:
# Import the modules you're going to use.
# You may need to 'pip install aiohttp'
from urllib.parse import urlencode
import asyncio
import aiohttp
import json
# Test data
STREET_ADDRESSES = [
"1600 Pennsylvania Avenue, Washington DC, USA",
"11 Wall Street New York, NY",
"350 Fifth Avenue New York, NY 10118",
"221B Baker St, London, England",
"Tour Eiffel Champ de Mars, Paris",
"4059 Mt Lee Dr.Hollywood, CA 90068",
"Buckingham Palace, London, England",
"Statue of Liberty, Liberty Island New York, NY 10004",
"Manger Square, Bethlehem, West Bank",
"2 Macquarie Street, Sydney"
]
# Constants for accessing the Geoapify API
GEOCODING_API = "https://api.geoapify.com/v1/geocode/search"
YOUR_API_KEY = "xxxx-xxxx-xxxx-xxxx"
async def get_location(address):
"""Return (latitude, longitude) from an address."""
# Construct the URL to do the lookup for a single address
query_string = urlencode({
"apiKey": YOUR_API_KEY,
"text": address,
"limit": 1,
"format": "json"
})
url = f"{GEOCODING_API}?{query_string}"
# Make the request to the API
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
data = await response.read()
# Read the json string and return the latitude and longitude
# from the first result (there will only be one)
results = json.loads(data.decode())["results"]
return results[0]["lat"], results[0]["lon"]
async def main():
# Print the city for each IP address
tasks = []
for address in STREET_ADDRESSES:
location = await get_location(address)
print(f"{address} -> {location}")
# Because it's an async function you need to run it using the asyncio event loop
loop = asyncio.new_event_loop()
loop.run_until_complete(main())
您可能已经注意到,上面的代码仍然在为每个地址顺序调用 API。尽管使用了 async,但是 for 循环目前正在等待每个请求完成,然后再移动到下一个地址。要解决这个问题,您可以使用 asyncio 函数“gather”。通过将任务收集在一起,并在最后等待它们,你不需要单独等待它们。
更新后的主函数现在看起来像这样:
async def main():
# Get the location for each address
tasks = []
for address in STREET_ADDRESSES:
tasks.append(get_location(address))
# Wait for all tasks to complete
locations = await asyncio.gather(*tasks)
# Print them all once all requests have completed
for address, location in zip(STREET_ADDRESSES, locations):
print(f"{address} -> {location}")
您仍在向服务器发送多个请求。接下来,您将看到批处理模式如何将这些请求批处理在一起,以减少请求的数量,而无需修改您的主函数。
示例:使用批处理 API 提取多个位置
使用批处理 API,您可以一次提交多个请求。如果处理请求的服务器能够比处理单个请求更有效地处理一个批处理请求,那么在处理多个查询时,使用批处理请求会快得多。
您将使用上面使用的地理编码 API 的批处理版本。有点复杂。你不需要提交一个地址作为 URL 的一部分,而是需要提交一个 POST 请求。由于处理一个批处理可能需要一段时间,而不是立即返回结果,服务器将首先用一个请求 id 进行响应,然后您可以查询该请求 id 来检查结果是否准备好。这是实现批处理 API 时常用的模式。
下面的函数向 API 查询地址列表的位置。它使用对批处理 API 的单个请求来实现这一点。
# Constants for accessing the Geoapify batch API
GEOCODING_BATCH_API = "https://api.geoapify.com/v1/batch/geocode/search"
YOUR_API_KEY = "xxxx-xxxx-xxxx-xxxx"
async def get_locations(addresses):
"""Return a dictionary of address -> (lat, lon)."""
# Construct the URL to do the batch request
query_string = urlencode({"apiKey": YOUR_API_KEY})
url = f"{GEOCODING_BATCH_API}?{query_string}"
# Build the JSON payload for the batch POST request
data = json.dumps(addresses)
# And use Content-Type: application/json in the headers
headers = {
"Content-Type": "application/json",
"Accept": "application/json"
}
# Make the POST request to the API
async with aiohttp.ClientSession() as session:
async with session.post(url, data=data, headers=headers) as response:
response_json = await response.read()
response_data = json.loads(response_json)
# The API can return a dict with a pending status if it needs more
# time to complete. Poll the API until the result is ready.
while isinstance(response_data, dict) \
and response_data.get("status") == "pending":
# Wait a bit before calling the API
await asyncio.sleep(0.1)
# Query the result to see if it's ready yet
request_id = response_data.get("id")
async with aiohttp.ClientSession() as session:
async with session.get(url + f"&id={request_id}") as response:
response_json = await response.read()
response_data = json.loads(response_json)
# Gather the results into a dictionary of address -> (lat, lon)
locations = {}
for result in response_data:
address = result["query"]["text"]
coords = result["lat"], result["lon"]
locations[address] = coords
return locations
放在一起:批处理模式
现在您有了一个函数,可以调用批处理 API 来批量查找地址列表的位置。您的下一个任务是重构“get_location ”,以便它可以利用批处理 API,而不必更改您的“main”函数。
为什么不改变“主”功能?在这个简单的示例中,将 main 函数改为调用 get_locations 是微不足道的。在现实世界的项目中,这种重构通常不那么简单。其他时候,甚至不希望更改函数的输入,您通常希望向函数的最终用户隐瞒实现细节。
回到激发这篇文章的最初问题,关于使用 Excel 插件 PyXLL 从 Excel 调用 Python 函数。在这种情况下,最终用户是一个可能对 Python 一无所知的 Excel 用户。拥有一个接受一个输入并返回一个输出的函数符合他们作为 Excel 用户的期望。让他们接触批次的概念会使事情变得不必要的混乱。这也意味着他们将不得不构建他们的电子表格,以有效的方式调用它。在这种情况下,在后台处理请求的批处理,同时保持终端用户看到的界面绝对是一个优势。
它是如何工作的
在伪代码中,我们要写的内容是这样的:
async def get_location(address)
1\. Put the address on a queue of requests.
2\. Start a background task that:
i. Waits a short time for other requests to be enqueued.
ii. Processes all queued requests as a batch.
iii. Notifies the waiting 'get_location' functions.
3\. Wait for the result and return it.
批处理请求
您可以使用 asyncio 在 Python 中实现这一点。您的“get_location()”函数可以启动一个后台任务来处理任何排队的请求。它将等待,直到后台任务处理完包含您请求的批处理,然后返回它。后台任务应该只启动一次,所以您需要在启动它之前检查它是否已经在运行。如果“get_location”被多次调用,因为它是一个异步函数,它可以在其他函数等待的时候运行。每个后续调用都会向当前队列添加一个请求。
要将结果从后台任务返回到等待的 get_location 函数,您将使用 asyncio 原语" Future "。未来是一个可感知的物体,当它被等待时,它会一直阻塞,直到一个结果被设定。
您的“get_location()”函数被重写为批处理请求,使用一个 future 返回结果,如下所示:
# State for batching requests
ADDRESSES_BATCH = []
BATCH_LOOP_RUNNING = False
async def get_location(address):
"""Return (latitude, longitude) from an address."""
global BATCH_LOOP_RUNNING
# Create a Future that will be set with the location once the
# request has completed.
loop = asyncio.get_event_loop()
future = loop.create_future()
# Add the ip address and future to the batch
ADDRESSES_BATCH.append((address, future))
# Start 'process_batches' running on the asyncio event loop if it's
# not already running.
# We've not written 'process_batches_loop' yet!
if not BATCH_LOOP_RUNNING:
BATCH_LOOP_RUNNING = True
asyncio.create_task(process_batches_loop())
# Wait for the batch your address is in to return
await future
# And return the result
return future.result()
上面的代码创建了一个 asyncio。Future 对象,并将该对象和地址添加到将作为批处理的列表中。如果处理批处理的循环没有运行,它将使用“ asyncio.create_task ”启动它。函数“asyncio.create_task”在 asyncio 事件循环上调度您的“processes_batched_loop ”,以便在其他正在运行的任务等待时调用。您还没有定义函数“process_batches_loop ”,但是接下来您将会这样做。您等待未来,允许在 asyncio 事件循环上运行的其他任务运行,一旦设置了结果,您就返回它。
处理批次
“process_batches_loop”函数等待一小段时间,以允许其他函数向“ADDRESSES_BATCH”列表添加请求。然后,它将所有排队的请求作为一个调用提交给 REST API。一旦结果从 REST API 返回,它就解包结果并将结果设置在期货上,允许每个等待“get_location”的函数完成。
async def process_batches_loop():
global ADDRESSES_BATCH, BATCH_LOOP_RUNNING
# Loop while BATCH_LOOP_RUNNING is True
while BATCH_LOOP_RUNNING:
# Wait for more to be added to the batch
await asyncio.sleep(0.1)
# If nothing has been added to the batch then continue
# to the start of the loop as there's nothing to do.
if not ADDRESSES_BATCH:
continue
# Get the current items from the batch and reset the batch
batch = ADDRESSES_BATCH
ADDRESSES_BATCH = []
# Get the locations of the current batch
addresses = [address for (address, future) in batch]
locations = await get_locations(addresses)
# Set the results on the futures from this batch.
# This allows each awaiting 'get_location' function to continue.
for address, future in batch:
coords = locations.get(address)
future.set_result(coords)
你现在已经达到了最初的目标。您有一个函数“get_location ”,它看起来像您的原始函数。它接受一个地址并返回一个位置。在后台,它将这些单独的请求批处理在一起,并将它们提交给批处理 API。与只处理单个请求的 API 相比,批处理 API 可以提供更好的性能,现在您的函数可以利用这一点,而无需对函数的调用方式进行任何更改。
等待将请求添加到批处理中所花费的时间应该进行调整,以便与函数的使用方式相匹配。如果函数可能几乎同时被多次调用,例如,在 Excel 中同时计算多个单元格,那么可以使用短暂的延迟。在其他情况下,例如,如果调用是由一些可能需要几秒钟的用户输入引起的,则需要更长的延迟。记录每个项目添加到批中的时间以及处理每个批的时间将有助于我们了解等待的最佳时间。
改进的余地
这里给出的代码有很大的改进空间。我希望这能给你一些想法,让你在自己的项目中使用它!代码是以一种相对简单的方式编写的,试图使其背后的意图变得清晰,但是在您将它用于现实世界的应用程序之前,您需要考虑一些事情。
- 错误检查。这大概是最需要补充的。如果循环处理批处理失败会发生什么?您的代码应该处理任何可能发生的错误,或者至少记录它们,以便您可以跟踪发生了什么。
- 不必要的循环。即使无事可做,处理所写批处理的循环也会继续循环。您可以修改它来等待一个“ asyncio。Event "对象,直到您至少排队了一个项目。或者,当没有更多的项目要处理时,您可以退出循环,并在需要时重新启动它。
- 程序结束时停止循环。只要 BATCH_LOOP_RUNNING 为 True,循环就会继续循环。当程序结束时,你应该考虑如何优雅地结束循环。这可以简单地将 BATCH_LOOP_RUNNING 设置为 False,然后等待任务完成。函数“asyncio.create_task”返回一个任务对象,您可以将它存储为一个全局变量。
包扎
在本文中,您了解了什么是批处理 API,以及为什么使用批处理 API 会有好处。您研究了 Python 中的并发性,比较了多线程和异步编程。最后,您演示了如何使用批处理模式将处理单个请求的函数转换成使用批处理 API 的函数。
本文中使用的完整代码可从这里获得https://gist . github . com/tonyroberts/a 539885 e 12892 DC 43935860 f 75 cdfe 7 e。
REST APIs 是批处理 API 的一个例子。您可以将同样的技术应用于数据库查询,或者任何其他类型的函数,在这些函数中,批量排队数据更有效。
当您希望为函数的最终用户保持简单时,在后台批处理请求并对面向用户的函数或 API 隐藏细节非常有用。它也可以是一种将批处理 API 改造成难以重构的现有代码库的方法。
本文所基于的用例的动机是从 Excel 调用一个 Python 函数,而没有向 Excel 用户公开管理批处理调用的细节。用户调用一个简单的函数来执行单独的请求。如果他们构建一个跨不同单元格发出多个请求的表,那么这个解决方案会在后台自动将所有内容批处理在一起。Excel 加载项 PyXLL 支持将 Python 集成到 Excel 中,使得将 Python 函数作为 Excel 工作表函数调用成为可能。
有关 PyXLL 插件的更多信息,请访问https://www.pyxll.com。
在烧瓶中使用 pyGal 图
原文:https://www.blog.pythonlibrary.org/2015/04/16/using-pygal-graphs-in-flask/
我最近接了一个项目,需要用我从数据库中查询的数据在网页上绘制一些数据。由于我热爱 Python,所以我决定用它来完成这个任务。我和 Flask 一起提供网页服务,和 pygal 一起创建图表。在本教程中,我将向您展示如何做到这一点,但没有数据库逻辑。相反,我们将从地下气象站获取气象数据,并绘制成图表。我们开始吧!
入门指南
按照本教程,您需要安装 Flask 和 pygal。方法如下:
pip install Flask, pygal
如果您不想将 Flask 及其所有依赖项安装到您的系统 Python 上,您应该创建一个 virtualenv,激活它并在那里安装这些包。
在 Flask 中创建简单的图形
用 pygal 创建图表轻而易举。但在此之前,我们需要一些数据!我决定从气象地下网站获取一些数据。你需要和他们签约来获得一个 API 密匙,这样你也可以查询。一旦有了这些,您就可以使用下面的代码了:
import pygal
import json
from urllib2 import urlopen # python 2 syntax
# from urllib.request import urlopen # python 3 syntax
from flask import Flask
from pygal.style import DarkSolarizedStyle
app = Flask(__name__)
#----------------------------------------------------------------------
@app.route('/')
def get_weather_data(date='20140415', state='IA', city='Ames'):
"""
Date must be in YYYYMMDD
"""
api_key = 'API_KEY'
url = 'http://api.wunderground.com/api/{key}/history_{date}/q/{state}/{city}.json'
new_url = url.format(key=api_key,
date=date,
state=state,
city=city)
result = urlopen(new_url)
js_string = result.read()
parsed = json.loads(js_string)
history = parsed['history']['observations']
imp_temps = [float(i['tempi']) for i in history]
times = ['%s:%s' % (i['utcdate']['hour'], i['utcdate']['min']) for i in history]
# create a bar chart
title = 'Temps for %s, %s on %s' % (city, state, date)
bar_chart = pygal.Bar(width=1200, height=600,
explicit_size=True, title=title, style=DarkSolarizedStyle)
#bar_chart = pygal.StackedLine(width=1200, height=600,
# explicit_size=True, title=title, fill=True)
bar_chart.x_labels = times
bar_chart.add('Temps in F', imp_temps)
html = """
%s
%s
""" % (title, bar_chart.render())
return html
#----------------------------------------------------------------------
if __name__ == '__main__':
app.run()
让我们把它分解一下。我们要看的主要部分是 get_weather_data 函数。它有一个装饰器 @app.route('/') 告诉 Flask 如果我们去网站的主页就调用这个函数。在这种情况下,主页应该是 http://localhost:5000/ 。接下来,我们有我们的 Weather Underground API 键和我们想要使用的 URL。在这种情况下,我正在抓取历史数据。您可以使用稍微不同的 URL 来查询当前天气。之后,我们打开我们的 URL,读取它并解析它返回的 JSON。最后,我们取出英制格式的温度以及记录的一天中的小时数。
现在我们有了数据,我们可以创建图表了!我们将从创建一个条形图开始。这里我设置了条形图的大小,否则它会变得很大。然后我们设置它的 X 标签并将数据添加到图表中。最后,我们创建 HTML 来显示并调用 Pygal 的 render 方法,该方法返回 XML 格式的 SVG。您也可以调用其他将返回其他类型的呈现方法。
如果您想尝试 Pygal 提供的许多其他图表中的一个,请注释掉实例化条形图的调用行,并取消注释 StackedLine 图的行。然后重新运行脚本。您最终应该会得到一个类似这样的图表:
使用模板显示图表
大多数情况下,当您使用 Flask 时,您会希望使用模板。Flask 使用 Jinja 创建的模板。您需要在保存上面的 Python 代码的同一个目录中创建一个模板文件夹。在其中,您需要放入以下 HTML 代码:
{{ title }}
使用 Python 创建快捷方式
原文:https://www.blog.pythonlibrary.org/2010/01/23/using-python-to-create-shortcuts/
在我的工作中,我用 Python 编写了大量的系统管理脚本。例如,几乎所有的登录脚本都是用 Python 编写的(其中一些是从 Kixtart 移植过来的)。多年来,我一直负责创建新应用程序的快捷方式,这些应用程序需要放在用户的桌面上或开始菜单中,或者两者都放。在本文中,我将向您展示如何完成这项任务。
注意:这是一篇只适用于 Windows 的文章,所以如果你不使用那个操作系统,那么这篇文章可能会让你感到厌烦。见鬼,它可能会这样做!
在 Windows 上你需要做的第一件事就是 Mark Hammond 的 PyWin32 包(又名:Python for Windows extensions)。我还推荐蒂姆·戈尔登的 winshell 模块,因为它可以更容易地找到特定用户的文件夹。我将在本文中使用 Python 2.5,但据我所知,PyWin32 与 Python 3 兼容,因此本教程也适用于使用 Python 3 的读者。
我必须做的最简单的任务是在用户的桌面上创建一个 URL 链接。除非您需要指定特定的浏览器,否则这是一个非常简单的任务:
import os, winshell
desktop = winshell.desktop()
path = os.path.join(desktop, "myNeatWebsite.url")
target = "http://www.google.com/"
shortcut = file(path, 'w')
shortcut.write('[InternetShortcut]\n')
shortcut.write('URL=%s' % target)
shortcut.close()
在上面的代码中,我们导入了 os 和 winshell 模块。我们使用 winshell 来抓取当前用户的桌面路径,然后使用 os.path 的 join()函数来连接路径和 url 快捷方式的名称。因为这只是 Windows,所以您真的不需要这样做。字符串连接也同样有效。请注意,我们需要提供一个“url”扩展名,以便 Windows 知道该做什么。然后我们用之前创建的路径写一个文件,把【internet shortcut】写成第一行。在第二行,我们写下 url 目标,然后关闭文件。就是这样!
下一个例子会稍微复杂一点。在其中,我们将使用 PyWin32 包中的 win32com 模块创建一个到 Media Player Classic 的快捷方式,这是一个不错的开源媒体播放器。让我们来看看一些代码,这样我们就可以看到如何做这件事:
import os, winshell
from win32com.client import Dispatch
desktop = winshell.desktop()
path = os.path.join(desktop, "Media Player Classic.lnk")
target = r"P:\Media\Media Player Classic\mplayerc.exe"
wDir = r"P:\Media\Media Player Classic"
icon = r"P:\Media\Media Player Classic\mplayerc.exe"
shell = Dispatch('WScript.Shell')
shortcut = shell.CreateShortCut(path)
shortcut.Targetpath = target
shortcut.WorkingDirectory = wDir
shortcut.IconLocation = icon
shortcut.save()
这里主要带走的是外壳部分。首先从 win32com.client 导入 Dispatch ,然后调用 Dispatch('WScript。获取一个 Shell 对象(类似于),然后用它来创建一个快捷方式对象。一旦有了这些,就可以给快捷方式的属性赋值了,这些属性是 Targetpath、WorkingDirectory 和 IconLocation。注意,WorkingDirectory 对应于普通快捷方式属性对话框中的“开始于”字段。在上面的脚本中,IconLocation 可以是一个图标文件,也可以直接从可执行文件中提取图标。这里有一个小问题,如果你不调用保存,图标就不会被创建。在前面的例子中,我们不需要显式地调用 file 对象上的 close ,因为 Python 会在脚本完成时为我们处理这些。
让我们拿这两个例子来做一个可重用的函数,这样我们就可以在任何我们想要的登录脚本中使用它们:
from win32com.client import Dispatch
def createShortcut(path, target='', wDir='', icon=''):
ext = path[-3:]
if ext == 'url':
shortcut = file(path, 'w')
shortcut.write('[InternetShortcut]\n')
shortcut.write('URL=%s' % target)
shortcut.close()
else:
shell = Dispatch('WScript.Shell')
shortcut = shell.CreateShortCut(path)
shortcut.Targetpath = target
shortcut.WorkingDirectory = wDir
if icon == '':
pass
else:
shortcut.IconLocation = icon
shortcut.save()
我们可以添加的一个明显的改进是使用 os.path.dirname 方法从目标中提取工作目录,并消除传递该信息的需要。当然,我见过一些奇怪的快捷方式,它们根本没有指定目标或工作目录!无论如何,我希望这篇文章对您的脚本编写有所帮助。下次见!
使用 Python 编辑错误的快捷方式
原文:https://www.blog.pythonlibrary.org/2010/02/13/using-python-to-edit-bad-shortcuts/
几周前,我写了一些我们在工作中使用的脚本,用于为 Windows 中的各种程序创建快捷方式。嗯,我们也推出了一些程序的更新,改变了程序的路径,然后我们需要改变用户的快捷方式来匹配。不幸的是,一些用户会更改快捷方式的名称,这使得查找变得困难。Python 使得找到我需要改变的快捷方式变得很容易,在这篇文章中,我将向你展示如何去做。
对于这个脚本,你需要 Python 2.4-3.x、 PyWin32 包和蒂姆·戈登的 winshell 模块。现在,让我们看一下代码:
import glob
import win32com.client
import winshell
paths = glob.glob(winshell.desktop() + "\\*.lnk")
shell = win32com.client.Dispatch("WScript.Shell")
old_program_path = r"\\SomeUNC\path\old.exe"
# find the old links and change their target to the new executable
for path in paths:
shortcut = shell.CreateShortCut(path)
target = shortcut.Targetpath
if target == old_program_path:
shortcut.Targetpath = r"\\newUNC\path\new.exe"
shortcut.save()
首先,我们导入 glob、 win32com.client 和 winshell ,然后我们创建一个路径列表,其中包含我们需要更改的快捷方式。在这种情况下,我们只查看用户的桌面,但是您可以修改它以包括其他目录(比如启动)。然后我们创建一个 shell 对象,并设置一个变量来保存我们想要找到的旧程序的链接路径。
最后,我们遍历链接路径,并使用 shell 对象来查看快捷方式的属性。我们关心的唯一属性是快捷方式的目标,所以我们获取它并使用条件来检查它是否是正确的。如果不是,那么我们继续循环到下一个链接。如果是正确的,那么我们改变目标指向新的位置。
幸运的是,这种情况不常发生;但是当它出现时,您将知道 Python 会支持您。
使用 Python 将数据记录到日志中
原文:https://www.blog.pythonlibrary.org/2014/10/24/using-python-to-log-data-to-loggly/
我的一位读者建议我应该尝试将我的数据记录到一个名为 Loggly 的 web 服务中。据我所知,Loggly 是一种与企业中的每个人共享日志数据的方式,这样您就不再需要登录到单独的机器。他们还提供了日志的图表、过滤器和搜索。他们没有 Python API,但是通过 Pythonurllib 2模块和 simplejson 向 Loggly 发送数据还是很容易的。另请注意,您可以在 30 天的试用期内使用 Loggly。
让我们来看看一些代码。这段代码基于我的文章中关于当前运行进程日志的代码。您需要安装以下模块,此示例才能运行:
我刚刚用 pip 安装了它们。现在我们有了这些,让我们看看如何使用它们来连接 Loggly
import psutil
import simplejson
import time
import urllib2
#----------------------------------------------------------------------
def log():
""""""
token = "YOUR-LOGGLY-TOKEN"
url = "https://logs-01.loggly.com/inputs/%s/tag/python/" % token
while True:
proc_dict = {}
procs = psutil.get_process_list()
procs = sorted(procs, key=lambda proc: proc.name)
for proc in procs:
cpu_percent = proc.get_cpu_percent()
mem_percent = proc.get_memory_percent()
try:
name = proc.name()
except:
# this is a process with no name, so skip it
continue
data = {"cpu_percent": cpu_percent,
"mem_percent": mem_percent,
}
proc_dict[name] = data
log_data = simplejson.dumps(proc_dict)
urllib2.urlopen(url, log_data)
time.sleep(60)
if __name__ == "__main__":
log()
这是一个非常简单的函数,但是让我们来分解它。首先,我们设置 Loggly token 并创建一个 Loggly URL 来发送我们的数据。然后我们创建一个无限循环,每 60 秒获取一个当前正在运行的进程列表。接下来,我们提取出想要记录的信息,然后将这些信息放入字典中。最后,我们使用 simplejson 的 dumps 方法将我们的嵌套字典转换成 json 格式的字符串,并将其传递给我们的 url。这会将日志数据发送到 Loggly,在那里可以对其进行解析。
一旦你向 Loggly 发送了足够的数据供其分析,你就可以登录到你的帐户,并查看一些根据你的数据自动创建的条形图。我在这个例子中的数据没有很好地转化为图表或趋势,所以这些看起来很无聊。我建议发送一个系统日志或其他包含更多种类的东西,以便更好地了解这项服务对您的用处。
更新:log Gly 的人提到尝试将数据作为浮点数而不是字符串发送,所以我适当地编辑了上面的代码。请随意摆弄代码,看看您能想到什么。
相关代码
- 我的一个读者提出了我的例子的修改版本
使用 Python 简化漫游配置文件
原文:https://www.blog.pythonlibrary.org/2010/02/09/using-python-to-reduce-the-roaming-profile/
漫游配置文件是福也是祸。如果用户使用互联网,他们浏览器的缓存文件将疯狂增长。如果用户将程序下载到他们的桌面,或者在他们的配置文件中的任何地方创建大的 Powerpoint 文件,那么无论用户何时登录或注销,都必须对它们进行管理。这个问题有几种解决方案:磁盘配额,阻止下载或放入个人资料的能力,等等。在本文中,我将向您展示如何使用 Python 从用户配置文件中排除特定的目录。
这基本上只是一个 Windows 注册表黑客。和往常一样,在对注册表进行任何更改之前,请务必备份注册表,以防出现严重问题,导致您的计算机无法启动。
from _winreg import *
try:
key = OpenKey(HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon",
0, KEY_ALL_ACCESS)
except WindowsError:
key = CreateKey(HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon")
# Exclude directories from roaming profile
prof_dirs = "Local Settings;Temporary Internet Files;History;Temp;My Documents;Recent"
SetValueEx(key, "ExcludeProfileDirs", 0, REG_SZ, prof_dirs)
CloseKey(key)
这段代码非常简单。首先,我们从 _winreg 导入各种模块和常量。然后,我们尝试打开适当的注册表项,如果该项不存在,就创建它。接下来,我们创建了一个分号分隔的目录字符串,以从漫游配置文件中排除。最后,我们设置适当的值并关闭键。
这就是这个简单脚本的全部内容!
使用 Python 在本地提供文件(视频)
原文:https://www.blog.pythonlibrary.org/2022/06/29/using-python-to-serve-files-locally-video/
您知道可以使用 Python 在本地网络上提供文件吗?当你使用内置的 HTTP 服务器时,这很容易!
你甚至可以用这种技术将文件传输到你的手机上!
在这个短视频中了解更多关于这个话题的信息!
https://www.youtube.com/embed/ed6L-DD4wWY?feature=oembed
用 Python 讲授求数字根
原文:https://www.blog.pythonlibrary.org/2013/07/16/using-python-to-teach-about-finding-the-digital-root/
我妻子教三年级的数学,她最近了解了获得数字的数字根的过程。数字根是一个单个数字,它是通过对各个数字求和得到的。因此,对于 15,你将 1+5 相加得到 6。因此 6 是 15 的数字根。与我妻子一起工作的培训师解释说,你可以使用数字根来检查你的答案,这将给孩子们另一种方法来确定他们的答案是否正确。这里有一个例子:
15 1 + 5 = 6
+12 1 + 2 = 3
6 + 3 = 9
----
27 2 + 7 = 9
这里我们有两个操作数:15 和 12。如果你把这些加在一起,你得到 27。要用数字根来检查你的答案,你要把两个操作数中的数字相加,如上所述。所以 15 变成了 1+5 或 6,12 变成了 1+2 或 3。然后你把这两个根加在一起得到 9。然后你通过把它的数字相加来检查你的答案,在这种情况下是 2+7 等于 9。减法、乘法和除法的规则略有不同。我们将学习加法、减法和乘法。我们跳过除法,因为我还没有找到一个很好的解释它是如何工作的,我不想仅仅使用我无法解释的公式。
此时,您可能想知道 Python 从何而来。我们将使用 wxPython 创建一个简单的 GUI,让我们看看这是如何工作的。开始编码吧!
创建用于查找数字根的 GUI
创建 GUI 一开始有点棘手,但随着您了解数字根是如何工作的,很快就变得容易了。您唯一需要遵循的包是 wxPython,您可以在这里获得。一旦你安装好了,你就可以开始了。现在让我们看一下代码:
import wx
########################################################################
class DigiPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
size = (50, -1)
firstNumLbl = wx.StaticText(self, size=size)
self.firstNum = wx.TextCtrl(self, size=size)
self.digitalRootOne = wx.StaticText(self)
operators = ["+", "-", "x"]
self.operatorsCbo = wx.ComboBox(self, value="+", choices=operators,
size=size)
self.secondNum = wx.TextCtrl(self, size=size)
self.digitalRootTwo = wx.StaticText(self)
self.digiRootLbl = wx.StaticText(self, size=(108, -1))
self.digitalRootAnswer = wx.StaticText(self)
totalLbl = wx.StaticText(self, size=size, label="Answer:")
self.total = wx.TextCtrl(self, size=size, style=wx.TE_READONLY)
self.totalRoot = wx.StaticText(self)
calcBtn = wx.Button(self, label="Calculate")
calcBtn.Bind(wx.EVT_BUTTON, self.onCalculate)
digiRootBtn = wx.Button(self, label="Find Digital Root")
digiRootBtn.Bind(wx.EVT_BUTTON, self.onDigitalRoot)
# layout the widgets
mainSizer= wx.BoxSizer(wx.VERTICAL)
firstSizer = wx.BoxSizer(wx.HORIZONTAL)
secondSizer = wx.BoxSizer(wx.HORIZONTAL)
totalSizer = wx.BoxSizer(wx.HORIZONTAL)
digiAnswerSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
firstSizer.Add(firstNumLbl, 0, wx.ALL, 5)
firstSizer.Add(self.firstNum, 0, wx.ALL, 5)
firstSizer.Add(self.digitalRootOne, 0, wx.ALL, 5)
mainSizer.Add(firstSizer, 0, wx.ALL, 5)
secondSizer.Add(self.operatorsCbo, 0, wx.ALL, 5)
secondSizer.Add(self.secondNum, 0, wx.ALL, 5)
secondSizer.Add(self.digitalRootTwo, 0, wx.ALL, 5)
mainSizer.Add(secondSizer, 0, wx.ALL, 5)
digiAnswerSizer.Add(self.digiRootLbl, 0, wx.ALL, 5)
digiAnswerSizer.Add(self.digitalRootAnswer, 0, wx.ALL, 5)
mainSizer.Add(digiAnswerSizer, 0, wx.ALL, 5)
totalSizer.Add(totalLbl, 0 ,wx.ALL, 5)
totalSizer.Add(self.total, 0, wx.ALL, 5)
totalSizer.Add(self.totalRoot, 0, wx.ALL, 5)
mainSizer.Add(totalSizer, 0, wx.ALL, 5)
btnSizer.Add(calcBtn, 0, wx.ALL|wx.CENTER, 5)
btnSizer.Add(digiRootBtn, 0, wx.ALL|wx.CENTER, 5)
mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def getAddString(self, number):
"""
Calculate the digital string
"""
if "-" in str(number):
answer = number
strNumbers = [str(number)]
else:
strNumbers = [i for i in str(number)]
answer = sum(map(int, strNumbers))
newAnswer = None
root = " + ".join(strNumbers) + " = " + str(answer)
while answer >= 10:
strNumbers = [i for i in str(answer)]
answer = sum(map(int, strNumbers))
newRoot = " + ".join(strNumbers) + " = " + str(answer)
root += " => " + newRoot
return root, answer
#----------------------------------------------------------------------
def getMultiString(self, number, number2):
"""
Get the string associated with multiplication
"""
answer = number * number2
root = "%s x %s = %s" % (number, number2, answer)
newAnswer = 1
if answer >= 10:
root += " => " + self.getAddString(answer)[0]
return root
#----------------------------------------------------------------------
def getSubtractionString(self, number, number2, total=None):
"""
Get subtraction string
"""
if not total:
answer = number - number2
root = "%s - %s = %s" % (number, number2, answer)
else:
root = "%s" % total
answer = total
if answer < 0:
# then we need to add 9
newAns = answer + 9
root += " => %s + 9 = %s" % (answer, newAns)
return root
#----------------------------------------------------------------------
def onCalculate(self, event):
"""
Calculate the total
"""
firstNum = int( self.firstNum.GetValue() )
secondNum = int( self.secondNum.GetValue() )
operator = self.operatorsCbo.GetValue()
if operator == "+":
total = firstNum + secondNum
elif operator == "x":
total = firstNum * secondNum
elif operator == "-":
total = firstNum - secondNum
self.total.SetValue(str(total))
#----------------------------------------------------------------------
def onDigitalRoot(self, event):
"""
Show digital root
"""
firstNum = int( self.firstNum.GetValue() )
secondNum = int( self.secondNum.GetValue() )
total = int(self.total.GetValue())
operator = self.operatorsCbo.GetValue()
firstRoot, firstAnswer = self.getAddString(firstNum)
secondRoot, secondAnswer = self.getAddString(secondNum)
if operator == "+":
totalRoot, _ = self.getAddString(total)
rootTotal = sum([firstAnswer, secondAnswer])
ans = "%s + %s = %s" % (firstAnswer, secondAnswer, rootTotal)
if rootTotal >= 10:
root, _ = self.getAddString(rootTotal)
ansRoot += " => " + root
else:
ansRoot = ans
self.digiRootLbl.SetLabel("A + B = root")
elif operator == "x":
ansRoot = self.getMultiString(firstAnswer, secondAnswer)
totalRoot, _ = self.getAddString(total)
self.digiRootLbl.SetLabel("A x B = root")
elif operator == "-":
ansRoot = self.getSubtractionString(firstAnswer, secondAnswer)
totalRoot = self.getSubtractionString("", "", total)
self.digiRootLbl.SetLabel("A - B = root")
self.digitalRootOne.SetLabel("A: " + firstRoot)
self.digitalRootTwo.SetLabel("B: " + secondRoot)
self.digitalRootAnswer.SetLabel(ansRoot)
self.totalRoot.SetLabel(totalRoot)
########################################################################
class DigiFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
title = "Digital Root Finder"
size = (800, 600)
wx.Frame.__init__(self, None, title=title, size=size)
panel = DigiPanel(self)
self.Show()
#----------------------------------------------------------------------
def main():
"""
Run the program
"""
app = wx.App(False)
frame = DigiFrame()
app.MainLoop()
if __name__ == "__main__":
main()
在这里,我们按行创建了一组小部件。你可以用 wx。GridBagSizer 或类似的东西,而不是嵌套的 box sizer,但是我更喜欢使用 box sizer 的灵活性,即使它变得有点复杂。总之,我们布局了四行窗口小部件和一行较小的按钮。一些静态文本小部件充当间隔符来帮助对齐项目。我们有两个按钮:一个用于计算答案,一个用于寻找数字根。calculate 按钮检查组合框,找出应该执行的操作,然后采取相应的行动。另一个按钮的事件处理程序 onDigitalRoot 要复杂得多。
我们再次检查选择了哪个运算符(+、-或 x),然后我们算出数字根。对于加法,我们实际上最终调用了 getAddString 来检查数字是否为负。如果是,我们不加数字。是的,如果我们得到一个大于-9 的值,这可能是一个问题,但是如果发生这种情况,那么我们就做错了别的事情。无论如何,如果它高于 9,那么我们需要总结小部件,这就是下面几行为我们做的:
strNumbers = [i for i in str(number)]
answer = sum(map(int, strNumbers))
然后我们使用一个 while 循环来不断累加答案的数字,以防我们得到一个非常大的操作数。因为这主要是针对小学生的,这有点过分了,但是我知道有人会问为什么我们不检查。减法非常相似。最大的区别是,如果数字根答案小于零,你就要加九。这是补数(即使其完整所需的数量)。我真的没有找到一个更深入的答案来解释你为什么这么做。不管怎样,要得到减法数字根,你需要得到操作数的根,然后将它们相减。
举个例子:假设你有 15 - 12 = 3。15 变成 1+5 或 6,12 变成 1+2 或 3。所以现在你取 6-3 = 3,和 15-12 一样。
乘法也很简单。我们快速做个例子:12 x 10 = 120。12 变成 1+2 或 3,10 变成 1+0 或 1。因为这是乘法,所以你用 3x1 得到 3。答案是 120,但它的数字根是 1+2+0 或 3。
包扎
现在你知道如何在四则运算中找到数字根。您还了解了如何创建一个简单的 GUI 来显示它是如何完成的。数学和巨蟒摇滚!
附加阅读
下载源代码
使用 Python 在 Mac OSX 上实现文本到语音的转换(视频)
原文:https://www.blog.pythonlibrary.org/2022/06/24/using-python-to-turn-text-to-speech-on-mac-osx-video/
了解如何使用 Python 编程语言在 Mac OSX 上将文本转换为语音
https://www.youtube.com/embed/L7n-5JhJFWY?feature=oembed
通过 Python 和 Tweepy 使用 Twitter
原文:https://www.blog.pythonlibrary.org/2019/08/06/using-twitter-with-python-and-tweepy/
Twitter 是一个流行的社交网络,人们用它来相互交流。Python 有几个可以用来与 Twitter 交互的包。这些软件包对于创建 Twitter 机器人或下载大量数据进行离线分析非常有用。其中一个比较流行的 Python Twitter 包叫做 Tweepy 。在本文中,您将学习如何使用 Tweepy 和 Twitter。
Tweepy 让您可以访问 Twitter 的 API,它公开了以下内容(还有更多!):
- 小鸟叫声
- 转发
- 喜欢
- 直接消息
- 追随者
这允许你用 Tweepy 做很多事情。让我们来看看如何开始吧!
入门指南
您需要做的第一件事是创建一个 Twitter 帐户,并获得访问 Twitter 所需的凭证。为此,你需要在这里申请一个开发者账户。
创建之后,您可以获得或生成以下内容:
- 消费者 API 密钥
- 消费者 API 秘密
- 访问令牌
- 访问机密
如果您丢失了这些项目,您可以返回您的开发者帐户,重新生成新的项目。也可以撤销旧的。
注意:默认情况下,您收到的访问令牌是只读的。如果你想用 Tweepy 发送推文,那么你需要确保将应用类型设置为“读写”。
安装 Tweepy
接下来,您需要安装 Tweepy 包。这是通过使用 pip 实现的:
pip install tweepy
现在您已经安装了 Tweepy,您可以开始使用它了!
使用 Tweepy
你可以使用 Tweepy 以编程的方式在 Twitter 上做几乎任何事情。例如,您可以使用 Tweepy 来获取和发送推文。您可以使用它来访问有关用户的信息。你可以转发、关注/取消关注、发帖等等。
让我们看一个获取用户家庭时间表的例子:
import tweepy
consumer_key = 'CONSUMER_KEY'
consumer_secret = 'CONSUMER_SECRET'
access_token = 'ACCESS_TOKEN'
access_secret = 'ACCESS_SECRET'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
tweets = api.home_timeline()
for tweet in tweets:
print('{real_name} (@{name}) said {tweet}\n\n'.format(
real_name=tweet.author.name, name=tweet.author.screen_name,
tweet=tweet.text))
这里的前几行代码是您在 Twitter 开发者个人资料页面上找到的凭证。实际上不建议在您的代码中硬编码这些值,但是为了简单起见,我在这里这样做了。如果您要创建共享的代码,您会希望让代码的用户将他们的凭证导出到他们的环境中,并使用 Python 的os.getenv()
或通过使用argparse
的命令行参数。
接下来,您通过创建一个OAuthHandler()
登录 Twitter
对象并使用恰当命名的set_access_token()
设置访问令牌
功能。然后你可以创建一个API()
允许您访问 Twitter 的实例。
在这种情况下,您调用home_timeline()
它会返回您家庭时间轴中的前二十条推文。这些推文来自你的朋友或关注者,也可能是 Twitter 决定在你的时间表中推广的随机推文。在这里,您可以打印出作者的姓名、Twitter 句柄以及他们的推文文本。
让我们来看看您是如何使用api
获取自己的信息的
您之前创建的对象:
>>> me = api.me()
>>> me.screen_name
'driscollis'
>>> me.name
'Mike Driscoll'
>>> me.description
('Author of books, blogger @mousevspython and Python enthusiast. Also part of '
'the tutorial team @realpython')
您可以使用api
来获得关于你自己的信息。上面的代码演示了如何获取您的屏幕名称、实际名称以及您在 Twitter 上设置的描述。你可以得到比这更多的东西。比如你可以获得你的关注者,时间线等。
获取推文
使用 Tweepy 获取推文也很容易。如果你知道别人的用户名,你可以获得自己或别人的推文。
让我们从获取您的推文开始:
import tweepy
consumer_key = 'CONSUMER_KEY'
consumer_secret = 'CONSUMER_SECRET'
access_token = 'ACCESS_TOKEN'
access_secret = 'ACCESS_SECRET'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
my_tweets = api.user_timeline()
for t in my_tweets:
print(t.text)
在这里,您像在上一节中一样连接到 Twitter。然后你叫user_timeline()
以获得对象列表,然后对其进行迭代。在这种情况下,您最终只能打印出 tweet 的文本。
让我们尝试获取一个特定的用户。在这种情况下,我们将使用一个相当受欢迎的程序员,凯利沃恩:
>>> user = api.get_user('kvlly')
>>> user.screen_name
'kvlly'
>>> user.name
'Kelly Vaughn ðŸž'
>>> for t in tweets:
print(t.text)
她发了很多微博,所以我不会在这里复制她的微博。然而正如你所看到的,获得一个用户是很容易的。你需要做的就是通过get_user()
一个有效的 Twitter 用户名,然后你就可以访问该用户的任何公开信息。
发送推文
看推文很好玩,但是发呢?Tweepy 也可以为您完成这项任务。
让我们来看看如何实现:
import tweepy
consumer_key = 'CONSUMER_KEY'
consumer_secret = 'CONSUMER_SECRET'
access_token = 'ACCESS_TOKEN'
access_secret = 'ACCESS_SECRET'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
api.update_status("This is your tweet message")
您应该关注的主要代码是最后一行:update_status()
。这里你传入一个字符串,它是 tweet 消息本身。只要没有错误,您应该会在您的 Twitter 时间线中看到这条推文。
现在我们来学习如何发送附有照片的推文:
import tweepy
consumer_key = 'CONSUMER_KEY'
consumer_secret = 'CONSUMER_SECRET'
access_token = 'ACCESS_TOKEN'
access_secret = 'ACCESS_SECRET'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
api.update_with_media('/path/to/an/image.jpg',
"This is your tweet message")
在这种情况下,你需要使用update_with_media()
方法,它接受一个到您想要上传的图片的路径和一个字符串,也就是 tweet 消息。
请注意,虽然 update_with_media() 易于使用,但也不推荐使用。
所以让我们更新这个例子,改为使用 media_upload !
import tweepy
consumer_key = 'CONSUMER_KEY'
consumer_secret = 'CONSUMER_SECRET'
access_token = 'ACCESS_TOKEN'
access_secret = 'ACCESS_SECRET'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
media_list = []
response = api.media_upload('/home/mdriscoll/Downloads/sebst.png')
media_list.append(response.media_id_string)
api.update_status("This is your tweet message", media_ids=media_list)
在这里,您将媒体上传到 Twitter,并从返回的响应中获取它的 id 字符串。然后将该字符串添加到 Python 列表中。最后,您使用 update_status() 来发布一些东西,但是您也将 media_ids 参数设置为您的图片列表。
列出关注者
我要讲的最后一个话题是如何在 Twitter 上列出你的关注者。
让我们来看看:
import tweepy
consumer_key = 'CONSUMER_KEY'
consumer_secret = 'CONSUMER_SECRET'
access_token = 'ACCESS_TOKEN'
access_secret = 'ACCESS_SECRET'
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
followers = api.followers()
for follower in followers:
print(follower.screen_name)
此代码将列出您帐户的最新 20 个关注者。你只需要打电话给followers()
去拿名单。如果你需要得到的不仅仅是最新的 20 个,你可以使用count
参数来指定需要多少个结果。
包扎
使用 Tweepy,您可以做更多事情。例如,你可以获得赞,发送和阅读直接消息,上传媒体,等等。这是一个非常好和易于使用的软件包。您还可以使用 Tweepy 实时读写 Twitter,这允许您创建一个 Twitter 机器人。如果你还没有尝试过 Tweepy,你绝对应该试一试。很好玩啊!
相关阅读
- Tweepy 的文档
- 如何用 Tweepy - 真正的 Python 用 Python 制作一个 Twitter 机器人
在 Jupyter 笔记本中使用小工具(视频)
原文:https://www.blog.pythonlibrary.org/2020/07/05/using-widgets-in-jupyter-notebook-video/
在这个视频教程中学习如何使用 Jupyter 笔记本的内置部件。
https://www.youtube.com/embed/laj3qzhGm2Q?feature=oembed
拿到书:https://leanpub.com/jupyternotebook101/
使用 Python、Firefox 和 Selenium 实现网站自动化
原文:https://www.blog.pythonlibrary.org/2012/05/06/website-automation-with-python-firefox-and-selenium/
一个合适的替代标题是:如何用 Python 控制网页或测试你的网站。最近,我被分配了以下任务:
1)登录网站
2)点击工具栏并加载特定的搜索表单
3)在表单的一个字段中输入一些数据并搜索
4)如果找到,点击另一个按钮
5)重复
当然,我应该用 Python,我最喜欢的编程语言来做这些。我的第一个想法是使用 Mechanize 包,但是虽然我可以使用它登录网站,但是我无法让它加载表单。为什么?不幸的是,前面提到的工具栏是用 javascript 制作的,它似乎也能生成表单。不管出于什么原因,Python 端口不支持加载 javascript 元素,尽管我确实发现它所基于的项目有一个 javascript 插件,所以它最终可能会有希望。因此,我去寻找另一种解决方案,并回忆说硒可能符合要求。最后,效果相当不错。因为这是一个内部项目,所以我不能向你展示我到底做了什么,我们将自动运行 Gmail。让我们开始吧!
你需要什么
自动化网站
我尝试了一下,试图想出一个登录 Gmail 或 Yahoo 的好例子,但是每个都有问题。每次登录时,Gmail 的框架都有唯一的名称,所以当我运行代码时,我找不到框架。雅虎不知何故屏蔽了来自 selenium 的一封新邮件的正文部分,所以我无法记录我在那里输入的内容。你可能已经发现,我希望向你展示如何自动发送一堆电子邮件,只是为了好玩。哦好吧。
取而代之的是,我决定做一点元,只写我自己博客的联系方式。对于这个例子来说,它已经足够好了,但是由于验证码的原因,您实际上不能提交表单。然而,这应该给你一个大致的概念。你可以这样做:
第一步:打开 Firefox 的一个新实例,然后启动你的 Selenium IDE 插件(工具菜单,Selenium IDE)
第二步:去你选择的网站,做你想自动化的事情。在这种情况下,我点击了我的联系链接,然后填写了表格。(见下面截图)
步骤三在 Selenium IDE 窗口,转到文件,将测试用例导出为,选择 Python 2 (WebDriver) 。还有一个 Python 2(远程控制)也可以导出,但如果没有一些额外的软件,您将无法运行该脚本。
第四步:运行 web 驱动脚本,看看是否有效。我通常也导出遥控器,因为它帮助我找出一些 web 驱动脚本没有的字段或 CSS 名称。例如,在我最初的项目中,直到我查看了远程代码,我才意识到我需要切换帧。
现在让我们看看 Selenium IDE 基于我的愚蠢的博客示例创建的代码:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
import unittest, time, re
class BlogDriver(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "https://www.blog.pythonlibrary.org/"
self.verificationErrors = []
def test_blog_driver(self):
driver = self.driver
driver.get(self.base_url + "/")
driver.find_element_by_link_text("Contact the Author").click()
driver.find_element_by_id("si_contact_name1").clear()
driver.find_element_by_id("si_contact_name1").send_keys("Mike Driscoll")
driver.find_element_by_id("si_contact_subject1").clear()
driver.find_element_by_id("si_contact_subject1").send_keys("I love your blog!")
driver.find_element_by_id("si_contact_message1").clear()
driver.find_element_by_id("si_contact_message1").send_keys("Python rules and your website rocks!")
driver.find_element_by_id("si_contact_captcha_code1").clear()
driver.find_element_by_id("si_contact_captcha_code1").send_keys("22HU")
driver.find_element_by_id("fsc-submit-1").click()
def is_element_present(self, how, what):
try: self.driver.find_element(by=how, value=what)
except NoSuchElementException, e: return False
return True
def tearDown(self):
self.driver.quit()
self.assertEqual([], self.verificationErrors)
if __name__ == "__main__":
unittest.main()
如您所见,Selenium IDE 创建了一个简单的单元测试脚本。如果你运行它,一个新的 Firefox 实例将被加载,它将填充所有的内容并尝试提交它。当然,你会从网站上收到一个错误,因为我记录的验证码很可能与你看到的不同。该脚本运行速度非常快,使用 Selenium 了解其他人的 web 元素命名约定也很有趣。在我为我们的内部项目创建的脚本中,我删除了所有的单元测试内容,并创建了我自己的类,该类循环遍历一大堆输入进行查找,以便我们可以收集数据。
Selenium 没有做的一件事实际上是自动化 Firefox 本身。你不能让 Selenium 点击 Firefox 的任何菜单或书签。然而,如果你所需要做的只是自动化一个网站,那么这很好!另一方面,如果你真的需要能够点击火狐的菜单等等,那么你可能想看看 SendKeys 包。不过它只适用于 Windows。Selenium 脚本应该可以在 Windows、Linux 或 Mac 上运行。
希望这篇文章能帮助你开始测试你的网站,或者让你开始你自己的自动化项目。
每周 Python 链接综述(2012 年 6 月 1 日)
原文:https://www.blog.pythonlibrary.org/2012/06/01/weekly-python-links-roundup-612012/
这是六月的第一个星期五,这周我读了一堆新文章。从 Vern 关于获得 Python 工作的咆哮到对 web2py 食谱的评论,Python 领域发生了许多有趣的事情。以下是我认为有趣的其他几篇文章:
- Batchelder 讲 Python 递归,这永远是个好玩的话题。
- Python 线程本地人很奇怪是一篇奇怪的文章,但是线程是一个有趣的话题,我想更好地理解它
- Python:选择一种显而易见的方法是一篇关于 Rosetta 代码任务的文章。代码很多,解释不多。
- 一篇关于自动逼真化的文章...是啊,我也没听说过。
- Wesley Chun 本周有话要对 tuples 说。
像往常一样,如果你碰巧在本周读到了一些关于 Python 的精彩文章,请在评论中随意链接!
每周 Python 链接综述(2012 年 6 月 15 日)
原文:https://www.blog.pythonlibrary.org/2012/06/15/weekly-python-links-roundup-6152012/
我不认为我会做这些链接综述之一,但我决定无论如何要去做。事实上,我发现了一个很酷的小工具,用来获取关于已安装的 Python 包的信息,并查询 PyPI (Python 包索引)上可用的包,也叫做 yolk !总之,这是本周的阅读清单。我希望你喜欢它们!
- 尼古拉跑得很快
- 使用 GDB 探索 Python
- PSF 赞助了即将到来的国际 Python 会议——这是我为 PSF 研究和撰写的一篇有趣的文章
- 从内部编码是关于在一个“安全”的环境中为游戏编程创建一个 gimped Python
- 如何通过 Django 应用程序在 ReadTheDocs 上使用 Sphinx Autodoc
欢迎在下面的评论中给我留下你本周最喜欢的文章。
每周 Python 链接综述(2012 年 6 月 8 日)
原文:https://www.blog.pythonlibrary.org/2012/06/08/weekly-python-links-roundup-682012/
本周,Python 生态系统中有几件有趣的事情值得一读。您可以阅读 Python repo 的一个很酷的新 Mercurial 镜像,使用 redis 发现简单的队列,并了解如何获取 SQL 语句并将其反向工程为 SQLAlchemy 代码。我不知道你为什么要写最后一篇,因为我似乎记得 SQLALchemy 能够支持直接 SQL,但这仍然是一篇有趣的文章。除了这些,我还有几个你可能会感兴趣的!
- 亚特兰蒂斯人提供的水银镜
- 带缰绳的简单 Python 队列
- 用 AGM 和 mpmath 计算圆周率
- MySQL 连接器/Python 中的自动重新连接?
- 埃瓦尔真的很危险
- 反向 Sqlalchemy
- 屏幕抓取
下周我可能会跳过这篇文章,因为它会非常忙。我希望你喜欢本周的阅读清单。请随时让我知道你一直在读什么。
每周 Python 链接综述复活!
原文:https://www.blog.pythonlibrary.org/2012/05/25/weekly-python-links-roundup-resurrected/
我还没有做 Python 链接的每周综述,因为当我做这些的时候,似乎没有人真正关心。然而,我想我会给它一个更多的尝试,看看是否有任何兴趣这一次。在过去的一周里,我读完了我的第一本 web2py 食谱。我听说还有另外一本书,但是我还没有去看看。我怀疑如果我先读了另一本,这本食谱会更有意义。如果你是这个网站的常客,你会注意到我在过去几周也写了几篇书评。说到阅读,以下是本周让我印象深刻的几篇文章:
- Logsna 一种合理的日志输出格式(Ruslan Spivak 的博客
- Python.org 重新设计提案征集书
- 什么发电机用于(西米恩·富兰克林)
- Python 3.3 中最近的 Windows 变化
- 我认为现在有一个重新设计 Python 网站的 RFP 出来是令人兴奋的
- Nick Coghlan 写了一篇非常有趣的文章,介绍了现在用于部署、文档和测试的所有资源
我不想让你在链接上负担过重,我知道这个比较老,但是我真的很有兴趣关注凯蒂 C 的游戏冒险。所以我会给你留个便条。我希望你和我一样喜欢这些文章。
每周 Python 新闻(01/07/2011)
原文:https://www.blog.pythonlibrary.org/2011/01/07/weekly-python-news-01072011/
又是一周,又是一组 Python 新闻。本周
- Andrew Kuchling 获得 PSF 社区奖
- 道格·赫尔曼也获得了 PSF 社区奖。恭喜你们俩。我只是希望 PSF 的博客能更详细地描述一下为什么这些人会获奖,以及他们是如何被选中的。
- Sean Reifschneider 为 bottlesession 库创建了一个新的cookies session
- 厨房 0.2.2 本周发布。博客中写道,“厨房是一个 python 模块,包含一些小而有用的代码片段。它具有帮助应用程序国际化的功能,可以处理 unicode 和字节串、迭代器等等。”
- PyCon 2011 教程发布了
- 2 Python 和云的文章:云的承诺和云蛇
- 本周,狮身人面像项目有了新的发布。
- PyCon 2011 有一个他们接受的会谈列表(终于!)
本周有很多网络技术发布。每个人似乎都对 web 应用越来越感兴趣,所以我想我必须开始深入研究各种与 Python 相关的 web 框架,并了解它们的内部工作方式。
只是为了稍微混淆一下,我将包括我发现的一些随机的 Twitter 推文:
- luzifer1984:新的实验性#Python #GAE 数据存储 API via http://goo.gl/agx9T GVR > >
- lashingpumpkin: RT @ojiidotch:亲爱的#Python 开发者:不要再把你的下载放在 pypi 以外的地方了。谢谢大家!
- szul:# FF for app building @ appcelerator @ active state # titanium # python # JavaScript
祝你一周愉快!
Python 新闻周刊(2010 年 1 月 15 日)
原文:https://www.blog.pythonlibrary.org/2011/01/14/weekly-python-news-01152010/
又到了一期“每周 Python 新闻”的时候了!Python 世界这周发生了什么?你来对地方了。我们错过了上周关于德国 Python 学院课程安排的公告,所以一定要去看看。本周,我们有很多来自 Python web 世界的新闻(再次)以及关于一本伟大的 Python 新书的新闻。
- 使用 Django 设置文件 -我现在不怎么使用 Django,但是这看起来很有趣。
- 哈德逊项目可能会被搁置。新的分叉叫做詹金斯。你可以在格雷格·特恩奎斯特的博客上读到所有相关内容
- 金字塔计划是塔人们的新发展。在这个博客中,你可以了解金字塔的认证 API。
- 你知道新的 Python 托管服务 ep.io 吗?如果你喜欢,你可以在这里阅读
- ent thought 最近为他们的一些产品增加了 PySide 支持。
- 马克·卢茨和奥赖利发布了第四版编程 Python ,这是对 Python 3 的更新。你现在就可以从亚马逊买到它!
- Wingware 最新的 4.0 测试版现已发布。你可以在这里和这里读到它
如果你认为有一些新闻被遗漏了,一定要通过评论或联系表格给我写信。祝你一周愉快!
Python 每周新闻:2011 年 1 月 21 日
原文:https://www.blog.pythonlibrary.org/2011/01/21/weekly-python-news-01212011/
本周,我没有像我希望的那样多的时间来做这件事。啊,希望你不会发现新闻的质量有任何下降。恰好本周 PyCon 2011 有几个项目,它们都是好消息。一定要看看这些。另外,Python 获得了另一个奖项,
- PyCon 2011 将提前注册延长至 25 日...你还在等什么?
- PyCon 2011 增加了启动行。了解 PyCon 上最酷的 Python 相关创业公司!
- PyPy 项目需要你的帮助!难道你没有想过加入一个项目并获得一个令人敬畏的“核心开发人员”的称号吗?现在你的机会来了!
- 这是我在 Twitter 上发现的一个博客,它报道了一个 3 年前的关于一个 Python 程序员的“进化”的讨论
- Python 赢得了 2010 年的 TIOBE 编程语言奖!是的,我从来没有听说过这个,但是 Python 获奖总是很酷。(哈蒂普转向苏珊·劳
- Dave Beazley 的最新博客文章:将 Py65(和我的超级主板)移植到 Python 3
- 为什么我们在 Memonic 上喜欢 Python-了解真正的公司如何在真正的产品中使用 Python!
如果你有兴趣随时关注 Python 新闻,那么 Twitter 可能是你想去的地方。
Python 每周新闻:2011 年 1 月 28 日
原文:https://www.blog.pythonlibrary.org/2011/01/28/weekly-python-news-01282011/
现在已经是一月底了,2011 年美国 PyCon 即将到来。在这个版本中,我们涵盖了从 Python 2.7 到 3.x 的各种主题。我们有 sprints、新书、与 web 相关的项目和很好的旧递归文章。来吧,都来吧!阅读本周新闻!
- eWeek 报道说今年 Python 技能很受欢迎
- SqlAlchemy 背后的人 Michael Bayer 撰写的 zzzeek 的 Python 3 移植指南。
- Python 网络编程的基础:Python 2 的最后一搏。Brandon Rhodes(《Python》杂志前编辑)谈他刚写完的一本书。
- Python Sprints 赞助了 2011 年软件木工展。细节很少,我对这两个组织都不太熟悉,但这听起来很有趣。
- 香农·伯伦斯在他的博客上谈论了 Python SSL 地狱
- 了解如何在 nginx 后面部署 Python WSGI 应用
- 在 Mario Boikov 的博客上学习新型 PyQt 信号和插槽
- 用 LLVM 2.8 构建 Python(以及为什么我仍然热爱这个项目)作者 Brett Cannon。
- 关于尾部递归的一些讨论可以在下面的文章中找到:使用 Pysistence 的 Python 中的尾部递归
在 PyCon 方面,你应该注意到早期价格已经超过。你为什么要等?还好他们还在卖票,别磨蹭了,快来!PyCon 博客也提到他们仍在寻找赞助商,所以如果你或你认识的人有那种现金券,给他们指明方向。
现在我们让你回到你的定期博客阅读...
Python 每周新闻:2011 年 2 月 4 日
原文:https://www.blog.pythonlibrary.org/2011/02/04/weekly-python-news-02042011/
现在是你每周 Python 新闻的时间了。上周发生了什么?或者我发现了什么我认为是新闻性的?事实证明,很多东西。本周,你可以了解 PyPI、Tablib 的争议,与 Python 创始人的“炉边”聊天将于下个月在 PyCon USA 举行,等等。
- 为 PyPI 辩护
- Tablib 更新
- entthought ' s restructured text编辑
- 一篇有趣的关于斯芬克斯的短文文档
- PyCon 2011:宣布“与吉多·范·罗苏姆的炉边谈话”
- FLOSS 每周采访铁塔项目
- 使用 GeoNames 数据库向 MapPoint 添加城市覆盖范围
- 帮助迈克·皮尔纳特,为他的 PyCon 演讲提供主题
- 詹金斯(FKA:哈德森)现在可以上场了
我希望你能找到一些有用或有趣的链接。欢迎在评论中给我一些下周的建议!
Python 每周新闻:2011 年 2 月 11 日
原文:https://www.blog.pythonlibrary.org/2011/02/11/weekly-python-news-02112011/
Python 新闻的这一周版有各种各样的主题。您可以了解如何创建密码、新的 Enthought Python 发行版以及名为 pylibftdi 的东西。另外,我已经为 PyCon 相关的东西添加了一个部分,因为他们似乎在这个月发布了很多信息。例如,在那个版块里有一篇对阿明·罗纳彻(《长颈瓶》的作者)的采访。
- 彼得·费恩谈到了 200 个字符以内的更好的密码
- Sphinx 和 htmlhelp
- Enthought Python 发行版 7.0 本周发布了。
- 多设备支持
- Python 的字典实现
- 简单的自我升级构建模式
cute_profile
: 动态分析您的 Python 代码
PyCon 新闻
- 新闻稿:PyCon 2011 破纪录的投稿
- PyCon 2011: 采访阿明·罗纳彻——“打开瓶子”
到目前为止,还没有人给我任何关于我下一篇文章的线索,所以这要么意味着我在做正确的事情,要么没有人在看这些帖子。和往常一样,我会看看好心的读者给我的链接。
Python 新闻周刊(2010 年 12 月 31 日)
原文:https://www.blog.pythonlibrary.org/2010/12/31/weekly-python-news-12312010/
本周的 Python 新闻涵盖了新模块、旧模块的更新、一本新的 Python 杂志(不,不是那本的!),关于新 wxPython 的最新动态等等!
- " TurboGears 加入挂架项目(备选标题:TurboGears 成为 TurboPyramid)。我认为标题说明了一切...不,他们并没有真正给这个项目重新命名。
- wxPython 的“Project Phoenix”获得了一个更新——这提供了一个关于 Robin Dunn 如何脱离 SWIG 的更新,以使编写文档和 Python 3 移植变得更容易。
- 迈克尔·福德的新“e”模块首次亮相...在他的博客上阅读所有相关内容
- PET:第 1 期 -由阿根廷 Python 用户组创建的杂志的英文翻译。是的,这是一种旧新闻,但我认为它真的很酷,它需要你的支持!
。
- PSF 博客谈到高中生在 Vern Ceder 的指导下使用 Python 给机器人编程
- PyCrypto 从托尔斯滕·伯伦斯那里获得了一个 Python 3 端口。检查线程并下载您的副本,以帮助找到任何错误!
这就是这周的内容。下次我们会在 2011 年!太神奇了!无论如何,我希望你有一个愉快的假期,你会让我知道我应该在下一篇文章中谈论的任何 Python 新闻。谢谢!
Python 是什么?
原文:https://www.blog.pythonlibrary.org/2008/05/03/what-is-python/
两年前,我成为了现实世界中的一名电脑程序员。为了纪念这个周年纪念日,我想我应该谈谈我在这段时间里学到的一些东西。但在此之前,我想先了解一些背景。
我的学术生涯包括去爱荷华州立大学,在那里我获得了管理信息系统学士学位。在那里,他们教授编程方面的 C++和 C#,以及数据库方面的 Oracle/SQL。大学毕业后,我在一家拍卖公司找到了一份网站管理员的工作。这不是我想要的,但它支付了账单。然而,我很快就忘记了我所学的许多东西。
因此,当我在为我的就业进行谈判时,他们提到我必须学习 Python,这是我从未听说过的。我买了一本 Python 的书,书名是麦可·道森写的《绝对初学者的 Python 编程》。虽然我喜欢学习游戏编程,但这本书对于试图学习语言基础的人来说结构不太好。然而,它看起来很容易捡起来。就这样。
在我被雇佣的时候,我的任务是在大约 2-3 周内自学完 Python。然后我应该把所有的登录脚本从 Kixstart(另一种我从未听说过的语言)翻译成 Python。我们也在公司网站上使用 Zope 和 Plone,我的老板也想让我学习。我仍然没有得到 Zope 或 Plone,但我开始更好地理解它们,我可以用它一起编写非常基本的页面。
下一次我将给出一些需要翻译的脚本和它们的 Python 等价物(或者我认为等价的东西)的例子。
Python 101 第二版的新特性
原文:https://www.blog.pythonlibrary.org/2020/03/24/whats-new-in-python-101-2nd-edition/
原版 Python 101 是我写过的第一本书。在决定写第二版时,我需要决定我应该保留什么,应该从书中删除什么。我最终做的是从头开始重写这本书。
在原著中,Python 101 基于 Python 3.5,包含以下几个部分:
- 第一部分-学习基础知识
- 第二部分-向图书馆学习
- 第三部分-中间零碎东西
- 第四部分-提示、技巧和教程
- 第五部分-包装和分销
对于第二版,我将删除第二部分,因为大部分信息可以在后面的章节中涵盖。现在 Python 3.8 已经发布了,我可以介绍以下内容
- f 弦
- 赋值表达式
- 默认情况下字典是有序的
- 类型提示
- 源代码控制
- 复习大部分章节的问题
- 还有更多!
并提及其他一些简洁的东西,比如子解释器、futures 模块和审计挂钩,尽管这些不会详细讨论。我还计划将教程部分变成一个更具概念验证性的部分,在这里你可以获得并使用一些有用的脚本。这在某种程度上已经存在,但我认为这些脚本会比原来的更好。
分发部分也将有所改进,以涵盖将代码分发到 Python 打包索引的最新约定。原书的封面已经过时。
另外,虽然第一本书提到了许多创建可执行文件的不同选项,但这本书的新版本将只关注一两个,但它将展示如何为 Windows 和 Mac 创建二进制文件。我正在研究如何支持 Linux,但这可能包括也可能不包括。
我将在 Leanpub 上的免费样本中提供相当多的章节,这样人们可以在购买前查看。
我希望你能看看这本书。欢迎在评论中提问或者给我发邮件。
Python 101 第二版将于 2020 年 9 月发布
Python 3.11 的新特性(视频)
原文:https://www.blog.pythonlibrary.org/2022/06/15/whats-new-in-python-3-11-video/
在这个视频中,Mike Driscoll 谈论了 Python 的最新版本,该版本将于 2022 年秋季发布
- 更好的性能
- 改进的错误消息
- 例外组
- 新类型提示
- 新的“tomllib”模块
详情请参见 Python 网站
https://www.youtube.com/embed/aMJCxJADCB0?feature=oembed
Python 中的新特性:异步理解/生成器
Python 3.6 增加了创建异步理解和异步生成器的能力。你可以在 PEP 530 中阅读异步理解,而异步发电机在 PEP 525 中描述。该文档指出,您现在可以创建异步列表、集合和字典理解以及生成器表达式。他们的示例如下所示:
result = [i async for i in aiter() if i % 2]
基本上你只需要在你的表达式中添加 Python 新的 async 关键字,并调用一个实现了 aiter 的 callable。但是,尝试遵循这种语法实际上会导致语法错误:
>>> result = [i async for i in range(100) if i % 2]
File "", line 1
result = [i async for i in range(100) if i % 2]
^
SyntaxError: invalid syntax
这实际上是 be 的定义。如果你在 PEP 530 中查找,你会看到它声明如下:异步理解只允许在异步定义函数中使用。当然,你也不能把 Python 的wait放在理解中,因为这个关键字只能在 async def 函数体内使用。只是为了好玩,我尝试定义一个异步 def 函数,看看我的想法是否可行:
import asyncio
async def test():
return [i async for i in range(100) if i % 2]
loop = asyncio.get_event_loop()
loop.run_until_complete(test())
如果运行此代码,将会得到一个类型错误:“async for”需要一个具有 aiter 方法的对象,得到的范围为。你真正想做的是调用另一个异步 def 函数,而不是直接调用 range。这里有一个例子:
import asyncio
async def numbers(numbers):
for i in range(numbers):
yield i
await asyncio.sleep(0.5)
async def main():
odd_numbers = [i async for i in numbers(10) if i % 2]
print(odd_numbers)
if __name__ == '__main__':
event_loop = asyncio.get_event_loop()
try:
event_loop.run_until_complete(main())
finally:
event_loop.close()
从技术上讲, numbers 函数是一个异步生成器,它为我们的异步列表理解生成值。
包扎
创建异步列表理解与创建常规列表理解有很大不同。如您所见,要让它工作需要更多的代码。根据 PEP 525 的说法,除了增加开箱即用的异步能力,新的异步生成器实际上应该比实现为异步迭代器的等效生成器快2 倍。虽然我目前还没有这个新功能的用例,但看看我现在能做什么真的很好,我将把这些新概念归档,以备将来应用。
相关阅读
- Python 3.6 的新特性:异步理解
- Python 3.6 的新特性:异步生成器
- 人教版 530 -异步理解
- PEP 525 -异步发电机
- InfoWorld: Python 3.6 充满了优点
绕过隐藏函数
原文:https://www.blog.pythonlibrary.org/2014/06/16/working-around-shadowed-functions/
最近,我遇到了一个问题,调用 Python 的应用程序会将 int 插入 Python 的名称空间,这会覆盖 Python 的内置 int 函数。因为我必须使用这个工具,而且我需要使用 Python 的 int 函数,所以我需要一种方法来解决这个麻烦。
幸运的是,这很容易解决。你所需要做的就是从 builtin 导入 int 并重命名它,这样你就不会覆盖插入的版本:
from __builtin__ import int as py_int
这使您可以再次访问 Python 的 int 函数,尽管它现在被称为 py_int 。只要不叫 int ,随便你怎么叫。
隐藏内置变量或其他变量最常见的情况是当开发人员从包或模块中导入所有内容时:
from something import *
当你像上面一样进行导入时,你并不总是知道你导入了什么,你可能最终会写下你自己的变量或函数来隐藏你导入的变量或函数。这就是强烈反对从一个包或模块中导入任何东西的主要原因。
无论如何,我希望这个小提示对你有所帮助。在 Python 3 中,核心开发者添加了一个内置模块基本上就是为了这个目的。
正在编写 MySQL for Python 书评
原文:https://www.blog.pythonlibrary.org/2010/11/02/working-on-a-mysql-for-python-book-review/
Packt Publishing 给了我一本他们的新书的电子书,作者是 Albert Lukaszewski。我将在这个月努力通读,希望在 12 月之前有一个评论给你们所有人阅读。他们还给我这个链接到一个免费的章节。请随意阅读或跳过它。
使用 jupiter 笔记本小部件
原文:https://www.blog.pythonlibrary.org/2018/10/24/working-with-jupyter-notebook-widgets/
什么是 Jupyter widgets?小部件是一个“多事件的 python 对象”,在 Jupyter Notebook 的情况下,它驻留在浏览器中,是一个用户界面元素,如滑块或文本框。Jupyter 支持相当广泛的小部件,包括:
- 数字的
- 布尔代数学体系的
- 选择
- 线
- 图像
- 纽扣
- 输出
- 动画
- 日期选择器
- 颜色选择器
- 控制器(即游戏控制器)
- 布局
我们不会在本文中涵盖所有类型的小部件,但是我们将会看到相当多的小部件在工作。完整的列表可以查看文档。或者你可以在你的笔记本上运行下面的代码:
import ipywidgets as widgets
print(dir(widgets))
如果您过去曾经创建过用户界面,那么您可能已经理解了什么是小部件。如果你没有,那么让我们花一点时间来定义它们的用途。
小部件用于为您的用户创建交互式图形用户界面。小部件在 Python 和 Javascript 之间同步有状态和无状态信息。
在之前的文章中,我们简要介绍了如何使用交互和交互功能创建小部件。在本教程中,我们将直接创建小部件,并了解它们是如何工作的。
创建小部件
可以非常容易地创建一个小部件。出于演示目的,让我们创建一个简单的滑块。创建一个新的 Jupyter 笔记本,并将以下代码放入单元格中:
import ipywidgets as widgets
widgets.IntSlider()
当您运行该单元时,您应该会看到类似这样的内容:
如您所见,滑块的默认值为零。如果您想将小部件保存到一个变量中,那么您需要告诉 Jupyter 使用 display 函数来显示小部件:
import ipywidgets as widgets
from IPython.display import display
btn = widgets.Button(description='Press Me')
display(btn)
这里我们添加了显示小部件所需的额外导入,并创建了一个简单的按钮。注意,我们必须指定它的描述,这样按钮就会有一个标签。最后,我们调用了 display 函数,并向它传递了 widget 对象。结果看起来像这样:
现在让我们后退一秒钟,再次创建滑块,但这一次我们将使用 display 来显示它两次:
import ipywidgets as widgets
from IPython.display import display
btn = widgets.IntSlider()
display(btn)
display(btn)
如果您将上面的代码放入笔记本的一个单元格中并运行它,您应该会看到两个滑块:
尝试移动其中一个滑块。你会很快发现,当你移动一个滑块时,另一个滑块也会移动。发生这种情况的原因是我们只创建了一个 slider 对象。实际可见的小部件都指向下面的同一个对象,所以当你移动一个时,你实际上是在移动另一个。你可以认为这两个滑块是同一个对象的两个视图。当然,如果您已经创建了两个 IntSliders 并给它们分配了唯一的变量名,那么您可以独立地移动它们。
关闭小部件
您可以通过调用小部件的 close() 方法来关闭它。如果您想删除小部件,只需清除单元格。
小部件的属性和键
Jupyter 小部件遵循一组属性规则。如果您想获得小部件属性和方法的完整列表,可以使用 Python 的
dir()
函数为您内省小部件:
dir(btn)
假设您将上面的代码放在一个新的单元格中,该单元格就在包含您的“IntSlider”小部件代码的单元格下方。如果您运行这个新单元,您将得到如下输出:
尝试将滑块调整到零以上。然后创建一个新的单元格,并输入以下代码:
btn.value
当您运行这个程序时,它将打印出滑块的当前值。小工具也有键,它们是诸如‘描述’、‘最小’以及‘最大’和‘禁用’之类的东西。要获得小部件键的完整列表,您可以查阅小部件的文档,阅读源代码或运行以下代码:
btn.keys
同样值得一提的是,你也可以设置一个新的属性值。因此,如果您想将滑块设置为新值并添加描述,您可以执行以下操作:
btn.value = 50
btn.description = 'Hello slider'
如果您运行这段代码,您将看到滑块更新了它的值和描述。
小部件还支持在实例化小部件时设置属性。例如,如果我们愿意,我们可以在创建滑块时设置它的一些属性:
import ipywidgets as widgets
from IPython.display import display
btn = widgets.IntSlider(value=10, description='My Slider',
orientation='vertical')
display(btn)
当您运行这段代码时,您会看到这个小部件看起来有点不同:
链接两个小部件
一些 Jupyter 小工具可以链接在一起。例如,您可以将 FloatText 小部件链接到 FloatSlider 小部件。这意味着当一个小部件更新时,另一个也会更新。这就是所谓的“属性同步”。让我们看一个简单的例子:
text = widgets.FloatText()
slider = widgets.FloatSlider()
display(text,slider)
mylink = widgets.jslink((text, 'value'), (slider, 'value'))
这里我们创建两个小部件并显示它们。然后我们调用 widgets.jslink 并告诉它将文本和滑块的值链接在一起。当您使用 jslink 方法时,您正在链接来自客户端的小部件。这使得小部件同步更加健壮,因为它们将使用本地机器的硬件。您还可以使用 jsdlink ,它将在客户端的两个小部件之间创建一个单向链接。这意味着您可以让两个部件中的一个影响第二个部件,而第二个部件不会影响第一个部件。
试着改变上面的例子,像下面这样使用 jsdlink :
text = widgets.FloatText()
slider = widgets.FloatSlider()
display(text,slider)
mylink = widgets.jsdlink((text, 'value'), (slider, 'value'))
现在尝试编辑每个小部件的值,看看它们是如何交互的。这很难在书中演示,但基本上当你改变文本部件时,它会改变滑块来匹配。但是,如果您更改滑块的值,文本控件不会改变。
还有另外两种方法可以用来将两个小部件链接在一起:
- 环
- dlink
这些方法的工作方式分别与 jslink 和 jsdlink 相同,除了它们会与服务器对话,所以你在使用它们时可能会经历一些延迟。坦率地说,在您的本地机器上,您可能看不出两者有什么不同。
最后,我想指出的是,您也可以通过调用链接的 unlink 方法来取消小部件的链接:
mylink.unlink()
您可以通过将这段代码添加到创建链接小部件的单元格的末尾来尝试一下。如果您添加它并重新运行该单元,那么您将发现这两个小部件不再链接在一起。
事件
链接小部件与小部件事件密切相关。当您与小部件交互时,会发生一个事件。例如,当你按下一个按钮时,这就是所谓的 click 事件。让我们看一个使用按钮小部件的例子。
import ipywidgets as widgets
from IPython.display import display
btn = widgets.Button(description='Test')
display(btn)
def my_event_handler(btn_object):
print('You pressed the {} button!'.format(btn_object.description))
btn.on_click(my_event_handler)
在这里,我们导入了使用小部件所需的各种小部件并显示它们。然后我们创建一个简单的按钮和一个名为 my_event_handler 的函数。这是事件处理程序,您可以将小部件的事件绑定到它。要将按钮的 click 事件绑定到这个处理程序,您必须调用 on_click 并传递您希望它绑定到的函数。您会注意到,事件处理程序隐式接受一个参数,即按钮对象本身。
这允许您访问按钮的属性,所以在本例中,我让它打印出按钮的标签。下面是我的代码和按钮在按下按钮后运行时的截图:
这还允许您将多个按钮绑定到同一个事件处理程序。你可以这样做:
import ipywidgets as widgets
from IPython.display import display
btn = widgets.Button(description='Test')
other_btn = widgets.Button(description='Other')
display(btn)
display(other_btn)
def my_event_handler(btn_object):
print('You pressed {}'.format(btn_object.description))
btn.on_click(my_event_handler)
other_btn.on_click(my_event_handler)
在这段代码中,我们创建了第二个名为 other_btn 的按钮,它带有不同的标签,并与另一个按钮绑定到同一个事件处理程序。现在,您可以试着按几次每个按钮,看看它们的行为如何。下面是一个会话示例:
您可以对其他类型的小部件事件进行这种类型的事件绑定。您需要查看小部件的文档或小部件的源代码,以确定它支持哪些事件。
说到文档,它还提到了 Jupyter 称之为 Traitlet 事件的东西。这些是 IPython traitlets,它们基本上提供了一种不同的方法,使用观察方法将事件绑定到函数。为了了解如何使用 traitlet,让我们在 Jupyter 笔记本中运行以下代码:
print(btn.observe.__doc__)
当我运行这段代码时,我得到了以下输出:
Setup a handler to be called when a trait changes.
This is used to setup dynamic notifications of trait changes.
Parameters
----------
handler : callable
A callable that is called when a trait changes. Its
signature should be ``handler(change)``, where ``change`` is a
dictionary. The change dictionary at least holds a 'type' key.
* ``type``: the type of notification.
Other keys may be passed depending on the value of 'type'. In the
case where type is 'change', we also have the following keys:
* ``owner`` : the HasTraits instance
* ``old`` : the old value of the modified trait attribute
* ``new`` : the new value of the modified trait attribute
* ``name`` : the name of the modified trait attribute.
names : list, str, All
If names is All, the handler will apply to all traits. If a list
of str, handler will apply to all names in the list. If a
str, the handler will apply just to that name.
type : str, All (default: 'change')
The type of notification to filter by. If equal to All, then all
notifications are passed to the observe handler.
那么这是如何工作的呢?基本上,您可以用您想要绑定的函数名以及您想要“观察”的特征来调用 observe。您可以传入一个字符串列表,将其设置为“All”或传入一个单数字符串。文档中有一个很好的例子,但是我将把它稍微扩展一下,变成下面这样:
int_range = widgets.IntSlider()
display(int_range)
def on_value_change(change):
print(change)
print(change['new'])
int_range.observe(on_value_change, names='value')
这与您在文档中发现的不同之处在于,我也打印出了变更对象。这将告诉我们正在处理什么。在这种情况下,更改滑块时的输出如下所示:
{'owner': IntSlider(value=2), 'new': 2, 'old': 0, 'name': 'value', 'type': 'change'}
2
这告诉我们, change 参数是一个值的字典,键 new 会给我们滑块的新值。所以当我们告诉 Jupyter 去观察字符串的值时,它会发送一个值的字典。有趣的是,除了新值之外,这个字典还包含滑块的旧值。例如,如果您需要在其他小部件中实现撤销/重做之类的功能,了解这一点会很有用。
布局
Jupyter 小部件还有一个布局属性,它允许你设置一些 CSS 属性,你可以用它们来控制小部件的布局。这些属性包括大小、显示、盒子模型、定位等等。您可以使用布局功能让您的小部件根据可用空间改变大小。
让我们看一个简单的例子:
from ipywidgets import Button, Layout
from IPython.display import display
layout = Layout(width='50%', height='100px')
btn = Button(description='(50% width, 100px height) button',
layout=layout)
display(btn)
在这里,我们导入按钮和布局。我们设置布局,使小部件占据 50%的可用宽度和 100 像素高。然后,我们将按钮的布局参数设置为我们刚刚创建的布局。结果是这样的:
您可以通过创建一个布局实例来应用布局,就像我们在前面的例子中所做的那样,或者您可以让一个小部件直接使用另一个小部件的布局。为了进行演示,让我们将下面的代码添加到我们在上一个示例中刚刚创建的单元格的底部:
btn2 = Button(description='Another button', layout=btn.layout)
display(btn2)
在这里,我们将这个新按钮的布局设置为 btn.layout ,这基本上使我们的新按钮使用与原始按钮相同的布局对象。您最终应该会看到这样的按钮:
式样
有趣的是,有些小部件允许你放入太长而无法显示的描述。
from ipywidgets import IntSlider
IntSlider(description='A really long description')
当您运行这段代码时,您会发现 Jupyter 删除了一些文本:
您可以通过应用样式来解决这个问题。这种情况下使用的样式如下所示:
style = {'description_width': 'initial'}
奇怪的是,关于 Jupyter 样式的文档几乎不存在。查看 widget_int.py 中的源代码,似乎值 initial 指的是初始标签宽度。因此,当您创建一个样式,将 description_width 作为关键字,并将其设置为 initial 时,您实际上是在告诉 Jupyter 使用小部件标签的字符串长度作为其宽度。要应用该样式,您只需执行以下操作:
from ipywidgets import IntSlider
style = {'description_width': 'initial'}
IntSlider(description='A really long description', style=style)
现在,您的小部件将如下所示:
当然,这个解决方案的问题是现在你的滑块看起来太小了。我们可以通过使用一个助手函数来解决这个问题!
排列小部件
有一些助手功能,比如 HBox 和 VBox ,你可以用它们来组合窗口小部件,并以可视化方式进行布局。HBox 会一次横向添加一个小部件。你可以把它想象成从左到右把单个的棋子排成一条水平线。让我们使用一个 HBox 和一个标签小部件来解决我们的问题:
from ipywidgets import HBox, Label, IntSlider
label = Label('A really long description')
my_slider = IntSlider()
HBox([label, my_slider])
在这里,我们导入我们需要的部分,创建几个小部件,然后将它们添加到我们的 HBox 中。结果如下:
如您所料,您可以使用 HBox 和 VBox 布局的组合来制作复杂的小部件布局。例如,您可以向一个 HBox 添加几个小部件,向另一个 HBox 添加几个小部件,然后将两个 HBox 都添加到一个 VBox 来创建一个网格。让我们继续这样做,这样你就可以看到它是如何工作的。
from ipywidgets import Button, HBox, VBox
btn_one = Button(description='One')
btn_two = Button(description='Two')
btn_three = Button(description='Three')
btn_four = Button(description='Four')
first_line = HBox([btn_one, btn_two])
second_line = HBox([btn_three, btn_four])
VBox([first_line, second_line])
这里我们创建了四个按钮和两个盒子。每个 HBoxes 都有两个按钮。最后,我们将 VBox 添加到 VBox,这是我们最终得到的结果:
在文档中,所以我将把它留给你们作为练习阅读。我还想提一下,还有一个用于创建布局的小部件叫做 GridBox 。
在我使用跨平台桌面用户界面工具包 wxPython 的经验中,我发现使用 VBox 和 HBox 类型的容器对于创建我的小部件的复杂布局来说已经足够了,而更高级的布局工具往往会使事情变得更困难,除非你已经计划做一个类似网格的界面。非常欢迎您尝试这些其他类型的布局,因为它们肯定有自己的位置。
包扎
在这一章中,我们学习了很多关于如何创建和使用 Jupyter 小部件的知识。我们还学习了如何展示、设计和排列它们。通过查阅该主题的文档或阅读网上的一些教程,您可以了解关于小部件的许多其他内容。例如,Jupyter 支持您创建自己的定制小部件。它还支持异步小部件的使用。我强烈推荐在你的笔记本上使用小工具,给它们一些活力,让它们更有用!
wxPyMail:创建发送电子邮件的应用程序
原文:https://www.blog.pythonlibrary.org/2008/08/16/wxpymail-creating-an-application-to-send-emails/
我认为用 wxPython 编写一个示例应用程序来展示如何将所有的部分放在一起并做出有用的东西是一个好主意。在我的日常工作中,我创建了一个发送电子邮件的小程序,因为我们有很多用户错过了 mailto 功能,当我们从 Exchange/Outlook 切换到 Zimbra 时,我们就失去了这个功能。应该注意的是,目前这是一个只支持 Windows 的应用程序,但是让它更加独立于操作系统应该不会太难。
我将这篇文章分成三部分:第一部分是创建界面;第二步是设置数据处理,第三步是创建一个 Windows 可执行文件并将其连接到 mailto 处理程序。当我们完成后,图形用户界面将如下所示:
要跟进,您需要以下内容:
创建界面
让我们看看下面的代码。如您所见,我将这个应用程序基于 wx。框架对象和 wx 的一个实例。让应用程序运行。
import os
import sys
import urllib
import wx
import mail_ico
class SendMailWx(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'New Email Message (Plain Text)',
size=(600,400))
self.panel = wx.Panel(self, wx.ID_ANY)
# set your email address here
self.email = 'myEmail@email.com'
self.filepaths = []
self.currentDir = os.path.abspath(os.path.dirname(sys.argv[0]))
self.createMenu()
self.createToolbar()
self.createWidgets()
try:
print sys.argv
self.parseURL(sys.argv[1])
except Exception, e:
print 'Unable to execute parseURL...'
print e
self.layoutWidgets()
self.attachTxt.Hide()
self.editAttachBtn.Hide()
def createMenu(self):
menubar = wx.MenuBar()
fileMenu = wx.Menu()
send_menu_item = fileMenu.Append(wx.NewId(), '&Send', 'Sends the email')
close_menu_item = fileMenu.Append(wx.NewId(), '&Close', 'Closes the window')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
# bind events to the menu items
self.Bind(wx.EVT_MENU, self.onSend, send_menu_item)
self.Bind(wx.EVT_MENU, self.onClose, close_menu_item)
def createToolbar(self):
toolbar = self.CreateToolBar(wx.TB_3DBUTTONS|wx.TB_TEXT)
toolbar.SetToolBitmapSize((31,31))
bmp = mail_ico.getBitmap()
sendTool = toolbar.AddSimpleTool(-1, bmp, 'Send', 'Sends Email')
self.Bind(wx.EVT_MENU, self.onSend, sendTool)
toolbar.Realize()
def createWidgets(self):
p = self.panel
font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)
self.fromLbl = wx.StaticText(p, wx.ID_ANY, 'From', size=(60,-1))
self.fromTxt = wx.TextCtrl(p, wx.ID_ANY, self.email)
self.toLbl = wx.StaticText(p, wx.ID_ANY, 'To:', size=(60,-1))
self.toTxt = wx.TextCtrl(p, wx.ID_ANY, '')
self.subjectLbl = wx.StaticText(p, wx.ID_ANY, ' Subject:', size=(60,-1))
self.subjectTxt = wx.TextCtrl(p, wx.ID_ANY, '')
self.attachBtn = wx.Button(p, wx.ID_ANY, 'Attachments')
self.attachTxt = wx.TextCtrl(p, wx.ID_ANY, '', style=wx.TE_MULTILINE)
self.attachTxt.Disable()
self.editAttachBtn = wx.Button(p, wx.ID_ANY, 'Edit Attachments')
self.messageTxt = wx.TextCtrl(p, wx.ID_ANY, '', style=wx.TE_MULTILINE)
self.Bind(wx.EVT_BUTTON, self.onAttach, self.attachBtn)
self.Bind(wx.EVT_BUTTON, self.onAttachEdit, self.editAttachBtn)
self.fromLbl.SetFont(font)
self.toLbl.SetFont(font)
self.subjectLbl.SetFont(font)
def layoutWidgets(self):
mainSizer = wx.BoxSizer(wx.VERTICAL)
fromSizer = wx.BoxSizer(wx.HORIZONTAL)
toSizer = wx.BoxSizer(wx.HORIZONTAL)
subjSizer = wx.BoxSizer(wx.HORIZONTAL)
attachSizer = wx.BoxSizer(wx.HORIZONTAL)
fromSizer.Add(self.fromLbl, 0)
fromSizer.Add(self.fromTxt, 1, wx.EXPAND)
toSizer.Add(self.toLbl, 0)
toSizer.Add(self.toTxt, 1, wx.EXPAND)
subjSizer.Add(self.subjectLbl, 0)
subjSizer.Add(self.subjectTxt, 1, wx.EXPAND)
attachSizer.Add(self.attachBtn, 0, wx.ALL, 5)
attachSizer.Add(self.attachTxt, 1, wx.ALL|wx.EXPAND, 5)
attachSizer.Add(self.editAttachBtn, 0, wx.ALL, 5)
mainSizer.Add(fromSizer, 0, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(toSizer, 0, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(subjSizer, 0, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(attachSizer, 0, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(self.messageTxt, 1, wx.ALL|wx.EXPAND, 5)
self.panel.SetSizer(mainSizer)
self.panel.Layout()
def parseURL(self, url):
''' Parse the URL passed from the mailto link '''
sections = 1
mailto_string = url.split(':')[1]
if '?' in mailto_string:
sections = mailto_string.split('?')
else:
address = mailto_string
if len(sections) > 1:
address = sections[0]
new_sections = urllib.unquote(sections[1]).split('&')
for item in new_sections:
if 'subject' in item.lower():
Subject = item.split('=')[1]
self.subjectTxt.SetValue(Subject)
if 'body' in item.lower():
Body = item.split('=')[1]
self.messageTxt.SetValue(Body)
self.toTxt.SetValue(address)
def onAttach(self, event):
'''
Displays a File Dialog to allow the user to choose a file
and then attach it to the email.
'''
print "in onAttach method..."
def onAttachEdit(self, event):
''' Allow the editing of the attached files list '''
print "in onAttachEdit method..."
def onSend(self, event):
''' Send the email using the filled out textboxes.
Warn the user if they forget to fill part
of it out.
'''
print "in onSend event handler..."
def onClose(self, event):
self.Close()
#######################
# Start program
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = SendMailWx()
frame.Show()
app.MainLoop()
我已经在以前的帖子中解释了如何创建工具栏、菜单和大小器,所以我在这里将重点放在新的东西上。我导入了 urllib 模块来帮助解析从网页上的 mailto 链接发送的数据。我目前支持 mailto 协议的 To、Subject 和 Body 字段。各个文本框的设置取决于传递给 parseURL()方法的节的数量。你可以很容易地扩展这是必要的。我还通过使用下面这行代码来获取脚本运行的目录:
self.currentDir = os.path.abspath(os.path.dirname(sys.argv[0]))
最后,有三个事件处理程序存根:“onAttach”、“onAttachEdit”和“onSend”。让我们继续把这些充实一下。
附加电子邮件
第一种方法是 onAttach(),允许用户将文件附加到电子邮件中。我用 wx。FileDialog 来获取用户的选择。这就是“文件路径”属性的用武之地。我还调用了新方法 getFileSize,它将计算文件的大小。参见下面的代码:
def onAttach(self, event):
'''
Displays a File Dialog to allow the user to choose a file
and then attach it to the email.
'''
attachments = self.attachTxt.GetLabel()
filepath = ''
# create a file dialog
wildcard = "All files (*.*)|*.*"
dialog = wx.FileDialog(None, 'Choose a file', self.currentDir,
'', wildcard, wx.OPEN)
# if the user presses OK, get the path
if dialog.ShowModal() == wx.ID_OK:
self.attachTxt.Show()
self.editAttachBtn.Show()
filepath = dialog.GetPath()
print filepath
# Change the current directory to reflect the last dir opened
os.chdir(os.path.dirname(filepath))
self.currentDir = os.getcwd()
# add the user's file to the filepath list
if filepath != '':
self.filepaths.append(filepath)
# get file size
fSize = self.getFileSize(filepath)
# modify the attachment's label based on it's current contents
if attachments == '':
attachments = '%s (%s)' % (os.path.basename(filepath), fSize)
else:
temp = '%s (%s)' % (os.path.basename(filepath), fSize)
attachments = attachments + '; ' + temp
self.attachTxt.SetLabel(attachments)
dialog.Destroy()
def getFileSize(self, f):
''' Get the file's approx. size '''
fSize = os.stat(f).st_size
if fSize >= 1073741824: # gigabyte
fSize = int(math.ceil(fSize/1073741824.0))
size = '%s GB' % fSize
elif fSize >= 1048576: # megabyte
fSize = int(math.ceil(fSize/1048576.0))
size = '%s MB' % fSize
elif fSize >= 1024: # kilobyte
fSize = int(math.ceil(fSize/1024.0))
size = '%s KB' % fSize
else:
size = '%s bytes' % fSize
return size
您还会注意到,我保存了用户进入的最后一个目录。我仍然遇到不这样做或者不坚持这样做的程序。希望我的实现在大多数情况下都能工作。getFileSize()方法应该计算附加文件的大小。这只显示最接近的尺寸,不显示分数。除此之外,我认为这是不言自明的。
编辑您的附件
onAttachEdit()方法非常类似,除了它调用一个自定义对话框来允许用户编辑包含哪些文件,以防他们错误地选择了一个文件。
def onAttachEdit(self, event):
''' Allow the editing of the attached files list '''
print 'in onAttachEdit...'
attachments = ''
dialog = EditDialog(self.filepaths)
dialog.ShowModal()
self.filepaths = dialog.filepaths
print 'Edited paths:\n', self.filepaths
dialog.Destroy()
if self.filepaths == []:
# hide the attachment controls
self.attachTxt.Hide()
self.editAttachBtn.Hide()
else:
for path in self.filepaths:
# get file size
fSize = self.getFileSize(path)
# Edit the attachments listed
if attachments == '':
attachments = '%s (%s)' % (os.path.basename(path), fSize)
else:
temp = '%s (%s)' % (os.path.basename(path), fSize)
attachments = attachments + '; ' + temp
self.attachTxt.SetLabel(attachments)
class EditDialog(wx.Dialog):
def __init__(self, filepaths):
wx.Dialog.__init__(self, None, -1, 'Edit Attachments', size=(190,150))
self.filepaths = filepaths
instructions = 'Check the items below that you no longer wish to attach to the email'
lbl = wx.StaticText(self, wx.ID_ANY, instructions)
deleteBtn = wx.Button(self, wx.ID_ANY, 'Delete Items')
cancelBtn = wx.Button(self, wx.ID_ANY, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.onDelete, deleteBtn)
self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn)
mainSizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
mainSizer.Add(lbl, 0, wx.ALL, 5)
self.chkList = wx.CheckListBox(self, wx.ID_ANY, choices=self.filepaths)
mainSizer.Add(self.chkList, 0, wx.ALL, 5)
btnSizer.Add(deleteBtn, 0, wx.ALL|wx.CENTER, 5)
btnSizer.Add(cancelBtn, 0, wx.ALL|wx.CENTER, 5)
mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
self.Fit()
self.Layout()
def onCancel(self, event):
self.Close()
def onDelete(self, event):
print 'in onDelete'
numberOfPaths = len(self.filepaths)
for item in range(numberOfPaths):
val = self.chkList.IsChecked(item)
if val == True:
path = self.chkList.GetString(item)
print path
for i in range(len(self.filepaths)-1,-1,-1):
if path in self.filepaths[i]:
del self.filepaths[i]
print 'new list => ', self.filepaths
self.Close()
在上面的代码中要注意的主要事情是 EditDialog 是 wx.Dialog 的子类。框架是因为我希望我的对话框是非模态的,我认为使用 wx。对话类对此最有意义。这个类中最有趣的部分可能是我的 onDelete 方法,在这个方法中,我向后遍历路径。我这样做是为了在不影响列表完整性的情况下,以任意顺序删除条目。例如,如果我重复删除了元素 2,我可能会删除一个我不想删除的元素。
发送电子邮件
我的最后一个方法是 onSend()方法。我认为它可能是最复杂的,也是最需要重构的。在这个实现中,所有的 SMTP 元素都是硬编码的。让我们来看看它是如何工作的:
def OnSend(self, event):
''' Send the email using the filled out textboxes.
Warn the user if they forget to fill part
of it out.
'''
From = self.fromTxt.GetValue()
To = self.toTxt.GetValue()
Subject = self.subjectTxt.GetValue()
text = self.messageTxt.GetValue()
colon = To.find(';')
period = To.find(',')
if colon != -1:
temp = To.split(';')
To = self.sendStrip(temp) #';'.join(temp)
elif period != -1:
temp = To.split(',')
To = self.sendStrip(temp) #';'.join(temp)
else:
pass
if To == '':
print 'add an address to the "To" field!'
dlg = wx.MessageDialog(None, 'Please add an address to the "To" field and try again', 'Error', wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
elif Subject == '':
dlg = wx.MessageDialog(None, 'Please add a "Subject" and try again', 'Error', wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
elif From == '':
lg = wx.MessageDialog(None, 'Please add an address to the "From" field and try again',
'Error', wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
else:
msg = MIMEMultipart()
msg['From'] = From
msg['To'] = To
msg['Subject'] = Subject
msg['Date'] = formatdate(localtime=True)
msg.attach( MIMEText(text) )
if self.filepaths != []:
print 'attaching file(s)...'
for path in self.filepaths:
part = MIMEBase('application', "octet-stream")
part.set_payload( open(path,"rb").read() )
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(path))
msg.attach(part)
# edit this to match your mail server (i.e. mail.myserver.com)
server = smtplib.SMTP('mail.myserver.org')
# open login dialog
dlg = LoginDlg(server)
res = dlg.ShowModal()
if dlg.loggedIn:
dlg.Destroy() # destroy the dialog
try:
failed = server.sendmail(From, To, msg.as_string())
server.quit()
self.Close() # close the program
except Exception, e:
print 'Error - send failed!'
print e
else:
if failed: print 'Failed:', failed
else:
dlg.Destroy()
这其中的大部分你已经见过了,所以我将只讨论电子邮件模块调用。noice 的主要部分是,要创建带有附件的电子邮件,您需要使用 MIMEMultipart czll。我用它来添加“从”、“到”、“主题”和“日期”字段。要附加文件,您需要使用 MIMEBase。最后,要发送电子邮件,您需要设置 SMTP 服务器,这是我使用 smptlib 库和 login 完成的,这就是 LoginDlg 的用途。我将在下一步讨论这个问题,但在此之前,我建议阅读这两个模块各自文档以了解完整的细节,因为它们有很多我在这个示例中没有用到的功能。
登录
我注意到我的代码在我的组织之外不起作用,我花了一段时间才找出原因。原来我上班登录的时候也登录了我们的 webmail 系统,不需要用它认证。当我在外面的时候,我会的。因为这实际上是 SMTP 服务器的正常程序,所以我包含了一个相当简单的登录对话框。让我们看一下代码:
class LoginDlg(wx.Dialog):
def __init__(self, server):
wx.Dialog.__init__(self, None, -1, 'Login', size=(190,150))
self.server = server
self.loggedIn = False
# widgets
userLbl = wx.StaticText(self, wx.ID_ANY, 'Username:', size=(50, -1))
self.userTxt = wx.TextCtrl(self, wx.ID_ANY, '')
passwordLbl = wx.StaticText(self, wx.ID_ANY, 'Password:', size=(50, -1))
self.passwordTxt = wx.TextCtrl(self, wx.ID_ANY, '', size=(150, -1),
style=wx.TE_PROCESS_ENTER|wx.TE_PASSWORD)
loginBtn = wx.Button(self, wx.ID_YES, 'Login')
cancelBtn = wx.Button(self, wx.ID_ANY, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.OnLogin, loginBtn)
self.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter, self.passwordTxt)
self.Bind(wx.EVT_BUTTON, self.OnClose, cancelBtn)
# sizer / layout
userSizer = wx.BoxSizer(wx.HORIZONTAL)
passwordSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
mainSizer = wx.BoxSizer(wx.VERTICAL)
userSizer.Add(userLbl, 0, wx.ALL, 5)
userSizer.Add(self.userTxt, 0, wx.ALL, 5)
passwordSizer.Add(passwordLbl, 0, wx.LEFT|wx.RIGHT, 5)
passwordSizer.Add(self.passwordTxt, 0, wx.LEFT, 5)
btnSizer.Add(loginBtn, 0, wx.ALL, 5)
btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
mainSizer.Add(userSizer, 0, wx.ALL, 0)
mainSizer.Add(passwordSizer, 0, wx.ALL, 0)
mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
self.Fit()
self.Layout()
def OnTextEnter(self, event):
''' When enter is pressed, login method is run. '''
self.OnLogin('event')
def OnLogin(self, event):
'''
When the "Login" button is pressed, the credentials are authenticated.
If correct, the email will attempt to be sent. If incorrect, the user
will be notified.
'''
try:
user = self.userTxt.GetValue()
pw = self.passwordTxt.GetValue()
res = self.server.login(user, pw)
self.loggedIn = True
self.OnClose('')
except:
message = 'Your username or password is incorrect. Please try again.'
dlg = wx.MessageDialog(None, message, 'Login Error', wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
def OnClose(self, event):
self.Close()
在很大程度上,我们以前见过这种情况。需要注意的主要部分是,我向我的密码 TextCtrl: wx 添加了两种样式。TE_PROCESS_ENTER 和 wx。TE _ 密码。第一个将允许你按下回车登录,而不是显式地按下登录按钮。TE_PASSWORD 样式用黑色圆圈或星号隐藏了键入 TextCtrl 中的文本。
你也应该注意到你的用户名可能包括你的电子邮件的网址。例如,不仅仅是用户名,它可能是【username@hotmail.com】的。幸运的是,如果登录不正确,程序将抛出一个错误并显示一个对话框让用户知道。
*## 入侵注册表
在 Windows 上要做的最后一件事是设置它在用户点击 mailto 链接时使用这个脚本。为此,您需要修改 Windows 注册表。在你对注册表做任何事情之前,一定要备份它,因为你总是有可能破坏一些东西,包括操作系统。
首先,进入开始,运行并输入 regedit 。现在导航到以下位置:
HKEY _ 类 _ 根\邮件\外壳\打开\命令
只需展开右侧的树即可导航该树。在那里,你需要编辑右边的(默认)键。它的类型应该是 REG_SZ。只需双击即可编辑。这是你想放进去的东西:
cmd/C " SET python home = C:\ path \ to \ python 24 & & C:\ path \ to \ python 24 \ python . exe C:\ path \ to \ wxpymail . py % 1 "
基本上,这告诉 Windows 将 Python 的主目录和 python.exe 的路径设置为环境变量,我认为这只是暂时的。然后,它将我们创建的 wxPyMail.py 脚本传递给指定的 python.exe。“%1”是 mailto 链接传递的参数。一旦你点击了 OK 按钮,它就会被保存并开始工作。
包扎
现在您知道了如何用 wxPython 创建一个功能完整的应用程序。在我的下一篇文章中,我将展示如何把它打包成可执行文件,这样你就可以分发它了。
您可以添加的一些可能的改进:
- 存储个人资料信息(即您的姓名、电子邮件、签名等)
- 在 SQLite 中存储电子邮件地址
- 创建用于设置配置文件的向导
- 给电子邮件加密。
延伸阅读:
下载来源:
wxPython 101:创建闪屏
原文:https://www.blog.pythonlibrary.org/2018/09/11/wxpython-101-creating-a-splash-screen/
你过去经常看到的一个常见的 UI 元素是闪屏。闪屏只是一个对话框,上面有一个标志或图案,有时还会包含一条消息,告诉你应用程序已经加载了多长时间。一些开发人员使用闪屏来告诉用户应用程序正在加载,这样他们就不会多次尝试打开它。
wxPython 支持创建闪屏。在版本 4 之前的 wxPython 版本中,您可以在 wx 中找到闪屏小部件。闪屏。然而在 wxPython 的最新版本中,它被移到了 wx.adv.SplashScreen 。
让我们来看一个简单的闪屏示例:
import wx
import wx.adv
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(500,500))
bitmap = wx.Bitmap('py_logo.png')
splash = wx.adv.SplashScreen(
bitmap,
wx.adv.SPLASH_CENTER_ON_SCREEN|wx.adv.SPLASH_TIMEOUT,
5000, self)
splash.Show()
self.Show()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
这里我们创建了 wx 的一个子类。框架,我们用 wx.Bitmap 加载一个图像。位图实际上并不要求你只加载位图,因为我在这里使用的是 PNG。不管怎样,下一行实例化了我们的闪屏实例。在这里,我们传递给它我们想要显示的位图,一个告诉它如何定位自己的标志,一个以毫秒为单位的启动屏幕应该显示多长时间的超时,以及它的父对象应该是什么。这些都是必需的参数。
闪屏小部件还可以接受另外三个参数:位置、大小和样式。你会注意到,在这个例子中,我们告诉闪屏在屏幕上居中。我们也可以通过 SPLASH_CENTRE_ON_PARENT 告诉它以它的父节点为中心。
当然,您需要修改这个例子来使用您自己的图像。
包扎
如果你有一个需要很长时间加载的应用程序,闪屏实际上是非常有用的。你可以很容易地用它来分散用户的注意力,给人一种你的应用程序即使还没有完全加载也能响应的错觉。试一试,看看你的想法。
相关阅读
- wxPython 闪屏小工具文档
- 关于该主题的原始 wxPython wiki 页面(使用旧的小部件)
- 一个来自闪屏上态度为的极客的旧教程(也使用旧版本的小部件)
wxPython 101:创建任务栏图标
原文:https://www.blog.pythonlibrary.org/2011/12/13/wxpython-101-creating-taskbar-icons/
你有没有想过如何在 Windows 系统托盘中创建那些通常出现在屏幕右下角的小状态图标?wxPython 工具包提供了一种非常简单的方法来实现这一点,本文将带您完成这一过程。
编写代码
出于我不太明白的原因,我们想要的 wx 组件是 wx.TaskBarIcon,我假设之所以这么叫是因为系统托盘是任务栏的一部分,或者可能他们没有区分其他操作系统中的任务栏和托盘区域。无论如何,首先你需要一个图标来使用。你可以在任何你喜欢的地方得到你的图像。在这个例子中,我们将使用一个“信封”图像,我使用 wxPython 的 img2py 实用程序将它转换成 Python 代码。当您通过 img2py 运行一个图像时,您将得到如下结果:
#----------------------------------------------------------------------
# This file was generated by img2py.py
#
from wx import ImageFromStream, BitmapFromImage
from wx import EmptyIcon
import cStringIO, zlib
def getData():
return zlib.decompress(
'x\xda\x01\x86\x01y\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\
\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\
\x08d\x88\x00\x00\x01=IDATX\x85\xed\x97[\x9a\x83 \x0c\x85Ol\xf7\x1566#\xec\
\xac,\xace\x1e\xe4b \xfa\xa9\xa3\xe5\xc5A\
\x94|\xdd\x81E\x80)y~|(\x17\xd6\xa2\x15"0\x87\xe8`\xc9\xdf\x00@\xd9v\x19\xb2\
\xf0|}-
图像数据都在 **getData** 函数中。我将把这个文件命名为“email_ico.py”。我们将把它导入到我们的主程序中,并调用它的 **getIcon** 方法来获取我们想要使用的图标。现在让我们来看看主要的应用程序:
import wx
from mail_ico import getIcon
########################################################################
class MailIcon(wx.TaskBarIcon):
TBMENU_RESTORE = wx.NewId()
TBMENU_CLOSE = wx.NewId()
TBMENU_CHANGE = wx.NewId()
TBMENU_REMOVE = wx.NewId()
#----------------------------------------------------------------------
def __init__(self, frame):
wx.TaskBarIcon.__init__(self)
self.frame = frame
# Set the image
self.tbIcon = getIcon()
self.SetIcon(self.tbIcon, "Test")
# bind some events
self.Bind(wx.EVT_MENU, self.OnTaskBarClose, id=self.TBMENU_CLOSE)
self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.OnTaskBarLeftClick)
#----------------------------------------------------------------------
def CreatePopupMenu(self, evt=None):
"""
This method is called by the base class when it needs to popup
the menu for the default EVT_RIGHT_DOWN event. Just create
the menu how you want it and return it from this function,
the base class takes care of the rest.
"""
menu = wx.Menu()
menu.Append(self.TBMENU_RESTORE, "Open Program")
menu.Append(self.TBMENU_CHANGE, "Show all the Items")
menu.AppendSeparator()
menu.Append(self.TBMENU_CLOSE, "Exit Program")
return menu
#----------------------------------------------------------------------
def OnTaskBarActivate(self, evt):
""""""
pass
#----------------------------------------------------------------------
def OnTaskBarClose(self, evt):
"""
Destroy the taskbar icon and frame from the taskbar icon itself
"""
self.frame.Close()
#----------------------------------------------------------------------
def OnTaskBarLeftClick(self, evt):
"""
Create the right-click menu
"""
menu = self.CreatePopupMenu()
self.PopupMenu(menu)
menu.Destroy()
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(500,500))
panel = wx.Panel(self)
self.tbIcon = MailIcon(self)
self.Bind(wx.EVT_CLOSE, self.onClose)
#----------------------------------------------------------------------
def onClose(self, evt):
"""
Destroy the taskbar icon and the frame
"""
self.tbIcon.RemoveIcon()
self.tbIcon.Destroy()
self.Destroy()
----------------------------------------------------------------------
Run the program
if name == "main":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
第一个类基本上是直接从 wxPython 演示中复制出来的,并进行了一些简化。如果你需要一个更完整的例子,你可以去那里看看。总之,我们在子类 TaskBarIcon 中绑定了两个事件,分别允许我们关闭应用程序和显示菜单。您还会注意到,我们设置了在 **__init__** 中创建的图标,只需调用它的 **SetIcon** 方法并为它的工具提示传入一个字符串。
在 close 方法中,我们直接调用希望它关闭的框架。更好的方法是在这里使用 pubsub。如果你想停下来读一下 pubsub,我也写了一篇关于它的小文章。代码的其余部分非常简单明了。
现在我们可以继续到 wx。框架子类。这里我们基本上只是实例化了我们之前创建的 TaskBarIcon 类,并将框架绑定到**EVT _ 关闭**。你可能会对此感到疑惑。在 Windows 上使用任务栏图标有一些问题。如果我只是告诉框架关闭,它关闭得很好,但图标仍然存在,Python 只是挂在 lala land。如果你只允许用户使用任务栏图标的右键菜单来关闭,那么你可以添加一个 **RemoveIcon** 方法和一个 self。Destroy()就可以了(出于某种原因,RemoveIcon 不足以摆脱 TaskBarIcon,所以你也需要告诉它自我销毁)但是如果你允许用户按右上角的小“x”,那么你就需要抓住**EVT _ 关闭**并适当地处理它。当你抓住这个事件时,你不能仅仅调用 **self。Close()** 否则会陷入无限循环,这就是我们称 **self 的原因。反而破坏()**。
包扎
现在,您应该能够创建自己的 TaskBarIcon 应用程序了。我强烈建议看看 wxPython 演示,看看还能做些什么。我认为添加一个图标可以为你的应用程序增添一点光彩,特别是当你需要让它隐藏一段时间,然后在用户的命令下让它弹出来的时候。
进一步阅读
- 创建一个闪烁的任务栏图标
- 另一个博客的谈到了这个话题
- TaskBarIcon 文档
来源
wxPython 101:如何将小部件居中
原文:https://www.blog.pythonlibrary.org/2015/02/19/wxpython-101-how-to-center-a-widget/
我看到这种情况时有发生,人们会问如何在窗口中居中小部件。解决方案其实很简单。在大多数情况下,您只需要用一些间隔符将一个水平的 BoxSizer 嵌套在一个垂直的 BoxSizer 中。在本文中,我将向您展示完成这项任务的两种不同方式。
使用人造垫片
当我第一次学习如何做的时候,我被告知我可以使用一个元组作为我的间隔。语法看起来有点奇怪,但是很有效:
import wx
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Center the Button")
panel = wx.Panel(self)
h_sizer = wx.BoxSizer(wx.HORIZONTAL)
main_sizer = wx.BoxSizer(wx.VERTICAL)
btn = wx.Button(panel, label="Centered")
h_sizer.Add(btn, 0, wx.CENTER)
main_sizer.Add((0,0), 1, wx.EXPAND)
main_sizer.Add(h_sizer, 0, wx.CENTER)
main_sizer.Add((0,0), 1, wx.EXPAND)
panel.SetSizer(main_sizer)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
在这里,我们将一个水平分级器嵌套在顶级垂直分级器之外。但是我们用两个“间隔符”将水平尺寸标注包围起来,这两个“间隔符”恰好是元组,它们的比例都被设置为 1,并且 wx。展开样式标志集。
使用 AddStretchSpacer
wxPython 的 sizer 包含 AddStretchSpacer,这是一个非常方便的方法,基本上做了与上面相同的事情。让我们来看看:
import wx
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Center the Button")
panel = wx.Panel(self)
h_sizer = wx.BoxSizer(wx.HORIZONTAL)
main_sizer = wx.BoxSizer(wx.VERTICAL)
btn = wx.Button(panel, label="Centered")
h_sizer.Add(btn, 0, wx.CENTER)
main_sizer.AddStretchSpacer(prop=1)
main_sizer.Add(h_sizer, 0, wx.CENTER)
main_sizer.AddStretchSpacer(prop=1)
panel.SetSizer(main_sizer)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
您会注意到,这里唯一的区别是使用 AddStretchSpacer 方法及其设置为 1 的 prop 参数。
无嵌套分度器的居中
我的一个敏锐的读者提到了第三种方法来使小部件居中,这种方法不需要嵌套 sizers。让我们来看看:
import wx
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Center the Button")
panel = wx.Panel(self)
main_sizer = wx.BoxSizer(wx.VERTICAL)
btn = wx.Button(panel, label="Centered")
main_sizer.AddStretchSpacer()
main_sizer.Add(btn, 0, wx.CENTER)
main_sizer.AddStretchSpacer()
panel.SetSizer(main_sizer)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
在这里,我们只是创建一个垂直 sizer,添加一个拉伸间隔,然后告诉按钮居中,然后添加另一个拉伸间隔。代码与前面的例子非常相似,除了我们根本没有使用水平尺寸。特别感谢 Yoriz 在评论中向我提到这一点。
wxPython 101:简单框架
原文:https://www.blog.pythonlibrary.org/2015/04/14/wxpython-101-simple-frames/
在本教程中,我们将看看创建 wxPython 框架的三种简单方法。如果你是视觉学习者,我还创建了这篇文章的截屏版本,你可以在下面查看:
https://www.youtube.com/embed/j0OY7obzFsw
“愚蠢”的简单例子
我把下面的例子称为“愚蠢”的例子,因为它不是很有用。您不能很容易地扩展这个示例,但我将它作为一个示例,因为您仍然可以找到像这样的 wxPython 片段。它们确实有效,但不推荐使用。
import wx
app = wx.App(False)
frame = wx.Frame(None, title='test')
frame.Show()
app.MainLoop()
我们来看一个更现实的例子!
更好的框架示例
在这个例子中,我们子类化了 wx。帧并添加一个 wx。面板对象。面板在小部件之间添加了正确的跳转和正确的外观。例如,如果你的窗户上没有嵌板,你的框架看起来会有点奇怪。
import wx
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title='Test')
panel = wx.Panel(self)
btn = wx.Button(panel, label='Press Me')
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
您会注意到,在这个例子中,我还添加了一个按钮小部件。这只是为了向您展示向您的框架添加额外的小部件是多么容易。现在,我们将继续前进,将面板分成自己的类。
分离类
对于这个例子,我们将把面板分离到它自己的类中。我们来看看吧!
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Press Me')
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title='Test')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
我喜欢这样做的原因是它有助于减少每个类的代码行数。如果 panel 类变得太大,也可以很容易地将它移到自己的模块中。然后我就可以导入面板了!当然,这是有代价的,因为现在你必须找到一种方法在两个类之间进行交流。这通常很容易做到,为此我通常使用 pubsub,但是我在 wxPython 邮件列表上看到过其他方法。
包扎
现在您知道如何在 wxPython 中创建自己的框架。创建小部件和管理代码有很多不同的方法。我希望这些例子有助于你理解如何设计你自己的代码。
相关阅读
- wxPython 101:使用框架样式
- wxPython 文档关于 wx。基本框架
wxPython 101:使用框架样式
原文:https://www.blog.pythonlibrary.org/2013/11/06/wxpython-101-using-frame-styles/
几乎所有 wxPython 应用程序中都使用 wxPython 框架小部件。它上面有最小化、最大化和关闭按钮,顶部还有标识应用程序的标题。wx。Frame 允许您以这样的方式修改其样式,即您可以删除或禁用各种按钮和功能。在本文中,我们将研究一些改变 wx 行为的方法。框架小部件。具体来说,我们将涵盖以下内容:
- 创建默认框架的不同方法
- 如何创建没有标题的框架(即没有标题栏)
- 如何创建一个禁用关闭按钮的框架
- 如何创建一个没有最大化或最小化按钮的框架
- 如何创建一个不能调整大小的框架
- 如何在没有系统菜单的情况下创建框架
- 如何让你的框架停留在其他窗口的顶部
入门指南
看看默认样式是如何工作的,然后修改它,看看会发生什么,这总是一个好主意。那么我们就从框架的默认样式开始: wx。DEFAULT_FRAME_STYLE 您可以创建使用 wx 的框架。以 3 种不同的方式。第一个也是最简单的方法就是这样做:
import wx
########################################################################
class DefaultFrame(wx.Frame):
"""
The default frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Default Frame")
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DefaultFrame()
app.MainLoop()
这将创建一个正常的框架,具有任何用户期望的所有正常功能。现在让我们稍微改变一下,把 wx 传递给它。默认帧样式。
import wx
########################################################################
class DefaultFrame(wx.Frame):
"""
The default frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Default Frame", style=wx.DEFAULT_FRAME_STYLE)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DefaultFrame()
app.MainLoop()
这段代码做的事情与前面的代码完全一样。如果你做一点研究,你会发现 wx。DEFAULT_FRAME_STYLE 相当于传递以下内容:
wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN
因此,让我们再修改一次代码,以展示它是如何工作的。
import wx
########################################################################
class DefaultFrame(wx.Frame):
"""
The default frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
default = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN
wx.Frame.__init__(self, None, title="Default Frame", style=default)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DefaultFrame()
app.MainLoop()
那很容易。现在我们准备开始实验了!
创建不带标题的框架
让我们创建一个没有标题的框架。标题是框架顶部的按钮以及应用程序的标题。
import wx
########################################################################
class NoCaptionFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_caption = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.CLIP_CHILDREN
wx.Frame.__init__(self, None, title="No Caption", style=no_caption)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoCaptionFrame()
app.MainLoop()
运行这段代码时,面板在框架的左上角被压扁。你可以调整框架的大小,面板会“咬合”到位,但是看起来有点奇怪。您可能还会注意到,您无法关闭此应用程序,因为它上面没有关闭按钮。您需要终止 Python 进程来关闭这个应用程序。
创建一个禁用关闭按钮的框架
一些程序员认为他们需要一个没有关闭按钮的框架。你不能真的去掉关闭按钮同时保留其他按钮,但是你可以禁用关闭按钮。方法如下:
import wx
########################################################################
class NoCloseFrame(wx.Frame):
"""
This frame has no close box and the close menu is disabled
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_close = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLIP_CHILDREN
wx.Frame.__init__(self, None, title="No Close", style=no_close)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoCloseFrame()
app.MainLoop()
当然,你也不能关闭这个应用程序,所以这是一个相当烦人的应用程序。你可能想要添加一个 wx。可以关闭它的按钮。
创建没有最大化/最小化的框架
有时你会想创建一个不能最小化或最大化的应用程序。如果你要走那么远,让我们做一个应用程序,也不显示在任务栏上!
import wx
########################################################################
class NoMaxMinFrame(wx.Frame):
"""
This frame does not have maximize or minimize buttons
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_caption = wx.RESIZE_BORDER | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN | wx.FRAME_NO_TASKBAR
wx.Frame.__init__(self, None, title="No Max/Min", style=no_caption)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoMaxMinFrame()
app.MainLoop()
如你所见,我们刚刚移除了 wx。最小化 _BOX 和 wx。MAXIMIZE_BOX 样式标志并添加了 wx。FRAME_NO_TASKBAR 样式标志。
创建不可调整大小的框架
有时,您会想要创建一个无法调整大小的框架。你可以使用 SetSizeHints 或者你可以只设置一些框架样式标志。我们将在这里执行后者:
import wx
########################################################################
class NoResizeFrame(wx.Frame):
"""
This frame cannot be resized. It can only be minimized, maximized
and closed
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_resize = wx.DEFAULT_FRAME_STYLE & ~ (wx.RESIZE_BORDER |
wx.RESIZE_BOX |
wx.MAXIMIZE_BOX)
wx.Frame.__init__(self, None, title="No Resize", style=no_resize)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoResizeFrame()
app.MainLoop()
请注意,这里我们使用按位运算符从 wx 中移除 3 个样式标志。默认帧样式。正如你所看到的,这给了我们一个框架,我们不能以任何方式调整大小。
创建一个没有系统菜单的框架
这是一个相当愚蠢的要求,但我见过有人这样要求。基本上,他们想去掉所有的按钮,但留下标题。下面是如何做到这一点:
import wx
########################################################################
class NoSystemMenuFrame(wx.Frame):
"""
There is no system menu, which means the title bar is there, but
no buttons and no menu when clicking the top left hand corner
of the frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_sys_menu = wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.CAPTION | wx.CLIP_CHILDREN | wx.CLOSE_BOX
wx.Frame.__init__(self, None, title="No System Menu", style=no_sys_menu)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoSystemMenuFrame()
app.MainLoop()
正如你所看到的,有一个标题,你可以调整框架的大小,但你不能最大化,最小化或关闭应用程序。
更新:我收到一份报告,称这款电脑无法在 Windows XP 或 Windows 8.1 上运行。虽然在这一点上我并不特别关心那个操作系统,但是提供的解决方案是传递下面的样式而不是上面的样式:
no_sys_menu=wx.CAPTION
创建一个停留在顶部的框架
很多程序员问这个问题。他们希望自己的应用程序位于所有其他应用程序之上。虽然没有完全万无一失的方法来做到这一点,下面的小食谱将在大多数时候工作。
import wx
########################################################################
class StayOnTopFrame(wx.Frame):
"""
A frame that stays on top of all the others
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
on_top = wx.DEFAULT_FRAME_STYLE | wx.STAY_ON_TOP
wx.Frame.__init__(self, None, title="Stay on top", style=on_top)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = StayOnTopFrame()
app.MainLoop()
这里我们只是使用默认的样式标志并添加 wx。保持在顶部。
包扎
至此,您应该知道如何编辑几乎所有的框架样式。还有一些其他的样式标志是依赖于操作系统的(比如 wx。ICONIZE)或者只是没那么有用。如果你对这些感兴趣,看看下面的链接。否则,向前迈进,明智地使用你的知识。
注意:这段代码在 Windows 7 上使用 Python 2.6.6 和 wxPython 2.8.12.1 进行了测试
相关链接
wxPython 2.9 和更新的 Pubsub API:一个简单的教程
注:本文针对 wxPython 2.9-3.0。如果你用的是 wxPython 4 ,你应该去我的更新文章
几年前,我写了一篇关于 wxPython 2.8 及其内置 pubsub 模块的教程,你可以在这里阅读。当时,wxPython 2.8.11.0 中为 pubsub 添加了一个新的 API,可以通过执行以下操作来启用它:
import wx.lib.pubsub.setupkwargs
from wx.lib.pubsub import pub
导入 pubsub 的旧方法是执行以下操作:
from wx.lib.pubsub import Publisher
现在在 wxPython 2.9 中,它变成了这样:
from wx.lib.pubsub import pub
因此,您不能再使用我的旧教程中的代码,并期望它在 wxPython 的最新版本中工作。所以是时候稍微更新一下教程了。
新的 pubsub API
让我们从旧文章中提取原始代码,并使用 pubsub 较新的 API 对其进行修饰。不会花很长时间。事实上,这是一个非常小的代码更改。我们来看看吧!
import wx
from wx.lib.pubsub import pub
########################################################################
class OtherFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY, "Secondary Frame")
panel = wx.Panel(self)
msg = "Enter a Message to send to the main frame"
instructions = wx.StaticText(panel, label=msg)
self.msgTxt = wx.TextCtrl(panel, value="")
closeBtn = wx.Button(panel, label="Send and Close")
closeBtn.Bind(wx.EVT_BUTTON, self.onSendAndClose)
sizer = wx.BoxSizer(wx.VERTICAL)
flags = wx.ALL|wx.CENTER
sizer.Add(instructions, 0, flags, 5)
sizer.Add(self.msgTxt, 0, flags, 5)
sizer.Add(closeBtn, 0, flags, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onSendAndClose(self, event):
"""
Send a message and close frame
"""
msg = self.msgTxt.GetValue()
pub.sendMessage("panelListener", message=msg)
pub.sendMessage("panelListener", message="test2", arg2="2nd argument!")
self.Close()
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
pub.subscribe(self.myListener, "panelListener")
btn = wx.Button(self, label="Open Frame")
btn.Bind(wx.EVT_BUTTON, self.onOpenFrame)
#----------------------------------------------------------------------
def myListener(self, message, arg2=None):
"""
Listener function
"""
print "Received the following message: " + message
if arg2:
print "Received another arguments: " + str(arg2)
#----------------------------------------------------------------------
def onOpenFrame(self, event):
"""
Opens secondary frame
"""
frame = OtherFrame()
frame.Show()
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="New PubSub API Tutorial")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
正如我们已经讨论过的,进口是不同的。我们来看看还有什么变化。在 panel 类中,我们像这样创建一个侦听器:
pub.subscribe(self.myListener, "panelListener")
myListener 方法可以接受一个或多个参数。在这种情况下,我们将其设置为总是需要一个参数(message)和一个可选参数(arg2)。接下来我们转向 OtherFrame 类,在这里我们需要看一下 onSendAndClose 方法。在这个方法中,我们发现它发出了两个信息:
msg = self.msgTxt.GetValue()
pub.sendMessage("panelListener", message=msg)
pub.sendMessage("panelListener", message="test2", arg2="2nd argument!")
self.Close()
第一个只发送所需的信息,而第二个发送两者。您会注意到新的 API 需要使用显式的关键字参数。如果您将第一个 sendMessage 命令更改为pub . sendMessage(" panel ener ",msg) ,您将收到一个 TypeError 异常。
包扎
这是一个相当简单的变化,是吧?我认为新的 pubsub API 实际上比原来的可读性更好,而且不那么“神奇”。希望你也会。开心快乐编码!
附加阅读
- wxPython 和 PubSub: 一个简单的教程
- 在 wxPython 邮件列表的 pubsub 上的众多主题之一
- pubsub 网站
- wxPython: 如何从线程更新进度条
wxPython 201:同步两个网格之间的滚动
原文:https://www.blog.pythonlibrary.org/2013/12/17/wxpython-201-syncing-scrolling-two-grid/
这个星期我在 StackOverflow 上看到一个问题,关于将两个网格放入一个 SplitterWindow,它本身在一个笔记本页面上。我个人认为这有点复杂,但我认为这是一个有趣的挑战,我想出了一个解决方案。然后这个家伙想知道如何同步两个网格的滚动。嗯,我找到了一个答案并修改了我的代码,决定写一篇关于它的文章。下面是最终结果的截图:
是的,出于某种原因,想要这个的人也想要两个网格下面的面板。我觉得它有点丑,特别是用粉红色的背景,但是要各得其所。让我们看一下代码:
import wx
import wx.grid as gridlib
class ScrollSync(object):
def __init__(self, panel1, panel2):
self.panel1 = panel1
self.panel2 = panel2
self.panel1.grid.Bind(wx.EVT_SCROLLWIN, self.onScrollWin1)
self.panel2.grid.Bind(wx.EVT_SCROLLWIN, self.onScrollWin2)
def onScrollWin1(self, event):
if event.Orientation == wx.SB_HORIZONTAL:
self.panel2.grid.Scroll(event.Position, -1)
else:
self.panel2.grid.Scroll(-1, event.Position)
event.Skip()
def onScrollWin2(self, event):
if event.Orientation == wx.SB_HORIZONTAL:
self.panel1.grid.Scroll(event.Position, -1)
else:
self.panel1.grid.Scroll(-1, event.Position)
event.Skip()
########################################################################
class RegularPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.SetBackgroundColour("pink")
########################################################################
class GridPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.grid = gridlib.Grid(self, style=wx.BORDER_SUNKEN)
self.grid.CreateGrid(25,8)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.grid, 1, wx.EXPAND)
self.SetSizer(sizer)
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
notebook = wx.Notebook(self)
page = wx.SplitterWindow(notebook)
notebook.AddPage(page, "Splitter")
hSplitter = wx.SplitterWindow(page)
panelOne = GridPanel(hSplitter)
panelTwo = GridPanel(hSplitter)
ScrollSync(panelOne, panelTwo)
hSplitter.SplitVertically(panelOne, panelTwo)
hSplitter.SetSashGravity(0.5)
panelThree = RegularPanel(page)
page.SplitHorizontally(hSplitter, panelThree)
page.SetSashGravity(0.5)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.EXPAND)
self.SetSizer(sizer)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Nested Splitters",
size=(800,600))
panel = MainPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
我们最关心的部分是 ScrollSync 类。它接受网格所在的两个面板作为参数。然后,我们将网格绑定到 wx。然后在这个事件中,我们改变对面网格的位置。这段代码有几个限制。只有当你用鼠标移动滚动条时,它才起作用。如果您使用鼠标的滚轮、箭头键或上下翻页,则两个网格不再同步。我试图通过 wx 添加鼠标滚轮支持。EVT 鼠标滚轮事件,但它不像 EVT 鼠标滚轮事件那样提供方向或位置。其实它的定位是一个 wx。点,而 EVT _ 斯克罗温返回一个整数。添加这些功能会很有趣,但是它们超出了本文的范围。
这段代码应该可以让你开始同步工作。我还找到了几篇与此主题相关的文章,如果您需要在几个小部件中同步向上滚动,这些文章可能会对您有所帮助:
- 如何让两个 stylesTextCtrl 同步滚动?
- 同步两个滚动条
- 多个文本控件的同步滚动
wxPython 4 和 PubSub
原文:https://www.blog.pythonlibrary.org/2019/03/28/wxpython-4-and-pubsub/
发布-订阅模式在计算机科学中非常普遍,也非常有用。wxPython GUI 工具包在 wx.lib.pubsub 中已经实现了很长时间。这个实现基于 PyPubSub 包。虽然您总是可以下载 PyPubSub 并直接使用它,但是能够在没有额外依赖的情况下运行 wxPython 还是不错的。
然而,从 wxPython 4.0.4 , wx.lib.pubsub 现在已经被弃用,并将在 wxPython 的未来版本中删除。因此,如果您想在 wxPython 中轻松使用发布-订阅模式,现在您需要下载 PyPubSub 或 PyDispatcher 。
正在安装 PyPubSub
您可以使用 pip 安装 PyPubSub。
以下是如何做到这一点:
pip install pypubsub
PyPubSub 应该可以很快安装。一旦完成,让我们看看如何使用它!
使用 PyPubSub
让我们从我以前关于这个主题的文章中取一个例子,并更新它以使用 PyPubSub:
import wx
from pubsub import pub
class OtherFrame(wx.Frame):
""""""
def __init__(self):
"""Constructor"""
super().__init__(None, title="Secondary Frame")
panel = wx.Panel(self)
msg = "Enter a Message to send to the main frame"
instructions = wx.StaticText(panel, label=msg)
self.msg_txt = wx.TextCtrl(panel, value="")
close_btn = wx.Button(panel, label="Send and Close")
close_btn.Bind(wx.EVT_BUTTON, self.on_send_and_slose)
sizer = wx.BoxSizer(wx.VERTICAL)
flags = wx.ALL|wx.CENTER
sizer.Add(instructions, 0, flags, 5)
sizer.Add(self.msg_txt, 0, flags, 5)
sizer.Add(close_btn, 0, flags, 5)
panel.SetSizer(sizer)
def on_send_and_slose(self, event):
"""
Send a message and close frame
"""
msg = self.msg_txt.GetValue()
pub.sendMessage("panel_listener", message=msg)
pub.sendMessage("panel_listener", message="test2",
arg2="2nd argument!")
self.Close()
class MyPanel(wx.Panel):
""""""
def __init__(self, parent):
"""Constructor"""
super().__init__(parent)
pub.subscribe(self.my_listener, "panel_listener")
btn = wx.Button(self, label="Open Frame")
btn.Bind(wx.EVT_BUTTON, self.on_open_frame)
def my_listener(self, message, arg2=None):
"""
Listener function
"""
print(f"Received the following message: {message}")
if arg2:
print(f"Received another arguments: {arg2}")
def on_open_frame(self, event):
"""
Opens secondary frame
"""
frame = OtherFrame()
frame.Show()
class MyFrame(wx.Frame):
""""""
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None,
title="New PubSub API Tutorial")
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
这里使用内置 PubSub 的主要区别是导入。
你需要做的就是替换这个:
from wx.lib.pubsub import pub
有了这个:
from pubsub import pub
只要您使用的是 wxPython 2.9 或更高版本。如果您一直在使用 wxPython 2.8,那么您可能想看看我以前关于这个主题的一篇文章,看看 PubSub API 是如何变化的。
如果您使用的是 wxPython 2.9 或更高版本,那么这种改变非常容易,几乎没有痛苦。
像往常一样,你订阅一个话题:
pub.subscribe(self.myListener, "panelListener")
然后你发布到那个主题:
pub.sendMessage("panelListener", message=msg)
试一试,看看添加到您自己的代码中是多么容易!
包扎
我个人非常喜欢使用 wx.lib.pubsub ,所以我可能会继续使用 PyPubSub。然而,如果您曾经想尝试另一个包,如 PyDispatcher,这将是一个很好的时机。
相关阅读
- wxPython 2.9 和更新的 Pubsub API: 一个简单的教程
- wxPython 和 PubSub: 一个简单的教程
- wxPython: 使用 PyDispatcher 代替 Pubsub
wxPython:一个简单的笔记本例子
原文:https://www.blog.pythonlibrary.org/2010/09/15/wxpython-a-simple-notebook-example/
前几天,我接到一个投诉,说我的书控系列里我原来的笔记本例子太复杂了。我并不真的只写对 n00b 友好的文章,也从来没有这样说过,但是这个评论引起了我的不满,所以我决定为 wxPython 新手写一个超级简单的例子。希望你喜欢!
import random
import wx
########################################################################
class TabPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent=parent)
colors = ["red", "blue", "gray", "yellow", "green"]
self.SetBackgroundColour(random.choice(colors))
btn = wx.Button(self, label="Press Me")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL, 10)
self.SetSizer(sizer)
########################################################################
class DemoFrame(wx.Frame):
"""
Frame that holds all other widgets
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"Notebook Tutorial",
size=(600,400)
)
panel = wx.Panel(self)
notebook = wx.Notebook(panel)
tabOne = TabPanel(notebook)
notebook.AddPage(tabOne, "Tab 1")
tabTwo = TabPanel(notebook)
notebook.AddPage(tabTwo, "Tab 2")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DemoFrame()
app.MainLoop()
通常我写 wx 的时候。笔记本的例子,我希望每个标签看起来不同的方式。如果您查看前面提到的系列的第一篇文章,您会看到笔记本的每一页中都嵌入了一些复杂的小部件。然而,在这个例子中,我只是随机地给每一页涂上不同的颜色。让我们花点时间来解开这段代码。
在 DemoFrame 类中,我们有一个主面板,它是框架的唯一子部件。在那里面,我们有笔记本控制。笔记本的每一页都是我们的 TabPanel 类的一个实例,它应该有一个“随机”的背景颜色和一个不做任何事情的按钮。我们将笔记本添加到 sizer 中,并将其设置为以 1 的比例扩展。这意味着它将填充面板,并且由于面板填充框架,笔记本也将填充框架。说实话,真的就是这么回事。
另一个值得注意的话题是,笔记本事件,如 EVT _ 笔记本 _ 页面 _ 改变,可能需要有一个“事件”。Skip()"调用它们的事件处理程序,使它们正常工作。wxPython 中的事件层次有点难以理解,但可以把它想象成池塘中的气泡。如果您将一个小部件绑定到一个特定的事件,并且不调用 Skip(),那么您的事件只在那个特定的处理程序中处理。这就像让泡沫从池塘底部部分破裂一样。然而,有时你需要事件在更高的层次上处理,比如在部件的父级或祖父级。如果是这样,调用 Skip()并且您的事件“bubble”将上升到下一个处理程序。wxPython wiki 有更多关于这方面的内容,正如“ wxPython in Action ”一书一样。
好了,这涵盖了简单的笔记本电脑的例子。如果您需要了解关于笔记本或其他书籍控件的更多信息,请参阅本文的第一个链接或下载 wxPython 演示。
wxPython:按钮之旅(第 1 部分,共 2 部分)
原文:https://www.blog.pythonlibrary.org/2010/06/09/wxpython-a-tour-of-buttons-part-1-of-2/
大多数人并没有真正思考他们每天使用的小工具。相反,他们认为这是理所当然的。按钮是我们最常用的部件之一。从键盘上的按键到门锁上的按钮,我们到处都能找到它们。在按钮实际上可以是任何形状或大小的软件中,它们甚至更加普遍。有些纽扣看起来甚至不像纽扣!在本文中,我们将了解 wxPython 为您提供的几个按钮以及如何使用它们。
常见的 wx。按钮部件
大多数 wxPython 程序员可能会使用的第一个按钮是 wx.Button,它包装了三个主要平台上的原生按钮控件,在每个平台上看起来都“正确”。这里有一个非常简单的例子来说明我们如何使用它:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Button Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
button = wx.Button(panel, id=wx.ID_ANY, label="Press Me")
button.Bind(wx.EVT_BUTTON, self.onButton)
# self.Bind(wx.EVT_BUTTON, self.onButton, button)
#----------------------------------------------------------------------
def onButton(self, event):
"""
This method is fired when its corresponding button is pressed
"""
print "Button pressed!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
上面的代码只是创建了一个简单的带有一个按钮的表单。按钮被绑定到 onButton 事件处理程序。请注意,还有一种被注释掉的绑定方式。罗宾·邓恩在维基上解释了绑定一个事件的两种方式的区别,所以如果你不明白,去看看吧!如果你不想得到完整的解释,以下是我的观点:
- 自我。Bind 将事件绑定到父级(通常是 wx。框架)通过按钮。这意味着事件在到达父级之前可能必须向上浮动几个事件层。如果在这些处理程序层的任何地方,开发人员都不会调用 event。跳过(),则事件停止。
- 小部件。对于所有意图和目的,将事件绑定到那个小部件。如果你愿意,你可以调用事件。这里跳过()将事件向前向上传递,尽管我想不出什么时候需要用按钮事件来这样做。
将多个小部件绑定到同一个处理程序
需要将多个小部件绑定到一个处理程序,然后在处理程序中区分调用小部件,这是相当常见的做法。有几种方法可以获得所需的信息。我们将看看实际获得小部件本身的几种最流行的方法:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Button Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
buttonOne = wx.Button(panel, id=wx.ID_ANY, label="One", name="one")
buttonTwo = wx.Button(panel, id=wx.ID_ANY, label="Two", name="two")
buttonThree = wx.Button(panel, id=wx.ID_ANY, label="Three", name="three")
buttons = [buttonOne, buttonTwo, buttonThree]
for button in buttons:
self.buildButtons(button, sizer)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def buildButtons(self, btn, sizer):
""""""
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer.Add(btn, 0, wx.ALL, 5)
#----------------------------------------------------------------------
def onButton(self, event):
"""
This method is fired when its corresponding button is pressed
"""
button = event.GetEventObject()
print "The button you pressed was labeled: " + button.GetLabel()
print "The button's name is " + button.GetName()
button_id = event.GetId()
button_by_id = self.FindWindowById(button_id)
print "The button you pressed was labeled: " + button_by_id.GetLabel()
print "The button's name is " + button_by_id.GetName()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
这里我们有三个按钮,每个都绑定到 onButton 事件处理程序。有两种方法可以获得按钮对象:
- 事件。GetEventObject()
- 自我。FindWindowById(button_id)
第一种方法可以一下子直接获得小部件。第二个要求我们使用事件获取小部件 ID。GetId 。无论哪种方式,一旦你有了按钮,你可以调用它的任何方法。在我们的例子中,我们获得了按钮的名称和当前标签。我们还可以通过使用适当的 setter 方法(即 SetLabel)来更改按钮的标签或其他属性。现在让我们把注意力转向其他可用的按钮。
切换按钮和位图按钮
切换/位图按钮示例
第二个最流行的按钮可能是切换按钮和位图按钮。使用 wxPython,对于这两种类型的按钮,您有两种选择:使用本机版本(如果可能的话)或使用通用版本之一(即,用纯 Python 而不是 SWIGed 编写的按钮)。让我们看看这四个:
import wx
import wx.lib.buttons as buttons
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Button Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
# create a normal toggle button
button = wx.ToggleButton(panel, label="Press Me")
button.Bind(wx.EVT_TOGGLEBUTTON, self.onToggle)
# create a generic toggle button
gen_toggle_button = buttons.GenToggleButton(panel, -1, "Generic Toggle")
gen_toggle_button.Bind(wx.EVT_BUTTON, self.onGenericToggle)
# create a normal bitmap button
bmp = wx.Bitmap("agt_mp3.png", wx.BITMAP_TYPE_ANY)
bmapBtn = wx.BitmapButton(panel, id=wx.ID_ANY, bitmap=bmp,
size=(bmp.GetWidth()+10, bmp.GetHeight()+10))
# create a generic bitmap button
genBmapBtn = buttons.GenBitmapButton(panel, bitmap=bmp)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(button, 0, wx.ALL, 5)
sizer.Add(gen_toggle_button, 0, wx.ALL, 5)
sizer.Add(bmapBtn, 0, wx.ALL, 5)
sizer.Add(genBmapBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onGenericToggle(self, eventq):
""""""
print "Generic toggle button was pressed!"
#----------------------------------------------------------------------
def onToggle(self, event):
"""
Print a message when toggled
"""
print "Button toggled!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
这段代码与我们已经看到的非常相似。新的信息是如何得到通用按钮。我们需要做一个特殊的导入,就像这样:import wx.lib.buttons。现在我们可以访问一组通用按钮,包括普通按钮、切换按钮、位图、带文本的位图和平面按钮。甚至有几个主题按钮看起来几乎和原生按钮一样!
让我们注意一下上面代码中的一些特性。首先,我们对 wx 进行了奇怪的尺寸调整。BitmapButton 小工具。我们这样做是为了确保位图周围有一些“空白”空间。这使得辨别小部件是一个按钮变得更加容易。通用版本似乎可以自动做到这一点。另一件奇怪的事情是当你切换 wx 时。ToggleButton 按钮并保持鼠标指针在按钮上方,它看起来不切换。如果你将鼠标从按钮上移开,那么它就会离开(我在 Windows XP 上注意到了这一点...在其他平台上可能不是问题)。
据罗宾·邓恩(wxPython 的创造者)透露,wxPython 的 2.9 系列将会有更新的 wx。可以显示位图及其文本的按钮控件。这才是值得期待的!
板按钮
PlateButton 是另一个通用小部件。它是由《Editra 的作者 Cody Precord 创建的。在 wxPython 演示中有很多关于他的按钮的例子。我们只看几个例子:
import wx
import wx.lib.platebtn as platebtn
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Plate Button Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
## btn = platebtn.PlateButton(panel, label="Test", style=platebtn.PB_STYLE_DEFAULT)
btn = platebtn.PlateButton(panel, label="Gradient", style=platebtn.PB_STYLE_GRADIENT)
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
上面的例子显示了如何创建一个简单的 PlateButton。包括两个示例,展示了两种不同的按钮样式。您需要取消对其中一个的注释,并注释掉另一个,看看它们有什么不同。现在让我们看看如何向我们的 PlateButton 添加一个菜单:
import wx
import wx.lib.platebtn as platebtn
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Plate Button Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
menu = wx.Menu()
for url in ['http://wxpython.org', 'http://slashdot.org',
'http://editra.org', 'http://xkcd.com']:
menu.Append(wx.NewId(), url, "Open %s in your browser" % url)
btn = platebtn.PlateButton(panel, label="Menu", size=None, style=platebtn.PB_STYLE_DEFAULT)
btn.SetMenu(menu)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
这个示例基本上是从 wxPython 演示中剥离出来的一个示例。这里的想法是创建一个菜单,然后使用 PlateButton 的“SetMenu”方法将其附加到按钮上。
包扎
在本文中,我们学习了以下按钮:wx。按钮,wx。ToggleButton。BitmapButton,几个普通按钮和 PlateButton。在下半部分,我们将会看到更多的核心部件按钮和 Andrea Gavana 的按钮!下次见!
注意:本文中的例子是在 Windows XP、wxPython 2.8.10.1/2 . 8 . 11 . 0 和 Python 2.5 上测试的
wxPython:按钮之旅(第 2 部分,共 2 部分)
原文:https://www.blog.pythonlibrary.org/2010/06/10/wxpython-a-tour-of-buttons-part-2-of-2/
在上一篇文章中,我们介绍了标准 wxPython 包中的各种按钮。现在我们要看更多的东西!如果您还没有弄明白,wxPython 非常重视 Python 的“包含电池”哲学!在这篇文章中,我们将看看以下按钮:
- wx。单选按钮
- wx.SpinButton
- AquaButton (AGW)
- 梯度按钮(AGW)
- ShapedButton (AGW)
让我们开始吧!
wx。单选按钮-不要换频道!
单选按钮是一个众所周知的小部件,甚至对于年级学生来说也是如此,因为它在许多标准化测试中使用。单选按钮允许开发者强迫用户从选项列表中只选择一个选项。它们比 ComboBox 更直观,尽管它们基本上服务于相同的目的。这里有一个简单的例子:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Radio Button Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
radio1 = wx.RadioButton( panel, -1, " Radio1 ", style = wx.RB_GROUP )
radio2 = wx.RadioButton( panel, -1, " Radio2 " )
radio3 = wx.RadioButton( panel, -1, " Radio3 " )
radio1.Bind(wx.EVT_RADIOBUTTON, self.onButton)
radio2.Bind(wx.EVT_RADIOBUTTON, self.onButton)
radio3.Bind(wx.EVT_RADIOBUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(radio1, 0, wx.ALL, 5)
sizer.Add(radio2, 0, wx.ALL, 5)
sizer.Add(radio3, 0, wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
This method is fired when its corresponding button is pressed
"""
btn = event.GetEventObject()
label = btn.GetLabel()
message = "You just selected %s" % label
dlg = wx.MessageDialog(None, message, 'Message',
wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
注意,这个按钮和我们在上一篇文章中看到的按钮的主要区别是样式标志:RB_GROUP。这将导致以下所有单选按钮成为一个组的一部分。如果你再次设置那个标志,那么你将从那个点开始创建一个新的组(参见 Zetcode 教程)。我们还将每个按钮绑定到一个事件处理程序,该处理程序将向用户显示一个 MessageDialog,告诉他们选择了哪个按钮。做作?你打赌!但是这对于说明性的目的来说是很好的。
我们跟着 wx 转一圈吧。旋转按钮
SpinButton 实际上就是你在上面的截图中看到的文本控件右边的上下箭头。大多数时候,SpinCtrl 或 FloatSpin 小部件可能更好。但是这里有一个例子:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Spin Button Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
self.text = wx.TextCtrl(panel, value="1")
self.spin = wx.SpinButton(panel, style=wx.SP_VERTICAL)
self.spin.SetRange(1, 100)
self.spin.SetValue(1)
self.Bind(wx.EVT_SPIN, self.OnSpin, self.spin)
vSizer = wx.BoxSizer(wx.VERTICAL)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.text, 0, wx.CENTER)
sizer.Add(self.spin, 0, wx.CENTER)
vSizer.Add(sizer, 1, wx.CENTER)
panel.SetSizer(vSizer)
def OnSpin(self, event):
self.text.SetValue(str(event.GetPosition()))
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
上面的代码几乎一字不差地取自 wxPython 演示。请注意,您可以设置按钮的方向。在这种情况下,我们将其设置为垂直方向。您也可以将其设置为水平。还有其他样式,但似乎没有太多的文档,所以我无法找出哪些是 SpinButton 的,哪些是 SplitterWindow 的。哦好吧。回到上面的代码。如你所见,我们没有像过去那样绑定到 EVT 按钮,而是绑定到 EVT 自旋。出于某种原因,wxPython 演示使用了 event。获取微调器的位置。你可以调用“self.spin.GetValue”得到同样的东西。
使用 AquaButton 刷新
接下来的三个按钮都来自 wxPython 附带的高级通用小部件(AGW)库,由 Andrea Gavana 创建。我们将从他的 AquaButton 开始。AquaButton 是 AGW 库中最简单的小部件之一。让我们来看看我们是如何创建它们的:
# aquaBtnDemo.py
import wx
import wx.lib.agw.aquabutton as AB
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "AquaButton Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
bmp = wx.Bitmap("agt_mp3.png", wx.BITMAP_TYPE_ANY)
button = AB.AquaButton(panel, bitmap=bmp, label="Press Me")
button.SetForegroundColour("black")
button.Bind(wx.EVT_BUTTON, self.onButton)
buttonTwo = AB.AquaButton(panel, label="PulseOnFocus")
buttonTwo.SetForegroundColour("black")
buttonTwo.SetPulseOnFocus(True)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(button, 0, wx.CENTER|wx.ALL, 5)
sizer.Add(buttonTwo, 0, wx.CENTER|wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
This method is fired when its corresponding button is pressed
"""
message = "You pressed the button!"
dlg = wx.MessageDialog(None, message, 'Message',
wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
AquaButton 支持位图,所以在这个例子中我们显示了两个按钮:一个有位图,一个没有。AquaButton 的另一个简洁的特性是当它获得焦点时能够跳动。您可以打开或关闭此功能。第二个按钮打开了这个特性。设置前景颜色的原因是因为在 Windows 7 上,面板是亮白色的,如果使用默认字体颜色,很难阅读按钮上的文本。您还可以设置 AquaButton 的背景颜色及其悬停颜色。
梯度按钮
GradientButton 与 AquaButton 相似,它们都有圆角,并且上面可以有可选的位图。但是 GradientButton 不支持脉冲。然而,顾名思义,GradientButton 允许您设置从顶部到底部的渐变以及按下时顶部/底部的颜色。我们将快速看一下如何实例化一个,因为所有的颜色设置都是不言自明的,在 wxPython 演示中有很好的例子:
# gradientBtnDemo.py
import wx
import wx.lib.agw.gradientbutton as GB
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "GradientButton Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
bmp = wx.Bitmap("agt_mp3.png", wx.BITMAP_TYPE_ANY)
gbBtn = GB.GradientButton(panel, bitmap=bmp,
label="Gradient with bitmap")
gbBtnNoBmp = GB.GradientButton(panel, label="Press Me")
gbBtnNoBmp.Bind(wx.EVT_BUTTON, self.onPressMe)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(gbBtn, 0, wx.CENTER|wx.ALL, 5)
sizer.Add(gbBtnNoBmp, 0, wx.CENTER|wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onPressMe(self, event):
""""""
message = "Thank You!"
dlg = wx.MessageDialog(None, message, 'Message',
wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
如您所见,创建 GradientButton 非常简单明了。随时可以试一试!我会等的...已经完成了?然后,让我们以 ShapedButton 结束吧!
使用“塑形”按钮塑形
ShapedButton 可能是我们将在本文中看到的最复杂的按钮。复杂,我的意思是,功能齐全。你可以创建一个正常的按钮,一个位图按钮,一个奇怪的偏移位图+文本按钮,切换按钮,你可以在任何角度旋转文本。如果您查看 wxPython 演示,您还会看到如何将按钮做成椭圆形。ShapedButton 需要安装 Python 图像库,如果你还没有,现在就去获取吧!当你准备好了,我们将看看用来创建上面截图的代码:
# shapedBtnDemo.py
import wx
from wx.lib.agw.shapedbutton import SButton, SBitmapButton
from wx.lib.agw.shapedbutton import SBitmapToggleButton, SBitmapTextToggleButton
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "ShapedButton Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
bmp = wx.Bitmap("agt_mp3.png", wx.BITMAP_TYPE_ANY)
size = (75,75)
sBtn = SButton(panel, label="Press Me", size=size)
sBtn.Bind(wx.EVT_BUTTON, self.onShapedBtn)
bmpBtn = SBitmapButton(panel, wx.ID_ANY, bitmap=bmp)
bmpBtn.Bind(wx.EVT_BUTTON, self.onBmpShapedBtn)
bmpToggleBtn = SBitmapToggleButton(panel, wx.ID_ANY, bitmap=bmp)
bmpToggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
bmpToggleTxtBtn = SBitmapTextToggleButton(panel, wx.ID_ANY,
bitmap=bmp,
label="Toggle!",
size=(100,100))
rotatedTxtBtn = SButton(panel, label="Rotated!", size=size)
rotatedTxtBtn.SetAngleOfRotation(90)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(sBtn, 0, wx.ALL, wx.CENTER, 5)
sizer.Add(bmpBtn, 0, wx.ALL, wx.CENTER, 5)
sizer.Add(bmpToggleBtn, 0, wx.ALL, wx.CENTER, 5)
sizer.Add(bmpToggleTxtBtn, 0, wx.ALL, wx.CENTER, 5)
sizer.Add(rotatedTxtBtn, 0, wx.ALL, wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onBmpShapedBtn(self, event):
""""""
dlg = wx.ColourDialog(self)
dlg.GetColourData().SetChooseFull(True)
if dlg.ShowModal() == wx.ID_OK:
print "'You selected: %s\n" % str(data.GetColour().Get())
dlg.Destroy()
#----------------------------------------------------------------------
def onToggle(self, event):
""""""
if event.GetIsDown():
wx.CallAfter(self.showDialog, "You Toggled Me!")
else:
wx.CallAfter(self.showDialog, "You untoggled me!")
event.Skip()
#----------------------------------------------------------------------
def onShapedBtn(self, event):
""""""
self.showDialog("You Pressed the Normal ShapedButton!")
#----------------------------------------------------------------------
def showDialog(self, message):
"""
Displays a custom message
"""
dlg = wx.MessageDialog(None, message, 'Message',
wx.OK|wx.ICON_EXCLAMATION)
dlg.ShowModal()
dlg.Destroy()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
在上面的代码中,你可以看到如何创建一个普通的 ShapedButton,一对切换按钮和一个旋转了 90 度的文本按钮。若要旋转所述文本,请使用 SetAngleOfRotation 方法并传递角度值。目前有一些与 ShapedButton 的切换版本的鼠标/窗口捕获相关的错误。如果你试图在你的按钮处理程序中显示一个对话框,就像上面的 onToggle 方法,你会得到一个错误,除非你在 wx.CallAfter 中包装这个调用。
包扎
如果您已经读到这里,那么您现在对 wxPython 中几乎所有的按钮小部件都有所了解。恭喜你!我希望您发现这很有帮助,并且在将来,您不会认为任何小部件都是理所当然的。
注意:本文中的代码是在以下平台上测试的:
- Windows XP、wxPython 2.8.11.0、Python 2.5
- Windows 7,wxPython 2.8.10.1,Python 2.6
下载源代码
wxPython:向导教程
原文:https://www.blog.pythonlibrary.org/2011/01/27/wxpython-a-wizard-tutorial/
在本文中,我们将关注 wxPython 的向导小部件。不,这与邓布利多或甘道夫无关。相反,当您运行安装程序或设置模板时,您将看到该对话框。有时你甚至会看到它们被用来设置邮件合并。我们将在本教程中讨论两个例子:一个相当简单,另一个稍微复杂一些。我们开始吧!
注意:本文中的代码改编自 wxPython 演示应用程序
一个简单的巫师
当您需要在 wxPython 中使用向导时,您会希望以一种特殊的方式导入它。除了导入 wx ,你还必须这样做:导入 wx.wizard 。还要注意,有两种主要类型的向导页面:WizardPageSimple 和 PyWizardPage。前者是最简单的,所以我们将在简单的例子中使用它。代码如下:
import wx
import wx.wizard as wiz
########################################################################
class TitledPage(wiz.WizardPageSimple):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, title):
"""Constructor"""
wiz.WizardPageSimple.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
title = wx.StaticText(self, -1, title)
title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)
#----------------------------------------------------------------------
def main():
""""""
wizard = wx.wizard.Wizard(None, -1, "Simple Wizard")
page1 = TitledPage(wizard, "Page 1")
page2 = TitledPage(wizard, "Page 2")
page3 = TitledPage(wizard, "Page 3")
page4 = TitledPage(wizard, "Page 4")
wx.wizard.WizardPageSimple.Chain(page1, page2)
wx.wizard.WizardPageSimple.Chain(page2, page3)
wx.wizard.WizardPageSimple.Chain(page3, page4)
wizard.FitToPage(page1)
wizard.RunWizard(page1)
wizard.Destroy()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
main()
app.MainLoop()
这是相当多的代码。我们把它拆开一部分,看看是否能弄明白。首先,我们导入 wx 和向导,我们将其重命名为“wiz ”,以节省击键次数。接下来,我们创建一个 TitledPage 类,它是 WizardPageSimple 的子类。这个类将是我们向导中所有页面的基础。它基本上只是定义了一个页面,标题居中,字体为 18 磅,下面有一条线。
在主函数中,我们找到真正的肉。这里我们使用以下语法创建向导: wx.wizard.Wizard(None,-1,“简单向导”)。这为向导提供了一个无父级、一个 id 和一个标题。然后我们创建四个页面,它们是我们之前提到的title page类的实例。最后,我们使用wx . wizard . wizardpagesimple . chain将页面链接在一起。这允许我们使用几个自动生成的按钮在页面中向前和向后翻页。最后几行代码将运行向导,当用户完成时,将销毁向导。很简单,对吧?现在让我们来看一个更高级的例子。
使用 PyWizardPage
在本节中,我们将创建一个 PyWizardPage 的子类。我们还将有一个 WizardPageSimple 子类,以便我们可以混合和匹配这两者来创建一系列不同的页面。让我们直接跳到代码,这样你就可以自己看了!
import images
import wx
import wx.wizard as wiz
########################################################################
class TitledPage(wiz.WizardPageSimple):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, title):
"""Constructor"""
wiz.WizardPageSimple.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer = sizer
self.SetSizer(sizer)
title = wx.StaticText(self, -1, title)
title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)
########################################################################
class UseAltBitmapPage(wiz.PyWizardPage):
#----------------------------------------------------------------------
def __init__(self, parent, title):
wiz.PyWizardPage.__init__(self, parent)
self.next = self.prev = None
self.sizer = wx.BoxSizer(wx.VERTICAL)
title = wx.StaticText(self, label=title)
title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
self.sizer.Add(title)
self.sizer.Add(wx.StaticText(self, -1, "This page uses a different bitmap"),
0, wx.ALL, 5)
self.sizer.Layout()
#----------------------------------------------------------------------
def SetNext(self, next):
self.next = next
#----------------------------------------------------------------------
def SetPrev(self, prev):
self.prev = prev
#----------------------------------------------------------------------
def GetNext(self):
return self.next
#----------------------------------------------------------------------
def GetPrev(self):
return self.prev
#----------------------------------------------------------------------
def GetBitmap(self):
# You usually wouldn't need to override this method
# since you can set a non-default bitmap in the
# wxWizardPageSimple constructor, but if you need to
# dynamically change the bitmap based on the
# contents of the wizard, or need to also change the
# next/prev order then it can be done by overriding
# GetBitmap.
return images.WizTest2.GetBitmap()
#----------------------------------------------------------------------
def main():
""""""
wizard = wiz.Wizard(None, -1, "Dynamic Wizard",
images.WizTest1.GetBitmap())
page1 = TitledPage(wizard, "Page 1")
page2 = TitledPage(wizard, "Page 2")
page3 = TitledPage(wizard, "Page 3")
page4 = UseAltBitmapPage(wizard, "Page 4")
page5 = TitledPage(wizard, "Page 5")
wizard.FitToPage(page1)
page5.sizer.Add(wx.StaticText(page5, -1, "\nThis is the last page."))
# Set the initial order of the pages
page1.SetNext(page2)
page2.SetPrev(page1)
page2.SetNext(page3)
page3.SetPrev(page2)
page3.SetNext(page4)
page4.SetPrev(page3)
page4.SetNext(page5)
page5.SetPrev(page4)
wizard.GetPageAreaSizer().Add(page1)
wizard.RunWizard(page1)
wizard.Destroy()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
main()
app.MainLoop()
这段代码的开始方式与前面的代码非常相似。在本例中,我们还导入了一个 images 模块,该模块包含几个pyembeddeimage对象,我们将使用它们来演示如何向向导页面添加位图。反正第一节课和前面一模一样。接下来我们创建一个 UseAltBitmapPage 类,它是 PyWizardPage 的子类。我们必须重写一些方法来使它正确工作,但是它们是不言自明的。这个页面将只是用来改变一个页面的位图图像。
在 main 函数中,我们创建向导的方式与之前略有不同:
wizard = wiz.Wizard(None, -1, "Dynamic Wizard", images.WizTest1.GetBitmap())
正如您所看到的,这个方法允许我们添加一个位图,它将出现在向导页面的左侧。无论如何,在那之后,我们创建了五个页面,其中四个是 TitledPage 的实例,一个是 UseAltBitmapPage 的实例。我们将向导放在第一页,然后我们看到一些奇怪的东西:
page5.sizer.Add(wx.StaticText(page5, -1, "\nThis is the last page."))
那有什么用?嗯,在页面上添加一个小部件是一种愚蠢的方式。为了让用户知道他们已经到达了最后一页,我们添加了一个 StaticText 实例,明确地告诉用户他们已经到达了最后一页。接下来的几行使用 SetNext 和 SetPrev 设置页面的顺序。虽然这些方法可以让您更精细地控制页面的顺序,但它们不如 WizardPageSimple 方便。链式方法。最后几行代码与前面的示例相同。
额外提示:如何重新标记向导按钮
在创建这篇文章的时候,有人问如何在官方的 wxPython 邮件列表上修改向导的按钮标签。为了完整起见,我们将采用 Robin Dunn 的解决方案,并展示如何更改前一个和下一个按钮的标签。
prev_btn = self.FindWindowById(wx.ID_BACKWARD)
prev_btn.SetLabel("Foo")
next_btn = self.FindWindowById(wx.ID_FORWARD)
next_btn.SetLabel("Bar")
包扎
现在您知道了如何创建 wxPython 中包含的两种类型的向导。您还学习了一个有趣的技巧来更改向导中按钮的标签。如果你认为我忘记了什么,请告诉我,我会更新帖子或写一篇后续文章。
进一步阅读
源代码
wxPython:动态添加和移除小部件
原文:https://www.blog.pythonlibrary.org/2012/05/05/wxpython-adding-and-removing-widgets-dynamically/
我不断看到有人在启动 wxPython 应用程序后询问如何添加或删除小部件。这实际上是很容易做到的事情,所以我决定是时候写一个关于这个主题的简单教程了。我不得不根据访问我的程序的用户类型自己不时地这样做,这样我就可以显示稍微不同的选项。不管怎样,我们开始吧!
我决定让事情变得简单。这个应用程序所做的就是允许用户添加或删除按钮。下面的脚本将创建一个类似于本文开头的窗口。如果您按几次“添加”按钮,您应该会看到类似这样的内容:
正如你所看到的,你最终得到了更多的按钮!现在让我们花一点时间来阅读代码。你一读完我就解释。
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.number_of_buttons = 0
self.frame = parent
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
controlSizer = wx.BoxSizer(wx.HORIZONTAL)
self.widgetSizer = wx.BoxSizer(wx.VERTICAL)
self.addButton = wx.Button(self, label="Add")
self.addButton.Bind(wx.EVT_BUTTON, self.onAddWidget)
controlSizer.Add(self.addButton, 0, wx.CENTER|wx.ALL, 5)
self.removeButton = wx.Button(self, label="Remove")
self.removeButton.Bind(wx.EVT_BUTTON, self.onRemoveWidget)
controlSizer.Add(self.removeButton, 0, wx.CENTER|wx.ALL, 5)
self.mainSizer.Add(controlSizer, 0, wx.CENTER)
self.mainSizer.Add(self.widgetSizer, 0, wx.CENTER|wx.ALL, 10)
self.SetSizer(self.mainSizer)
#----------------------------------------------------------------------
def onAddWidget(self, event):
""""""
self.number_of_buttons += 1
label = "Button %s" % self.number_of_buttons
name = "button%s" % self.number_of_buttons
new_button = wx.Button(self, label=label, name=name)
self.widgetSizer.Add(new_button, 0, wx.ALL, 5)
self.frame.fSizer.Layout()
self.frame.Fit()
#----------------------------------------------------------------------
def onRemoveWidget(self, event):
if self.widgetSizer.GetChildren():
sizer_item = self.widgetSizer.GetItem(self.number_of_buttons-1)
widget = sizer_item.GetWindow()
self.widgetSizer.Hide(widget)
widget.Destroy()
self.number_of_buttons -= 1
self.frame.fSizer.Layout()
self.frame.Fit()
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="Add / Remove Buttons")
self.fSizer = wx.BoxSizer(wx.VERTICAL)
panel = MyPanel(self)
self.fSizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(self.fSizer)
self.Fit()
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
我认为这是非常简单的代码,所以我们只关注重要的部分。我要指出的第一个主题是,我在显示它之前调用了框架的 Fit()方法。我通常避免使用 Fit,但每当我添加或删除按钮时,我很难让框架适当地改变大小,Fit 为我解决了这个问题。我应该注意到,Fit 总是试图让窗口小部件适合容器,有时它以我不喜欢的方式结束了。
总之,onAddWidget 和 onRemoveWidget 方法中的另一位。您通常希望在容器对象上调用 Layout,以便每当您添加或移除小部件时,它都可以更新和布局控件。奇怪的是,Fit 似乎会自动这样做,所以您在上面的代码中看到的那些 Layout()调用实际上可以被删除。我试着去掉合适的,看看布局是否足够,但当你这样做时,框架不会更新它的大小,所以在这种情况下,合适似乎是必需的。现在,如果你碰巧添加或删除了一些小部件,而不会影响框架的整体大小,我认为布局就足够了。
最后,顺便提一下,有时在冻结/解冻更新的结尾也使用 Layout()。
好吧,就这样!现在,您也应该能够在应用程序运行后添加或删除小部件了。我希望你学到了新东西。
wxPython:向 ObjectListView 添加复选框
原文:https://www.blog.pythonlibrary.org/2013/02/27/wxpython-adding-checkboxes-to-objectlistview/
本周,我花了一些时间学习如何在 wxPython 中向 ObjectListView 小部件添加复选框。如果你不知道,ObjectListView 是 wx 的第三方包装器。ListCtrl 小部件,使得使用 ListCtrl 更加容易。你可以从档案中的这篇旧文章中读到它的全部内容。我有一个需求,我需要在小部件的报告视图中的每个项目旁边有一个复选框。在 ObjectListView 网站上做了一些挖掘之后,我找到了一篇文章,它解释了如何做这件事。根据文档,我可以使用 CreateCheckStateColumn 方法或者注册一个列并使用 InstallCheckStateColumn 。在本文中,我们将重点关注 CreateCheckStateColumn 方法。
入门指南
首先,如果您还没有安装以下软件,您需要安装:
- wxPython
- 来自 Python 包索引的 ObjectListView
如何创建和切换复选框
现在你已经完成了,我们可以开始编码了。我总是发现一个可行的例子是最好的学习方法,所以这是第一个难题:
# OLVcheckboxes.py
import wx
from ObjectListView import ObjectListView, ColumnDefn
class Results(object):
""""""
def __init__(self, tin, zip_code, plus4, name, address):
"""Constructor"""
self.tin = tin
self.zip_code = zip_code
self.plus4 = plus4
self.name = name
self.address = address
class ProvPanel(wx.Panel):
""""""
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.test_data = [Results("123456789", "50158", "0065", "Patti Jones",
"111 Centennial Drive"),
Results("978561236", "90056", "7890", "Brian Wilson",
"555 Torque Maui"),
Results("456897852", "70014", "6545", "Mike Love",
"304 Cali Bvld")
]
self.resultsOlv = ObjectListView(self,
style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.setResults()
toggleBtn = wx.Button(self, label="Toggle Checks")
toggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
mainSizer.Add(self.resultsOlv, 1, wx.EXPAND|wx.ALL, 5)
mainSizer.Add(toggleBtn, 0, wx.CENTER|wx.ALL, 5)
self.SetSizer(mainSizer)
def onToggle(self, event):
"""
Toggle the check boxes
"""
objects = self.resultsOlv.GetObjects()
for obj in objects:
print self.resultsOlv.IsChecked(obj)
self.resultsOlv.ToggleCheck(obj)
self.resultsOlv.RefreshObjects(objects)
def setResults(self):
""""""
self.resultsOlv.SetColumns([
ColumnDefn("TIN", "left", 100, "tin"),
ColumnDefn("Zip", "left", 75, "zip_code"),
ColumnDefn("+4", "left", 50, "plus4"),
ColumnDefn("Name", "left", 150, "name"),
ColumnDefn("Address", "left", 200, "address")
])
self.resultsOlv.CreateCheckStateColumn()
self.resultsOlv.SetObjects(self.test_data)
class ProvFrame(wx.Frame):
""""""
def __init__(self):
"""Constructor"""
title = "OLV Checkbox Tutorial"
wx.Frame.__init__(self, parent=None, title=title,
size=(1024, 768))
panel = ProvPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = ProvFrame()
frame.Show()
app.MainLoop()
让我们稍微分解一下。如你所见,我们把 wx。框架和 wx。分成单独的类。这使得将来如果需要的话更容易模块化。例如,如果面板代码有太多行,我们可以将它复制到一个新的模块中,然后导入到主代码中。总之,这里我们在 panel 类的 init 方法中创建了我们关心的各种小部件。要获得复选框,我们需要在 setResults 方法中的 SetObjects 调用之前添加 CreateCheckStateColumn 调用。
我们关心的另一个重要方法是ontogle方法,在这里我们学习如何使用 ToggleCheck 方法切换复选框。我们首先通过 GetObjects 抓取小部件中的所有项目对象,然后对它们进行循环。在这个例子中,我们通过使用方便的 IsChecked 方法打印出它们是否被检查。这对于调试非常有用,或者如果您希望根据项目的状态单独切换项目,这非常有用。最后,您需要调用 RefreshObjects 来刷新 ObjectListCtrl,这样您就可以看到复选框的新状态。否则,显示不会更新,并且您无法判断这些项目是否被选中。您也可以通过选择项目并按空格键来切换复选框。
使用 SetCheckState 切换复选框
还有另一种方法来改变复选框的状态,那就是使用 SetCheckState 方法。下面是一些演示如何实现的代码:
# OLVcheckboxes2.py
import wx
from ObjectListView import ObjectListView, ColumnDefn
class Results(object):
""""""
def __init__(self, tin, zip_code, plus4, name, address):
"""Constructor"""
self.tin = tin
self.zip_code = zip_code
self.plus4 = plus4
self.name = name
self.address = address
class OLVCheckPanel(wx.Panel):
""""""
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
mainSizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
self.test_data = [Results("123456789", "50158", "0065", "Patti Jones",
"111 Centennial Drive"),
Results("978561236", "90056", "7890", "Brian Wilson",
"555 Torque Maui"),
Results("456897852", "70014", "6545", "Mike Love",
"304 Cali Bvld")
]
self.resultsOlv = ObjectListView(self,
style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.setResults()
checkBtn = wx.Button(self, label="Check")
checkBtn.Bind(wx.EVT_BUTTON, self.onCheck)
btnSizer.Add(checkBtn, 0, wx.ALL, 5)
uncheckBtn = wx.Button(self, label="Uncheck")
uncheckBtn.Bind(wx.EVT_BUTTON, self.onUncheck)
btnSizer.Add(uncheckBtn, 0, wx.ALL, 5)
mainSizer.Add(self.resultsOlv, 1, wx.EXPAND|wx.ALL, 5)
mainSizer.Add(btnSizer, 0, wx.CENTER|wx.ALL, 5)
self.SetSizer(mainSizer)
def onCheck(self, event):
""""""
objects = self.resultsOlv.GetObjects()
for obj in objects:
self.resultsOlv.SetCheckState(obj, True)
self.resultsOlv.RefreshObjects(objects)
def onUncheck(self, event):
""""""
objects = self.resultsOlv.GetObjects()
for obj in objects:
self.resultsOlv.SetCheckState(obj, False)
self.resultsOlv.RefreshObjects(objects)
def setResults(self):
""""""
self.resultsOlv.SetColumns([
ColumnDefn("TIN", "left", 100, "tin"),
ColumnDefn("Zip", "left", 75, "zip_code"),
ColumnDefn("+4", "left", 50, "plus4"),
ColumnDefn("Name", "left", 150, "name"),
ColumnDefn("Address", "left", 200, "address")
])
self.resultsOlv.CreateCheckStateColumn()
self.resultsOlv.SetObjects(self.test_data)
class OLVCheckFrame(wx.Frame):
""""""
def __init__(self):
"""Constructor"""
title = "OLV Checkbox Tutorial"
wx.Frame.__init__(self, parent=None, title=title, size=(1024, 768))
panel = OLVCheckPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = OLVCheckFrame()
frame.Show()
app.MainLoop()
如你所见,在这个例子中我们有两个按钮。选中和取消选中按钮。代码基本上是一样的,但是这一次当我们遍历对象时,我们调用 SetCheckState 并向它传递 item 对象和一个布尔值 True 或 False。然后我们像以前一样刷新屏幕。
添加全选/取消全选功能
更新(2013 年 12 月 12 日):本周当我决定重用上面显示的实现时,我实际上注意到了复选框切换中的一个恼人的 bug。我想切换窗口小部件,使它们要么被选中,要么不被选中。不管它们已经处于什么状态,上面的代码都会切换它们。这意味着,如果您选中了 5 个项目中的 2 个,并点击了切换按钮,则选中的项目将变为未选中,未选中的项目将变为选中。为了减轻这种情况,我最终需要将事件处理程序稍微修改如下:
def onToggle(self, event):
"""
Toggle the check boxes
"""
objects = self.resultsOlv.GetObjects()
for obj in objects:
if self.toggleBtn.GetValue():
if not self.resultsOlv.IsChecked(obj):
self.resultsOlv.ToggleCheck(obj)
else:
if self.resultsOlv.IsChecked(obj):
self.resultsOlv.ToggleCheck(obj)
self.resultsOlv.RefreshObjects(objects)
这段代码通过 GetValue 检查切换按钮是否被选中。如果是,那么我们做一个额外的检查,看看行项目是否没有被检查。如果不是,那么我们检查它。条件句的另一部分正好相反。还要注意,您需要将 toggleBtn 变量更改为一个类属性( self.toggleBtn )来使代码工作。
更新(2015 年 7 月 28 日):我的一位精明的读者(Amit Vinchhi)最近联系了我,他说我可以将上面提到的切换方法简化为下面这段代码:
def onToggle(self, event):
"""
Toggle the check boxes
"""
objects = self.resultsOlv.GetObjects()
check = self.toggleBtn.GetValue()
for obj in objects:
self.resultsOlv.SetCheckState(obj, check)
self.resultsOlv.RefreshObjects(objects)
当然,这是假设你改变了原来的 wx。按钮到一个 wx。切换按钮并将按钮的绑定改为使用 wx。EVT _ 切换按钮。基本上你会得到这样的结果:
self.toggleBtn = wx.ToggleButton(self, label="Toggle Checks")
self.toggleBtn.Bind(wx.EVT_TOGGLEBUTTON, self.onToggle)
那么它工作得相当好。谢谢艾米特。
包扎
现在,如果您正在使用 ObjectListView,您知道了如何向 wxPython 项目添加复选框。如果你需要在普通的 wx 中使用复选框。ListCtrl,您可以使用 wx 附带的 mixin。以下是您访问它的方式:
from wx.lib.mixins.listctrl import CheckListCtrlMixin
wxPython 演示包中有一个例子,可以从 wxPython 网站下载。我想你也可以在 DVCListCtrl 中使用复选框,或者用 UltimateListCtrl 进行定制。不过我现在更喜欢使用 ObjectListView 控件。我使用 Python 2.6.6 和 Python 2.7.3 以及 wxPython 2.8.12 和 wxPython 2.9.4 classic 测试了这段代码。我也用过 ObjectListview 1.2。
进一步阅读
- 对象列表视图食谱
- wxPython: 使用 ObjectListView 代替 ListCtrl
下载
wxPython:向 ObjectListView 添加工具提示
原文:https://www.blog.pythonlibrary.org/2013/12/12/wxpython-adding-tooltips-objectlistview/
最近,我试图弄清楚如何在 Windows 上的 wxPython 中为 ObjectListView 小部件中的每个项目添加工具提示。wxPython wiki 有一个使用 PyWin32 的例子,但是我不想走这条路。所以我在 wxPython 谷歌群上问了一下,得到了一个有趣的答案。他们实际上使用了我的一篇旧的文章来为我构建他们的解决方案。我稍微整理了一下,觉得值得与我的读者分享:
import wx
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class Book(object):
"""
Model of the Book object
Contains the following attributes:
'ISBN', 'Author', 'Manufacturer', 'Title'
"""
#----------------------------------------------------------------------
def __init__(self, title, author, isbn, mfg):
self.isbn = isbn
self.author = author
self.mfg = mfg
self.title = title
########################################################################
class MainPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.products = [Book("wxPython in Action", "Robin Dunn",
"1932394621", "Manning"),
Book("Hello World", "Warren and Carter Sande",
"1933988495", "Manning")
]
self.dataOlv = ObjectListView(self, wx.ID_ANY, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.setBooks()
# Allow the cell values to be edited when double-clicked
self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
# create an update button
updateBtn = wx.Button(self, wx.ID_ANY, "Update OLV")
updateBtn.Bind(wx.EVT_BUTTON, self.updateControl)
# Create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(updateBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
self.dataOlv.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onSetToolTip)
#----------------------------------------------------------------------
def updateControl(self, event):
"""
Update the control
"""
print "updating..."
#product_dict = [{"title":"Core Python Programming", "author":"Wesley Chun",
#"isbn":"0132269937", "mfg":"Prentice Hall"},
#{"title":"Python Programming for the Absolute Beginner",
#"author":"Michael Dawson", "isbn":"1598631128",
#"mfg":"Course Technology"},
#{"title":"Learning Python", "author":"Mark Lutz",
#"isbn":"0596513984", "mfg":"O'Reilly"}
#]
product_list = [Book("Core Python Programming", "Wesley Chun",
"0132269937", "Prentice Hall"),
Book("Python Programming for the Absolute Beginner",
"Michael Dawson", "1598631128", "Course Technology"),
Book("Learning Python", "Mark Lutz", "0596513984",
"O'Reilly")
]
data = self.products + product_list
self.dataOlv.SetObjects(data)
#----------------------------------------------------------------------
def setBooks(self, data=None):
"""
Sets the book data for the OLV object
"""
self.dataOlv.SetColumns([
ColumnDefn("Title", "left", 220, "title"),
ColumnDefn("Author", "left", 200, "author"),
ColumnDefn("ISBN", "right", 100, "isbn"),
ColumnDefn("Mfg", "left", 180, "mfg")
])
self.dataOlv.SetObjects(self.products)
#----------------------------------------------------------------------
def onSetToolTip(self, event):
"""
Set the tool tip on the selected row
"""
item = self.dataOlv.GetSelectedObject()
tooltip = "%s is a good writer!" % item.author
event.GetEventObject().SetToolTipString(tooltip)
event.Skip()
########################################################################
class MainFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
title="ObjectListView Demo", size=(800,600))
panel = MainPanel(self)
########################################################################
class GenApp(wx.App):
#----------------------------------------------------------------------
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
#----------------------------------------------------------------------
def OnInit(self):
# create frame here
frame = MainFrame()
frame.Show()
return True
#----------------------------------------------------------------------
def main():
"""
Run the demo
"""
app = GenApp()
app.MainLoop()
if __name__ == "__main__":
main()
我需要做的就是添加一个绑定到 wx。EVT _ 列表 _ 项目 _ 选定。然后在我的事件处理程序中,我需要获取事件对象并设置它的工具提示字符串。我真正想做的是找到一种方法来复制这个网格配方,这样我就可以将鼠标放在项目上并改变工具提示,但是看起来 ObjectListView / ListCtrl 没有我需要的方法来将鼠标坐标转换成列/行。不管怎样,给出的解决方案确实像宣传的那样有效。多谢了,二信!
Update: 我的一位精明的读者注意到了我的代码中的一个错误,当我点击 Update 按钮时,它向 ObjectListView 小部件添加了一个字典。虽然您可以添加字典,但这破坏了 onSetToolTip 方法,因为添加的一些条目不再是 Book 实例。因此,我已经更新了代码,添加了额外的条目作为 Book 实例,并注释掉了 dictionary 示例。
更新(2013/12/30)
在玩了一个我找到的 StackOverflow 答案之后,我发现我可以动态更新工具提示,尽管它仍然不是我想要的。但是首先,下面是更新后的代码示例:
import wx
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class Book(object):
"""
Model of the Book object
Contains the following attributes:
'ISBN', 'Author', 'Manufacturer', 'Title'
"""
#----------------------------------------------------------------------
def __init__(self, title, author, isbn, mfg):
self.isbn = isbn
self.author = author
self.mfg = mfg
self.title = title
########################################################################
class MainPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.products = [Book("wxPython in Action", "Robin Dunn",
"1932394621", "Manning"),
Book("Hello World", "Warren and Carter Sande",
"1933988495", "Manning")
]
self.dataOlv = ObjectListView(self, wx.ID_ANY,
style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.dataOlv.Bind(wx.EVT_MOTION, self.updateTooltip)
self.setBooks()
# Allow the cell values to be edited when double-clicked
self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
# create an update button
updateBtn = wx.Button(self, wx.ID_ANY, "Update OLV")
updateBtn.Bind(wx.EVT_BUTTON, self.updateControl)
# Create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(updateBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def updateControl(self, event):
"""
Update the control
"""
print "updating..."
#product_dict = [{"title":"Core Python Programming", "author":"Wesley Chun",
#"isbn":"0132269937", "mfg":"Prentice Hall"},
#{"title":"Python Programming for the Absolute Beginner",
#"author":"Michael Dawson", "isbn":"1598631128",
#"mfg":"Course Technology"},
#{"title":"Learning Python", "author":"Mark Lutz",
#"isbn":"0596513984", "mfg":"O'Reilly"}
#]
product_list = [Book("Core Python Programming", "Wesley Chun",
"0132269937", "Prentice Hall"),
Book("Python Programming for the Absolute Beginner",
"Michael Dawson", "1598631128", "Course Technology"),
Book("Learning Python", "Mark Lutz", "0596513984",
"O'Reilly")
]
data = self.products + product_list
self.dataOlv.SetObjects(data)
#----------------------------------------------------------------------
def setBooks(self, data=None):
"""
Sets the book data for the OLV object
"""
self.dataOlv.SetColumns([
ColumnDefn("Title", "left", 220, "title"),
ColumnDefn("Author", "left", 200, "author"),
ColumnDefn("ISBN", "right", 100, "isbn"),
ColumnDefn("Mfg", "left", 180, "mfg")
])
self.dataOlv.SetObjects(self.products)
#----------------------------------------------------------------------
def updateTooltip(self, event):
"""
Update the tooltip!
"""
pos = wx.GetMousePosition()
mouse_pos = self.dataOlv.ScreenToClient(pos)
item_index, flag = self.dataOlv.HitTest(mouse_pos)
print flag
if flag == wx.LIST_HITTEST_ONITEMLABEL:
msg = "%s is a good book!" % self.dataOlv.GetItemText(item_index)
self.dataOlv.SetToolTipString(msg)
else:
self.dataOlv.SetToolTipString("")
event.Skip()
########################################################################
class MainFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
title="ObjectListView Demo", size=(800,600))
panel = MainPanel(self)
#----------------------------------------------------------------------
def main():
"""
Run the demo
"""
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
#----------------------------------------------------------------------
if __name__ == "__main__":
main()
这里的主要变化是移除 setToolTip 方法并添加一个 updateTooltip 方法。在上述方法中,我们获取鼠标位置并使用它来更新工具提示。我对这种方法的问题是,工具提示只有在您将鼠标悬停在第一列的单元格上时才会更新。除此之外,它工作得很好。如果你碰巧发现了其他的方法,请在评论中告诉我。
更新(2014/01/23) :
我的一个读者今天联系我,告诉我对这段代码的另一个修正。他设法找到了添加工具提示的方法,这样无论鼠标悬停在行的哪一部分,工具提示都会出现。变化很简单。对 updateTooltip 方法的更改。以下是原始版本:
def updateTooltip(self, event):
"""
Update the tooltip!
"""
pos = wx.GetMousePosition()
mouse_pos = self.dataOlv.ScreenToClient(pos)
item_index, flag = self.dataOlv.HitTest(mouse_pos)
print flag
if flag == wx.LIST_HITTEST_ONITEMLABEL:
msg = "%s is a good book!" % self.dataOlv.GetItemText(item_index)
self.dataOlv.SetToolTipString(msg)
else:
self.dataOlv.SetToolTipString("")
event.Skip()
以下是修复版本:
def updateTooltip(self, event):
"""
Update the tooltip!
"""
pos = wx.GetMousePosition()
mouse_pos = self.dataOlv.ScreenToClient(pos)
item_index, flag = self.dataOlv.HitTest(mouse_pos)
print flag
if item_index != -1:
msg = "%s is a good book!" % self.dataOlv.GetItemText(item_index)
self.dataOlv.SetToolTipString(msg)
else:
self.dataOlv.SetToolTipString("")
event.Skip()
对 if 语句的修改是,我们现在看一下 item_index 。实际上我不知道这为什么有效,但对我来说确实有效。特别感谢迈克·斯托弗发现了这一点。
wxPython:关于加速器
原文:https://www.blog.pythonlibrary.org/2017/09/28/wxpython-all-about-accelerators/
wxPython 工具包通过加速器和加速器表的概念支持使用键盘快捷键。您也可以直接绑定到按键,但在很多情况下,您会希望使用加速器。加速器提供了向应用程序添加键盘快捷键的能力,比如大多数应用程序用来保存文件的无处不在的“CTRL+S”。只要您的应用程序有焦点,就可以轻松地添加这个键盘快捷键。
请注意,您通常会将一个加速表添加到您的 wx 中。框架实例。如果您的应用程序中碰巧有多个帧,那么您可能需要根据您的设计向多个帧添加一个加速器表。
我们来看一个简单的例子:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Accelerator Tutorial",
size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
randomId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onKeyCombo, id=randomId)
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('Q'),
randomId )])
self.SetAcceleratorTable(accel_tbl)
def onKeyCombo(self, event):
""""""
print "You pressed CTRL+Q!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
如果你有很多键盘快捷键需要添加到你的应用程序中,这可能看起来有点难看,因为你最终会得到一个看起来有点奇怪的元组列表。你会发现这种方式或者写一个加速表更常见。然而,还有其他方法可以添加条目到你的加速表。让我们来看看 wxPython 的文档中的一个例子:
entries = [wx.AcceleratorEntry() for i in xrange(4)]
entries[0].Set(wx.ACCEL_CTRL, ord('N'), ID_NEW_WINDOW)
entries[1].Set(wx.ACCEL_CTRL, ord('X'), wx.ID_EXIT)
entries[2].Set(wx.ACCEL_SHIFT, ord('A'), ID_ABOUT)
entries[3].Set(wx.ACCEL_NORMAL, wx.WXK_DELETE, wx.ID_CUT)
accel = wx.AcceleratorTable(entries)
frame.SetAcceleratorTable(accel)
这里我们创建了一个包含四个 wx 的列表。AcceleratorEntry() 使用列表理解对象。然后我们使用 Python 列表的索引来访问列表中的每个条目,以调用每个条目的 Set 方法。代码的其余部分与您之前看到的非常相似。让我们花点时间让这段代码实际上可以运行:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="AcceleratorEntry Tutorial",
size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
exit_menu_item = wx.MenuItem(id=wx.NewId(), text="Exit",
helpString="Exit the application")
about_menu_item = wx.MenuItem(id=wx.NewId(), text='About')
ID_NEW_WINDOW = wx.NewId()
ID_ABOUT = wx.NewId()
self.Bind(wx.EVT_MENU, self.on_new_window, id=ID_NEW_WINDOW)
self.Bind(wx.EVT_MENU, self.on_about, id=ID_ABOUT)
entries = [wx.AcceleratorEntry() for i in range(4)]
entries[0].Set(wx.ACCEL_CTRL, ord('N'),
ID_NEW_WINDOW, exit_menu_item)
entries[1].Set(wx.ACCEL_CTRL, ord('X'), wx.ID_EXIT)
entries[2].Set(wx.ACCEL_SHIFT, ord('A'), ID_ABOUT,
about_menu_item)
entries[3].Set(wx.ACCEL_NORMAL, wx.WXK_DELETE, wx.ID_CUT)
accel_tbl = wx.AcceleratorTable(entries)
self.SetAcceleratorTable(accel_tbl)
def on_new_window(self, event):
""""""
print("You pressed CTRL+N!")
def on_about(self, event):
print('You pressed SHIFT+A')
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
首先,我要指出的是,我没有把所有的加速器都连接上。例如,“CTRL+X”实际上不会退出程序。但是我确实把“CTRL+N”和“SHIFT+A”连接起来了。尝试运行代码,看看它是如何工作的。
您还可以稍微明确一点,逐个创建 AcceleratorEntry()对象,而不是使用列表理解。让我们稍微修改一下代码,看看它是如何工作的:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None,
title="AcceleratorEntry Tutorial",
size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
exit_menu_item = wx.MenuItem(id=wx.NewId(), text="Exit",
helpString="Exit the application")
about_menu_item = wx.MenuItem(id=wx.NewId(), text='About')
ID_NEW_WINDOW = wx.NewId()
ID_ABOUT = wx.NewId()
self.Bind(wx.EVT_MENU, self.on_new_window, id=ID_NEW_WINDOW)
self.Bind(wx.EVT_MENU, self.on_about, id=ID_ABOUT)
entry_one = wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('N'),
ID_NEW_WINDOW,
exit_menu_item)
entry_two = wx.AcceleratorEntry(wx.ACCEL_SHIFT, ord('A'),
ID_ABOUT,
about_menu_item)
entries = [entry_one, entry_two]
accel_tbl = wx.AcceleratorTable(entries)
self.SetAcceleratorTable(accel_tbl)
def on_new_window(self, event):
""""""
print("You pressed CTRL+N!")
def on_about(self, event):
print('You pressed SHIFT+A')
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
坦白地说,我认为这个版本是最好的,因为它是最明确的。“Python 的禅”总是提倡显式而非隐式地做事,所以我认为这也很好地遵循了这一范式。
包扎
现在,您已经知道了为您的应用程序创建键盘快捷键(加速器)的几种不同方法。它们非常方便,可以增强应用程序的有用性。
相关阅读
- wxPython: 键盘快捷键
- wxPython: 菜单、工具栏和加速器
- 关于 wx 的 wxPython 文档。加速表
- 关于 wx 的 wxPython 文档。加速器入口
wxPython:关于菜单的一切
原文:https://www.blog.pythonlibrary.org/2012/02/14/wxpython-all-about-menus/
菜单无处不在。几乎所有的桌面程序中都有。您可以使用它们来编辑首选项或配置您的程序。在 wxPython 中,有几个菜单选项可供选择。最熟悉的可能是 wx.Menu,但也有弹出菜单和一个纯 Python 实现,称为 FlatMenu。我们只会报道 wx。菜单和弹出菜单,因为它们是相互关联的。FlatMenu 还包括一个工具栏 API,所以您必须等待另一篇文章来单独介绍这个小部件。让我们开始这个菜单派对吧!
一个简单的菜单例子
我们将从非常简单的东西开始:一个只有退出菜单项的菜单。代码如下:
import wx
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="wx.Menu Tutorial")
self.panel = wx.Panel(self, wx.ID_ANY)
menuBar = wx.MenuBar()
fileMenu = wx.Menu()
exitMenuItem = fileMenu.Append(wx.NewId(), "Exit",
"Exit the application")
menuBar.Append(fileMenu, "&File")
self.Bind(wx.EVT_MENU, self.onExit, exitMenuItem)
self.SetMenuBar(menuBar)
#----------------------------------------------------------------------
def onExit(self, event):
""""""
self.Close()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
让我们稍微分解一下。为了创建 menubar,我们实例化 wx.MenuBar 的一个实例。我们称为文件菜单的菜单。最后,为了添加“退出”项,我们向它追加了一些数据。本质上,我们附加了一个 wx。MenuItem,但是这是一种捷径,因为我们实际上并没有创建 wx。首先是 MenuItem 实例。不过,我们将在下一个示例中展示如何做到这一点。注意,当我们追加项目时,我们必须传递一个 id、一个标签字符串和一个状态字符串。假设你有一个状态栏,当你将鼠标悬停在菜单项上时,最后一个会出现在状态栏中。请注意,要将事件处理程序附加到菜单项,您需要使用 EVT _ 菜单事件并将其绑定到框架。接下来,我们将菜单本身附加到 MenuBar 对象,并传递给它一个字符串,在本例中是“File”。最后,我们调用框架的 SetMenuBar 方法将菜单栏附加到框架上。
向菜单添加图片
这就是创建菜单的全部内容!现在我们来看一个更复杂的例子!注意:按照下面的例子,你需要使用你自己的图像文件。
import wx
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="wx.Menu Tutorial")
self.panel = wx.Panel(self, wx.ID_ANY)
# create the menubar
menuBar = wx.MenuBar()
# create the first menu (starting on left)
carMenu = wx.Menu()
carMenu.Append(101, "&Ford", "An American Automaker")
carMenu.Append(102, "&Nissan", "")
carMenu.Append(103, "&Toyota", "Buy Japanese!")
carMenu.Append(104, "&Close", "Close the application")
# add a picture to a menu
picMenu = wx.Menu()
item = wx.MenuItem(picMenu, wx.ID_ANY, "Snake", "This menu has a picture!")
img = wx.Image('snake32.bmp', wx.BITMAP_TYPE_ANY)
item.SetBitmap(wx.BitmapFromImage(img))
picMenu.AppendItem(item)
# add menus to menubar
menuBar.Append(carMenu, "&Vehicles")
menuBar.Append(picMenu, "&Picture")
self.SetMenuBar(menuBar)
#----------------------------------------------------------------------
def onExit(self, event):
""""""
self.Close()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
这个例子和第一个类似。主要的区别是我们添加了多个项目到我们的文件菜单,我们有两个菜单。注意,这一次,我们实际上明确地指定了我们的 ID 号。通常不建议这样做,因为您可能会意外地隐藏 wx 的一个必需 id。但是,您确实会在网上不时看到这种例子,所以您应该知道这种做法。下一个大的不同直到我们到达图片菜单才出现。这里我们实际上创建了一个 wx。MenuItem,并通过 wx 向其添加一张图片。Image 和 MenuItem 的 SetBitmap 方法。其余的都差不多。
现在我们将花一些时间来看看如何添加单选和复选按钮到我们的菜单中。
添加单选或复选按钮
向菜单中添加单选按钮或复选按钮实际上非常简单。让我们花点时间来看看它是如何做到的!
# radiocheck.py
import wx
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="wx.Menu Tutorial")
self.panel = wx.Panel(self, wx.ID_ANY)
# Create menu bar
menuBar = wx.MenuBar()
# Create radio menu
radioMenu = wx.Menu()
idleItem = radioMenu.Append(wx.NewId(), "IDLE",
"a Python shell using tcl/tk as GUI",
wx.ITEM_RADIO)
pyCrustItem = radioMenu.Append(wx.NewId(),"PyCrust",
"a Python shell using wxPython as GUI",
wx.ITEM_RADIO)
psiItem = radioMenu.Append(wx.NewId(), "psi",
"a simple Python shell using wxPython as GUI",
wx.ITEM_RADIO)
menuBar.Append(radioMenu, "&Radio")
# create check menu
checkMenu = wx.Menu()
wgItem = checkMenu.Append(wx.NewId(), "Wells Fargo", "", wx.ITEM_CHECK)
citiItem = checkMenu.Append(wx.NewId(), "Citibank", "", wx.ITEM_CHECK)
geItem = checkMenu.Append(wx.NewId(), "GE Money Bank", "", wx.ITEM_CHECK)
menuBar.Append(checkMenu, "&Check")
# Attach menu bar to frame
self.SetMenuBar(menuBar)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
是的,如你所见,你所要做的就是添加一个 wx。ITEM_RADIO 或 wx。ITEM_CHECK 标志为第四个参数种类。为什么像其他 widgets 一样叫“亲切”而不是“风格”?在 wxPython IRC 频道上讨论这个问题时,Robin Dunn(wxPython 的创建者)指出,这可能是因为这些是不同种类的菜单项。
子菜单
wxPython 库也支持子菜单。这里有一个非常简单的例子来告诉你怎么做。
# submenu.py
import wx
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="wx.Menu Tutorial")
self.panel = wx.Panel(self, wx.ID_ANY)
menuBar = wx.MenuBar()
fileMenu = wx.Menu()
openMenuItem = fileMenu.Append(wx.NewId(), "Open")
# create a submenu
subMenu = wx.Menu()
historyMenuItem = subMenu.Append(wx.NewId(), "Show History")
fileMenu.AppendMenu(wx.NewId(), "History", subMenu)
exitMenuItem = fileMenu.Append(wx.NewId(), "Exit",
"Exit the application")
menuBar.Append(fileMenu, "&File")
self.Bind(wx.EVT_MENU, self.onExit, exitMenuItem)
self.SetMenuBar(menuBar)
#----------------------------------------------------------------------
def onExit(self, event):
""""""
self.Close()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
这里的关键是,我们不使用 filemenu 的 Append 方法,而是使用它的 AppendMenu 方法。顾名思义,它允许程序员实际添加一个菜单,而不是一个菜单项。对,就是这样!
弹出菜单(又名:上下文菜单)
当你在浏览器或文件的链接上点击鼠标右键时,你会看到弹出菜单。它们也被称为上下文菜单。这里有一个相当简单的例子供您研究:
# submenu.py
import wx
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Popup Menu Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
lbl = wx.StaticText(panel, label="Right click anywhere!")
self.Bind(wx.EVT_CONTEXT_MENU, self.onContext)
#----------------------------------------------------------------------
def onContext(self, event):
"""
Create and show a Context Menu
"""
# only do this part the first time so the events are only bound once
if not hasattr(self, "popupID1"):
self.popupID1 = wx.NewId()
self.itemTwoId = wx.NewId()
self.itemThreeId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onPopup, id=self.popupID1)
self.Bind(wx.EVT_MENU, self.onPopup, id=self.itemTwoId)
self.Bind(wx.EVT_MENU, self.onExit, id=self.itemThreeId)
# build the menu
menu = wx.Menu()
itemOne = menu.Append(self.popupID1, "ItemOne")
itemTwo = menu.Append(self.itemTwoId, "ItemTwo")
itemThree = menu.Append(self.itemThreeId, "Exit")
# show the popup menu
self.PopupMenu(menu)
menu.Destroy()
#----------------------------------------------------------------------
def onExit(self, event):
"""
Exit program
"""
self.Close()
#----------------------------------------------------------------------
def onPopup(self, event):
"""
Print the label of the menu item selected
"""
itemId = event.GetId()
menu = event.GetEventObject()
menuItem = menu.FindItemById(itemId)
print menuItem.GetLabel()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
首先,我们绑定 wx。EVT _ 上下文 _ 菜单到框架。这允许我们右击任意位置并触发上下文菜单事件,这将创建并显示我们的弹出菜单。 onContext 方法中的代码大致基于弹出菜单的 wxPython 演示。如您所见,我们使用条件语句来检查我们是否已经绑定了菜单事件。如果有,就不要再绑定了。接下来,我们用和以前一样的方式创建菜单。最后,我们调用框架的弹出菜单方法,并将我们的新菜单传递给它。这将向用户显示菜单。当用户点击一个菜单项时,它将触发一个事件,菜单将被销毁。
前两个菜单项都绑定到我们的 onPopup 方法。这允许我们了解如何访问菜单和 MenuItem 属性。可以用事件获取菜单的 id,用事件的 GetEventObject 方法获取菜单本身。然后,您可以使用菜单的 FindItemById 方法来获取菜单项本身的句柄。最后,我们打印出菜单项的标签。
包扎
现在你应该知道大多数的菜单方法以及如何创建它们,绑定事件和制作不同种类的菜单项。你甚至知道如何创建弹出菜单!现在你可以让你的应用程序也有漂亮的菜单。
进一步阅读
- wxPython:使用菜单、工具栏和加速器
- 使用菜单- wxPython Wiki
- 菜单和工具栏- Zetcode
来源
wxPython:UltimateListCtrl 简介
原文:https://www.blog.pythonlibrary.org/2011/11/02/wxpython-an-intro-to-the-ultimatelistctrl/
wxPython 中包含的一个新的 agw 小部件叫做 UltimateListCtrl。这是一个纯 Python 小部件,几乎可以将任何其他小部件嵌入到任何单元格中,这使得它非常灵活。它还允许熟练的程序员添加自定义渲染器,使界面与众不同。在本文中,我们将快速浏览一下这个迷人的小部件。
入门指南
学习一个新的小部件最简单的方法是看一个例子。如果您想查看这个令人敬畏的小部件的几个复杂演示,可以查看 2.9 系列的 wxPython 演示,但是出于我们的目的,我们将创建一个基于其中一个演示的精简版本,即报告视图版本。这是让你享受观赏乐趣的代码:
import wx
from wx.lib.agw import ultimatelistctrl as ULC
########################################################################
class TestPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
try:
font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
boldfont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
except AttributeError:
# wxPython 4 / Phoenix updated SystemSettings
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
boldfont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
boldfont.SetWeight(wx.BOLD)
boldfont.SetPointSize(12)
self.ultimateList = ULC.UltimateListCtrl(self, agwStyle = wx.LC_REPORT
| wx.LC_VRULES
| wx.LC_HRULES)
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
info._image = []
info._format = 0
info._kind = 1
info._text = "Artist Name"
self.ultimateList.InsertColumnInfo(0, info)
info = ULC.UltimateListItem()
info._format = wx.LIST_FORMAT_RIGHT
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_FONT
info._image = []
info._text = "Title"
info._font = boldfont
self.ultimateList.InsertColumnInfo(1, info)
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
info._format = 0
info._text = "Genre"
info._font = font
info._image = []
self.ultimateList.InsertColumnInfo(2, info)
self.ultimateList.InsertStringItem(0, "Newsboys")
self.ultimateList.SetStringItem(0, 1, "Go")
self.ultimateList.SetStringItem(0, 2, "Rock")
self.ultimateList.InsertStringItem(1, "Puffy")
self.ultimateList.SetStringItem(1, 1, "Bring It!")
self.ultimateList.SetStringItem(1, 2, "Pop")
self.ultimateList.InsertStringItem(2, "Family Force 5")
self.ultimateList.SetStringItem(2, 1, "III")
self.ultimateList.SetStringItem(2, 2, "Crunk")
self.ultimateList.SetColumnWidth(0, 150)
self.ultimateList.SetColumnWidth(1, 200)
self.ultimateList.SetColumnWidth(2, 100)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.ultimateList, 1, wx.EXPAND)
self.SetSizer(sizer)
########################################################################
class TestFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="MvP UltimateListCtrl Demo")
panel = TestPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = TestFrame()
app.MainLoop()
让我们花点时间来分解一下。首先,要导入这个小部件,我们需要做如下事情:从 wx.lib.agw 导入 ultimatelistctrl 作为 ULC 。然后为了实例化它,我们称为 ULC。UltimateListCtrl() 并给它传递几个键值。在本例中,我们传入一个父类和三个样式:wx。LC_REPORT,wx。LC_VRULES 和 wx。LC_HRULES。第一个 agwStyle 是 LC_REPORT,它将小部件置于“报告”模式,这可能是 ListCtrl 最常见的模式,也是最有用的模式之一。另外两种样式分别放入垂直线和水平线。
接下来,我们想要创建我们的列。我们用 ULC。虽然根据文档,这也可以用来创建“项目”。如您所见,UltimateListItem 有许多我们可以设置的属性。你可以添加一个图像,一个复选框(通过面具和风格:ULC。ULC _ 掩码 _ 检查),一种(0 -正常,1 -复选框,2 -单选按钮),一种格式(控制标签定位),字体和文本和其他几个。一旦你设置好了这些东西,你可以调用 UltimateListItem 对象的 InsertColumnInfo() 方法来应用你的设置。
最后,为了向 UltimateListCtrl 添加数据,我们做了与普通 ListCtrl 相同的事情。也就是说,我们首先调用 InsertStringItem(index,label) 其中 index 是行号。然后要向其他列添加字符串,您需要调用 SetStringItem(index,col,label) 。您可以调用许多其他方法来添加其他类型的数据,但是您需要阅读演示的源代码或文档来了解这一点。现在我们完成了我们的第一个演示!
包扎
在官方的 wxPython 2.9 系列演示中可以找到更多的信息。事实上,有几个演示展示了这个小部件可以做的各种风格和 UI 变化。您可以在本文开头的截图中看到其中一个演示的例子。每当您需要将其他小部件插入 ListCtrl 的单元格时,或者每当您需要对小部件的表示进行大量控制时,我都会推荐这个小部件。黑客快乐!
进一步阅读
- ultimetalistctrl 文档
wxPython:网格简介
原文:https://www.blog.pythonlibrary.org/2010/03/18/wxpython-an-introduction-to-grids/
wxPython 中的 grid 小部件是您将在这个工具包中使用的最复杂的 GUI 元素之一。在本文中,您将学习网格创建和使用的基础知识。网格的主要用途之一是显示表格数据。另一个用途是创建某种电子表格。如果您需要可以轻松编辑的大量单元格,那么 grid 小部件可能就是您想要的。报表模式中的 ListCtrl 在外观上类似于网格,可以根据您的需要用作网格的替代。
创建简单的网格
让我们来看看如何实际创建网格:
import wx
import wx.grid as gridlib
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="A Simple Grid")
panel = wx.Panel(self)
myGrid = gridlib.Grid(panel)
myGrid.CreateGrid(12, 8)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(myGrid, 1, wx.EXPAND)
panel.SetSizer(sizer)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
这是创建网格小部件最简单的方法之一。我们在这里做的只是实例化一个网格对象,然后调用它的 CreateGrid 方法来告诉它我们想要多少行和多少列。我们将它放在 sizer 中,主要是因为这是包含小部件的正确方式,并且使添加额外的小部件更加容易。现在你应该有一个功能网格了!
介绍一些网格方法
前面的例子有一个问题:这个网格并没有真正做任何有用的事情!我们需要一种方法将数据放入网格,然后将数据取出。让我们来看看如何做到这一点以及更多。
import wx
import wx.grid as gridlib
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="Grid Tutorial Two", size=(650,320))
panel = wx.Panel(self)
myGrid = gridlib.Grid(panel)
myGrid.CreateGrid(15, 6)
myGrid.SetCellValue(0,0, "Hello")
myGrid.SetCellFont(0, 0, wx.Font(12, wx.ROMAN, wx.ITALIC, wx.NORMAL))
print myGrid.GetCellValue(0,0)
myGrid.SetCellValue(1,1, "I'm in red!")
myGrid.SetCellTextColour(1, 1, wx.RED)
myGrid.SetCellBackgroundColour(2, 2, wx.CYAN)
myGrid.SetCellValue(3, 3, "This cell is read-only")
myGrid.SetReadOnly(3, 3, True)
myGrid.SetCellEditor(5, 0, gridlib.GridCellNumberEditor(1,1000))
myGrid.SetCellValue(5, 0, "123")
myGrid.SetCellEditor(6, 0, gridlib.GridCellFloatEditor())
myGrid.SetCellValue(6, 0, "123.34")
myGrid.SetCellEditor(7, 0, gridlib.GridCellNumberEditor())
myGrid.SetCellSize(11, 1, 3, 3)
myGrid.SetCellAlignment(11, 1, wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
myGrid.SetCellValue(11, 1, "This cell is set to span 3 rows and 3 columns")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(myGrid)
panel.SetSizer(sizer)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
app.MainLoop()
上面的代码与第一个例子中的代码非常相似。但是,您很快就会看到,这里有许多特定于网格的方法,用于设置网格小部件的各种属性。例如,我们使用 SetCellValue(0,0,“Hello”)将右上角单元格的值设置为字符串“Hello”。接下来,我们通过调用以下代码来设置同一单元格的字体大小和样式:
SetCellFont(0,0,wx。字体(12,wx。罗曼,wx。斜体,wx。正常))
可以创建 wx。也使用您系统上的字体的字体对象。为了设置字体颜色,我们使用 SetCellTextColour,为了设置单元格的背景颜色,我们使用 SetCellBackgroundColour。如您所见,网格的方法非常简单,名字也很直观。当然,如果你是美国人,那么你会注意到命名倾向于英式拼写(即颜色而不是颜色),所以你需要注意这一点。
如果需要将单元格设为只读,可以执行以下操作:SetReadOnly(row,col,isReadOnly=True)。如果您需要将整个网格设为只读,则使用 EnableEditing(False)。最后,如果您需要将整行或整列设置为只读,那么您将需要使用单元格属性函数。类似这样的事情应该让你开始:
GetCellAttr(行,列)。SetReadOnly(isReadOnly)
然后使用 SetRowAttr()和 SetColAttr()将相应的行或列设置为只读。我从罗宾·邓恩的极有帮助的书《wxPython in Action》中得到了这个信息。一定要注意 SetCellAlignment,它将设置单元格内容的对齐方式。
更新(2013-09-12): 我试图弄清楚如何实际使用上面的信息来使整个行或列成为只读的,但是我不认为它工作或者它不再以那种方式工作。相反,您应该这样做:
attr = gridlib.GridCellAttr()
attr.SetReadOnly(True)
myGrid.SetRowAttr(0, attr)
这段代码将使第一行(即第零行)成为只读的。
本例中最后一个重要部分是如何设置自定义单元格编辑器。这非常简单,因为您所需要做的就是调用 SetCellEditor 并传入(row,col)元组以及您选择的编辑器。在我们的示例中,我们使用 GridCellNumberEditor、GridCellFloatEditor 和 GridCellNumberEditor。其他选择见官方文档。
网格事件
在我们旋风网格之旅的最后一个例子中,我们将看看网格的特殊事件。下面是一些让我们开始的代码:
# gridEvents.py
import wx
import wx.grid as gridlib
########################################################################
class MyGrid(gridlib.Grid):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
gridlib.Grid.__init__(self, parent)
self.CreateGrid(12, 8)
# test all the events
self.Bind(gridlib.EVT_GRID_CELL_LEFT_CLICK, self.OnCellLeftClick)
self.Bind(gridlib.EVT_GRID_CELL_RIGHT_CLICK, self.OnCellRightClick)
self.Bind(gridlib.EVT_GRID_CELL_LEFT_DCLICK, self.OnCellLeftDClick)
self.Bind(gridlib.EVT_GRID_CELL_RIGHT_DCLICK, self.OnCellRightDClick)
self.Bind(gridlib.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClick)
self.Bind(gridlib.EVT_GRID_LABEL_RIGHT_CLICK, self.OnLabelRightClick)
self.Bind(gridlib.EVT_GRID_LABEL_LEFT_DCLICK, self.OnLabelLeftDClick)
self.Bind(gridlib.EVT_GRID_LABEL_RIGHT_DCLICK, self.OnLabelRightDClick)
self.Bind(gridlib.EVT_GRID_ROW_SIZE, self.OnRowSize)
self.Bind(gridlib.EVT_GRID_COL_SIZE, self.OnColSize)
self.Bind(gridlib.EVT_GRID_RANGE_SELECT, self.OnRangeSelect)
self.Bind(gridlib.EVT_GRID_CELL_CHANGE, self.OnCellChange)
self.Bind(gridlib.EVT_GRID_SELECT_CELL, self.OnSelectCell)
self.Bind(gridlib.EVT_GRID_EDITOR_SHOWN, self.OnEditorShown)
self.Bind(gridlib.EVT_GRID_EDITOR_HIDDEN, self.OnEditorHidden)
self.Bind(gridlib.EVT_GRID_EDITOR_CREATED, self.OnEditorCreated)
def OnCellLeftClick(self, evt):
print "OnCellLeftClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnCellRightClick(self, evt):
print "OnCellRightClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnCellLeftDClick(self, evt):
print "OnCellLeftDClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnCellRightDClick(self, evt):
print "OnCellRightDClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnLabelLeftClick(self, evt):
print "OnLabelLeftClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnLabelRightClick(self, evt):
print "OnLabelRightClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnLabelLeftDClick(self, evt):
print "OnLabelLeftDClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnLabelRightDClick(self, evt):
print "OnLabelRightDClick: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnRowSize(self, evt):
print "OnRowSize: row %d, %s\n" % (evt.GetRowOrCol(),
evt.GetPosition())
evt.Skip()
def OnColSize(self, evt):
print "OnColSize: col %d, %s\n" % (evt.GetRowOrCol(),
evt.GetPosition())
evt.Skip()
def OnRangeSelect(self, evt):
if evt.Selecting():
msg = 'Selected'
else:
msg = 'Deselected'
print "OnRangeSelect: %s top-left %s, bottom-right %s\n" % (msg, evt.GetTopLeftCoords(),
evt.GetBottomRightCoords())
evt.Skip()
def OnCellChange(self, evt):
print "OnCellChange: (%d,%d) %s\n" % (evt.GetRow(), evt.GetCol(), evt.GetPosition())
# Show how to stay in a cell that has bad data. We can't just
# call SetGridCursor here since we are nested inside one so it
# won't have any effect. Instead, set coordinates to move to in
# idle time.
value = self.GetCellValue(evt.GetRow(), evt.GetCol())
if value == 'no good':
self.moveTo = evt.GetRow(), evt.GetCol()
def OnSelectCell(self, evt):
if evt.Selecting():
msg = 'Selected'
else:
msg = 'Deselected'
print "OnSelectCell: %s (%d,%d) %s\n" % (msg, evt.GetRow(),
evt.GetCol(), evt.GetPosition())
# Another way to stay in a cell that has a bad value...
row = self.GetGridCursorRow()
col = self.GetGridCursorCol()
if self.IsCellEditControlEnabled():
self.HideCellEditControl()
self.DisableCellEditControl()
value = self.GetCellValue(row, col)
if value == 'no good 2':
return # cancels the cell selection
evt.Skip()
def OnEditorShown(self, evt):
if evt.GetRow() == 6 and evt.GetCol() == 3 and \
wx.MessageBox("Are you sure you wish to edit this cell?",
"Checking", wx.YES_NO) == wx.NO:
evt.Veto()
return
print "OnEditorShown: (%d,%d) %s\n" % (evt.GetRow(), evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnEditorHidden(self, evt):
if evt.GetRow() == 6 and evt.GetCol() == 3 and \
wx.MessageBox("Are you sure you wish to finish editing this cell?",
"Checking", wx.YES_NO) == wx.NO:
evt.Veto()
return
print "OnEditorHidden: (%d,%d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
evt.Skip()
def OnEditorCreated(self, evt):
print "OnEditorCreated: (%d, %d) %s\n" % (evt.GetRow(),
evt.GetCol(),
evt.GetControl())
########################################################################
class MyForm(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="An Eventful Grid")
panel = wx.Panel(self)
myGrid = MyGrid(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(myGrid, 1, wx.EXPAND)
panel.SetSizer(sizer)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
代码很多,但大部分只是事件处理程序。我们只看其中的几个。你很聪明,所以我相信你能想出其余的!请注意,本示例中的所有事件都带有“gridlib”前缀。这意味着这些事件只适用于网格实例,对任何其他小部件都没有影响。对于我们的第一个事件示例,我们将查看 LEFT _ 网格 _ 单元格 _ 左击。当您绑定到此事件时,它允许您捕获当用户在网格中的单元格上单击鼠标指针时触发的事件。据我所知,这个事件在选择事件(即 EVT 网格选择单元格)之前被触发,所以如果你愿意,你可以用它来否决单元格选择。请注意,两个鼠标按钮都有一个右键单击和相应的双击事件。
LEFT _ 网格 _ 标签 _ 左击事件允许我们知道用户何时点击网格的列或行标签。与前面的事件示例一样,您也可以捕获双击和右键单击。请注意,虽然没有中击事件。为此,您可能需要使用普通的中击鼠标事件。
当你开始编辑一个单元格时,EVT _ 网格 _ 编辑器 _ 显示被激发。您可以使用此事件来创建要显示的自定义编辑器或否决编辑。看看你是否能自己找出其他事件的作用。
现在让我们看看一些处理程序。其中很多都包含以下代码:
(evt.GetRow(),
evt.GetCol(),
evt.GetPosition())
这对于故障排除来说非常方便,因为它告诉我们是否在我们期望的单元格中,并且它也给我们我们点击的像素坐标。后者对于上下文弹出菜单或工具提示定位非常方便。另一个有趣的处理程序是 OnRangeSelect ,它向我们展示了如何判断我们选择的范围。请注意,我们需要做的只是调用 GetTopLeftCoords 和 GetBottomRightCoords 来解决这个问题。这不是很好玩吗?
现在您已经了解了网格事件处理的基础知识。走出去,用你新发现的知识构建一些很酷的应用程序!请密切关注接下来的几个网格教程,在那里您将学习如何使用虚拟表格、更改网格和行标签、向列添加工具提示以及其他有趣的东西!
注意:这篇文章中的代码是在以下平台上测试的:
- Windows XP Professional、wxPython 2.8.10.1(unicode)、Python 2.5
- Windows 7 专业版、wxPython 2.8.10.1(unicode)、Python 2.6.4
进一步阅读
wxPython:大小控件简介
原文:https://www.blog.pythonlibrary.org/2014/01/23/wxpython-an-introduction-to-sized-controls/
wxPython 工具包提供了一种使用 sizer 进行布局的替代方法,称为“sized_controls”。这些控件或小部件基本上是顶层小部件(如框架、面板、对话框等),内置了尺寸逻辑。本文将涵盖所有四种类型的 sized_controls。它们如下:
- 尺寸面板
- SizedScrolledPanel
- 尺寸框架
- 尺寸对话框
SizedScrolledPanel 小部件是 wxPython 2.8.8 的新功能,但是其他 3 种类型的控件在 wxPython 2.8.8 和更老的版本中都有(更多信息请参见 T2 的 Trac。如果您使用的是 wxPython 2.8,请记住这一点。如果你准备好了,我们可以开始了!
尺寸面板
SizedPanel 小部件将自动为自己创建一个 sizer。sizer 默认为垂直框 sizer。小部件还会自动将您添加到面板的任何子组件添加到 sizer 中。如果您需要更改 sizer 类型,只需调用 SetSizerType() 并传递它“水平”、“垂直”、“表格”(一个 2 列 flex 网格 sizer),或“网格”。如果选择“grid”,那么还可以再传递两个参数:“cols”和“rows”。根据 wxPython 演示:这个类在 Win、GTK 和 Mac 上也应用了符合本地平台人机界面指南(HIG)的控件边框。
让我们看一个简单的例子:
# sized_panel.py
import wx
import wx.lib.sized_controls as sc
########################################################################
class SizedPanel(sc.SizedPanel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
sc.SizedPanel.__init__(self, parent)
for item in range(5):
txt = wx.TextCtrl(self)
########################################################################
class RegularFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="SizedPanel demo",
size=(400,300))
panel = SizedPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = RegularFrame()
app.MainLoop()
当使用调整大小的控件小部件时,将小部件添加到 sizer 的代码要简单得多。如果您想尝试一个小实验,请尝试在 SizedPanel 类的循环之前添加以下代码行:
self.SetSizerType("horizontal")
这将导致小部件被水平添加而不是垂直添加。为了便于比较,让我们花点时间来看看用 sizer 进行这种布局的常规方法:
import wx
########################################################################
class RegularPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
for item in range(5):
txt = wx.TextCtrl(self)
sizer.Add(txt, 0, wx.ALL, 5)
self.SetSizer(sizer)
########################################################################
class RegularFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="SizedPanel demo",
size=(400,300))
panel = RegularPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = RegularFrame()
app.MainLoop()
这两段代码之间的主要区别在于, RegularPanel 代码有几行额外的代码与设置 sizer、向 sizer 添加小部件以及设置 sizer 相关。
SizedScrolledPanel 面板
SizedScrolledPanel 与 ScrolledPanel 小部件非常相似,因为如果有太多的小部件要在一个面板上显示,它会创建一个滚动条。主要的区别是,大小控制将再次为我们做所有的 sizer 的东西。这里有一个例子:
# sized_scrolled_panel.py
import wx
import wx.lib.sized_controls as sc
########################################################################
class MySizedScrolledPanel(sc.SizedScrolledPanel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
sc.SizedScrolledPanel.__init__(self, parent)
for item in range(25):
txt = wx.TextCtrl(self)
txt.SetSizerProps(expand=True)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="SizedScrolledPanel Demo",
size=(400, 500))
panel = MySizedScrolledPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
上面的代码非常类似于 SizedPanel 示例,但是我添加了一点额外的逻辑来扩展文本控件。你看到了吗?在我们创建文本控件小部件之后,看一下这个循环:
txt.SetSizerProps(expand=True)
通过使用大小控制,它为我们的小部件添加了一个特殊的方法,称为 SetSizerProps 。此方法的有效值为“比例”、“hgrow”、“vgrow”、“align”、“halign”、“valign”、“border”、“minsize”和“expand”。在这个例子中,我们将扩展设置为布尔真。
尺寸框架
SizedFrame 与最后两个 sized 控件略有不同,它自动将 SizedPanel 设置为它的第一个子控件。如果您想要访问该面板,您将需要调用 GetContentsPane() 。让我们看一个例子:
# sized_frame.py
import wx
import wx.lib.sized_controls as sc
########################################################################
class SizedFrame(sc.SizedFrame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
sc.SizedFrame.__init__(self, None, title="SizedFrame Demo",
size=(400,500))
panel = self.GetContentsPane()
for item in range(5):
btn = wx.Button(panel, label=str(item))
btn.SetSizerProps(border=("all", 5))
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App()
frame = SizedFrame()
app.MainLoop()
这里我们看到我们需要提取 SizedPanel ,这样我们就可以向它添加按钮。那很容易!
尺寸对话框
sized 对话框与 SizedFrame 非常相似,因为它也内置了一个 SizedPanel。让我们从 wxPython 演示中派生出一个演示,这样我们就可以看到它是如何工作的:
# sized_dialog.py
import wx
import wx.lib.sized_controls as sc
########################################################################
class MySizedDialog(sc.SizedDialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
sc.SizedDialog.__init__(self, None, title="SizedDialog Demo",
size=(400,500))
panel = self.GetContentsPane()
panel.SetSizerType("form")
# row 1
wx.StaticText(panel, -1, "Name")
textCtrl = wx.TextCtrl(panel)
textCtrl.SetSizerProps(expand=True)
# row 2
wx.StaticText(panel, -1, "Email")
emailCtrl = wx.TextCtrl(panel)
emailCtrl.SetSizerProps(expand=True)
# row 3
wx.StaticText(panel, -1, "Gender")
wx.Choice(panel, -1, choices=["male", "female"])
# row 4
wx.StaticText(panel, -1, "State")
wx.TextCtrl(panel, size=(60, -1)) # two chars for state
# row 5
wx.StaticText(panel, -1, "Title")
# here's how to add a 'nested sizer' using sized_controls
radioPane = sc.SizedPanel(panel, -1)
radioPane.SetSizerType("horizontal")
# make these children of the radioPane to have them use
# the horizontal layout
wx.RadioButton(radioPane, -1, "Mr.")
wx.RadioButton(radioPane, -1, "Mrs.")
wx.RadioButton(radioPane, -1, "Dr.")
# end row 5
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
dlg = MySizedDialog()
dlg.ShowModal()
app.MainLoop()
这段代码演示了两个新概念。它展示了一个新的 sizer 类型(“form”),并展示了如何嵌套 sizer。sizer 类型“form”基本上告诉 wxPython 将小部件放入两列,而不是垂直堆叠。因此,我们有一个 StaticText 和一个 TextCtrl 在同一层上。单选按钮都在自己的一行。那是怎么发生的?这就是嵌套的 sizer 部分的用武之地。我们创建一个新的尺寸面板,分配给无线面板,然后我们将其方向改为“水平”。接下来,我们通过将 radioPane 设置为它们的父级来添加 3 个单选按钮。
包扎
至此,您应该有足够的信息来开始使用 wxPython 的大小控件了。您刚刚学习了如何使用所有四种变体:SizedPanel、SizedScrolledPanel、SizedFrame 和 SizedDialog。您还了解了一些关于如何将不同大小的控件嵌套在一起的知识。嵌套是一个非常强大的工具,可以帮助你创建非常复杂的界面。我认为大小控件最好的一点是,它们使得使用 sizer 更加直观。
额外资源
下载源代码
注意:本文中的代码是在 Windows 7 上使用 wxPython 2.9.4.0(经典)和 Python 2.7.3 测试的
wxPython:splitter windows 简介
原文:https://www.blog.pythonlibrary.org/2013/10/18/wxpython-an-introduction-to-splitterwindows/
wxPython GUI 工具包附带了许多小部件。我们将介绍一些让人难以理解的小部件。在这种情况下,我们将讨论拆分窗口。WxPython 包括三种类型的拆分器窗口:
- wx。拆分窗口
- 你可以在 wx.lib.agw 中找到它
- 可以在 wx.lib.splitter 中找到的 MultiSplitterWindow
在这篇文章中,我们将讨论如何使用这些不同种类的拆分窗口。还有一个小部件的行为有点像一个拆分窗口,我们不会谈论它。它是 SplitTree 小部件,是 wx.gizmos 的一部分。
wx。拆分窗口
wx。SplitterWindow 是基于 C++的 wxWidgets 小部件的包装器,可能是您在野外看到的最常见的一个。让我们写一小段代码来学习如何使用它。
import wx
import wx.grid as gridlib
########################################################################
class LeftPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
grid = gridlib.Grid(self)
grid.CreateGrid(25,12)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(grid, 0, wx.EXPAND)
self.SetSizer(sizer)
########################################################################
class RightPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
txt = wx.TextCtrl(self)
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, title="Splitter Tutorial")
splitter = wx.SplitterWindow(self)
leftP = LeftPanel(splitter)
rightP = RightPanel(splitter)
# split the window
splitter.SplitVertically(leftP, rightP)
splitter.SetMinimumPaneSize(20)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(splitter, 1, wx.EXPAND)
self.SetSizer(sizer)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
如你所见,我们制作了 wx。SplitterWindow widget 框架的唯一子窗口。在大多数应用程序中,通常使用 wx。专家组唯一的孩子,但这是一个特例。接下来,我们创建两个面板,并使它们成为 wx.SplitterWindow 的子窗口。SplitterWindow 垂直拆分,并将其最小窗格大小设置为 20。您需要设置大小,以确保两个面板都可见。SplitterWindow 可以绑定到四个不同的特定于小部件的事件:
- EVT _ 分离器 _ 窗扇 _ 位置 _ 改变
- EVT _ 拆分器 _ 窗框 _ 位置 _ 已更改
- EVT _ 拆分器 _ 取消拆分
- EVT _ 拆分器 _ 点击
在这个例子中,我们不使用它们中的任何一个,但是你应该知道它们。还应注意,您可以设置窗扇位置。
另一件值得了解的事情是,你可以设置窗框的重力。当您移动存储时,重力控制窗格如何调整大小。它的默认值是 0.0,这意味着只有底部或右边的窗口会自动调整大小。您也可以将其设置为 0.5,此时两个窗口的增长相等,或者设置为 1.0,此时只有左侧/顶部的窗口增长。
嵌套拆分器
有时,您会希望将拆分器窗口嵌套在一起,以创建复杂的布局,就像 wxPython 演示中使用的布局一样。让我们花点时间,用下面的代码看看如何做到这一点:
import wx
########################################################################
class RandomPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, color):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.SetBackgroundColour(color)
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
topSplitter = wx.SplitterWindow(self)
vSplitter = wx.SplitterWindow(topSplitter)
panelOne = RandomPanel(vSplitter, "blue")
panelTwo = RandomPanel(vSplitter, "red")
vSplitter.SplitVertically(panelOne, panelTwo)
vSplitter.SetSashGravity(0.5)
panelThree = RandomPanel(topSplitter, "green")
topSplitter.SplitHorizontally(vSplitter, panelThree)
topSplitter.SetSashGravity(0.5)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(topSplitter, 1, wx.EXPAND)
self.SetSizer(sizer)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Nested Splitters",
size=(800,600))
panel = MainPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
这里我们创建了一个顶级拆分器,它是框架的唯一子元素。然后,我们创建第二个分割器,并添加几个面板。接下来,我们垂直拆分第二个拆分器,水平拆分顶部的拆分器,得到您在本节开始时看到的应用程序。现在我们准备学习我们的下一种类型的拆分窗口!
FourWaySplitter 小部件
FourWaySplitter 小部件是一个自定义小部件,用纯 Python 编写,是 agw 子库的一部分,可以在 wx.lib.agw 中找到。agw 代表高级通用小部件,尽管我认为它也可以代表 Andrea Gavana 小部件,该子库中所有小部件的作者。无论如何,这是一个非常方便的拆分器,因为你不需要做任何嵌套的拆分窗口来获得这个小部件默认的效果。我们将使用来自 wxPython 文档的一个例子来看看我们如何使用这个小部件:
import wx
import wx.lib.agw.fourwaysplitter as fws
########################################################################
class MyFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, title="FourWaySplitter Example")
splitter = fws.FourWaySplitter(self, agwStyle=wx.SP_LIVE_UPDATE)
# Put in some coloured panels...
for colour in [wx.RED, wx.WHITE, wx.BLUE, wx.GREEN]:
panel = wx.Panel(splitter)
panel.SetBackgroundColour(colour)
splitter.AppendWindow(panel)
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
在这个例子中,我们创建了一个 FourWaySplitter 的实例,然后我们遍历一个四种颜色的列表。当我们循环时,我们创建一个面板,它的父面板是 FourWaySplitter,它有自己独特的颜色。最后,我们将面板附加到拆分器上。这个小部件可以做的一件事就是嵌套一组 wx。SplitterWindows 不能一次调整所有面板的大小。如果你抓住相交处的窗扇,你可以调整任何东西的大小。这对于常规的 splitter 小部件来说是不可能的。
多分割窗口
MultiSplitterWindow 是 wx 的一种扩展。SplitterWindow 允许两个以上的窗户/面板和一个以上的窗框。否则,大多数样式、常量和方法的行为都是一样的。如果您查看 wxPython 演示,您会注意到它以与 FourWaySplitter 相同的方式使用了 AppendWindow 。让我们基于官方的 wxPython 演示创建一个简单的演示:
import wx
from wx.lib.splitter import MultiSplitterWindow
########################################################################
class SamplePanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, colour):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.SetBackgroundColour(colour)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="MultiSplitterWindow Tutorial")
splitter = MultiSplitterWindow(self, style=wx.SP_LIVE_UPDATE)
colours = ["pink", "yellow", "sky blue", "Lime Green"]
for colour in colours:
panel = SamplePanel(splitter, colour)
splitter.AppendWindow(panel)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
如您所见,添加“窗口”或面板所需要做的就是使用 AppendWindow 方法将它们添加到拆分器中。在这种情况下,我们只是添加了四个不同的面板,每个面板都有不同的背景颜色。
包扎
应该注意的是,您添加到 MultiSplitterWindow 或 FourWaySplitter 的面板可以包含其他小部件,就像第一个 SplitterWindow 示例一样。使用这些小部件(或这些小部件的组合),你可以创建非常复杂和灵活的界面。我看到的使用这类小部件最多的应用程序之一是像 Filezilla 这样的 FTP 客户端。然而,大多数摄影软件都有可移动的窗框,可以在右边、左边或底部扩展选项。我建议尝试复制一些其他的程序来帮助你学习如何有效地使用这些小部件。
附加阅读
源代码
wxPython:XRC 简介
原文:https://www.blog.pythonlibrary.org/2010/05/11/wxpython-an-introduction-to-xrc/
您是否想过是否可以使用 XML 创建一个 wxPython 程序?嗯,我也从来没有,但有一种方法,它的名字是 XRC。事实上,wxPython 附带了一个名为 XRCed 的编辑器,您可以用它来设计 GUI 并生成 XML 代码。在本文中,我们将向您快速介绍 XRC,以及如何使用它来创建几个 GUI 框架。我们将看两个只使用 XRC 控件的例子,然后第三个例子混合了一些额外的非 XRC 部件。
使用 XRC 创建登录屏幕
我们经常看到的一个常见对话框是登录对话框。我使用 XRCed 创建了以下 XML 代码:
`
wxPython:一个 XRCed 教程
原文:https://www.blog.pythonlibrary.org/2010/10/28/wxpython-an-xrced-tutorial/
如果您对 wxPython 不熟悉,但对 XML 不熟悉,您可能会发现本文对您有用。为什么?因为 wxPython 支持 XRC,这是一种用 XML 描述 GUI 的 XML 文件格式。事实上,wxPython 的 Documentation & Demos 包中包含了一个名为 XRCed 的编辑器,专门用于创建和操作这些文件。这篇文章将带您了解 XRCed 的特性和一般用法。
XRCed 的一个令人困惑的方面是,它曾经是一个独立于 wxPython 的项目,它的网站仍然存在这里。有人告诉我,与演示包附带的新版本相比,该网站的旧版本在屏幕阅读器上运行得非常好。所以如果你有视力问题,你可能会发现那个版本更合适。当然,旧版本自 2007 年以来就没有更新过...所以选你的毒药吧。
入门指南
一旦安装了演示应用程序,运行名为 XRC 资源编辑器的工具。您应该会看到类似上面的内容。这是一个双屏界面,主屏幕在左边,小工具屏幕在右边。要开始,我们应该做一个简单的应用!
创建我们的第一个应用
让我们用 XRCed 创建一个简单的双按钮应用程序。它不会做任何事情,但是它会告诉你如何制作一个快速的 GUI。打开 XRCed,在窗口小部件(下图)中点击 wxFrame 按钮。
您应该看到一个未命名的 wxFrame 作为一个树小部件的根出现在正确的应用程序中(参见本节开头的屏幕截图)。在本例中,我们将该框架命名为“大型机”。现在选中树中的框架,添加一个名为“MainPanel”的面板。接下来,在第二个浮动屏幕中,顶部有一排按钮。单击左边第四个,看起来像几个红色矩形的那个,然后选择 BoxSizer 的那个(不过要确保面板对象首先在另一个屏幕中高亮显示)。
现在选中框尺寸树项目,点击浮动窗口的第三个按钮,添加两个按钮到树中,命名如下。保存您的工作,您应该得到如下所示的文件:
<object class="wxFrame" name="MainFrame"><object class="wxPanel" name="MainPanel"><object class="wxBoxSizer"><object class="sizeritem"><object class="wxButton" name="okBtn">OK</object></object>
<object class="sizeritem"><object class="wxButton" name="cancelBtn">Cancel</object></object>
<orient>wxHORIZONTAL</orient></object></object></object>
令人震惊的是,XRCed 实际上产生了易读的 XML 代码。现在我们只需要弄清楚如何用 wxPython 加载 XML。幸运的是,这其实很容易。看看这个:
import wx
from wx import xrc
class MyApp(wx.App):
def OnInit(self):
self.res = xrc.XmlResource("twoBtns.xrc")
self.frame = self.res.LoadFrame(None, 'MainFrame')
self.frame.Show()
return True
if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
为了加载 XML,我们需要从 wx 导入 xrc 模块。然后我们用下面一行加载 XML:xrc。XmlResource("twoBtns.xrc") 。注意,我们必须传入 xrc 文件的名称(或路径)。您可能需要将其更改为您所称的副本。然后为了加载框架,我们调用 xml 资源对象的 LoadFrame 方法,不传递任何值(即没有父值)和我们在 xrc 文件中给框架的名称。这是很容易出错的地方。您必须在 Python 代码中键入小部件的名称,就像您在 xrc 文件中那样,否则它将不起作用(或者它可能起作用,但不是以您期望的方式)。是的,名称区分大小写。无论如何,一旦完成了,你只需要做你通常在 wxPython 文件中做的事情。
创造更复杂的东西
上一节中的例子非常简单。让我们看看如何在 XRC 创建应用程序的一部分,在 wxPython 中创建一部分。在上面的截图中,我们有一个两页的笔记本,下面有一个 PlateButton。笔记本,框架和面板都是 XRC 制造的,而 PlateButton 只是普通的 wx。下面是 XML:
<resource><object class="wxFrame" name="DemoFrame"><object class="wxPanel" name="DemoPanel"><object class="wxBoxSizer"><orient>【wzvertical】</orient><object class="sizeritem"><object class="notebookpage"><object class="notebookpage">【tabou】</object></object></object></object></object></object></resource>
现在让我们添加 plate 按钮:
import wx
from wx import xrc
import wx.lib.platebtn as platebtn
class MyApp(wx.App):
def OnInit(self):
self.res = xrc.XmlResource("notebook2.xrc")
frame = self.res.LoadFrame(None, 'DemoFrame')
panel = xrc.XRCCTRL(frame, "DemoPanel")
notebook = xrc.XRCCTRL(panel, "DemoNotebook")
sizer = wx.BoxSizer(wx.VERTICAL)
btn = platebtn.PlateButton(panel, label="Test",
style=platebtn.PB_STYLE_DEFAULT)
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn)
panel.SetSizer(sizer)
frame.Show()
return True
#----------------------------------------------------------------------
def onButton(self, event):
""""""
print "You pressed the button!"
if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
如您所见,这与在普通 wxPython 中创建应用程序一样简单。如果有 wx 的话。按钮在 XRC 定义,我们会做同样的事情,我们为面板创建一个句柄。一旦我们有了句柄,我们就可以像平常一样将事件绑定到按钮上。
使用 XRCed 生成 Python 代码
XRCed 应用程序包括一个 Python 代码生成器,我们可以为自己的代码创建子类。首先,我们将使用本文中的第一个简单示例,然后我们将扩展该示例并向您展示如何绑定事件。在 XRCed 中,加载第一个示例,然后转到文件,生成 Python。接受默认值并点击生成模块按钮。您现在应该有一些自动生成的代码,如下所示:
# This file was automatically generated by pywxrc.
# -*- coding: UTF-8 -*-
import wx
import wx.xrc as xrc
__res = None
def get_resources():
""" This function provides access to the XML resources in this module."""
global __res
if __res == None:
__init_resources()
return __res
class xrcMainFrame(wx.Frame):
#!XRCED:begin-block:xrcMainFrame.PreCreate
def PreCreate(self, pre):
""" This function is called during the class's initialization.
Override it for custom setup before the window is created usually to
set additional window styles using SetWindowStyle() and SetExtraStyle().
"""
pass
#!XRCED:end-block:xrcMainFrame.PreCreate
def __init__(self, parent):
# Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
pre = wx.PreFrame()
self.PreCreate(pre)
get_resources().LoadOnFrame(pre, parent, "MainFrame")
self.PostCreate(pre)
# Define variables for the controls, bind event handlers
# ------------------------ Resource data ----------------------
def __init_resources():
global __res
__res = xrc.EmptyXmlResource()
__res.Load('twoBtns.xrc')
这有点难看,但是如果你能读懂正常的 wxPython,那么你应该能弄明白这一点。现在让我们创建这个代码的一个子类。我们想这样做的主要原因是,我们可以改变 XRC 文件和后续生成的代码,我们的子类基本上可以保持不变。它帮助我们将模型(XML)与视图(wxPython 代码)分开。无论如何,这里有一个简单的例子:
# twoBtns_xrc_subclass.py
import twoBtns_xrc
import wx
########################################################################
class XrcFrameSubClass(twoBtns_xrc.xrcMainFrame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
twoBtns_xrc.xrcMainFrame.__init__(self, parent=None)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = XrcFrameSubClass()
app.MainLoop()
注意,我们导入了模块“twoBtns_xrc”,它类似于我所说的 XRCfile。XRCed 将“_xrc”部分添加到 Python 文件名中。导入后,我们就可以访问 XRC 框架对象并对其进行子类化。这个例子有点无聊,所以让我们添加一些事件。在 XRCed 中重新打开 XRC 文件,并选择其中一个按钮。左侧的最后一个选项卡应标记为代码。选择该选项,并在“EVT 按钮”事件旁边打上勾号。对另一个按钮做同样的事情。保存文件,然后重新生成 Python 文件。你应该有这样的东西:
# This file was automatically generated by pywxrc.
# -*- coding: UTF-8 -*-
import wx
import wx.xrc as xrc
__res = None
def get_resources():
""" This function provides access to the XML resources in this module."""
global __res
if __res == None:
__init_resources()
return __res
class xrcMainFrame(wx.Frame):
#!XRCED:begin-block:xrcMainFrame.PreCreate
def PreCreate(self, pre):
""" This function is called during the class's initialization.
Override it for custom setup before the window is created usually to
set additional window styles using SetWindowStyle() and SetExtraStyle().
"""
pass
#!XRCED:end-block:xrcMainFrame.PreCreate
def __init__(self, parent):
# Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
pre = wx.PreFrame()
self.PreCreate(pre)
get_resources().LoadOnFrame(pre, parent, "MainFrame")
self.PostCreate(pre)
# Define variables for the controls, bind event handlers
self.Bind(wx.EVT_BUTTON, self.OnButton_okBtn, id=xrc.XRCID('okBtn'))
self.Bind(wx.EVT_BUTTON, self.OnButton_cancelBtn, id=xrc.XRCID('cancelBtn'))
#!XRCED:begin-block:xrcMainFrame.OnButton_okBtn
def OnButton_okBtn(self, evt):
# Replace with event handler code
print "OnButton_okBtn()"
#!XRCED:end-block:xrcMainFrame.OnButton_okBtn
#!XRCED:begin-block:xrcMainFrame.OnButton_cancelBtn
def OnButton_cancelBtn(self, evt):
# Replace with event handler code
print "OnButton_cancelBtn()"
#!XRCED:end-block:xrcMainFrame.OnButton_cancelBtn
# ------------------------ Resource data ----------------------
def __init_resources():
global __res
__res = xrc.EmptyXmlResource()
__res.Load('twoBtns_v2.xrc')
现在我们有进展了。让我们稍微改变一下子类代码,以符合我们在父类中所做的更改。
# twoBtns_xrc_subclass_v2.py
from twoBtns_xrc_v2 import xrcMainFrame
import wx
########################################################################
class XrcFrameSubClass(xrcMainFrame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
xrcMainFrame.__init__(self, parent=None)
self.Show()
#----------------------------------------------------------------------
def OnButton_okBtn(self, event):
""""""
print "You pressed the OK button!"
#----------------------------------------------------------------------
def OnButton_cancelBtn(self, event):
""""""
print "You pressed the Cancel button!"
if __name__ == "__main__":
app = wx.App(False)
frame = XrcFrameSubClass()
app.MainLoop()
现在我们可以覆盖按钮事件。这意味着所有的按钮构建和事件绑定都是由 XRCed 自动完成的。我们所要做的就是生成 Python 代码的子类,并充实事件处理程序。你可能已经注意到还有一个“生成 gettext 字符串”选项。您可以使用它来生成一个函数,该函数将返回代码中的所有标签。你为什么要这么做?嗯,这让你很容易把标签翻译成其他语言。更多信息请参见 wxPython 关于国际化的 wiki 页面。
包扎
这涵盖了使用 XRCed 应用程序的基础知识。希望你现在已经足够了解如何明智地使用它,并且能够使用这些快捷方式创建一些真正令人惊叹的代码。如果你需要帮助,一定要查看下面的链接,给 wxPython 邮件列表发邮件,或者试着在 IRC 频道上骚扰 wx 的人。
进一步阅读
- XRC 教程
- XRCed wiki 页面
- XRC 组件插件 wiki 页面
- XRC 数据模型维基页面
- XRCed 重构项目 wiki 页面
- XRCed 教程 wiki 页面
- XRCed 愿望清单维基页面`
wxPython 和 PubSub:一个简单的教程
原文:https://www.blog.pythonlibrary.org/2010/06/27/wxpython-and-pubsub-a-simple-tutorial/
我在 wxPython 邮件列表或其 IRC 频道上看到许多关于框架间通信的问题,大多数时候开发人员需要的是 PubSub 模块。发布者/订阅者模型是向一个或多个侦听器发送消息的一种方式。你可以在这里阅读。据说观察者模式是基于发布/订阅模式的。在 wxPython land 中,我们有 pubsub 模块,可以从 wx.lib.pubsub 中访问。它实际上包含在 wxPython 中,但你也可以从其源 Forge 中下载它作为一个独立的模块。pubsub 的替代方案是 PyDispatcher 模块。
无论如何,在这篇文章中,我们不会研究这些模块背后的理论。相反,我们将在 wxPython 中使用一个半实用的示例来展示如何使用内置版本的 pubsub 在两个框架之间进行通信。如果你还同意我的观点,那么我鼓励你继续读下去!
更新:本文为 wxPython 2.8。如果你碰巧在使用 wxPython 的新版本,那么你会想要阅读我这篇文章的新版本这里
如何在两个框架之间传递信息
我发现有时我需要打开一个非模态框架来获取用户的信息,然后将这些信息传递回应用程序的主框架。其他时候,我只需要告诉我的一个框架,另一个已经关闭。在这两种情况下,pubsub 来拯救。下面的例子将实际演示这两个问题的解决方案。
import wx
from wx.lib.pubsub import Publisher
########################################################################
class OtherFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY, "Secondary Frame")
panel = wx.Panel(self)
msg = "Enter a Message to send to the main frame"
instructions = wx.StaticText(panel, label=msg)
self.msgTxt = wx.TextCtrl(panel, value="")
closeBtn = wx.Button(panel, label="Send and Close")
closeBtn.Bind(wx.EVT_BUTTON, self.onSendAndClose)
sizer = wx.BoxSizer(wx.VERTICAL)
flags = wx.ALL|wx.CENTER
sizer.Add(instructions, 0, flags, 5)
sizer.Add(self.msgTxt, 0, flags, 5)
sizer.Add(closeBtn, 0, flags, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onSendAndClose(self, event):
"""
Send a message and close frame
"""
msg = self.msgTxt.GetValue()
Publisher().sendMessage(("show.mainframe"), msg)
self.Close()
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
self.frame = parent
Publisher().subscribe(self.showFrame, ("show.mainframe"))
self.pubsubText = wx.TextCtrl(self, value="")
hideBtn = wx.Button(self, label="Hide")
hideBtn.Bind(wx.EVT_BUTTON, self.hideFrame)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.pubsubText, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(hideBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def hideFrame(self, event):
""""""
self.frame.Hide()
new_frame = OtherFrame()
new_frame.Show()
#----------------------------------------------------------------------
def showFrame(self, msg):
"""
Shows the frame and shows the message sent in the
text control
"""
self.pubsubText.SetValue(msg.data)
frame = self.GetParent()
frame.Show()
########################################################################
class MainFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Pubsub Tutorial")
panel = MainPanel(self)
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
这段代码的第一站是 MainPanel 类。请注意下面一行:
Publisher().subscribe(self.showFrame, ("show.mainframe"))
这创建了一个订阅“show.mainframe”主题的监听器 singleton(又名:接收器)。程序的其他部分可以发布到该主题,监听器将获取它们并调用“showFrame”方法。要了解这一点,请查看“OtherFrame”类的“onSendAndClose”方法。
# OtherFrame Class
def onSendAndClose(self, event):
"""
Send a message and close frame
"""
msg = self.msgTxt.GetValue()
Publisher().sendMessage(("show.mainframe"), msg)
self.Close()
这里,我们获取文本控件的值,并将其发送回主框架。要发送它,我们调用发布者对象的 sendMessage 方法,并向其传递主题字符串和消息。该消息可以是对象列表,也可以只是单个对象。在这种情况下,它只是一个字符串。回到主面板,调用 showFrame 方法。看起来是这样的:
# MainPanel class
def showFrame(self, msg):
"""
Shows the frame and shows the message sent in the
text control
"""
self.pubsubText.SetValue(msg.data)
frame = self.GetParent()
frame.Show()
在这个方法中,我们通过 pubsub 的数据属性提取通过 pubsub 发送的数据。如果我们使用一个列表发送多个条目,我们需要做一些类似 msg.data[0]的事情来获得正确的条目(假设字符串在元素一中)。最新的 pubsub 有一个稍微不同的 API,你可以在它的食谱中查看。最新的 API 是从 wxPython 2.8.11.0 开始提供的。注意:我在用最新的 pubsub 创建二进制文件时遇到了一些麻烦,因为我使用的是稍旧的 API。参见此线程以了解详细信息和一些可能的解决方法。
现在您已经了解了在项目中使用 pubsub 的基本知识。这个例子展示了如何在两个框架之间进行通信,即使其中一个是隐藏的!它还展示了如何将信息从一个框架传递到另一个框架。玩得开心!
附加阅读
wxPython 和 SqlAlchemy:MVC 和 CRUD 简介
原文:https://www.blog.pythonlibrary.org/2011/11/10/wxpython-and-sqlalchemy-an-intro-to-mvc-and-crud/
在本文中,我们将创建一个程序来存储我们的个人书籍,或者仅仅是你读过的书籍。它将演示如何结合 wxPython 和 SQL 对象关系映射器(ORM)SqlAlchemy。这个程序还将向您介绍模型-视图-控制器(MVC)和“创建、读取、更新和销毁”(CRUD)方法。目的是向您展示如何创建一个能够完成以下任务的应用程序:
- 创建数据库(创建)
- 向数据库添加记录(类似于创建)
- 显示记录(阅读和查看)
- 修改记录(更新)
- 删除记录(销毁)
本教程的顺序将遵循 MVC,所以我们将从模型开始。无论如何,模型是 GUI(视图)的基础,所以这是一个很好的起点。
先决条件
在我们进入模型部分之前,您可能需要下载一些额外的内容。这是你需要的:
一旦你安装了所有这些,你就可以进入下一部分了!
模型
我们的模型包含我们的 SqlAlchemy 数据库类和 ObjectListView 小部件的模型,稍后我们将使用它来显示我们的数据。SqlAlchemy 类使用的是声明系统,它结合了表创建和类创建。如果你只是看一下代码,就更容易理解了。
# model.py
from sqlalchemy import Table, Column, create_engine
from sqlalchemy import Integer, ForeignKey, String, Unicode
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref, relation
engine = create_engine("sqlite:///devdata.db", echo=True)
DeclarativeBase = declarative_base(engine)
metadata = DeclarativeBase.metadata
########################################################################
class OlvBook(object):
"""
Book model for ObjectListView
"""
#----------------------------------------------------------------------
def __init__(self, id, title, author, isbn, publisher, last_name, first_name):
self.id = id # unique row id from database
self.title = title
self.author = author
self.isbn = isbn
self.publisher = publisher
self.last_name = last_name
self.first_name = first_name
########################################################################
class Person(DeclarativeBase):
""""""
__tablename__ = "people"
id = Column(Integer, primary_key=True)
first_name = Column("first_name", String(50))
last_name = Column("last_name", String(50))
#----------------------------------------------------------------------
def __repr__(self):
""""""
return "" % (self.first_name, self.last_name)
########################################################################
class Book(DeclarativeBase):
""""""
__tablename__ = "books"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("people.id"))
title = Column("title", Unicode)
isbn = Column("isbn", Unicode)
publisher = Column("publisher", Unicode)
person = relation("Person", backref="books", cascade_backrefs=False)
metadata.create_all()
我们不打算花太多时间来解析这段代码,因为有几个 SqlAlchemy 教程,而且 SqlAlchemy 文档非常好。相反,我们将简单介绍一下。代码的开头导入了我们需要的所有 SqlAlchemy 内容。最重要的一点是在哪里创建引擎:
engine = create_engine("sqlite:///devdata.db", echo=True)
这意味着我们将在模型所在的目录中创建一个 SQLite 数据库。您也可以传入一个路径,就像这样:create _ engine(" SQLite:///c:/path/to/dev data . db ")您可以随意命名数据库,包括扩展名。出于调试目的,打开回声参数。它将输出 SqlAlchemy 发出的所有 SQL,因此我们可以使用它来解决我们的查询或在创建数据库时可能突然出现的问题。
我们遇到的第一个类是 OlvBook ,它定义了一个 Book 类。稍后我们将使用它来帮助我们填充前面提到的 ObjectListView 小部件,我们将使用它来显示我们的记录。接下来的两类分别叫做人和书。表名是用神奇的方法定义的:tablename。Person 是 Book 中的外键,我们用它来指代我们的作者。最初,我认为我可以使用 Person 来添加多个作者或其他与这本书相关的人,比如艺术家。或者可能是我的一张 CD 的曲作者,但我决定保持简单。您可以稍微增强这个模型,使其更加多态,这样它就能以那种方式运行。
无论如何,如果您运行这个脚本,它将执行最后一行: metadata.create_all() 这将导致创建数据库文件并定义这两个表。现在我们准备向它添加数据。但首先,我们需要看看风景!
景色
视图是我们的 wxPython 接口,我们将使用它来显示记录以及过滤、添加、修改和删除它们。我们的 GUI 代码只有 150 多行。我们来看看吧!
import addModRecord
import commonDlgs
import controller
import wx
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class BookPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
try:
self.bookResults = controller.getAllRecords()
except:
self.bookResults = []
mainSizer = wx.BoxSizer(wx.VERTICAL)
searchSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)
# create the search related widgets
cat = ["Author", "Title", "ISBN", "Publisher"]
searchByLbl = wx.StaticText(self, label="Search By:")
searchByLbl.SetFont(font)
searchSizer.Add(searchByLbl, 0, wx.ALL, 5)
self.categories = wx.ComboBox(self, value="Author", choices=cat)
searchSizer.Add(self.categories, 0, wx.ALL, 5)
self.search = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
self.search.Bind(wx.EVT_TEXT_ENTER, self.onSearch)
searchSizer.Add(self.search, 0, wx.ALL, 5)
self.bookResultsOlv = ObjectListView(self, style=wx.LC_REPORT
|wx.SUNKEN_BORDER)
self.bookResultsOlv.SetEmptyListMsg("No Records Found")
self.setBooks()
# create the button row
addRecordBtn = wx.Button(self, label="Add")
addRecordBtn.Bind(wx.EVT_BUTTON, self.onAddRecord)
btnSizer.Add(addRecordBtn, 0, wx.ALL, 5)
editRecordBtn = wx.Button(self, label="Edit")
editRecordBtn.Bind(wx.EVT_BUTTON, self.onEditRecord)
btnSizer.Add(editRecordBtn, 0, wx.ALL, 5)
deleteRecordBtn = wx.Button(self, label="Delete")
deleteRecordBtn.Bind(wx.EVT_BUTTON, self.onDelete)
btnSizer.Add(deleteRecordBtn, 0, wx.ALL, 5)
showAllBtn = wx.Button(self, label="Show All")
showAllBtn.Bind(wx.EVT_BUTTON, self.onShowAllRecord)
btnSizer.Add(showAllBtn, 0, wx.ALL, 5)
mainSizer.Add(searchSizer)
mainSizer.Add(self.bookResultsOlv, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(btnSizer, 0, wx.CENTER)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def onAddRecord(self, event):
"""
Add a record to the database
"""
dlg = addModRecord.AddModRecDialog()
dlg.ShowModal()
dlg.Destroy()
self.showAllRecords()
#----------------------------------------------------------------------
def onEditRecord(self, event):
"""
Edit a record
"""
selectedRow = self.bookResultsOlv.GetSelectedObject()
if selectedRow == None:
commonDlgs.showMessageDlg("No row selected!", "Error")
return
dlg = addModRecord.AddModRecDialog(selectedRow, title="Modify",
addRecord=False)
dlg.ShowModal()
dlg.Destroy()
self.showAllRecords()
#----------------------------------------------------------------------
def onDelete(self, event):
"""
Delete a record
"""
selectedRow = self.bookResultsOlv.GetSelectedObject()
if selectedRow == None:
commonDlgs.showMessageDlg("No row selected!", "Error")
return
controller.deleteRecord(selectedRow.id)
self.showAllRecords()
#----------------------------------------------------------------------
def onSearch(self, event):
"""
Searches database based on the user's filter choice and keyword
"""
filterChoice = self.categories.GetValue()
keyword = self.search.GetValue()
print "%s %s" % (filterChoice, keyword)
self.bookResults = controller.searchRecords(filterChoice, keyword)
self.setBooks()
#----------------------------------------------------------------------
def onShowAllRecord(self, event):
"""
Updates the record list to show all of them
"""
self.showAllRecords()
#----------------------------------------------------------------------
def setBooks(self):
self.bookResultsOlv.SetColumns([
ColumnDefn("Title", "left", 350, "title"),
ColumnDefn("Author", "left", 150, "author"),
ColumnDefn("ISBN", "right", 150, "isbn"),
ColumnDefn("Publisher", "left", 150, "publisher")
])
self.bookResultsOlv.SetObjects(self.bookResults)
#----------------------------------------------------------------------
def showAllRecords(self):
"""
Show all records in the object list view control
"""
self.bookResults = controller.getAllRecords()
self.setBooks()
########################################################################
class BookFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="MvP Media Organizer",
size=(800, 600))
panel = BookPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = BookFrame()
app.MainLoop()
您会注意到我们导入了一些自定义项目,如 addModRecord、commonDlgs 和 controller。 addModRecord 是一个对话框,可用于添加记录和修改记录。 commonDlgs 模块只是用来简化创建消息对话框。如果我们给这个程序添加一些新功能,我们会在那个模块中添加其他小的对话框代码。控制器模块是所有 SqlAlchemy 代码执行的地方。大多数程序都有一些配置选项,所以这是存储代码的好地方。
无论如何,让我们回顾一下 GUI 的亮点。搜索栏被设置为当用户按 enter 时进行搜索。这就是风格标志 wx。TE_PROCESS_ENTER 的意思是。这和 ObjectListView 的 SetEmptyListMsg 方法可能是这里唯一真正陌生的东西。当控件中没有数据时,该方法只向用户显示一条消息。
onAddRecord 的和 onEditRecord 的都调用我们的 addModRecord 模块来显示添加/修改对话框。它应该是这样的:
方法 onEditRecord 设置了几个影响小部件外观的参数,并传递一些信息来填充字段。 onDelete 方法只是获取选中的项目并调用控制器删除它,然后更新显示。
我们的 onSearch 方法可以实现您的预期。它根据用户选择的过滤器和他们的关键字在我们的数据库中搜索记录。他们可以选择“作者”、“书名”、“ISBN”或“出版商”作为他们的过滤器。
onShowAllRecord 方法只是在显示屏上显示所有的记录。未来的增强将是限制当前显示记录的数量,这样我们就不会导致应用程序速度大幅下降。 setBooks 方法只是更新显示的记录。
现在我们已经到了拼图的最后一块:控制器。
控制器和 CRUD
控制器是将模型和视图结合在一起的粘合剂。至少,在我看来是这样。它使用该模型进行查询以及添加、更新和删除记录。它还向视图(我们的 GUI)报告数据库的更新,这样它就可以适当地更新。让我们看看我们的控制器代码:
# controller.py
from model import Book, Person, OlvBook
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
#----------------------------------------------------------------------
def addRecord(data):
"""
Data should be a tuple of two dictionaries in the following format:
("author":{"first_name":"John", "last_name":"Doe"},
"book":{"title":"Some book", "isbn":"1234567890",
"publisher":"Packt"}
)
"""
book = Book()
book.title = data["book"]["title"]
book.isbn = data["book"]["isbn"]
book.publisher = data["book"]["publisher"]
author = Person()
author.first_name = data["author"]["first_name"]
author.last_name = data["author"]["last_name"]
book.person = author
# connect to session and commit data to database
session = connectToDatabase()
session.add(book)
session.commit()
session.close()
#----------------------------------------------------------------------
def connectToDatabase():
"""
Connect to our SQLite database and return a Session object
"""
engine = create_engine("sqlite:///devdata.db", echo=True)
Session = sessionmaker(bind=engine)
session = Session()
return session
#----------------------------------------------------------------------
def convertResults(results):
"""
Convert results to OlvBook objects
"""
print
books = []
for record in results:
author = "%s %s" % (record.person.first_name,
record.person.last_name)
book = OlvBook(record.id, record.title, author,
record.isbn, record.publisher,
record.person.last_name,
record.person.first_name
)
books.append(book)
return books
#----------------------------------------------------------------------
def deleteRecord(idNum):
"""
Delete a record from the database
"""
session = connectToDatabase()
record = session.query(Book).filter_by(id=idNum).one()
session.delete(record)
session.commit()
session.close()
#----------------------------------------------------------------------
def editRecord(idNum, row):
"""
Edit a record
"""
session = connectToDatabase()
record = session.query(Book).filter_by(id=idNum).one()
print
record.title = row["title"]
record.person.first_name = row["first_name"]
record.person.last_name = row["last_name"]
record.isbn = row["isbn"]
record.publisher = row["publisher"]
session.add(record)
session.commit()
session.close()
#----------------------------------------------------------------------
def getAllRecords():
"""
Get all records and return them
"""
session = connectToDatabase()
result = session.query(Book).all()
books = convertResults(result)
session.close()
return books
#----------------------------------------------------------------------
def searchRecords(filterChoice, keyword):
"""
Searches the database based on the filter chosen and the keyword
given by the user
"""
session = connectToDatabase()
if filterChoice == "Author":
qry = session.query(Person)
result = qry.filter(Person.first_name.contains('%s' % keyword)).all()
records = []
for record in result:
for book in record.books:
records.append(book)
result = records
print result
elif filterChoice == "Title":
qry = session.query(Book)
result = qry.filter(Book.title.contains('%s' % keyword)).all()
elif filterChoice == "ISBN":
qry = session.query(Book)
result = qry.filter(Book.isbn.contains('%s' % keyword)).all()
else:
qry = session.query(Book)
result = qry.filter(Book.publisher.contains('%s' % keyword)).all()
books = convertResults(result)
print
return books
我们的控制器定义了 7 个功能:
- addRecord(添加记录)
- 连接数据库
- 转换结果
- 删除记录
- 编辑记录
- getAllRecords
- 搜索记录
这些都是不言自明的。然而,我们将花一点时间来解释 convertResults 做什么以及 searchRecords 如何工作。
convertResults 方法用于将我们通过查询数据库获得的 SqlAlchemy 图书结果转换为 ObjectListView 图书模型对象。这是必要的,这样我们就可以在数据库更新或显示查询结果时显示它们。如您所见,我们只是迭代结果并适当地处理数据。
在 searchRecords 方法中,我们传入过滤器和关键字,并通过条件“if”语句使用我们的会话对象对数据库进行查询。根据过滤器的类型,我们进行不同的查询。在很大程度上,查询是相同的,所以这可能需要一些重构。我将把这作为一项任务留给读者去解决。一旦我们转换了结果,我们就将它们返回给调用函数,在本例中,调用函数恰好是 GUI 的搜索事件处理程序。
CRUD 部分包含在我们创建、读取、更新和删除记录的其他方法中。
包扎
现在您应该知道使用 MVC 框架创建 wxPython 程序背后的基础知识了。这不是一个完美的例子,我也不认为它是,但是它应该给你一个如何开始制作自己的例子的想法。它还展示了如何使用 SqlAlchemy 与 SQLite 数据库进行交互。我希望这对您有所帮助,并期待听到您的评论。
源代码
- 您可以从位桶中检查代码
wxPython 和 SQLAlchemy:加载随机 SQLite 数据库以供查看
最近有人联系我,询问是否有任何 wxPython 应用程序可以提供 SQLite 数据库。据我所知,他们希望能够自省数据库并查看表格,可能会使用 wx.grid.Grid 小部件。我发现网格小部件非常强大,但也很难使用。所以我花了一些时间编写了一个超级简单的应用程序,它使用了 ObjectListView 小部件。
入门指南
首先,我们需要一个数据库来测试。所以我最终用 SQLAlchemy 编写了一个简单的数据库创建脚本,用几行填充几个表。这是我正在使用的脚本:
from sqlalchemy import create_engine
from sqlalchemy.orm import relationship, backref, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey, Integer, String
engine = create_engine('sqlite:///example.db', echo=True)
Base = declarative_base()
########################################################################
class Book(Base):
""""""
__tablename__ = "books"
id = Column(Integer, primary_key = True)
title = Column(String)
author = Column(String)
#----------------------------------------------------------------------
def __init__(self, title, author):
"""Constructor"""
self.title = title
self.author = author
########################################################################
class Character(Base):
""""""
__tablename__ = "characters"
id = Column(Integer, primary_key = True)
first_name = Column(String)
last_name = Column(String)
book_id = Column(ForeignKey("books.id"))
book = relationship("Book", backref=backref("characters", order_by=id))
#----------------------------------------------------------------------
def __init__(self, first_name, last_name):
"""Constructor"""
self.first_name = first_name
self.last_name = last_name
#----------------------------------------------------------------------
@property
def fullname(self):
""""""
return "%s %s" % (self.first_name, self.last_name)
#----------------------------------------------------------------------
def __repr__(self):
""""""
return "" % self.fullname
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
print
new_char = Character("Hermione", "Granger")
new_char.book = Book("Harry Potter", "JK Rowling")
session.add(new_char)
new_char = Character("Sherlock", "Holmes")
new_char.book = Book("The Adventure of the Creeping Man", "Arthur Conan Doyle")
session.add(new_char)
session.commit()
我将假设您对 SQLAlchemy 有足够的了解,能够理解这一点。如果没有,他们有一些我用过的 Python 项目中最好的文档。
创建查看器
现在我们只需要创建数据库表查看器。这花了我一点时间,但我最终弄明白了。请注意,它基本上是阿尔法质量,没有任何错误检查。
import os
import wx
from ObjectListView import ObjectListView, ColumnDefn
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import mapper, sessionmaker, clear_mappers
########################################################################
class GenericDBClass(object):
""""""
pass
########################################################################
class MainPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.db_data = []
self.current_directory = os.getcwd()
self.dataOlv = ObjectListView(self, wx.ID_ANY, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.dataOlv.Hide()
# Allow the cell values to be edited when double-clicked
self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
# load DB
loadDBBtn = wx.Button(self, label="Load DB")
loadDBBtn.Bind(wx.EVT_BUTTON, self.loadDatabase)
self.table_names = []
self.tableCbo = wx.ComboBox(self, value="", choices=self.table_names)
self.tableCbo.Bind(wx.EVT_COMBOBOX, self.loadTable)
# Create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(loadDBBtn, 0, wx.ALL|wx.CENTER, 5)
mainSizer.Add(self.tableCbo, 0, wx.ALL|wx.CENTER, 5)
mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def loadTable(self, event):
""""""
print
current_table = self.tableCbo.GetValue()
metadata = MetaData(self.engine)
table = Table(current_table, metadata, autoload=True, autoload_with=self.engine)
self.columns = table.columns.keys()
clear_mappers() #http://docs.sqlalchemy.org/en/rel_0_6/orm/mapper_config.html#sqlalchemy.orm.clear_mappers
mapper(GenericDBClass, table)
Session = sessionmaker(bind=self.engine)
session = Session()
self.db_data = session.query(GenericDBClass).all()
self.setData()
self.dataOlv.Show()
self.Layout()
#----------------------------------------------------------------------
def loadDatabase(self, event):
""""""
wildcard = "All files (*.*)|*.*"
dlg = wx.FileDialog(
self, message="Choose a file",
defaultDir=self.current_directory,
defaultFile="",
wildcard=wildcard,
style=wx.OPEN | wx.CHANGE_DIR
)
if dlg.ShowModal() == wx.ID_OK:
db_path = dlg.GetPath()
dlg.Destroy()
else:
dlg.Destroy()
return
self.engine = create_engine('sqlite:///%s' % db_path, echo=True)
self.table_names = self.engine.table_names()
self.tableCbo.SetItems(self.table_names)
self.tableCbo.SetValue(self.table_names[0])
self.loadTable("")
#----------------------------------------------------------------------
def setData(self, data=None):
olv_columns = []
for column in self.columns:
olv_columns.append(ColumnDefn(column.title(), "left", 120, column.lower()))
self.dataOlv.SetColumns(olv_columns)
self.dataOlv.SetObjects(self.db_data)
########################################################################
class MainFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
title="Database Viewer", size=(800,600))
panel = MainPanel(self)
########################################################################
class GenApp(wx.App):
#----------------------------------------------------------------------
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
#----------------------------------------------------------------------
def OnInit(self):
# create frame here
frame = MainFrame()
frame.Show()
return True
#----------------------------------------------------------------------
def main():
"""
Run the demo
"""
app = GenApp()
app.MainLoop()
if __name__ == "__main__":
main()
让我们花点时间来分析一下这是如何工作的。您会注意到,在我们创建了 ObjectListView 小部件之后,我们隐藏了它,因为我们还不知道它包含了什么。这不会发生,直到用户点击 Load DB 按钮。在 loadDatabase 处理程序中,我们弹出一个文件对话框,允许用户选择他们想要加载的 SQLite 数据库文件。对于这个测试,我建议使用我们之前创建的数据库。选择之后,我们创建一个 SQLALchemy 引擎,从中提取表名,将 combobox 下拉列表设置为表名列表,然后通过调用我们的 loadTable 方法加载列表中的第一个表。
在 loadTable 中,我们使用 SQLAlchemy 方便的 autoload 特性将数据库中的数据“反映”到一个表对象中。我们调用 clear_mappers 是因为我们需要确保当前没有任何东西被映射到我们的虚拟类,然后我们将新表映射到这个类。最后,我们创建一个 SQLAlchemy 会话,并执行一个简单的 **SELECT *** 查询,从数据库中提取所有记录,并将它们传递给 ObjectListView 小部件,然后我们将展示这个小部件。
包扎
我试着对 Mozilla 的 places 数据库运行这个脚本,但是我的小应用程序不喜欢它。请随意尝试用您的数据库来打破它。目前这只是一个概念验证。大约一年前,我自己也有过类似的想法,我可能会在未来尝试改进一下。同时,我想贴上我的初稿,看看我能得到什么样的反馈。我希望你喜欢它!
注意:使用 Python 2.6.6 在 Windows 7、wxPython 2.8.12.1 上测试
进一步阅读
源代码
wxPython 和线程
原文:https://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
如果你经常使用 Python 中的 GUI,你就会知道有时你需要时不时地执行一些长时间运行的过程。当然,如果您像使用命令行程序那样做,那么您将会大吃一惊。在大多数情况下,你最终会阻塞 GUI 的事件循环,用户会看到你的程序冻结。你能做些什么来避免不幸呢?当然,在另一个线程或进程中启动任务!在本文中,我们将看看如何使用 wxPython 和 Python 的线程模块来实现这一点。
wxPython 的线程安全方法
在 wxPython 世界中,有三种相关的“线程安全”方法。如果你在更新用户界面时没有使用这三个中的一个,那么你可能会遇到奇怪的问题。有时你的图形用户界面会工作得很好。其他时候,它会莫名其妙地使 Python 崩溃。因此需要线程安全的方法。下面是 wxPython 提供的三种线程安全方法:
- wx。事件后
- wx.CallAfter
- wx.calllater 后期版本
据罗宾·邓恩(wxPython 的创作者)说,wx。CallAfter 使用 wx。向应用程序对象发送事件。应用程序将有一个绑定到该事件的事件处理程序,并在收到事件时根据程序员编写的代码做出反应。我的理解是 wx。CallLater 调用 wx。带有指定时间限制的 CallAfter,以便您可以告诉它在发送事件之前要等待多长时间。
Robin Dunn 还指出,Python 全局解释器锁(GIL)将阻止多个线程同时执行 Python 字节码,这可能会限制程序使用多少 CPU 内核。另一方面,他还说“wxPython 在调用 wx APIs 时释放 GIL,这样其他线程就可以在那时运行”。换句话说,在多核机器上使用线程时,您的收益可能会有所不同。我发现这个讨论既有趣又令人困惑...
无论如何,这对于三个 wx 方法来说意味着。CallLater 是 wx 中最抽象的线程安全方法。CallAfter next 和 wx。PostEvent 是最低级的。在下面的例子中,您将看到如何使用 wx。CallAfter 和 wx。更新您的 wxPython 程序。
wxPython,线程,wx。CallAfter 和 PubSub
在 wxPython 邮件列表上,您会看到专家告诉其他人使用 wx。CallAfter 和 PubSub 一起从另一个线程与 wxPython 应用程序通信。我可能已经告诉人们这么做了。所以在下面的例子中,这正是我们要做的:
import time
import wx
from threading import Thread
from wx.lib.pubsub import Publisher
########################################################################
class TestThread(Thread):
"""Test Worker Thread Class."""
#----------------------------------------------------------------------
def __init__(self):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.start() # start the thread
#----------------------------------------------------------------------
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
for i in range(6):
time.sleep(10)
wx.CallAfter(self.postTime, i)
time.sleep(5)
wx.CallAfter(Publisher().sendMessage, "update", "Thread finished!")
#----------------------------------------------------------------------
def postTime(self, amt):
"""
Send time to GUI
"""
amtOfTime = (amt + 1) * 10
Publisher().sendMessage("update", amtOfTime)
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.displayLbl = wx.StaticText(panel, label="Amount of time since thread started goes here")
self.btn = btn = wx.Button(panel, label="Start Thread")
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.displayLbl, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
# create a pubsub receiver
Publisher().subscribe(self.updateDisplay, "update")
#----------------------------------------------------------------------
def onButton(self, event):
"""
Runs the thread
"""
TestThread()
self.displayLbl.SetLabel("Thread started!")
btn = event.GetEventObject()
btn.Disable()
#----------------------------------------------------------------------
def updateDisplay(self, msg):
"""
Receives data from thread and updates the display
"""
t = msg.data
if isinstance(t, int):
self.displayLbl.SetLabel("Time since thread started: %s seconds" % t)
else:
self.displayLbl.SetLabel("%s" % t)
self.btn.Enable()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
我们将使用 Python 的 time 模块来模拟我们的长时间运行过程。然而,请随意将更好的东西放在它的位置上。在一个真实的例子中,我使用一个线程打开 Adobe Reader 并将 PDF 发送到打印机。这可能看起来没什么特别的,但是当我不使用线程时,当文档被发送到打印机时,我的应用程序中的打印按钮会保持按下状态,我的 GUI 会一直挂起,直到这个操作完成。甚至一两秒钟对用户来说都是显而易见的!
无论如何,让我们看看这是如何工作的。在我们的线程类中(如下所示),我们覆盖了“run”方法,这样它就能做我们想做的事情。这个线程是在我们实例化它时启动的,因为我们在它的 init 方法中有“self.start()”。在“run”方法中,我们在 6 的范围内循环,在迭代之间休息 10 秒,然后使用 wx 更新我们的用户界面。CallAfter 和 PubSub。当循环结束时,我们向应用程序发送一条最终消息,让用户知道发生了什么。
########################################################################
class TestThread(Thread):
"""Test Worker Thread Class."""
#----------------------------------------------------------------------
def __init__(self):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.start() # start the thread
#----------------------------------------------------------------------
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
for i in range(6):
time.sleep(10)
wx.CallAfter(self.postTime, i)
time.sleep(5)
wx.CallAfter(Publisher().sendMessage, "update", "Thread finished!")
#----------------------------------------------------------------------
def postTime(self, amt):
"""
Send time to GUI
"""
amtOfTime = (amt + 1) * 10
Publisher().sendMessage("update", amtOfTime)
您会注意到,在我们的 wxPython 代码中,我们使用一个按钮事件处理程序来启动线程。我们还禁用了该按钮,这样我们就不会意外地启动额外的线程。如果我们有一堆程序在运行,UI 会随机地说它已经完成了,而实际上并没有完成,这将会非常令人困惑。这对读者来说是一个很好的练习。你可以显示线程的 PID,这样你就知道哪个是哪个了...您可能希望将这些信息输出到一个滚动文本控件,这样您就可以看到各种线程的活动。
这里最后一个有趣的部分可能是 PubSub 接收器及其事件处理程序:
def updateDisplay(self, msg):
"""
Receives data from thread and updates the display
"""
t = msg.data
if isinstance(t, int):
self.displayLbl.SetLabel("Time since thread started: %s seconds" % t)
else:
self.displayLbl.SetLabel("%s" % t)
self.btn.Enable()
看看我们如何从线程中提取消息并使用它来更新我们的显示?我们还使用接收到的数据类型来告诉我们应该向用户显示什么。很酷吧。现在让我们往下一层,看看如何用 wx 来做这件事。改为 PostEvent。
wx。事件和线程
以下代码基于来自 wxPython wiki 的一个例子。它比 wx 稍微复杂一点。我们刚才看到的 CallAfter 代码,但我有信心我们可以解决它。
import time
import wx
from threading import Thread
# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
"""Define Result Event."""
win.Connect(-1, -1, EVT_RESULT_ID, func)
class ResultEvent(wx.PyEvent):
"""Simple event to carry arbitrary result data."""
def __init__(self, data):
"""Init Result Event."""
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
########################################################################
class TestThread(Thread):
"""Test Worker Thread Class."""
#----------------------------------------------------------------------
def __init__(self, wxObject):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.wxObject = wxObject
self.start() # start the thread
#----------------------------------------------------------------------
def run(self):
"""Run Worker Thread."""
# This is the code executing in the new thread.
for i in range(6):
time.sleep(10)
amtOfTime = (i + 1) * 10
wx.PostEvent(self.wxObject, ResultEvent(amtOfTime))
time.sleep(5)
wx.PostEvent(self.wxObject, ResultEvent("Thread finished!"))
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.displayLbl = wx.StaticText(panel, label="Amount of time since thread started goes here")
self.btn = btn = wx.Button(panel, label="Start Thread")
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.displayLbl, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
# Set up event handler for any worker thread results
EVT_RESULT(self, self.updateDisplay)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Runs the thread
"""
TestThread(self)
self.displayLbl.SetLabel("Thread started!")
btn = event.GetEventObject()
btn.Disable()
#----------------------------------------------------------------------
def updateDisplay(self, msg):
"""
Receives data from thread and updates the display
"""
t = msg.data
if isinstance(t, int):
self.displayLbl.SetLabel("Time since thread started: %s seconds" % t)
else:
self.displayLbl.SetLabel("%s" % t)
self.btn.Enable()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
让我们把它分解一下。对我来说,最令人困惑的是前三部分:
# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
"""Define Result Event."""
win.Connect(-1, -1, EVT_RESULT_ID, func)
class ResultEvent(wx.PyEvent):
"""Simple event to carry arbitrary result data."""
def __init__(self, data):
"""Init Result Event."""
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
EVT 结果标识是这里的关键。它将线程链接到 wx。PyEvent 和奇怪“EVT 结果”函数。在 wxPython 代码中,我们将一个事件处理程序绑定到 EVT _ 结果函数。这使我们能够使用 wx。将事件发送到我们的自定义事件类 ResultEvent。这是做什么的?它通过发出我们绑定的自定义 EVT 结果,将数据发送到 wxPython 程序。我希望这一切都有意义。
一旦你在脑子里想通了,继续读下去。你准备好了吗?很好!你会注意到我们的 TestThread 类和之前的几乎一样,除了我们使用了 wx。将我们的消息发送到 GUI 而不是 PubSub。我们 GUI 的显示更新器中的 API 没有改变。我们仍然只是使用消息的数据属性来提取我们想要的数据。这就是全部了!
包扎
希望您现在知道如何在 wxPython 程序中使用基本的线程技术。还有其他几种线程方法,我们没有机会在这里介绍,比如使用 wx。产量或队列。幸运的是,wxPython wiki 很好地涵盖了这些主题,所以如果您对这些方法感兴趣,请务必查看下面的链接。
进一步阅读
- LongRunningTasks 维基页面
- 非阻塞图形用户界面维基页面
- 工作线程维基页面
- Python:运行 Ping、Traceroute 等
下载
wxPython: ANN:名称空间差异工具
原文:https://www.blog.pythonlibrary.org/2011/11/10/wxpython-ann-namespace-diff-tool/
昨晚,Andrea Gavana 向全世界发布了他的新命名空间比较工具(NDT)。我得到了他的许可,可以在这里为所有不关注 wxPython 邮件列表的人转载他的声明。我认为这听起来是一个非常酷的工具。你应该去看看,看看你有什么想法。下面是公告 :
描述
= = = = = = = = = =
“名称空间差异工具”( NDT)是一个图形用户界面,可以用来发现库的不同版本之间的差异,甚至是同一库的不同迭代/子版本之间的差异。
该工具可用于确定缺少什么并且仍然需要实现
,或者新版本中有什么新内容,哪些项目没有
文档字符串等等。
罗宾·邓恩对最初想法的完整描述:
http://SVN . wxwidgets . org/view VC/wx/wxPython/Phoenix/trunk/todo . txt?六...
:警告:由于 GUI 中的大多数小部件都是所有者绘制或自定义的,
在其他
平台上,界面本身很可能会看起来很混乱(Mac,我在跟你说话)。请尝试创建一个补丁来修复
在这个意义上的任何可能的问题。
:注意:请参考 TODOs 部分,了解仍然需要
实现的事项列表。
需求
为了运行无损检测,需要安装以下软件包:
- Python 2。x(其中 5<= X <= 7); - wxPython >= 2 . 8 . 10;
- SQLAlchemy > = 0.6.4。
更多关于如何使用它的详细说明,待办事项,我测试 NDT 的
库/包列表,截图和下载链接可以在这里找到:
http://xoomer.virgilio.it/infinity77/main/NDT.html
如果你偶然发现了一个 bug(这很有可能),请务必让我知道。但最重要的是,请努力为这个 bug 创建一个
补丁。
根据线程的说法,已经发现并修复了一些 bug。
wxPython 应用程序手册目录更新
原文:https://www.blog.pythonlibrary.org/2019/01/29/wxpython-applications-book-table-of-contents-update/
我目前正在写一本新书,书名是用 wxPython 创建 GUI 应用程序,你可以在 Kickstarter 上支持它。这是本书的当前目录:
- 第 1 章 wxPython 简介
- 第 2 章-创建图像查看器
- 第 3 章-增强图像浏览器
- 第 4 章-创建数据库查看器
- 第 5 章——用 wxPython 编辑数据库
- 第 6 章-计算器
- 第 7 章-归档程序(tarball 创建工具)
- 第 8 章- MP3 标签编辑器
- 第 9 章- XML 编辑器
- 第 10 章- NASA 图像下载/搜索工具
- 第 11 章- PDF 合并/拆分器
还有一章是关于为你的应用程序创建可执行文件和安装程序的,还有几个附录。
我正在做第二个延伸目标,给这本书增加 3 个章节。查看 Kickstarter 了解更多信息。
wxPython:将多个小部件绑定到同一个处理程序
原文:https://www.blog.pythonlibrary.org/2011/09/20/wxpython-binding-multiple-widgets-to-the-same-handler/
如果您已经在 wxPython 社区呆了几个月以上,您可能会认识到以下问题:“如何将多个按钮绑定到同一个事件处理程序,并让它们做不同的事情?”那么,这篇文章将告诉你如何做到这一点。
注意:这篇文章基于这篇博客上一篇关于按钮的文章中的一些代码!
我们开始吧
首先,我们需要编写一些实际包含多个按钮的代码。我们将通过一个例子来展示获取按钮对象的两种不同方法,这样你就可以根据需要操作你的程序。这是您一直在等待的代码:
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Button Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
buttonOne = wx.Button(panel, label="One", name="one")
buttonTwo = wx.Button(panel, label="Two", name="two")
buttonThree = wx.Button(panel, label="Three", name="three")
buttons = [buttonOne, buttonTwo, buttonThree]
for button in buttons:
self.buildButtons(button, sizer)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def buildButtons(self, btn, sizer):
""""""
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer.Add(btn, 0, wx.ALL, 5)
#----------------------------------------------------------------------
def onButton(self, event):
"""
This method is fired when its corresponding button is pressed
"""
button = event.GetEventObject()
print "The button you pressed was labeled: " + button.GetLabel()
print "The button's name is " + button.GetName()
button_id = event.GetId()
button_by_id = self.FindWindowById(button_id)
print "The button you pressed was labeled: " + button_by_id.GetLabel()
print "The button's name is " + button_by_id.GetName()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
首先,我们创建三个按钮对象。然后,为了使事情不那么混乱,我们将它们放入一个列表中,并遍历列表,将按钮添加到一个 sizer 中,并将它们绑定到一个事件处理程序。这是一个减少杂乱代码(即复制和粘贴代码)的好方法,使代码更整洁,更容易调试。有些人继续前进,创建一些精心制作的助手方法,如 buildButtons 可以处理其他小部件,并且更加灵活。
不过,我们真正关心的是事件处理程序本身。在事件处理程序中获取小部件最简单的方法是调用事件对象的 GetEventObject ()方法。这将返回小部件,然后你可以做任何你喜欢的事情。有些人会更改小部件的值或标签,其他人会使用小部件 ID 或唯一名称,并设置一些条件结构,以便在按下该按钮时执行某些操作,而在按下不同的按钮时执行其他操作。功能由您决定。
获取小部件的第二种方法是一个两步过程,我们需要使用事件的 GetID ()方法从事件中提取 ID。然后我们将结果传递给我们的框架对象的 FindWindowById ()方法,我们又一次有了这个小部件。
包扎
现在您知道了将多个小部件绑定到同一个事件处理程序的“秘密”。勇往直前,像没有明天一样编码,创造出令人惊叹的东西!代码可以在新博客的 Mercurial 库上下载。
额外资源
- wxPython:按钮之旅(第 1 部分,共 2 部分)
- 自我。Bind vs. self.button.Bind
- wx。按钮文档
- 在 Youtube 上创建按钮教程
wxPython by Example -添加背景图像(视频)
原文:https://www.blog.pythonlibrary.org/2020/09/22/wxpython-by-example-adding-a-background-image-video/
在本教程中,你将学习如何添加一个图像到你的面板,这样你就有一个背景图像来放置你的小部件。
https://www.youtube.com/embed/C3VX74g75Kc?feature=oembed
wxPython by Example:向标题栏添加图标(视频)
在本视频教程中,您将学习如何向 wxPython 应用程序的标题栏添加图标。这是一个很好的特性,可以添加到你的应用程序中,给你的程序打上一些烙印。
https://www.youtube.com/embed/077hU10WuK0?feature=oembed
wxPython by Example -创建 wx。笔记本(视频)
原文:https://www.blog.pythonlibrary.org/2020/07/10/wxpython-by-example-creating-a-wx-notebook/
在本教程中,您将学习如何添加 wx。笔记本添加到您的 GUI 应用程序中。笔记本小部件是您如何将选项卡式界面添加到您的应用程序中的。
https://www.youtube.com/embed/7g3V7CsQpDA?feature=oembed
相关文章
wxPython by Example:创建闪烁文本(视频)
原文:https://www.blog.pythonlibrary.org/2020/09/08/wxpython-by-example-creating-flashing-text-video/
在这个 wxPython 教程中,你将学习如何让你的标签闪烁。当出现问题时,这是引起用户注意的有效方法。
https://www.youtube.com/embed/gcN8JFK_6UY?feature=oembed
wxPython by Example -拖放一个图像(视频)
原文:https://www.blog.pythonlibrary.org/2020/09/29/wxpython-by-example-drag-and-drop-an-image-video/
在本教程中,您将学习如何将图像拖动到#wxPython 应用程序中,并将其显示给用户。
https://www.youtube.com/embed/-NOVckn0GHM?feature=oembed
如果你喜欢这个视频,你可能想看看我的书,《用 wxPython 创建 GUI 应用程序》,可以在 Leanpub 和亚马逊上找到。
wxPython by Example:如何重置背景颜色(视频)
在本教程中,您将学习如何将应用程序的背景色重置为默认颜色。
https://www.youtube.com/embed/lAgzb6PwiTo?feature=oembed
wxPython by Example:让您的应用程序淡入淡出(视频)
在这个截屏中,你将学习如何改变你的应用程序的透明度。这将允许您使您的应用程序淡入或淡出。
https://www.youtube.com/embed/PF2Dmtxl1YE?feature=oembed
wxPython by 示例存储库
原文:https://www.blog.pythonlibrary.org/2012/07/06/wxpython-by-example-repository/
我的长期读者可能记得我提到过,我试图将我的文章代码放入 bitbucket 上的 Mercurial 存储库中。我最近在这方面做得不是很好,但是我决定在那里发布一大堆 wxPython 示例脚本。当我帮助 wxPython 邮件列表、IRC、StackOverflow 等上的人时,我经常使用它们。它基本上是有组织的,有一些分散在各处的脚本想法,没有真正的脚本。然而,大多数脚本都是可用的,我希望浏览它们并在必要时更新它们。一些脚本可能会变成成熟的文章,而另一些则被搁置。总之,我唠叨够了。这里是回购的链接:https://bitbucket.org/driscollis/mousevspython/src。只需点击标有“wxpython by example”的文件夹,开始浏览。我提前为一些混杂在新内容中的旧代码道歉。过去几年我一直在把它们放在一起,但我并不经常使用它们,所以有些比其他的更新。
wxPython:从任何地方捕捉异常
原文:https://www.blog.pythonlibrary.org/2014/03/14/wxpython-catching-exceptions-from-anywhere/
前几天,wxPython Google Group 正在讨论捕捉 wxPython 中异常的不同方法。如果您经常使用 wxPython,您将很快意识到有些异常很难捕捉。 wxPython Wiki 解释了原因。无论如何,名单上的人都推荐使用 sys.excepthook 。所以我采用了他们提到的一种方法,创建了一个小例子:
import sys
import traceback
import wx
########################################################################
class Panel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label="Raise Exception")
btn.Bind(wx.EVT_BUTTON, self.onExcept)
#----------------------------------------------------------------------
def onExcept(self, event):
"""
Raise an error
"""
1/0
########################################################################
class Frame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Exceptions")
sys.excepthook = MyExceptionHook
panel = Panel(self)
self.Show()
########################################################################
class ExceptionDialog(GMD.GenericMessageDialog):
""""""
#----------------------------------------------------------------------
def __init__(self, msg):
"""Constructor"""
GMD.GenericMessageDialog.__init__(self, None, msg, "Exception!",
wx.OK|wx.ICON_ERROR)
#----------------------------------------------------------------------
def MyExceptionHook(etype, value, trace):
"""
Handler for all unhandled exceptions.
:param `etype`: the exception type (`SyntaxError`, `ZeroDivisionError`, etc...);
:type `etype`: `Exception`
:param string `value`: the exception error message;
:param string `trace`: the traceback header, if any (otherwise, it prints the
standard Python header: ``Traceback (most recent call last)``.
"""
frame = wx.GetApp().GetTopWindow()
tmp = traceback.format_exception(etype, value, trace)
exception = "".join(tmp)
dlg = ExceptionDialog(exception)
dlg.ShowModal()
dlg.Destroy()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = Frame()
app.MainLoop()
在这个例子中,我们创建了一个带有按钮的面板,这个按钮会故意引发一个异常。我们通过将 sys.excepthook 重定向到我们的 MyExceptionHook 函数来捕获异常。这个函数将格式化异常的回溯,格式化它使它可读,然后显示一个带有异常信息的对话框。wxPython 的创建者 Robin Dunn 认为,如果有人提出一个装饰器,我们可以用它来捕捉异常,然后作为一个例子添加到 wiki 页面,这将是一件好事。
我对装潢师的第一个想法如下:
import logging
import wx
########################################################################
class ExceptionLogging(object):
#----------------------------------------------------------------------
def __init__(self, fn):
self.fn = fn
# create logging instance
self.log = logging.getLogger("wxErrors")
self.log.setLevel(logging.INFO)
# create a logging file handler / formatter
log_fh = logging.FileHandler("error.log")
formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s")
log_fh.setFormatter(formatter)
self.log.addHandler(log_fh)
#----------------------------------------------------------------------
def __call__(self, evt):
try:
self.fn(self, evt)
except Exception, e:
self.log.exception("Exception")
########################################################################
class Panel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label="Raise Exception")
btn.Bind(wx.EVT_BUTTON, self.onExcept)
#----------------------------------------------------------------------
@ExceptionLogging
def onExcept(self, event):
"""
Raise an error
"""
1/0
########################################################################
class Frame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Exceptions")
panel = Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = Frame()
app.MainLoop()
在这段代码中,我们创建了一个创建日志实例的类。然后我们覆盖 call 方法,将方法调用包装在异常处理程序中,这样我们就可以捕捉异常。基本上我们在这里做的是创建一个类装饰器。接下来,我们用异常日志记录类修饰一个事件处理程序。这并不完全是邓恩先生想要的,因为装饰者也需要能够包装其他功能。所以我编辑了一下,做了如下的小调整:
import logging
import wx
class ExceptionLogging(object):
def __init__(self, fn, *args, **kwargs):
self.fn = fn
# create logging instance
self.log = logging.getLogger("wxErrors")
self.log.setLevel(logging.INFO)
# create a logging file handler / formatter
log_fh = logging.FileHandler("error.log")
formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s")
log_fh.setFormatter(formatter)
self.log.addHandler(log_fh)
def __call__(self, *args, **kwargs):
try:
self.fn(self, *args, **kwargs)
except Exception, e:
self.log.exception("Exception")
########################################################################
class Panel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label="Raise Exception")
btn.Bind(wx.EVT_BUTTON, self.onExcept)
#----------------------------------------------------------------------
@ExceptionLogging
def onExcept(self, event):
"""
Raise an error
"""
1/0
########################################################################
class Frame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Exceptions")
panel = Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = Frame()
app.MainLoop()
这一次, call 方法可以接受任意数量的参数或关键字参数,这给了它更多的灵活性。这仍然不是罗宾·邓恩想要的,所以他写了下面的例子:
from __future__ import print_function
import logging
import wx
print(wx.version())
def exceptionLogger(func, loggerName=''):
"""
A simple decorator that will catch and log any exceptions that may occur
to the root logger.
"""
assert callable(func)
mylogger = logging.getLogger(loggerName)
# wrap a new function around the callable
def logger_func(*args, **kw):
try:
if not kw:
return func(*args)
return func(*args, **kw)
except Exception:
mylogger.exception('Exception in %s:', func.__name__)
logger_func.__name__ = func.__name__
logger_func.__doc__ = func.__doc__
if hasattr(func, '__dict__'):
logger_func.__dict__.update(func.__dict__)
return logger_func
def exceptionLog2Logger(loggerName):
"""
A decorator that will catch and log any exceptions that may occur
to the named logger.
"""
import functools
return functools.partial(exceptionLogger, loggerName=loggerName)
########################################################################
class Panel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label="Raise Exception")
btn.Bind(wx.EVT_BUTTON, self.onExcept)
#----------------------------------------------------------------------
@exceptionLog2Logger('testLogger')
def onExcept(self, event):
"""
Raise an error
"""
print(self, event)
print(isinstance(self, wx.Panel))
#trigger an exception
1/0
########################################################################
class Frame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Exceptions")
panel = Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
# set up the default logger
log = logging.getLogger('testLogger')
log.setLevel(logging.INFO)
# create a logging file handler / formatter
log_fh = logging.FileHandler("error.log")
formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s")
log_fh.setFormatter(formatter)
log.addHandler(log_fh)
app = wx.App(False)
frame = Frame()
app.MainLoop()
这展示了几个不同的装饰器示例。这个例子展示了更传统的装饰器构造方法。虽然它有更多的元编程。第一个示例检查以确保传递给它的内容实际上是可调用的。然后,它创建一个记录器,并用一个异常处理程序包装可调用的。在它返回包装的函数之前,包装的函数被修改,以便它与传递给它的原始函数具有相同的名称和 docstring。我相信你可以放弃它,使用 functools.wraps 来代替,但是在教程中明确一点可能更好。
包扎
现在你知道如何用几种不同的方法捕捉异常了。希望这对您自己的应用程序设计有所帮助。玩得开心!
wxPython:捕获键和字符事件
原文:https://www.blog.pythonlibrary.org/2009/08/29/wxpython-catching-key-and-char-events/
在这篇文章中,我将详细说明如何捕捉特定的按键,以及为什么这很有用。这是我的“要求”系列教程中的另一个。捕捉按键真的没什么大不了的,但是当一个小部件的行为与另一个小部件稍有不同时,可能会有点混乱。当你需要捕捉 EVT _ 查尔时,真正复杂的东西就来了。
首先我将报道关键事件,wx。EVT 向下键和 wx。EVT 向上,然后我会去看看 wx 的复杂性。EVT 夏尔。我认为,如果您看到一些示例代码,编程是最容易理解的,所以我将从一个简单的示例开始:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Key Press Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
btn = wx.Button(panel, label="OK")
btn.Bind(wx.EVT_KEY_DOWN, self.onKeyPress)
def onKeyPress(self, event):
keycode = event.GetKeyCode()
print keycode
if keycode == wx.WXK_SPACE:
print "you pressed the spacebar!"
event.Skip()
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
app.MainLoop()
您会注意到这段代码中唯一重要的小部件是一个面板和一个按钮。我将按钮绑定到 EVT 键,并在处理程序中检查用户是否按下了空格键。该事件仅在按钮有焦点时触发。你会注意到我也称之为“事件”。跳过结尾的。如果你不调用 Skip,那么这个键将被“吃掉”,并且不会有相应的 char 事件。这在按钮上并不重要,但是在文本控件中可能会很重要,因为 char 事件是捕捉大小写、重音、元音变音等的正确方式。
在我的一个电子表格类型的应用程序中,我使用了类似的方法来捕捉箭头键。我希望能够检测到这些键,这样,如果我正在编辑一个单元格,按下箭头键将使选择更改为不同的单元格。这不是默认行为。在网格中,每个单元格都有自己的编辑器,按箭头键只是在单元格内移动光标。
只是为了好玩,我创建了一个与上面类似的例子,其中我绑定了 key 和 key down 事件,但是使用了两个不同的小部件。请查看以下内容:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Key Press Tutorial 2")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
btn = self.onWidgetSetup(wx.Button(panel, label="OK"),
wx.EVT_KEY_UP,
self.onButtonKeyEvent, sizer)
txt = self.onWidgetSetup(wx.TextCtrl(panel, value=""),
wx.EVT_KEY_DOWN, self.onTextKeyEvent,
sizer)
panel.SetSizer(sizer)
def onWidgetSetup(self, widget, event, handler, sizer):
widget.Bind(event, handler)
sizer.Add(widget, 0, wx.ALL, 5)
return widget
def onButtonKeyEvent(self, event):
keycode = event.GetKeyCode()
print keycode
if keycode == wx.WXK_SPACE:
print "you pressed the spacebar!"
event.Skip()
def onTextKeyEvent(self, event):
keycode = event.GetKeyCode()
print keycode
if keycode == wx.WXK_DELETE:
print "you pressed the delete key!"
event.Skip()
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
app.MainLoop()
诚然,这主要是为了说明。主要要知道的是,你真的不用 EVT_KEY_UP,除非你需要跟踪多按键组合,比如 CTRL+K+Y 之类的(关于半相关的注意事项,参见 wx。可加速)。虽然在我的例子中我没有这样做,但是需要注意的是,如果您正在检查 CTRL 键,那么最好使用 event。CmdDown()而不是 event.ControlDown .原因是 CmdDown 在 Windows 和 Linux 上相当于 ControlDown,但在 Mac 上它模拟 Command 键。因此,CmdDown 是跨平台检查 CTRL 键是否被按下的最佳方式。
这就是你需要知道的关于关键事件的全部内容。让我们继续,看看我们能从 char 事件中学到什么。这里有一个简单的例子:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Char Event Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
btn = wx.TextCtrl(panel, value="")
btn.Bind(wx.EVT_CHAR, self.onCharEvent)
def onCharEvent(self, event):
keycode = event.GetKeyCode()
controlDown = event.CmdDown()
altDown = event.AltDown()
shiftDown = event.ShiftDown()
print keycode
if keycode == wx.WXK_SPACE:
print "you pressed the spacebar!"
elif controlDown and altDown:
print keycode
event.Skip()
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
app.MainLoop()
我认为最主要的不同是你想检查口音或国际字符。因此,您将有复杂的条件来检查某些键是否被按下以及按下的顺序。robin Dunn(wxPython 的创建者)说 wxSTC 检查键和字符事件。如果你计划支持美国以外的用户,你可能想了解这一切是如何工作的。
罗宾·邓恩接着说如果你想获得按键事件以便在应用程序中处理“命令”,那么在 EVT _ 按键 _ 按下处理程序中使用原始值是合适的。然而,如果目的是处理“文本”的输入,那么应用程序应该使用 EVT_CHAR 事件处理程序中的熟值,以便获得非美国键盘和输入法编辑器的正确处理。(注意:向上键和向下键事件被认为是“未加工的”,而 char 事件已经为您“加工”好了。)正如 Robin Dunn 向我解释的那样,在非美国键盘上,将键事件烹饪成字符事件的一部分是将物理键映射到国家键盘映射,以产生带有重音、元音等的字符。
很抱歉,本教程没有涉及更多关于 char 事件的内容,但是我找不到太多的例子。
这些代码样本在以下方面进行了测试
- Windows Vista SP2、wxPython 2.8.9.2(unicode)、Python 2.5.2
下载量
延伸阅读
wxPython:更改列/行的自定义渲染器
原文:https://www.blog.pythonlibrary.org/2019/01/03/wxpython-changing-custom-renderers-for-columns-rows/
wxPython GUI toolkit 有一个非常丰富和强大的网格小部件,我在之前的博客中已经提到过。它允许您创建类似于 Microsoft Excel 中的单元格表。
还有一个简洁的 mixin,允许您将定制的渲染器应用到网格的列和行的标签上。
让我们来看看它是如何工作的:
import wx
import wx.grid as grid
import wx.lib.mixins.gridlabelrenderer as glr
class MyGrid(grid.Grid, glr.GridWithLabelRenderersMixin):
def __init__(self, *args, **kw):
grid.Grid.__init__(self, *args, **kw)
glr.GridWithLabelRenderersMixin.__init__(self)
class MyColLabelRenderer(glr.GridLabelRenderer):
def __init__(self, bgcolor):
self._bgcolor = bgcolor
def Draw(self, grid, dc, rect, col):
dc.SetBrush(wx.Brush(self._bgcolor))
dc.SetPen(wx.TRANSPARENT_PEN)
dc.DrawRectangle(rect)
hAlign, vAlign = grid.GetColLabelAlignment()
text = grid.GetColLabelValue(col)
self.DrawBorder(grid, dc, rect)
self.DrawText(grid, dc, rect, text, hAlign, vAlign)
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
grid = MyGrid(self, size=(100, 100))
grid.CreateGrid(numRows=10, numCols=10)
for col in range(0, 10, 3):
grid.SetColLabelRenderer(
col+0, MyColLabelRenderer('#e0ffe0'))
grid.SetColLabelRenderer(
col+1, MyColLabelRenderer('#e0e0ff'))
grid.SetColLabelRenderer(
col+2, MyColLabelRenderer('#ffe0e0'))
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(grid, 1, wx.EXPAND)
self.SetSizer(main_sizer)
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Custom Grid Renderers')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop(
让我们把它分解一下。您会注意到在代码的顶部,我们需要在 wxPython 中单独导入网格小部件。我们还需要导入一个名为的 mixin。我们子类化 Grid 类,添加 mixin,然后初始化两者。
接下来我们创建一个gridlabellerenderer的子类,它也来自 mixin。这允许我们创建一个间距绘制方法,这将使我们能够在网格中的标签上应用不同的颜色或字体。在这种情况下,我只是让我们可以改变标签中文本的颜色。
我们感兴趣的最后一段代码是在 MyPanel 类中,我们实际上实例化了网格并改变了列中标签的背景颜色。这是网格最终的样子:
Custom Grid Column Renderers
包扎
wxPython 工具包有几十个预构建的小部件,可以用来创建跨平台的用户界面。wxPython 演示有一个比本文更复杂的例子,您可能也会感兴趣。如果您还没有尝试过 wxPython,那么您真的应该去尝试一下。它可以从 PyPI 进行 pip 安装,并且与 Python 3 兼容。
wxPython:正在转换 wx。日期时间到 Python 日期时间
原文:https://www.blog.pythonlibrary.org/2014/08/27/wxpython-converting-wx-datetime-python-datetime/
wxPython GUI 工具包包括自己的日期/时间功能。大多数时候,你只需要使用 Python 的 datetime 和 time 模块就可以了。但是偶尔你会发现自己需要从 wxPython 的 wx 转换过来。datetime 对象转换为 Python 的 DateTime 对象。使用 wx 时可能会遇到这种情况。DatePickerCtrl 小部件。
幸运的是,wxPython 的日历模块有一些助手函数,可以帮助您在 wxPython 和 Python 之间来回转换 datetime 对象。让我们来看看:
def _pydate2wxdate(date):
import datetime
assert isinstance(date, (datetime.datetime, datetime.date))
tt = date.timetuple()
dmy = (tt[2], tt[1]-1, tt[0])
return wx.DateTimeFromDMY(*dmy)
def _wxdate2pydate(date):
import datetime
assert isinstance(date, wx.DateTime)
if date.IsValid():
ymd = map(int, date.FormatISODate().split('-'))
return datetime.date(*ymd)
else:
return None
您可以在自己的代码中使用这些方便的函数来帮助您进行转换。我可能会将这些放入控制器或实用程序脚本中。我还会稍微重写它,这样我就不会在函数中导入 Python 的 datetime 模块。这里有一个例子:
import datetime
import wx
def pydate2wxdate(date):
assert isinstance(date, (datetime.datetime, datetime.date))
tt = date.timetuple()
dmy = (tt[2], tt[1]-1, tt[0])
return wx.DateTimeFromDMY(*dmy)
def wxdate2pydate(date):
assert isinstance(date, wx.DateTime)
if date.IsValid():
ymd = map(int, date.FormatISODate().split('-'))
return datetime.date(*ymd)
else:
return None
您可以在这个旧的 wxPython 邮件线程上阅读更多关于这个主题的内容。开心快乐编码!
wxPython 食谱艺术家访谈:丽莎·特雷蒂亚科娃
原文:https://www.blog.pythonlibrary.org/2017/01/05/wxpython-cookbook-artist-interview-liza-tretyakova/
我总是在书的封面上花很多心思。我的第一本书是关于 wxPython 的,我认为写一本食谱会很有趣,因为我的博客上已经有很多食谱了。所以我有了写一本食谱的想法。对于封面,我的第一个想法是有一些老鼠厨师的厨房场景。后来,我觉得这太明显了,于是决定采用西部牛仔(或牛老鼠)在火上烹饪的老创意。
我请 wxPython Cookbook 的封面艺术家 Liza Tretyakova 做了一个关于她自己的简短采访。以下是她要说的话:
你能告诉我们一些关于你自己的情况吗(爱好、教育等)
我叫 Liza Tretyakova,是一名自由职业的插图画家,目前在莫斯科工作。
教育:
- 莫斯科国立大学艺术历史学院
- 普利茅斯大学插图文学学士(荣誉)
我从事插画工作 10 年左右。我喜欢马,我曾经养过一匹马。另外,我对射箭很感兴趣。我喜欢阅读,并和我 7 岁的女儿 Yara 一起度过了很多时光。
与其他职业相比,是什么促使你成为一名插画师?
当我还是个孩子的时候,我就一直在画画,只是碰巧我开始从事插图画家的工作,这就变成了一种职业。
当你创作一件新的艺术品时,你会经历什么样的过程?
每次都不一样,没有特定的“食谱”🙂
对于想成为插画师的人,你有什么建议吗?
你应该试着每天画画,越多越好。
你还有什么想说的吗?
和你一起工作很愉快!
非常感谢你接受采访并同意做我的 wxPython 食谱的插图画家。
你可以在网站上看到更多丽莎的作品。
wxPython 食谱可供预购
原文:https://www.blog.pythonlibrary.org/2016/09/27/wxpython-cookbook-available-for-pre-order/
我很高兴地宣布,wxPython 食谱现在可以预购了。你现在就可以在 Gumroad 或 T2 Leanpub 获得你的电子版。如果你想“先试后买”,你可以在 Leanpub 上得到这本书的样本。
这本书里会有 50 多个食谱。我书中的例子既适用于 wxPython 3.0.2 Classic,也适用于 wxPython Phoenix,后者是 wxPython 支持 Python 3 的前沿技术。如果我发现任何不适合凤凰城的食谱,它们会被清楚地标记出来,或者会有一个可行的替代例子。
以下是当前食谱的部分列表,排名不分先后:
- 动态添加/移除小部件
- 如何在面板上放置背景图像
- 将多个小部件绑定到同一个处理程序
- 从任何地方捕捉异常
- wxPython 的上下文管理器
- 转换 wx。日期时间到 Python 日期时间
- 创建“关于”框
- 如何创建登录对话框
- 如何创建“黑暗模式”
- 从配置文件生成对话框
- 如何禁用向导的“下一步”按钮
- 如何使用拖放
- 如何将文件从应用程序拖放到操作系统
- 如何使用 reload()交互式编辑您的 GUI
- 如何在标题栏中嵌入图像
- 从 RichTextCtrl 中提取 XML
- 如何淡入一个框架/对话框
- 如何触发多个事件处理程序
- 使您的框架最大化或全屏
- 使用 wx。框架样式
- 获取事件名称而不是整数
- 如何从 Sizer 中获取子部件
- 如何使用剪贴板
- 捕捉按键和字符事件
- 了解 wxPython 中焦点的工作方式
- 让您的文本闪烁
- 最小化到系统托盘
- 使用 ObjectListView 而不是 ListCtrl
你可以在我的 Kickstarter 公告文章中读到更多关于这个项目的信息。请注意,Kickstarter 活动已经结束。
相关职位
wxPython 食谱封面故事
原文:https://www.blog.pythonlibrary.org/2016/09/08/wxpython-cookbook-cover-story/
我总是花一些时间思考我希望我的书的封面是什么样子。当我设计这本烹饪书的封面时,我主要考虑的是食物和厨师。我原本想我可能会有一些厨房场景,老鼠戴着厨师帽,一条蛇在壁炉架上。但是我想把烹饪的想法融入其中。
我想到的不是厨房,而是放牛的牛仔,以及他们通常如何带着厨师。所以我采用了这个想法,尽管我没有把成群的动物加到封面上。
为了帮助区分食谱和我以前的作品,我从我以前的作品中聘请了一位不同的艺术家,名叫丽莎·特雷蒂亚科娃。你可以在 Behance 上查看她的一些作品,或者如果你碰巧需要一位伟大的艺术家,甚至可以通过电子邮件(schimmel@inbox.ru)直接联系她。
我想,当我和这位艺术家一起把我的封面创意变成现实的时候,让你看看封面艺术是如何演变的,可能会很有趣。让我们从我从 Liza 那里得到的第一张草图开始:
这张草图给我留下了很深的印象,因为我以前的艺术家总是给我更粗糙的草图来看。下一步是给封面增添一点色彩:
在这一点上,我真的没有想太多如何让每只老鼠都有独特的颜色。莉莎提出了那个想法,我同意了。倒数第二步是给封面添加几乎全彩:
这看起来真的很好,所以我批准了,丽莎完成了封面。正如您可能已经看到的,它看起来像这样:
整个过程花了我和丽莎大约一周半的时间。我觉得结果挺好的!
如果你想支持这本书,或者你想在我以前的书上得到一笔好交易,一定要看看 Kickstarter。
wxPython 食谱-提供精装版
原文:https://www.blog.pythonlibrary.org/2016/09/15/wxpython-cookbook-hard-cover-editions-available/
很长一段时间以来,我一直在考虑为我的书做一个精装版,所以我想我可以用烹饪书来试试。我继续用我的按需打印机资源计算数字,发现普通的硬皮封面并不贵。因此,我为这本书的精装版增加了一个定价为 35 美元的新优惠。
但是等等!还有呢!由于这本烹饪书将有如此多的截图和代码示例,我想到提供这本书的彩色版本可能会很有趣。这样你可以看到全彩色的截图,也可以看到代码示例中的语法高亮显示!不幸的是,彩色印刷非常昂贵。但是如果你对那个选项感兴趣,它可以在 $100 买到。
这本书的所有版本都要签名。如果人们对黑白硬皮封面很感兴趣,我会增加这种封面的商品数量。
要得到这些版本或普通平装本,你需要在 Kickstarter 上认捐。
wxPython Cookbook 正式发布!
原文:https://www.blog.pythonlibrary.org/2016/12/30/wxpython-cookbook-is-officially-released/
我最近完成了我的第三本书, wxPython Cookbook ,我今天正式发布它。我的 wxPython 食谱是我迄今为止最长的书,超过 340 页,超过 50 个食谱。我在 Leanpub 上有一个完整的目录表,可以提供给任何对细节感兴趣的人。
以下是目前你可以买到这本书的地方:
- Leanpub (PDF,epub,mobi)
- Gumroad (PDF,epub,mobi)
- 露露(精装)
- 露露(软皮)
- 亚马逊 Kindle
非常感谢我所有的读者和 Kickstarter 支持者,他们在我写作的过程中一直鼓励我。
wxPython Cookbook 示例章节
原文:https://www.blog.pythonlibrary.org/2016/08/29/wxpython-cookbook-sample-chapters/
我最新的书将是我自己自制的 wxPython 食谱。如果你有兴趣了解更多信息,请查看 Kickstarter 活动。简要的大纲是,这本书将有 50 多个食谱和大约 300 页的内容。
为了帮助你做出明智的决定,你是否愿意支持这本书,我发布了几个样本章节。你可以从这里下载 PDF 格式的文件。请注意,这些章节处于测试状态。我将会更新这本书的大部分内容,包括新的截图和更新的代码示例,以及其他各种调整和改进。
wxPython 食谱写作更新:测试版可用
我很高兴地宣布,我现在已经准备好了我最新的一本书 wxPython Cookbook 的所有章节,随时可以阅读。我仍然认为这本书处于测试阶段,因为我需要在这个月尽可能多地阅读每一章并检查它们,但我也很有信心这本书已经完成了 90%以上。一些章节仍然需要添加一两个截图,我也计划再添加一两个章节。
喜欢原始数据的朋友,目前在书+入门和安装章节里有 51 个食谱。内容有 300 多页,比我之前的两本书都多!
我希望通过添加丢失的截图和写一个全新的篇章来做一些润色。我也希望在本周将一些代码示例放入 Github。我很抱歉耽搁了这件事。我这边的生活真的很疯狂。
你可以在 Leanpub 和 Gumroad 上提前看到这本书。如果您从这些网站中的任何一个购买了这本书,您还将收到最终产品+更新。你也可以查看最初的 Kickstarter 活动来了解更多关于这本书的信息。
再次感谢大家的支持!
wxPython:创建一个简单的 RSS 阅读器
原文:https://www.blog.pythonlibrary.org/2014/01/09/wxpython-create-rss-reader/
真正简单的聚合(RSS)已经伴随我们很长一段时间了,它让我们可以很容易地在我们最喜欢的网站上看到新文章。Python 在标准库中没有 RSS 阅读器模块,所以我们将使用通用提要解析器 5.1.3 来进行解析。如果你还没有它,你应该现在就去 Python 包索引下载。我们将使用这个博客的 RSS 提要来简化测试。该提要的 url 是:【https://www.blog.pythonlibrary.org/feed/】的。在我的实验中,我注意到我的博客软件会将破折号更改为以下 unicode 字符:\u2013。这将导致 feedparser 抛出一个 UnicodeEncodeError,所以我也建议下载方便的 unidecode 模块来帮助解决这个问题。
入门指南
一旦你具备了上面提到的先决条件,我们就可以开始了。 feedparser 包实际上是一个单独的模块,非常容易使用。这里有一个简单的例子:
import feedparser
rss = 'https://www.blog.pythonlibrary.org/feed/'
feed = feedparser.parse(rss)
for key in feed["entries"]:
print unidecode.unidecode(key["title"])
如果运行这段代码,它将打印出博客中 10 篇最新的文章标题。如果你很好奇,试着打印出提要变量。如果你这样做,你会看到一本相当难懂的粗糙的字典。最后,我做了以下事情,以便于阅读:
import pprint
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(feed)
这将为您提供类似于以下代码片段的打印输出:
{ 'bozo': 0,
'encoding': u'UTF-8',
'entries': [ { 'author': u'Mike',
'author_detail': { 'name': u'Mike'},
'authors': [{ }],
'comments': u'https://www.blog.pythonlibrary.org/2014/01/03/reportlab-create-landscape-pages/#comments',
'content': [ { 'base': u'https://www.blog.pythonlibrary.org/feed/',
'language': None,
'type': u'text/html',
'guidislink': False,
'id': u'https://www.blog.pythonlibrary.org/?p=3639',
'link': u'https://www.blog.pythonlibrary.org/2014/01/03/reportlab-create-landscape-pages/',
'links': [ { 'href': u'https://www.blog.pythonlibrary.org/2014/01/03/reportlab-create-landscape-pages/',
'rel': u'alternate',
'type': u'text/html'}],
'published': u'Fri, 03 Jan 2014 22:54:11 +0000',
'published_parsed': time.struct_time(tm_year=2014, tm_mon=1, tm_mday=3, tm_hour=22, tm_min=54, tm_sec=11, tm_wday=4, tm_yday=3, tm_isdst=0),
'slash_comments': u'0',
'summary': u'前几天,我需要用 Reportlab 完成一个有趣的任务。我需要创建一个横向的 PDF,保存时必须旋转 90 度。为了使文档的布局更容易,我创建了一个带有标志的类,它允许我保存[…]帖子 [Reportlab:如何创建风景页面](https://www.blog.pythonlibrary.org/2014/01/03/reportlab-create-landscape-pages/)最早出现在[鼠标大战 Python](https://www.blog.pythonlibrary.org) 上。',
'summary_detail': { 'base': u'https://www.blog.pythonlibrary.org/feed/',
'language': None,
'type': u'text/html',
'value': u'前几天,我需要用 Reportlab 完成一个有趣的任务。我需要创建一个横向的 PDF,保存时必须旋转 90 度。为了使文档的布局更容易,我创建了一个带有标志的类,它允许我保存[…]帖子 [Reportlab:如何创建风景页面](https://www.blog.pythonlibrary.org/2014/01/03/reportlab-create-landscape-pages/)最早出现在[鼠标大战 Python](https://www.blog.pythonlibrary.org) 上。'},
'tags': [ { 'label': None,
'scheme': None,
'term': u'Cross-Platform'},
{ 'label': None,
'scheme': None,
'term': u'Python'},
{ 'label': None,
'scheme': None,
'term': u'Reportlab'}],
'title': u'Reportlab: How to Create Landscape Pages',
'title_detail': { 'base': u'https://www.blog.pythonlibrary.org/feed/',
'language': None,
'type': u'text/plain',
'value': u'Reportlab: How to Create Landscape Pages'},
'wfw_commentrss': u'https://www.blog.pythonlibrary.org/2014/01/03/reportlab-create-landscape-pages/feed/'},
我把它缩短了很多,因为你会看到很多细节。无论如何,回到解析提要字典。如果您打印出这些键,您会看到它们如下:feed、status、updated、updated_parsed、encoding、bozo、headers、href、version、entries、namespaces。我认为可能最有用的是条目键。如果您访问它的值,您会发现一个字典列表,其中包含关于这十篇文章中每一篇的信息。
当然,如果我们没有显示数据的好方法,这就没什么意思了。让我们花点时间使用 wxPython 创建一个简单的用户界面!
创建用户界面
我推荐在下一篇文章中使用 wxPython 2.9 或更高版本,因为 WebView 小部件是在该系列文章中添加的,这使得显示 HTML 更加简单。我将使用 wxPython 2.9.4(经典版)。我还将使用 ObjectListView,所以在尝试做这个练习之前,请确保您安装了正确的包。让我们直接进入代码!
# wxRss.py
import feedparser
import os
import unidecode
import wx
import wx.html2 as webview
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class RSS(object):
"""
RSS object
"""
#----------------------------------------------------------------------
def __init__(self, title, link, website, summary, all_data):
"""Constructor"""
self.title = title
self.link = link
self.all_data = all_data
self.website = website
self.summary = summary
########################################################################
class RssPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent, style=wx.NO_FULL_REPAINT_ON_RESIZE)
self.data = []
lbl = wx.StaticText(self, label="Feed URL:")
self.rssUrlTxt = wx.TextCtrl(self, value="https://www.blog.pythonlibrary.org/feed/")
urlBtn = wx.Button(self, label="Get Feed")
urlBtn.Bind(wx.EVT_BUTTON, self.get_data)
self.rssOlv = ObjectListView(self,
style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.rssOlv.SetEmptyListMsg("No data")
self.rssOlv.Bind(wx.EVT_LIST_ITEM_SELECTED, self.on_select)
self.rssOlv.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_double_click)
self.summaryTxt = webview.WebView.New(self)
self.wv = webview.WebView.New(self)
# add sizers
rowSizer = wx.BoxSizer(wx.HORIZONTAL)
rowSizer.Add(lbl, 0, wx.ALL, 5)
rowSizer.Add(self.rssUrlTxt, 1, wx.EXPAND|wx.ALL, 5)
rowSizer.Add(urlBtn, 0, wx.ALL, 5)
vSizer = wx.BoxSizer(wx.VERTICAL)
vSizer.Add(self.rssOlv, 1, wx.EXPAND|wx.ALL, 5)
vSizer.Add(self.summaryTxt, 1, wx.EXPAND|wx.ALL, 5)
dispSizer = wx.BoxSizer(wx.HORIZONTAL)
dispSizer.Add(vSizer, 1, wx.EXPAND|wx.ALL, 5)
dispSizer.Add(self.wv, 2, wx.EXPAND|wx.ALL, 5)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(rowSizer, 0, wx.EXPAND)
mainSizer.Add(dispSizer, 1, wx.EXPAND)
self.SetSizer(mainSizer)
self.update_display()
#----------------------------------------------------------------------
def get_data(self, event):
"""
Get RSS feed and add it to display
"""
msg = "Processing feed..."
busyDlg = wx.BusyInfo(msg)
rss = self.rssUrlTxt.GetValue()
feed = feedparser.parse(rss)
website = feed["feed"]["title"]
for key in feed["entries"]:
title = unidecode.unidecode(key["title"])
link = key["link"]
summary = key["summary"]
self.data.append(RSS(title, link, website, summary, key))
busyDlg = None
self.update_display()
#----------------------------------------------------------------------
def on_double_click(self, event):
"""
Load the selected link in the browser widget
"""
obj = self.rssOlv.GetSelectedObject()
self.wv.LoadURL(obj.link)
#----------------------------------------------------------------------
def on_select(self, event):
"""
Load the summary in the text control
"""
base_path = os.path.dirname(os.path.abspath(__file__))
obj = self.rssOlv.GetSelectedObject()
html = "%s" % obj.summary
fname = "summary.html"
full_path = os.path.join(base_path, fname)
try:
with open(full_path, "w") as fh:
fh.write(html)
print "file:///" + full_path
self.summaryTxt.LoadURL("file:///" + full_path)
except (OSError, IOError):
print "Error writing html summary"
#----------------------------------------------------------------------
def update_display(self):
"""
Update the RSS feed display
"""
self.rssOlv.SetColumns([
ColumnDefn("Title", "left", 200, "title"),
ColumnDefn("Website", "left", 200, "website"),
])
self.rssOlv.SetObjects(self.data)
########################################################################
class RssFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="RSS Reader", size=(1200,800))
panel = RssPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = RssFrame()
app.MainLoop()
这段代码有点复杂,所以让我们花点时间弄清楚这里发生了什么。在应用程序的顶部,我们有一个标签,一个文本控件和一个按钮。文本控件用于添加 RSS 提要 URL。添加后,单击按钮检索 RSS 提要。这通过调用 get_data 事件处理程序来实现。在上述处理程序中,我们显示了一个 BusyInfo 对话框,告诉用户在我们解析提要时发生了一些事情。解析完成后,我们关闭对话框并更新显示。
如果您从左边的 ObjectListView 小部件中选择一个项目,那么您将看到下面这篇文章的摘要。左下角的小部件是 WebView 小部件的一个小例子。它基本上是一个完全成熟的网络浏览器,所以如果你点击一个链接,它会试图加载它。您会注意到,在选择事件处理程序中,我们实际上将 HTML 保存到一个文件中,然后将其加载到 WebView 小部件中。
要实际查看整篇文章,您可以双击该项目,它将在右侧加载到另一个 WebView 小部件中。
包扎
那不是很有趣吗?我以为是。无论如何,现在您应该知道如何用 Python 解析 RSS 提要,并实际创建一些有用的东西。以下是一些改进的想法:
- 添加一种保存提要数据的方法,这样您就不必在每次启动程序时解析它
- 创建一个保存你喜欢的文章的方法
- 隐藏你阅读的文章,但也添加一个过滤器,只显示阅读的文章或可能所有的文章
创建这样的应用程序总能激发我的想象力。我希望它对你也一样。开心快乐编码!
相关链接
- Feedparser 文档
- 这是一组有趣的例子,展示了使用 feedparser 的各种方法
wxPython:创建“黑暗模式”
原文:https://www.blog.pythonlibrary.org/2011/11/05/wxpython-creating-a-dark-mode/
上个月的一天,有人告诉我,我的一个程序有一个功能需求。当他们在晚上使用我的应用程序时,他们想要一个“黑暗模式”,因为正常的颜色有点刺眼。我的程序在警车的笔记本电脑上使用,所以我能理解他们的沮丧。我花了一些时间研究这个问题,并得到了一个基本可行的脚本,我将与我的读者分享。当然,如果您是一个长期读者,您可能知道我在谈论一个 wxPython 程序。我几乎用 wxPython 编写了所有的 GUI。不管怎样,我们继续讲故事吧!
陷入黑暗
让小部件在 wxPython 中改变颜色非常容易。你只需要两个方法 SetBackgroundColour 和 SetForegroundColour 。我在这样做时遇到的唯一主要问题是让我的 ListCtrl / ObjectListView 小部件适当地改变颜色。您需要循环遍历每个 ListItem 并分别更改它们的颜色。我改变行的颜色,使事情变得更有趣。另一个问题是恢复 ListCtrl 的背景色。通常你可以设置一个小部件的背景颜色为 wx。NullColour(或 wx。NullColor ),它将恢复到默认颜色。然而,有些小部件不是这样工作的,你必须实际指定一种颜色。还需要注意的是,有些小部件似乎根本不关注 SetBackgroundColour。我发现的一个这样的小部件是 wx.ToggleButton。
现在你知道我所知道的,所以让我们看看我想出的解决我的问题的代码:
import wx
try:
from ObjectListView import ObjectListView
except:
ObjectListView = False
#----------------------------------------------------------------------
def getWidgets(parent):
"""
Return a list of all the child widgets
"""
items = [parent]
for item in parent.GetChildren():
items.append(item)
if hasattr(item, "GetChildren"):
for child in item.GetChildren():
items.append(child)
return items
#----------------------------------------------------------------------
def darkRowFormatter(listctrl, dark=False):
"""
Toggles the rows in a ListCtrl or ObjectListView widget.
Based loosely on the following documentation:
http://objectlistview.sourceforge.net/python/recipes.html#recipe-formatter
and http://objectlistview.sourceforge.net/python/cellEditing.html
"""
listItems = [listctrl.GetItem(i) for i in range(listctrl.GetItemCount())]
for index, item in enumerate(listItems):
if dark:
if index % 2:
item.SetBackgroundColour("Dark Grey")
else:
item.SetBackgroundColour("Light Grey")
else:
if index % 2:
item.SetBackgroundColour("Light Blue")
else:
item.SetBackgroundColour("Yellow")
listctrl.SetItem(item)
#----------------------------------------------------------------------
def darkMode(self, normalPanelColor):
"""
Toggles dark mode
"""
widgets = getWidgets(self)
panel = widgets[0]
if normalPanelColor == panel.GetBackgroundColour():
dark_mode = True
else:
dark_mode = False
for widget in widgets:
if dark_mode:
if isinstance(widget, ObjectListView) or isinstance(widget, wx.ListCtrl):
darkRowFormatter(widget, dark=True)
widget.SetBackgroundColour("Dark Grey")
widget.SetForegroundColour("White")
else:
if isinstance(widget, ObjectListView) or isinstance(widget, wx.ListCtrl):
darkRowFormatter(widget)
widget.SetBackgroundColour("White")
widget.SetForegroundColour("Black")
continue
widget.SetBackgroundColour(wx.NullColor)
widget.SetForegroundColour("Black")
self.Refresh()
return dark_mode
这段代码有点复杂,但是它完成了任务。让我们把它分解一下,看看它是如何工作的。首先,我们尝试导入 ObjectListView,这是一个很酷的包装 wx 的第三方小部件。ListCtrl 并使它更容易使用。然而,它现在不是 wxPython 的一部分,所以您需要测试它是否存在。如果它不存在,我就把它设置为 False。
GetWidgets 函数接受一个父参数,通常是一个 wx。框架或 wx。面板,并遍历它的所有子面板来创建一个小部件列表,然后返回给调用函数。主要功能是黑暗模式。它还带有两个参数,一个是名不副实的“self”,指的是父窗口小部件,另一个是默认的面板颜色。它调用 GetWidgets,然后使用一个条件语句来决定是否应该启用黑暗模式。接下来,它遍历小部件并相应地改变颜色。完成后,它将刷新传入的父节点,并返回一个 bool 让您知道黑暗模式是开还是关。
还有一个名为 darkRowFormatter 的函数,仅用于设置 wx 中列表项的颜色。ListCtrl 或 ObjectListView 小工具。这里我们使用列表理解来创建一个 wx 列表。然后我们迭代列表项,改变它们的颜色。为了实际应用颜色变化,我们需要调用 SetItem 并向它传递一个 wx。ListItem 对象实例。
尝试黑暗模式
所以现在你可能想知道如何实际使用上面的脚本。这一节将向您展示如何做到这一点。这是一个简单的程序,里面有一个列表控件和一个切换按钮!
import wx
import darkMode
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.defaultColor = self.GetBackgroundColour()
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
btn = wx.ToggleButton(self, label="Toggle Dark")
btn.Bind(wx.EVT_TOGGLEBUTTON, self.onToggleDark)
normalBtn = wx.Button(self, label="Test")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn, 0, wx.ALL, 5)
sizer.Add(normalBtn, 0, wx.ALL, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def onToggleDark(self, event):
""""""
darkMode.darkMode(self, self.defaultColor)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"MvP ListCtrl Dark Mode Demo")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
如果您运行上面的程序,您应该会看到类似这样的内容:
如果您单击 toggle 按钮,您应该会看到类似这样的内容:
注意切换按钮是如何不受 SetBackgroundColour 方法影响的。还要注意,列表控件的列标题也没有改变颜色。不幸的是,wxPython 没有公开对列标题的访问,所以没有办法控制它们的颜色。
无论如何,让我们花一点时间来看看黑暗模式代码是如何使用的。首先我们需要导入它。在这种情况下,该模块被称为黑暗模式。为了实际调用它,我们需要查看 ToggleButton 的事件处理程序:
darkMode.darkMode(self, self.defaultColor)
正如你所看到的,我们所做的只是用面板对象(“self ”)和我们在 wx 开头设置的 defaultColor 调用 darkMode.darkMode。面板的 init 方法。这也是我们必须做的。我们可能应该用一个变量来设置它,以捕捉返回值,但是对于这个例子,我们并不关心。
包扎
现在我们完成了,你也可以为你的应用程序创建一个“黑暗模式”。在某种程度上,我想把它更一般化一些,做成一个颜色改变器脚本,我可以把我想要的任何颜色传递给它。真正酷的是把它做成 mixin。但那是未来的事。现在,享受吧!
进一步阅读
源代码
- 2011-11-5-wxPython-dark-mode
- 您也可以从位桶中提取源
wxPython:创建文件下载应用程序
原文:https://www.blog.pythonlibrary.org/2014/01/29/wxpython-creating-a-file-downloading-app/
我一直在考虑用 wxPython 创建一个简单的下载脚本。然后我在 stack overflow 上看到有人询问如何做这件事,我决定是时候弄清楚了。用 Python 下载文件很简单。我在的上一篇文章中写了一些不同的方法。最大的问题是如何在下载文件时更新用户界面。这其实很简单,这篇文章将告诉你怎么做!
入门指南
本文中的脚本将要求您安装第三方请求包。当然,您还需要 wxPython。在本文中,我将使用 wxPython 2.9。
潜入水中
我最后取了一些我以前的文章,一点一点地组合它们的代码,直到我得到我想要的。我总是发现钻研代码是了解如何做事的最快方法,所以让我们来看看源代码:
import requests
import os
import wx
import wx.lib.scrolledpanel as scrolled
from threading import Thread
from wx.lib.pubsub import pub
########################################################################
class DownloadThread(Thread):
"""Downloading thread"""
#----------------------------------------------------------------------
def __init__(self, gnum, url, fsize):
"""Constructor"""
Thread.__init__(self)
self.fsize = fsize
self.gnum = gnum
self.url = url
self.start()
#----------------------------------------------------------------------
def run(self):
"""
Run the worker thread
"""
local_fname = os.path.basename(self.url)
count = 1
while True:
if os.path.exists(local_fname):
tmp, ext = os.path.splitext(local_fname)
cnt = "(%s)" % count
local_fname = tmp + cnt + ext
count += 1
else:
break
req = requests.get(self.url, stream=True)
total_size = 0
print local_fname
with open(local_fname, "wb") as fh:
for byte in req.iter_content(chunk_size=1024):
if byte:
fh.write(byte)
fh.flush()
total_size += len(byte)
if total_size < self.fsize:
wx.CallAfter(pub.sendMessage,
"update_%s" % self.gnum,
msg=total_size)
print "DONE!"
wx.CallAfter(pub.sendMessage,
"update_%s" % self.gnum,
msg=self.fsize)
########################################################################
class MyGauge(wx.Gauge):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, range, num):
"""Constructor"""
wx.Gauge.__init__(self, parent, range=range)
pub.subscribe(self.updateProgress, "update_%s" % num)
#----------------------------------------------------------------------
def updateProgress(self, msg):
""""""
self.SetValue(msg)
########################################################################
class MyPanel(scrolled.ScrolledPanel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
scrolled.ScrolledPanel.__init__(self, parent)
self.data = []
self.download_number = 1
# create the sizers
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
dl_sizer = wx.BoxSizer(wx.HORIZONTAL)
# create the widgets
lbl = wx.StaticText(self, label="Download URL:")
self.dl_txt = wx.TextCtrl(self)
btn = wx.Button(self, label="Download")
btn.Bind(wx.EVT_BUTTON, self.onDownload)
# layout the widgets
dl_sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
dl_sizer.Add(self.dl_txt, 1, wx.EXPAND|wx.ALL, 5)
dl_sizer.Add(btn, 0, wx.ALL, 5)
self.main_sizer.Add(dl_sizer, 0, wx.EXPAND)
self.SetSizer(self.main_sizer)
self.SetAutoLayout(1)
self.SetupScrolling()
#----------------------------------------------------------------------
def onDownload(self, event):
"""
Update display with downloading gauges
"""
url = self.dl_txt.GetValue()
try:
header = requests.head(url)
fsize = int(header.headers["content-length"]) / 1024
sizer = wx.BoxSizer(wx.HORIZONTAL)
fname = os.path.basename(url)
lbl = wx.StaticText(self, label="Downloading %s" % fname)
gauge = MyGauge(self, fsize, self.download_number)
sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(gauge, 0, wx.ALL|wx.EXPAND, 5)
self.main_sizer.Add(sizer, 0, wx.EXPAND)
self.Layout()
# start thread
DownloadThread(self.download_number, url, fsize)
self.dl_txt.SetValue("")
self.download_number += 1
except Exception, e:
print "Error: ", e
########################################################################
class DownloaderFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Downloader", size=(800, 400))
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DownloaderFrame()
app.MainLoop()
让我们把这段代码分成一部分一部分,这样更容易解释。首先,我们必须子类化线程类,如下所示:
########################################################################
class DownloadThread(Thread):
"""Downloading thread"""
#----------------------------------------------------------------------
def __init__(self, gnum, url, fsize):
"""Constructor"""
Thread.__init__(self)
self.fsize = fsize
self.gnum = gnum
self.url = url
self.start()
#----------------------------------------------------------------------
def run(self):
"""
Run the worker thread
"""
local_fname = os.path.basename(self.url)
count = 1
while True:
if os.path.exists(local_fname):
tmp, ext = os.path.splitext(local_fname)
cnt = "(%s)" % count
local_fname = tmp + cnt + ext
count += 1
else:
break
req = requests.get(self.url, stream=True)
total_size = 0
print local_fname
with open(local_fname, "wb") as fh:
for byte in req.iter_content(chunk_size=1024):
if byte:
fh.write(byte)
fh.flush()
total_size += 1024
if total_size < self.fsize:
wx.CallAfter(pub.sendMessage,
"update_%s" % self.gnum,
msg=total_size)
print "DONE!"
wx.CallAfter(pub.sendMessage,
"update_%s" % self.gnum,
msg=self.fsize)
我们传入规格号(gnum)、下载的 url 和文件大小。我们传入量表编号的原因是每个量表实例需要能够独立于其他量表进行更新,因此我们需要跟踪我们需要更新哪个量表。在 run 方法中,我们检查并查看文件名是否已经存在。如果是,我们尝试给它附加一个数字,并检查该文件是否存在,等等。一旦我们发现一个不存在的文件名,我们继续并告诉请求以流的形式下载该文件。然后我们一次拉下一个字节,写入磁盘,并告诉显示器更新。一旦我们退出下拉文件的循环的,我们向计量器发送最后一次更新,告诉它下载完成。
接下来,我们必须继承 wx 的子类。仪表:
########################################################################
class MyGauge(wx.Gauge):
""""""
#----------------------------------------------------------------------
def __init__(self, parent, range, num):
"""Constructor"""
wx.Gauge.__init__(self, parent, range=range)
pub.subscribe(self.updateProgress, "update_%s" % num)
#----------------------------------------------------------------------
def updateProgress(self, msg):
""""""
self.SetValue(msg)
这很简单。我们只继承 wx 的子类。测量并设置一个 pubsub 订阅者,以便它只监听自己的更新。然后,我们添加一个名为 updateProgress 的方法,每当侦听器触发以实际更新 gauge 小部件时,我们都会调用该方法。现在我们准备好查看面板代码:
########################################################################
class MyPanel(scrolled.ScrolledPanel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
scrolled.ScrolledPanel.__init__(self, parent)
self.data = []
self.download_number = 1
# create the sizers
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
dl_sizer = wx.BoxSizer(wx.HORIZONTAL)
# create the widgets
lbl = wx.StaticText(self, label="Download URL:")
self.dl_txt = wx.TextCtrl(self)
btn = wx.Button(self, label="Download")
btn.Bind(wx.EVT_BUTTON, self.onDownload)
# layout the widgets
dl_sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
dl_sizer.Add(self.dl_txt, 1, wx.EXPAND|wx.ALL, 5)
dl_sizer.Add(btn, 0, wx.ALL, 5)
self.main_sizer.Add(dl_sizer, 0, wx.EXPAND)
self.SetSizer(self.main_sizer)
self.SetAutoLayout(1)
self.SetupScrolling()
#----------------------------------------------------------------------
def onDownload(self, event):
"""
Update display with downloading gauges
"""
url = self.dl_txt.GetValue()
try:
header = requests.head(url)
fsize = int(header.headers["content-length"]) / 1024
sizer = wx.BoxSizer(wx.HORIZONTAL)
fname = os.path.basename(url)
lbl = wx.StaticText(self, label="Downloading %s" % fname)
gauge = MyGauge(self, fsize, self.download_number)
sizer.Add(lbl, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(gauge, 0, wx.ALL|wx.EXPAND, 5)
self.main_sizer.Add(sizer, 0, wx.EXPAND)
self.Layout()
# start thread
DownloadThread(self.download_number, url, fsize)
self.dl_txt.SetValue("")
self.download_number += 1
except Exception, e:
print "Error: ", e
对于面板,我决定使用 ScrolledPanel 小部件,因为我认为我可能想要下载一堆文件,如果我下载的文件在标尺空间用完时添加一个滚动条会很好。我们像往常一样在 init 方法中设置小部件和大小设置器。 onDownload 方法是关键所在。这里,我们从文本控件中获取 URL,并尝试使用请求包从服务器获取文件大小。我们除以 1024 得到千字节大小。如果这是成功的,我们创建一个水平的框 sizer,并向它添加一个静态文本和一个标尺。请注意,我们告诉 gauge 它是哪个下载的,并且我们还将该数字传递给一个新线程。我们还将文本控件重置为空,以便于添加新的 URL。最后,我们增加下载次数,这样我们就可以下载新文件了。
其余的代码几乎都是样板文件。
包扎
如您所见,这段代码非常容易编写。不过,我可以看到很多需要改进的地方。例如,如果应用程序有办法改变下载的最终位置,那就太好了。目前,它们只是保存在与脚本相同的文件夹中。回想起来,它应该把新的下载添加到顶部,把旧的推下来,而不是反过来。如果能显示文件的实际大小以及还剩多少,那就太酷了。我们甚至可以为已经完成下载的文件添加一个标签!无论如何,我希望这能给你一些有趣的想法来改进剧本。
相关阅读
- Python 101: 如何下载文件
- wxPython 和线程
- wxPython 关于线程、队列等的 wiki 文章
- wxPython: 如何从线程更新进度条
wxPython:用 XRC 创建网格
原文:https://www.blog.pythonlibrary.org/2013/07/24/wxpython-creating-a-grid-with-xrc/
我最近试图帮助某人(在 wxPython 邮件列表上)弄清楚如何通过 XRC 使用网格小部件(wx.grid.Grid)。这应该很简单,但是如果您运行下面的代码,您会发现一个奇怪的问题:
import wx
from wx import xrc
########################################################################
class MyApp(wx.App):
def OnInit(self):
self.res = xrc.XmlResource("grid.xrc")
frame = self.res.LoadFrame(None, 'MyFrame')
panel = xrc.XRCCTRL(frame, "MyPanel")
grid = xrc.XRCCTRL(panel, "MyGrid")
print type(grid)
grid.CreateGrid(25, 6)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(grid, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(sizer)
frame.Show()
return True
if __name__ == "__main__":
app = MyApp(False)
app.MainLoop()
您会注意到,当您运行这个命令时,输出的类型是“wx”。_windows。ScrolledWindow”,不是网格对象。因此,您将得到以下回溯结果:
AttributeError: 'ScrolledWindow' object has no attribute 'CreateGrid'
File "c:\Users\mdriscoll\Desktop\xrcGridDemo.py", line 26, in app = MyApp(False)
File "C:\Python26\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py", line 7981, in __init__
self._BootstrapApp()
File "C:\Python26\Lib\site-packages\wx-2.8-msw-unicode\wx\_core.py", line 7555, in _BootstrapApp
return _core_.PyApp__BootstrapApp(*args, **kwargs)
File "c:\Users\mdriscoll\Desktop\xrcGridDemo.py", line 14, in OnInit
grid.CreateGrid(25, 6)
现在你可能想知道 XRC 文件里有什么,以下是它的内容:
`