PythonLibrary-博客中文翻译-七-
PythonLibrary 博客中文翻译(七)
wxPython:如何创建登录对话框
原文:https://www.blog.pythonlibrary.org/2014/07/11/wxpython-how-to-create-a-login-dialog/
我使用 wxPython 已经有一段时间了,我发现某些问题经常出现。其中一个流行的问题是如何在加载应用程序的其余部分之前要求用户提供凭证。有几种方法可以解决这个问题,但是我将集中讨论一个简单的解决方案,因为我相信这个解决方案可以作为更复杂的解决方案的基础。
基本上,我们想让用户看到一个登录对话框,他们必须输入他们的用户名和密码。如果他们输入正确,那么程序将继续加载,他们将看到主界面。你在网站上经常看到这种情况,常见的使用案例是网络电子邮件客户端。桌面应用程序通常不包括这一功能,尽管你会在 Stamps.com 的应用程序和执法软件中看到这一功能。我们将创建一个如下所示的对话框:
让我们来看看一些代码:
import wx
if "2.8" in wx.version():
import wx.lib.pubsub.setupkwargs
from wx.lib.pubsub import pub
else:
from wx.lib.pubsub import pub
########################################################################
class LoginDialog(wx.Dialog):
"""
Class to define login dialog
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Login")
# user info
user_sizer = wx.BoxSizer(wx.HORIZONTAL)
user_lbl = wx.StaticText(self, label="Username:")
user_sizer.Add(user_lbl, 0, wx.ALL|wx.CENTER, 5)
self.user = wx.TextCtrl(self)
user_sizer.Add(self.user, 0, wx.ALL, 5)
# pass info
p_sizer = wx.BoxSizer(wx.HORIZONTAL)
p_lbl = wx.StaticText(self, label="Password:")
p_sizer.Add(p_lbl, 0, wx.ALL|wx.CENTER, 5)
self.password = wx.TextCtrl(self, style=wx.TE_PASSWORD|wx.TE_PROCESS_ENTER)
p_sizer.Add(self.password, 0, wx.ALL, 5)
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(user_sizer, 0, wx.ALL, 5)
main_sizer.Add(p_sizer, 0, wx.ALL, 5)
btn = wx.Button(self, label="Login")
btn.Bind(wx.EVT_BUTTON, self.onLogin)
main_sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(main_sizer)
#----------------------------------------------------------------------
def onLogin(self, event):
"""
Check credentials and login
"""
stupid_password = "pa$$w0rd!"
user_password = self.password.GetValue()
if user_password == stupid_password:
print "You are now logged in!"
pub.sendMessage("frameListener", message="show")
self.Destroy()
else:
print "Username or password is incorrect!"
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Main App")
panel = MyPanel(self)
pub.subscribe(self.myListener, "frameListener")
# Ask user to login
dlg = LoginDialog()
dlg.ShowModal()
#----------------------------------------------------------------------
def myListener(self, message, arg2=None):
"""
Show the frame
"""
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
这段代码的大部分被 wx 的子类所占据。我们称之为的对话框登录对话框。您会注意到,我们已经将密码文本控件设置为使用 wx。TE_PASSWORD 样式,它将隐藏用户在控件中输入的字符。事件处理程序是真正的动作所在。这里我们定义了一个愚蠢的密码,用来与用户输入的密码进行比较。在现实世界中,您可能会将输入的密码散列,与存储在数据库中的密码进行比较。或者,您可以将凭证发送到您的身份验证服务器,让它告诉您用户的凭证是否合法。出于演示目的,我们选择简单的方法,只需检查密码。您会注意到我们完全忽略了用户输入的用户名。这是不现实的,但同样,这只是一个例子。
无论如何,如果用户输入正确的密码,事件处理程序通过 pubsub 向我们的主机对象发送一条消息,告诉它完成加载,然后对话框被销毁。还有其他方法告诉主框架继续,比如在对话框类中使用一个我们可以检查的标志。下面是演示后一种方法的实现:
import wx
########################################################################
class LoginDialog(wx.Dialog):
"""
Class to define login dialog
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Login")
self.logged_in = False
# user info
user_sizer = wx.BoxSizer(wx.HORIZONTAL)
user_lbl = wx.StaticText(self, label="Username:")
user_sizer.Add(user_lbl, 0, wx.ALL|wx.CENTER, 5)
self.user = wx.TextCtrl(self)
user_sizer.Add(self.user, 0, wx.ALL, 5)
# pass info
p_sizer = wx.BoxSizer(wx.HORIZONTAL)
p_lbl = wx.StaticText(self, label="Password:")
p_sizer.Add(p_lbl, 0, wx.ALL|wx.CENTER, 5)
self.password = wx.TextCtrl(self, style=wx.TE_PASSWORD|wx.TE_PROCESS_ENTER)
self.password.Bind(wx.EVT_TEXT_ENTER, self.onLogin)
p_sizer.Add(self.password, 0, wx.ALL, 5)
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(user_sizer, 0, wx.ALL, 5)
main_sizer.Add(p_sizer, 0, wx.ALL, 5)
btn = wx.Button(self, label="Login")
btn.Bind(wx.EVT_BUTTON, self.onLogin)
main_sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(main_sizer)
#----------------------------------------------------------------------
def onLogin(self, event):
"""
Check credentials and login
"""
stupid_password = "pa$$w0rd!"
user_password = self.password.GetValue()
if user_password == stupid_password:
print "You are now logged in!"
self.logged_in = True
self.Close()
else:
print "Username or password is incorrect!"
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Main App")
panel = MyPanel(self)
# Ask user to login
dlg = LoginDialog()
dlg.ShowModal()
authenticated = dlg.logged_in
if not authenticated:
self.Close()
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
在这个例子中,我们在对话框子类中添加了一个标记,我们称之为 self.logged_in 。如果用户输入正确的密码,我们告诉对话框关闭。这导致 wxPython 将控制权返回给 MainFrame 类,在那里我们检查该变量以查看用户是否登录。如果不是,我们关闭应用程序。否则我们加载应用程序。
包扎
我们可以添加一些增强功能,比如将焦点设置到第一个文本控件或者添加一个取消按钮。我相信你自己也能想出几个其他的。总的来说,这应该让你开始。
wxPython:如何禁用向导的“下一步”按钮
原文:https://www.blog.pythonlibrary.org/2014/03/06/wxpython-how-to-disable-a-wizards-next-button/
前几天有人在 StackOverflow 上问了很多关于如何在 wxPython 中使用向导的问题。可以在这里看两个原问题,在这里看。这个例子中我们要看的代码是我用来回答 Stack 上的问题的。主要问题是如何在 wxPython 向导中禁用下一个 T4。
如何禁用向导的“下一步”按钮?
最初的人在发布问题时的想法是,他们希望用户在能够继续之前填写两个文本控件。这意味着我们需要禁用 Next 按钮,直到两个文本小部件都有内容。我想到了一个使用 wx 的方法。计时器每秒检查一次文本控件,看它们是否有数据。如果是,那么计时器的事件处理程序将启用“下一步”按钮。让我们来看看:
import wx
import wx.wizard
########################################################################
class WizardPage(wx.wizard.PyWizardPage):
#----------------------------------------------------------------------
def __init__(self, parent, title):
wx.wizard.PyWizardPage.__init__(self, parent)
self.next = None
self.prev = None
self.initializeUI(title)
#----------------------------------------------------------------------
def initializeUI(self, title):
# create grid layout manager
self.sizer = wx.GridBagSizer()
self.SetSizerAndFit(self.sizer)
#----------------------------------------------------------------------
def addWidget(self, widget, pos, span):
self.sizer.Add(widget, pos, span, wx.EXPAND)
#----------------------------------------------------------------------
# getters and setters
def SetPrev(self, prev):
self.prev = prev
#----------------------------------------------------------------------
def SetNext(self, next):
self.next = next
#----------------------------------------------------------------------
def GetPrev(self):
return self.prev
#----------------------------------------------------------------------
def GetNext(self):
return self.next
########################################################################
class MyWizard(wx.wizard.Wizard):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.wizard.Wizard.__init__(self, None, -1, "Some Title")
self.SetPageSize((500, 350))
mypage1 = self.create_page1()
forward_btn = self.FindWindowById(wx.ID_FORWARD)
forward_btn.Disable()
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onUpdate, self.timer)
self.timer.Start(1)
self.RunWizard(mypage1)
#----------------------------------------------------------------------
def create_page1(self):
page1 = WizardPage(self, "Page 1")
d = wx.StaticText(page1, label="test")
page1.addWidget(d, (2, 1), (1,5))
self.text1 = wx.TextCtrl(page1)
page1.addWidget(self.text1, (3,1), (1,5))
self.text2 = wx.TextCtrl(page1)
page1.addWidget(self.text2, (4,1), (1,5))
page2 = WizardPage(self, "Page 2")
page2.SetName("page2")
self.text3 = wx.TextCtrl(page2)
self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.onPageChanged)
page3 = WizardPage(self, "Page 3")
# Set links
page2.SetPrev(page1)
page1.SetNext(page2)
page3.SetPrev(page2)
page2.SetNext(page3)
return page1
#----------------------------------------------------------------------
def onPageChanged(self, event):
""""""
page = event.GetPage()
if page.GetName() == "page2":
self.text3.SetValue(self.text2.GetValue())
#----------------------------------------------------------------------
def onUpdate(self, event):
"""
Enables the Next button if both text controls have values
"""
value_one = self.text1.GetValue()
value_two = self.text2.GetValue()
if value_one and value_two:
forward_btn = self.FindWindowById(wx.ID_FORWARD)
forward_btn.Enable()
self.timer.Stop()
#----------------------------------------------------------------------
def main():
""""""
wizard = MyWizard()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
main()
app.MainLoop()
让我们把它分解一下。我们要看的第一个类是 MyWizard ,它是所有动作发生的地方。MyWizard 是 wxPython 的 Wizard 类的子类。在 init 中,我们创建了一个页面并找到了 Next 按钮,这样我们就可以禁用它了。然后我们创建并启动计时器对象,同时将它绑定到 onUpdate 方法。最后,我们运行向导。当我们创建向导页面时,我们实例化了 WizardPage 类。这个类实际上是不言自明的。无论如何,我们最终创建了几个放置在向导页面上的小部件。另一个有趣的地方是在 onUpdate 方法中。这里我们检查用户是否在两个文本控件中输入了数据。
如果有,那么我们找到下一个按钮,启用它并停止计时器。这里有一个潜在的错误。如果用户在填写完内容后删除了一些内容,会发生什么呢?“下一步”按钮不会再次自行禁用。这里有一个更新版本的 onUpdate 方法可以解决这个问题:
#----------------------------------------------------------------------
def onUpdate(self, event):
"""
Enables the Next button if both text controls have values
"""
value_one = self.text1.GetValue()
value_two = self.text2.GetValue()
forward_btn = self.FindWindowById(wx.ID_FORWARD)
if value_one and value_two:
forward_btn.Enable()
else:
if forward_btn.IsEnabled():
forward_btn.Disable()
在这里,我们从不停止计时器。相反,计时器会不断检查文本控件的值,如果发现其中一个控件没有数据,并且“下一步”按钮被启用,处理程序会禁用该按钮。
包扎
在 wxPython 向导中禁用 Next 按钮并不特别难,只是有点复杂。如果向导小部件的 API 允许对它创建的标准小部件进行更多的访问,那就太好了。然而,现在你知道如何与他们合作,改变他们的状态。明智地使用这些知识!
相关阅读
- wxPython: 一个向导教程
- wxPython: 如何创建一个通用向导
wxPython:如何双击 ObjectListView 小部件中的项目
本周,我需要弄清楚如何附加一个事件处理程序,当我双击处于 LC_REPORT 模式的 ObjectListView 小部件中的一个项目(即行)时,该事件处理程序将被触发。出于某种原因,没有明显的鼠标事件。有一个 EVT _ 列表 _ 项目 _ 右键单击和一个 EVT _ 列表 _ 项目 _ 中键单击,但是没有任何种类的左键单击。在谷歌上搜索了一会儿后,我发现我可以通过使用 EVT _ 列表 _ 项目 _ 激活来让它工作。这将在双击某项、选择某项并且用户按 ENTER 时触发。下面是一个代码示例:
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 DCPanel(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.resultsOlv.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onDoubleClick)
self.setResults()
mainSizer.Add(self.resultsOlv, 1, wx.EXPAND|wx.ALL, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def onDoubleClick(self, event):
"""
When the item is double-clicked or "activated", do something
"""
print "in onDoubleClick method"
#----------------------------------------------------------------------
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 DCFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="Double-click Tutorial")
panel = DCPanel(self)
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DCFrame()
frame.Show()
app.MainLoop()
很直接,对吧?如果你需要知道如何做到这一点,我希望它能帮助你。这个方法也应该和 ListCtrl 一起使用。
wxPython:如何将文件从应用程序拖放到操作系统中
今天在 StackOverflow 上,我看到有人想知道如何从 wx 中拖动文件。ListCtrl 放到他们的桌面上或者文件系统中的其他地方。他们使用了来自 zetcode 的文件管理器框架,但是不知道如何添加 DnD 部分。经过一番搜索和黑客攻击,我基于罗宾·邓恩在论坛上提到的东西想到了这个。
import wx
import os
import time
########################################################################
class MyListCtrl(wx.ListCtrl):
#----------------------------------------------------------------------
def __init__(self, parent, id):
wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT)
files = os.listdir('.')
self.InsertColumn(0, 'Name')
self.InsertColumn(1, 'Ext')
self.InsertColumn(2, 'Size', wx.LIST_FORMAT_RIGHT)
self.InsertColumn(3, 'Modified')
self.SetColumnWidth(0, 220)
self.SetColumnWidth(1, 70)
self.SetColumnWidth(2, 100)
self.SetColumnWidth(3, 420)
j = 0
for i in files:
(name, ext) = os.path.splitext(i)
ex = ext[1:]
size = os.path.getsize(i)
sec = os.path.getmtime(i)
self.InsertStringItem(j, "%s%s" % (name, ext))
self.SetStringItem(j, 1, ex)
self.SetStringItem(j, 2, str(size) + ' B')
self.SetStringItem(j, 3, time.strftime('%Y-%m-%d %H:%M',
time.localtime(sec)))
if os.path.isdir(i):
self.SetItemImage(j, 1)
elif ex == 'py':
self.SetItemImage(j, 2)
elif ex == 'jpg':
self.SetItemImage(j, 3)
elif ex == 'pdf':
self.SetItemImage(j, 4)
else:
self.SetItemImage(j, 0)
if (j % 2) == 0:
self.SetItemBackgroundColour(j, '#e6f1f5')
j = j + 1
########################################################################
class FileHunter(wx.Frame):
#----------------------------------------------------------------------
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, -1, title)
panel = wx.Panel(self)
p1 = MyListCtrl(panel, -1)
p1.Bind(wx.EVT_LIST_BEGIN_DRAG, self.onDrag)
sizer = wx.BoxSizer()
sizer.Add(p1, 1, wx.EXPAND)
panel.SetSizer(sizer)
self.Center()
self.Show(True)
#----------------------------------------------------------------------
def onDrag(self, event):
""""""
data = wx.FileDataObject()
obj = event.GetEventObject()
id = event.GetIndex()
filename = obj.GetItem(id).GetText()
dirname = os.path.dirname(os.path.abspath(os.listdir(".")[0]))
fullpath = str(os.path.join(dirname, filename))
data.AddFile(fullpath)
dropSource = wx.DropSource(obj)
dropSource.SetData(data)
result = dropSource.DoDragDrop()
print fullpath
#----------------------------------------------------------------------
app = wx.App(False)
FileHunter(None, -1, 'File Hunter')
app.MainLoop()
这里有几个要点。首先,您需要绑定到 EVT 列表开始拖动来捕捉适当的事件。然后,在您的处理程序中,您需要创建一个 wx。FileDataObject 对象,并使用其 AddFile 方法将完整路径附加到其内部文件列表中。根据 wxPython 的文档,AddFile 是 Windows 专用的,但是因为 Robin Dunn(wxPython 的创建者)推荐了这个方法,我就用了它。可能是文档有误。无论如何,我们还需要定义 DropSource 并调用它的 DoDragDrop 方法,这样就完成了。这段代码在 Windows 7、Python 2.6.6 和 wxPython 2.8.12.1 上对我来说是有效的。
wxPython:如何使用 reload()交互式编辑您的 GUI
今天,我在 StackOverflow 上遇到了一个有趣的问题,作者问我如何动态地编写 wxPython 程序。换句话说,他希望能够编辑代码并基本上刷新应用程序,而无需关闭并重新运行他的代码。最简单的方法是使用 Python 内置的重载功能。如果我们走这条路,那么我们将需要构建一个小前端来导入我们想要交互更改的代码。
创建重装应用程序
创建重载应用程序非常简单。让我们来看看代码!
import testApp
import wx
########################################################################
class ReloaderPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.testFrame = None
showAppBtn = wx.Button(self, label="Show App")
showAppBtn.Bind(wx.EVT_BUTTON, self.onShowApp)
reloadBtn = wx.Button(self, label="Reload")
reloadBtn.Bind(wx.EVT_BUTTON, self.onReload)
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(showAppBtn, 0, wx.ALL|wx.CENTER, 5)
mainSizer.Add(reloadBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def onReload(self, event):
"""
Reload the code!
"""
if self.testFrame:
self.testFrame.Close()
reload(testApp)
self.showApp()
else:
self.testFrame = None
#----------------------------------------------------------------------
def onShowApp(self, event):
"""
Call the showApp() method
"""
self.showApp()
#----------------------------------------------------------------------
def showApp(self):
"""
Show the application we want to edit dynamically
"""
self.testFrame = testApp.TestFrame()
########################################################################
class ReloaderFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Reloader")
panel = ReloaderPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = ReloaderFrame()
app.MainLoop()
在这里,我们导入我们计划在脚本运行时编辑的模块。在这种情况下,该模块被称为 testApp (文件为 testApp.py)。接下来,我们添加几个按钮;一个用于显示 testApp 的框架,另一个用于重新加载 testApp 代码,并重新显示所做的任何更改。是的,我们可能应该在这里添加一些异常处理,以防我们在代码中犯了一个错别字,然后试图重新加载它,但我将把它留给读者作为练习。现在我们需要创建 testApp.py 文件。这里有一个简单的脚本,您可以使用它:
import wx
########################################################################
class TestPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
########################################################################
class TestFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Test program")
panel = TestPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = TestFrame()
app.MainLoop()
现在,您所要做的就是编辑第二个文件,并用第一个文件重新加载它,以查看更改。我建议在 TestPanel 类中添加一个按钮,保存它,然后在另一个脚本中点击 Reload 按钮来查看更改。
是的,就这么简单。玩得开心!
wxPython:如何触发多个事件处理程序
原文:https://www.blog.pythonlibrary.org/2012/07/24/wxpython-how-to-fire-multiple-event-handlers/
今天在 StackOverflow 上,我看到有人想知道如何在 wxPython 中将两个函数/方法绑定到同一个事件。这真的很容易。这里有一个例子:
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label="Press Me")
btn.Bind(wx.EVT_BUTTON, self.HandlerOne)
btn.Bind(wx.EVT_BUTTON, self.HandlerTwo)
#----------------------------------------------------------------------
def HandlerOne(self, event):
""""""
print "handler one fired!"
event.Skip()
#----------------------------------------------------------------------
def HandlerTwo(self, event):
""""""
print "handler two fired!"
event.Skip()
########################################################################
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()
正如您所看到的,您所要做的就是调用小部件的 Bind 方法两次,并向它传递相同的事件,但传递不同的处理程序。下一个关键是你必须使用事件。Skip() 。Skip 将导致 wxPython 寻找可能需要处理该事件的其他处理程序。事件沿着层次结构向上传递到父级,直到它们被处理或什么都没发生。罗宾·邓恩的《Wxpython in ActionWxpython " target = " _ blank ">Wxpython in Action一书很好地解释了这个概念。
wxPython:如何从 Sizer 中获取子部件
原文:https://www.blog.pythonlibrary.org/2012/08/24/wxpython-how-to-get-children-widgets-from-a-sizer/
前几天,我在 StackOverflow 上偶然发现了一个问题,询问如何获得 BoxSizer 的子窗口部件。在 wxPython 中,您可能会调用 sizer 的 GetChildren()方法。但是,这将返回 SizerItems 对象的列表,而不是实际小部件本身的列表。如果你调用一个 wx,你就能看出区别。Panel 的 GetChildren()方法。现在我不会在 wxPython 用户组列表上问很多问题,但我对这个很好奇,并最终收到了来自 Cody Precord 的快速回答,wxPython 食谱和 Editra 的作者。总之,他最终给我指出了正确的方向,我想出了下面的代码:
import wx
########################################################################
class MyApp(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Example")
panel = wx.Panel(self)
lbl = wx.StaticText(panel, label="I'm a label!")
txt = wx.TextCtrl(panel, value="blah blah")
btn = wx.Button(panel, label="Clear")
btn.Bind(wx.EVT_BUTTON, self.onClear)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(lbl, 0, wx.ALL, 5)
self.sizer.Add(txt, 0, wx.ALL, 5)
self.sizer.Add(btn, 0, wx.ALL, 5)
panel.SetSizer(self.sizer)
#----------------------------------------------------------------------
def onClear(self, event):
""""""
children = self.sizer.GetChildren()
for child in children:
widget = child.GetWindow()
print widget
if isinstance(widget, wx.TextCtrl):
widget.Clear()
if __name__ == "__main__":
app = wx.App(False)
frame = MyApp()
frame.Show()
app.MainLoop()
重要的位在 onClear 方法中。这里我们需要调用 SizerItem 的 GetWindow()方法来返回实际的小部件实例。一旦我们有了它,我们就可以对小部件做一些事情,比如改变标签、值,或者在这个例子中,清除文本控件。现在您也知道如何访问 sizer 的子部件了。
wxPython:如何获取网格中选定的单元格
原文:https://www.blog.pythonlibrary.org/2013/10/31/wxpython-how-to-get-selected-cells-in-a-grid/
今天我们将看看如何从 wxPython 网格对象中获取选定的单元格。大多数情况下,获取节很容易,但是当用户选择多个单元格时,获取选择就变得复杂了。我们将需要创建一些示例代码来显示所有这些是如何组合在一起的。我们开始吧!
网格单元选择
网上有一篇关于这个话题的有趣文章。这里可以看。然而,这篇文章有几个问题,我们也将看到。下面是我们要看的代码:
import wx
import wx.grid as gridlib
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.currentlySelectedCell = (0, 0)
self.myGrid = gridlib.Grid(self)
self.myGrid.CreateGrid(12, 8)
self.myGrid.Bind(gridlib.EVT_GRID_SELECT_CELL, self.onSingleSelect)
self.myGrid.Bind(gridlib.EVT_GRID_RANGE_SELECT, self.onDragSelection)
selectBtn = wx.Button(self, label="Get Selected Cells")
selectBtn.Bind(wx.EVT_BUTTON, self.onGetSelection)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.myGrid, 1, wx.EXPAND)
sizer.Add(selectBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def onDragSelection(self, event):
"""
Gets the cells that are selected by holding the left
mouse button down and dragging
"""
if self.myGrid.GetSelectionBlockTopLeft():
top_left = self.myGrid.GetSelectionBlockTopLeft()[0]
bottom_right = self.myGrid.GetSelectionBlockBottomRight()[0]
self.printSelectedCells(top_left, bottom_right)
#----------------------------------------------------------------------
def onGetSelection(self, event):
"""
Get whatever cells are currently selected
"""
cells = self.myGrid.GetSelectedCells()
if not cells:
if self.myGrid.GetSelectionBlockTopLeft():
top_left = self.myGrid.GetSelectionBlockTopLeft()[0]
bottom_right = self.myGrid.GetSelectionBlockBottomRight()[0]
self.printSelectedCells(top_left, bottom_right)
else:
print self.currentlySelectedCell
else:
print cells
#----------------------------------------------------------------------
def onSingleSelect(self, event):
"""
Get the selection of a single cell by clicking or
moving the selection with the arrow keys
"""
print "You selected Row %s, Col %s" % (event.GetRow(),
event.GetCol())
self.currentlySelectedCell = (event.GetRow(),
event.GetCol())
event.Skip()
#----------------------------------------------------------------------
def printSelectedCells(self, top_left, bottom_right):
"""
Based on code from http://ginstrom.com/scribbles/2008/09/07/getting-the-selected-cells-from-a-wxpython-grid/
"""
cells = []
rows_start = top_left[0]
rows_end = bottom_right[0]
cols_start = top_left[1]
cols_end = bottom_right[1]
rows = range(rows_start, rows_end+1)
cols = range(cols_start, cols_end+1)
cells.extend([(row, col)
for row in rows
for col in cols])
print "You selected the following cells: ", cells
for cell in cells:
row, col = cell
print self.myGrid.GetCellValue(row, col)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="Single Cell Selection")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
让我们花点时间来分析一下。首先,我们创建一个网格对象,我们称之为 self.myGrid 。我们绑定到两个特定于网格的事件,EVT 网格选择单元格和 EVT 网格范围选择。这是为了演示的目的,因为你通常不需要绑定到 EVT 网格选择单元。对于单个单元格选择事件,我们调用 onSingleSelect 处理程序。在其中,我们使用事件对象来获取正确的行和列。如果你看上面链接的文章,你会注意到他们正在使用 GetGridCursorRow 和 GetGridCursorCol。我发现这些只返回之前选中的单元格,而不是当前选中的单元格。这就是我们使用事件对象的方法的原因。另请注意,我们正在更新self . currently selected cell的值,使其等于当前选择的单元格。
另一个网格事件被绑定到 onDragSelection 。在这个事件处理程序中,我们调用网格的 GetSelectionBlockTopLeft ()方法,并检查以确保它返回一些东西。如果没有,那么我们什么也不做。但是如果它确实返回了一些东西,那么我们获取它的内容以及从 GetSelectionBlockBottomRight()返回的内容。然后我们将这些传递给我们的 printSelectedCells 方法。这段代码基于前面提到的文章,尽管它已经被简化了一点,因为我发现原来的 for 循环抛出了一个错误。基本上,这个方法所做的就是使用 Python 的 range 函数创建两个值列表。然后,它使用嵌套列表理解来扩展列表。最后,它打印出选择到 stdout 的单元格。
最后要看的方法是按钮事件处理程序: onGetSelection 。这个方法调用网格的 GetSelectedCells ()方法。这将返回单击的选定单元格。如果用户拖动选择一些单元格,它也可以工作。如果用户只选择了一个单元格,那么我们将打印self . currently selected cell,因为它总是等于当前选择的值。
包扎
正如您所看到的,从 grid 对象中获取选定的一个或多个单元格可能有点棘手。但是通过一些努力,我们能够克服。希望您会发现这在您当前或未来的项目中很有用。
相关阅读
- wxPython: 网格简介
- wxPython: 网格提示和技巧
- 从 wxPython 网格中获取选定的单元格
wxPython:如何制作“闪烁文本”
原文:https://www.blog.pythonlibrary.org/2012/08/09/wxpython-how-to-make-flashing-text/
人们不断在 StackOverflow 上提出有趣的 wxPython 问题。今天他们想知道如何在 wxPython 中制作"闪烁文本。这其实很容易做到。让我们来看看一些简单的代码:
import random
import time
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.font = wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
self.flashingText = wx.StaticText(self, label="I flash a LOT!")
self.flashingText.SetFont(self.font)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
self.timer.Start(1000)
#----------------------------------------------------------------------
def update(self, event):
""""""
now = int(time.time())
mod = now % 2
print now
print mod
if mod:
self.flashingText.SetLabel("Current time: %i" % now)
else:
self.flashingText.SetLabel("Oops! It's mod zero time!")
colors = ["blue", "green", "red", "yellow"]
self.flashingText.SetForegroundColour(random.choice(colors))
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Flashing text!")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
基本上你需要的就是一个 wx。StaticText 实例和一个 wx.Timer。对于 flash,我们的意思是它会改变颜色,文本本身也会改变。最初提出这个问题的人想知道如何使用 Python 的 time.time()方法显示时间,他们希望消息根据时间除以 2 的模数是否等于零而变化。我知道这看起来有点奇怪,但是我实际上在我自己的一些代码中使用了这个想法。无论如何,这在我使用 Python 2.6.6 和 wxPython 2.8.12.1 的 Windows 7 上工作。
请注意,有时 SetForegroundColour 方法并不适用于所有平台上的所有小部件,因为原生小部件并不总是允许改变颜色,所以您的收益可能会有所不同。
wxPython:如何最小化到系统托盘
原文:https://www.blog.pythonlibrary.org/2013/07/12/wxpython-how-to-minimize-to-system-tray/
我在网上各个地方看到有人时不时的问这个话题。让 wxPython 最小化到托盘非常简单,但是至少有一件事需要注意。我们会谈到这一点,但首先我们需要花一些时间来看一些代码。事实上,我在几年前写过关于 TaskBarIcons 的文章。首先我们来看看任务栏图标代码,它大致基于前面提到的文章:
import wx
########################################################################
class CustomTaskBarIcon(wx.TaskBarIcon):
""""""
#----------------------------------------------------------------------
def __init__(self, frame):
"""Constructor"""
wx.TaskBarIcon.__init__(self)
self.frame = frame
img = wx.Image("24x24.png", wx.BITMAP_TYPE_ANY)
bmp = wx.BitmapFromImage(img)
self.icon = wx.EmptyIcon()
self.icon.CopyFromBitmap(bmp)
self.SetIcon(self.icon, "Restore")
self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.OnTaskBarLeftClick)
#----------------------------------------------------------------------
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
"""
self.frame.Show()
self.frame.Restore()
如您所见,我们需要继承 wx 的子类。TaskBarIcon,然后给它一个图标。对于本文,我们将使用来自 deviantart 的免费图标。好的,在 init 中,我们必须通过几道关卡将 PNG 文件转换成 wx 的 icon 方法可以使用的格式。当子类化 wx.TaskBarIcon 时,其余的方法是必需的。你会注意到我们绑定到 LEFT _ 任务栏 _ 左 _ 下,这样当用户点击图标时,我们可以恢复窗口。
现在我们准备看看框架的代码。
import custTray
import wx
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Minimize to Tray")
panel = wx.Panel(self)
self.tbIcon = custTray.CustomTaskBarIcon(self)
self.Bind(wx.EVT_ICONIZE, self.onMinimize)
self.Bind(wx.EVT_CLOSE, self.onClose)
self.Show()
#----------------------------------------------------------------------
def onClose(self, evt):
"""
Destroy the taskbar icon and the frame
"""
self.tbIcon.RemoveIcon()
self.tbIcon.Destroy()
self.Destroy()
#----------------------------------------------------------------------
def onMinimize(self, event):
"""
When minimizing, hide the frame so it "minimizes to tray"
"""
if self.IsIconized():
self.Hide()
#----------------------------------------------------------------------
def main():
""""""
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
if __name__ == "__main__":
main()
这里我们有两个事件绑定。一个是 EVT 附近的,另一个是 EVT 附近的。后者在用户最小化框架时触发,所以我们用它来最小化托盘,实际上只是隐藏框架。另一个事件在您关闭框架时触发,它更重要一点。为什么?你需要捕捉关闭事件,以防用户试图通过托盘图标关闭应用程序。你需要确保移除图标并销毁它,否则你的应用看起来会关闭,但实际上只是挂在后台。
包扎
现在您知道了如何将 wxPython 应用程序最小化到系统托盘区域。我以前用它做过一个简单的邮件检查程序。你可以用它来做很多其他的事情,比如一个监视器,它通过提升框架来响应事件。
附加阅读
- wxpython:如何最小化到任务栏
- wxPython 101: 创建任务栏图标
wxPython:如何打开第二个窗口/框架
原文:https://www.blog.pythonlibrary.org/2018/10/19/wxpython-how-to-open-a-second-window-frame/
我经常看到与本文标题相关的问题。如何打开第二个框架/窗口?当我关闭主应用程序时,如何关闭所有的框架?当您第一次学习 wxPython 时,这类问题可能很难找到答案,因为您对框架或术语不够熟悉,不知道如何寻找答案。
希望这篇文章能有所帮助。我们将学习如何打开多个框架,以及如何关闭它们。我们开始吧!
打开多个框架
创建多个帧实际上非常简单。你只需要创建一个 wx 的子类。Frame 用于您想要创建的每个新帧。或者如果新的框架看起来相同,那么您只需要多次实例化第二个类。你还需要一个 wx 的子类。主应用程序框架的框架。让我们写一些代码,因为我认为这将使事情更清楚:
import wx
class OtherFrame(wx.Frame):
"""
Class used for creating frames other than the main one
"""
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Create New Frame')
btn.Bind(wx.EVT_BUTTON, self.on_new_frame)
self.frame_number = 1
def on_new_frame(self, event):
title = 'SubFrame {}'.format(self.frame_number)
frame = OtherFrame(title=title)
self.frame_number += 1
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(800, 600))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
所以我们在这里创建了两个类来继承 wx。框架:其他框架和主机。OtherFrame 是我们额外使用的框架,而 MainFrame 是我们的主应用程序窗口。我们还创建了一个 wx 的子类。面板容纳一个按钮和一个按钮事件处理程序。该按钮将用于创建额外的框架。
试着运行这段代码,你会发现它可以通过多次点击按钮来创建任意数量的额外帧。然而,如果你试图在一个或多个其他框架仍在运行的情况下关闭主应用程序,你会发现该应用程序将继续运行。
让我们学习如何修改这段代码,以便在主框架关闭时关闭所有框架。
关闭所有框架
您可能已经注意到了这一点,但是 MainFrame 类和 OtherFrame 类都设置了父类。MainFrame 类被硬编码为相当微妙的 None,而 OtherFrame 的父类则默认为 None。这实际上是我们问题的关键。当你设置一个 wx。Frame 的父级设置为 None,它将成为没有依赖关系的独立实体。所以它不在乎另一个框架是否关闭。但是,如果我们将 OtherFrame 实例的所有父实例都设置为 MainFrame 实例,那么当我们关闭主框架时,它们也会关闭。
要进行这一更改,我们需要做的就是将 on_new_frame 函数更改为以下内容:
def on_new_frame(self, event):
title = 'SubFrame {}'.format(self.frame_number)
frame = OtherFrame(title=title, parent=wx.GetTopLevelParent(self))
self.frame_number += 1
这里我们通过使用 wxPython 的 wx 将父节点设置为大型机。GetTopLevelParent 函数。另一种方法是修改我的面板的 init 来保存对父参数的引用:
def __init__(self, parent):
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Create New Frame')
btn.Bind(wx.EVT_BUTTON, self.on_new_frame)
self.frame_number = 1
self.parent = parent
def on_new_frame(self, event):
title = 'SubFrame {}'.format(self.frame_number)
frame = OtherFrame(title=title, parent=self.parent)
self.frame_number += 1
在这种情况下,我们只使用 instance 属性来设置我们的 parent。为了彻底起见,我想提一个我们可以为大型机实例设置父实例的方法,那就是调用 GetParent() :
def on_new_frame(self, event):
title = 'SubFrame {}'.format(self.frame_number)
frame = OtherFrame(title=title, parent=self.GetParent())
self.frame_number += 1
这里我们只是调用 MyPanel 的 GetParent()方法来获取面板的父面板。这是可行的,因为我的面板只被主机使用。但是,如果我们碰巧在其他框架中使用了 panel 类,事情就会变得混乱,所以我个人更喜欢使用gettoplevelparant方法,因为它很大程度上保证了我们将获得正确的小部件。
包扎
使用多个框架应该不难。我希望这篇教程已经向你展示了做起来是多么容易。您也可以使用本文中的概念来创建对话框,因为它们的工作方式与框架非常相似,尽管它们往往是模态的。感谢阅读和快乐编码!
wxPython:如何以编程方式更改 wx。笔记本页面
偶尔我会在 wxPython 用户组上看到有人询问如何制作 wx。笔记本以编程方式更改页面(或选项卡)。所以我决定是时候弄清楚了。下面是一些适合我的代码:
import random
import wx
########################################################################
class TabPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent, page):
""""""
wx.Panel.__init__(self, parent=parent)
self.page = page
colors = ["red", "blue", "gray", "yellow", "green"]
self.SetBackgroundColour(random.choice(colors))
btn = wx.Button(self, label="Change Selection")
btn.Bind(wx.EVT_BUTTON, self.onChangeSelection)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL, 10)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def onChangeSelection(self, event):
"""
Change the page!
"""
notebook = self.GetParent()
notebook.SetSelection(self.page)
########################################################################
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, 1)
notebook.AddPage(tabOne, "Tab 1")
tabTwo = TabPanel(notebook, 0)
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()
需要知道的主要事情是,您需要使用 SetSelection(或 ChangeSelection)来强制 Notebook 小部件改变页面。就是这样!这段代码在 Windows 7 上用 Python 2.7.3 和 Python 2.7.3(经典)进行了测试。另请参见关于启用的讨论。
wxPython:如何将 Python 的日志模块重定向到 TextCtrl
今天,我正在阅读 wxPython Google group /邮件列表,有人询问如何让 Python 的日志模块将其输出写入文件和 TextCtrl。事实证明,您需要创建一个定制的日志处理程序来完成这项工作。起初,我尝试使用普通的 StreamHandler,并通过 sys 模块(sys.stdout)将 stdout 重定向到我的文本控件,但这只能重定向我的打印语句,而不能重定向日志消息。
让我们看看我最后得到了什么:
import logging
import logging.config
import wx
########################################################################
class CustomConsoleHandler(logging.StreamHandler):
""""""
#----------------------------------------------------------------------
def __init__(self, textctrl):
""""""
logging.StreamHandler.__init__(self)
self.textctrl = textctrl
#----------------------------------------------------------------------
def emit(self, record):
"""Constructor"""
msg = self.format(record)
self.textctrl.WriteText(msg + "\n")
self.flush()
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.logger = logging.getLogger("wxApp")
self.logger.info("Test from MyPanel __init__")
logText = wx.TextCtrl(self,
style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
btn = wx.Button(self, label="Press Me")
btn.Bind(wx.EVT_BUTTON, self.onPress)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(logText, 1, wx.EXPAND|wx.ALL, 5)
sizer.Add(btn, 0, wx.ALL, 5)
self.SetSizer(sizer)
txtHandler = CustomConsoleHandler(logText)
self.logger.addHandler(txtHandler)
#----------------------------------------------------------------------
def onPress(self, event):
"""
"""
self.logger.error("Error Will Robinson!")
self.logger.info("Informational message")
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Logging test")
panel = MyPanel(self)
self.logger = logging.getLogger("wxApp")
self.Show()
#----------------------------------------------------------------------
def main():
"""
"""
dictLogConfig = {
"version":1,
"handlers":{
"fileHandler":{
"class":"logging.FileHandler",
"formatter":"myFormatter",
"filename":"test.log"
},
"consoleHandler":{
"class":"logging.StreamHandler",
"formatter":"myFormatter"
}
},
"loggers":{
"wxApp":{
"handlers":["fileHandler", "consoleHandler"],
"level":"INFO",
}
},
"formatters":{
"myFormatter":{
"format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
}
}
logging.config.dictConfig(dictLogConfig)
logger = logging.getLogger("wxApp")
logger.info("This message came from main!")
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
if __name__ == "__main__":
main()
您会注意到,我最终使用了 Python 的 logging.config 模块。Python 2.7 中添加了 dictConfig 方法,因此如果您没有该方法或更好的方法,那么这段代码就不适合您。基本上,您可以在 dictionary 中设置日志处理程序和格式化程序,然后将它传递给 logging.config。如果您运行这段代码,您会注意到前几条消息会发送到 stdout 和 log,但不会发送到文本控件。在 panel 类的 init 的末尾,我们添加了我们的自定义处理程序,这时开始将日志消息重定向到文本控件。您可以按下按钮来查看它的运行情况!
您可能还想看看下面的一些参考资料。它们有助于更详细地解释我在做什么。
相关文章
- wxPython: 重定向 stdout 和 stderr
- Python 101: 日志介绍
- Python 日志记录- 如何记录到多个位置
- 关于测井模块字典配置的官方文件
- StackOverflow: 如何编写定制的 Python 日志处理程序
wxPython:如何在面板之间切换
原文:https://www.blog.pythonlibrary.org/2010/06/16/wxpython-how-to-switch-between-panels/
每隔几个月,我都会看到有人询问如何在他们正在开发的 wxPython 应用程序的两个视图或面板之间切换。因为这是一个非常常见的问题,而且因为我上周在 IRC 的 wxPython 频道上被问到过,所以我写了一个简短的脚本来展示如何做到这一点。请注意,在大多数情况下,用户可能会发现许多笔记本小部件中的一个足以满足他们的需求。不管怎样,我们来看看这个东西是怎么做的吧!
在这个例子中,我们将使用一个菜单在两个面板之间切换。第一个面板上只有一个文本控件,第二个面板上只有一个网格小部件。
import wx
import wx.grid as gridlib
########################################################################
class PanelOne(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
txt = wx.TextCtrl(self)
########################################################################
class PanelTwo(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 MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Panel Switcher Tutorial")
self.panel_one = PanelOne(self)
self.panel_two = PanelTwo(self)
self.panel_two.Hide()
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panel_one, 1, wx.EXPAND)
self.sizer.Add(self.panel_two, 1, wx.EXPAND)
self.SetSizer(self.sizer)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
switch_panels_menu_item = fileMenu.Append(wx.ID_ANY,
"Switch Panels",
"Some text")
self.Bind(wx.EVT_MENU, self.onSwitchPanels,
switch_panels_menu_item)
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
#----------------------------------------------------------------------
def onSwitchPanels(self, event):
""""""
if self.panel_one.IsShown():
self.SetTitle("Panel Two Showing")
self.panel_one.Hide()
self.panel_two.Show()
else:
self.SetTitle("Panel One Showing")
self.panel_one.Show()
self.panel_two.Hide()
self.Layout()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
我们唯一关心的代码位于 onSwitchPanels 事件处理程序中。这里我们使用一个条件来检查哪个面板正在显示,然后隐藏当前面板并显示另一个面板。我们还设置了框架的标题,使哪个面板是哪个面板变得很明显。我们还需要调用框架的 Layout()方法来使面板可见。否则,您可能会看到一些奇怪的视觉异常,就像没有任何东西真正显示在框架中,除非您稍微调整它的大小。
现在你也知道如何切换面板了。如果您计划做大量的可视化工作,比如添加或删除小部件,那么您可能希望研究冻结和解冻方法,然后使用 Layout。它们有助于隐藏当您修改面板的子面板时可以看到的闪烁。
wxPython:如何从线程更新进度条
原文:https://www.blog.pythonlibrary.org/2013/09/04/wxpython-how-to-update-a-progress-bar-from-a-thread/
不时地,我看到有人想知道如何创建一个进度条并更新它。所以我决定编写一个更新进度条的示例应用程序(技术上是一个 wx。仪表部件)。在本教程中,我们将创建一个带有按钮的框架。当按钮被按下时,它将启动一个包含进度条的对话框,并启动一个线程。该线程是一个伪线程,因为它除了在 20 秒内每秒向对话框发送一次更新之外,不做任何特别的事情。然后对话被破坏。我们来看看吧!
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(20):
time.sleep(1)
wx.CallAfter(Publisher().sendMessage, "update", "")
########################################################################
class MyProgressDialog(wx.Dialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Progress")
self.count = 0
self.progress = wx.Gauge(self, range=20)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.progress, 0, wx.EXPAND)
self.SetSizer(sizer)
# create a pubsub listener
Publisher().subscribe(self.updateProgress, "update")
#----------------------------------------------------------------------
def updateProgress(self, msg):
"""
Update the progress bar
"""
self.count += 1
if self.count >= 20:
self.Destroy()
self.progress.SetValue(self.count)
########################################################################
class MyFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, title="Progress Bar Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.btn = btn = wx.Button(panel, label="Start Thread")
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Runs the thread
"""
btn = event.GetEventObject()
btn.Disable()
TestThread()
dlg = MyProgressDialog()
dlg.ShowModal()
btn.Enable()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
frame.Show()
app.MainLoop()
让我们花几分钟来分析一下。我们从底层开始。首先运行的是 MyFrame 类。当您运行这个脚本时,您应该会看到类似这样的内容:
如您所见,这些代码只是创建了一个简单的框架,上面有一个按钮。如果您按下按钮,将创建以下对话框,并启动一个新线程:
让我们来看看构成对话框的那部分代码:
########################################################################
class MyProgressDialog(wx.Dialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Progress")
self.count = 0
self.progress = wx.Gauge(self, range=20)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.progress, 0, wx.EXPAND)
self.SetSizer(sizer)
# create a pubsub listener
Publisher().subscribe(self.updateProgress, "update")
#----------------------------------------------------------------------
def updateProgress(self, msg):
"""
Update the progress bar
"""
self.count += 1
if self.count >= 20:
self.Destroy()
self.progress.SetValue(self.count)
这段代码创建了一个带有 wx 的对话框。仪表小部件。标尺是进度条后面的实际小部件。无论如何,我们在对话框的 init 的最后创建了一个 pubsub 监听器。这个侦听器接受将触发 updateProgress 方法的消息。我们将看到消息在线程类中被发送。在 updateProgress 方法中,我们递增计数器并更新 wx。通过设置其值来测量。我们还检查计数是否大于或等于 20,这是仪表的范围。如果是的话,我们就毁掉这个对话。
现在,我们可以开始查看线程代码了:
########################################################################
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(20):
time.sleep(1)
wx.CallAfter(Publisher().sendMessage, "update", "")
这里我们创建一个线程并立即启动它。该线程在 20 的范围内循环,并在每次迭代中使用时间模块休眠一秒钟。每次睡眠后,它向对话框发送一条消息,告诉它更新进度条。
更新 wxPython 2.9 的代码
上一节中的代码是使用 pubsub 的旧 API 编写的,随着 wxPython 2.9 的出现,它已经被抛弃了。所以如果你试图在 2.9 中运行上面的代码,你可能会遇到问题。因此,为了完整起见,下面是使用新的 pubsub API 的代码版本:
import time
import wx
from threading import Thread
from wx.lib.pubsub import pub
########################################################################
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(20):
time.sleep(1)
wx.CallAfter(pub.sendMessage, "update", msg="")
########################################################################
class MyProgressDialog(wx.Dialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Progress")
self.count = 0
self.progress = wx.Gauge(self, range=20)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.progress, 0, wx.EXPAND)
self.SetSizer(sizer)
# create a pubsub receiver
pub.subscribe(self.updateProgress, "update")
#----------------------------------------------------------------------
def updateProgress(self, msg):
""""""
self.count += 1
if self.count >= 20:
self.Destroy()
self.progress.SetValue(self.count)
########################################################################
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.btn = btn = wx.Button(panel, label="Start Thread")
btn.Bind(wx.EVT_BUTTON, self.onButton)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onButton(self, event):
"""
Runs the thread
"""
btn = event.GetEventObject()
btn.Disable()
TestThread()
dlg = MyProgressDialog()
dlg.ShowModal()
btn.Enable()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
注意,现在您导入的是 pub 模块,而不是 Publisher 模块。还要注意,你必须使用关键字参数。更多信息参见公共订阅文档。
包扎
此时,您应该知道如何创建自己的进度对话框,并从线程中更新它。您可以使用此代码的变体来创建文件下载程序。如果你这样做了,你将需要检查你正在下载的文件的大小,然后分块下载,这样你就可以创建 wx 了。使用适当的范围进行测量,并在下载每个块时更新它。我希望这能给你一些如何在你自己的项目中使用这个小部件的想法。
附加阅读
- wxPython 和线程
- wxPython wiki: 长时间运行的任务
wxPython:如何使用剪贴板
原文:https://www.blog.pythonlibrary.org/2012/05/25/wxpython-how-to-use-the-clipboard/
经常使用电脑的人都知道电脑可以复制粘贴文字。他们可能不知道的是,当你拷贝一些东西时,它会进入一个叫做“剪贴板”的位置。大多数程序都提供对某种剪贴板的访问,无论是在程序内部还是对系统剪贴板的访问,剪贴板允许将项目复制到其他应用程序。wxPython GUI toolkit 还提供了剪贴板访问,您可以使用它在程序中来回复制文本,甚至复制到系统剪贴板。您也可以将图像复制到剪贴板。在本教程中,我们将看看如何在自己的代码中做到这一点。
我们将从一个非常简单的示例代码片段开始。下面包含两个按钮,一个复制任何添加到文本控件的文本,然后你可以粘贴到其他地方,如文本框,搜索引擎或其他地方。另一个按钮也复制到剪贴板,然后在刷新数据后关闭应用程序。这应该使数据在系统剪贴板中可用,即使在应用程序关闭之后。两者在 Windows 上都能很好地工作,但是 wxGTK(即 Linux 版本)在后一种情况下不能工作。更多信息见 bug 单。
不管怎样,我们来看看代码吧!
import wx
########################################################################
class ClipboardPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
lbl = wx.StaticText(self, label="Enter text to copy to clipboard:")
self.text = wx.TextCtrl(self, style=wx.TE_MULTILINE)
copyBtn = wx.Button(self, label="Copy")
copyBtn.Bind(wx.EVT_BUTTON, self.onCopy)
copyFlushBtn = wx.Button(self, label="Copy and Flush")
copyFlushBtn.Bind(wx.EVT_BUTTON, self.onCopyAndFlush)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(lbl, 0, wx.ALL, 5)
sizer.Add(self.text, 1, wx.EXPAND)
sizer.Add(copyBtn, 0, wx.ALL|wx.CENTER, 5)
sizer.Add(copyFlushBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def onCopy(self, event):
""""""
self.dataObj = wx.TextDataObject()
self.dataObj.SetText(self.text.GetValue())
if wx.TheClipboard.Open():
wx.TheClipboard.SetData(self.dataObj)
wx.TheClipboard.Close()
else:
wx.MessageBox("Unable to open the clipboard", "Error")
#----------------------------------------------------------------------
def onCopyAndFlush(self, event):
""""""
self.dataObj = wx.TextDataObject()
self.dataObj.SetText(self.text.GetValue())
if wx.TheClipboard.Open():
wx.TheClipboard.SetData(self.dataObj)
wx.TheClipboard.Flush()
else:
wx.MessageBox("Unable to open the clipboard", "Error")
self.GetParent().Close()
########################################################################
class ClipboardFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Clipboard Tutorial")
panel = ClipboardPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = ClipboardFrame()
app.MainLoop()
正如您可能已经猜到的,这个脚本的核心在按钮事件处理程序中。主位是 wx。TextDataObject 将存储来自文本控件的数据。接下来,我们尝试打开剪贴板。如果成功,我们将文本添加到剪贴板,然后关闭它。数据现在可以粘贴了。第二个事件处理程序做同样的事情,但是它刷新到剪贴板,而不仅仅是关闭它。如果你想复制一个位图,那么你应该使用一个 wx。BitmapDataObject 并传递给它一个 wx。位图对象。否则,其余都一样。
包扎
现在你知道如何使用 wxPython 的剪贴板了!走出去,开始创造一些让你的朋友和陌生人都感到惊讶的东西吧!
额外资源
wxPython:拖放简介
原文:https://www.blog.pythonlibrary.org/2012/06/20/wxpython-introduction-to-drag-and-drop/
这个时代的大多数计算机用户本能地使用拖放(DnD)。这周你可能用它把一些文件从一个文件夹转移到另一个文件夹。wxPython GUI 工具包提供了内置的拖放功能。在本教程中,我们将看到它是多么容易实现!
入门指南
wxPython 提供了几种不同的拖放方式。您可以有以下类型之一:
- wx(地名)。文件 DropTarget
- wx。TextDropTarget
- wx。PyDropTarget
前两个非常简单明了。最后一个,wx。PyDropTarget 只是 wx 的一个松散包装。DropTarget 本身。它增加了一些额外的方便方法。DropTarget 没有。我们从 wx 开始。FileDropTarget 示例。
创建 FileDropTarget
拖放文件
wxPython 工具包使得创建拖放目标变得非常简单。您确实需要重写一个方法来使它正常工作,但是除此之外,它非常简单。让我们花点时间看看这个示例代码,然后花点时间解释它。
import wx
########################################################################
class MyFileDropTarget(wx.FileDropTarget):
""""""
#----------------------------------------------------------------------
def __init__(self, window):
"""Constructor"""
wx.FileDropTarget.__init__(self)
self.window = window
#----------------------------------------------------------------------
def OnDropFiles(self, x, y, filenames):
"""
When files are dropped, write where they were dropped and then
the file paths themselves
"""
self.window.SetInsertionPointEnd()
self.window.updateText("\n%d file(s) dropped at %d,%d:\n" %
(len(filenames), x, y))
for filepath in filenames:
self.window.updateText(filepath + '\n')
########################################################################
class DnDPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
file_drop_target = MyFileDropTarget(self)
lbl = wx.StaticText(self, label="Drag some files here:")
self.fileTextCtrl = wx.TextCtrl(self,
style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)
self.fileTextCtrl.SetDropTarget(file_drop_target)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(lbl, 0, wx.ALL, 5)
sizer.Add(self.fileTextCtrl, 1, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def SetInsertionPointEnd(self):
"""
Put insertion point at end of text control to prevent overwriting
"""
self.fileTextCtrl.SetInsertionPointEnd()
#----------------------------------------------------------------------
def updateText(self, text):
"""
Write text to the text control
"""
self.fileTextCtrl.WriteText(text)
########################################################################
class DnDFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="DnD Tutorial")
panel = DnDPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DnDFrame()
app.MainLoop()
那还不算太糟,是吗?首先要做的是子类化 wx。FileDropTarget ,这是我们用我们的 MyFileDropTarget 类完成的。在里面我们有一个被覆盖的方法, OnDropFiles 。它接受鼠标的 x/y 位置和放置的文件路径,然后将它们写出到文本控件中。要将拖放目标与文本控件挂钩,您需要查看 DnDPanel 类,在该类中我们调用文本控件的 SetDropTarget 方法,并将其设置为 drop target 类的一个实例。我们的 panel 类中还有两个方法,drop target 类调用它们来更新文本控件:SetInsertionPointEnd 和 updateText。注意,因为我们将面板对象作为放置目标传递,所以我们可以随意调用这些方法。如果 TextCtrl 是 drop 目标,我们必须做不同的事情,我们将在下一个例子中看到!
创建 TextDropTarget
拖放文本
wx。当您希望能够将一些选定的文本拖放到文本控件中时,可以使用 TextDropTarget。最常见的例子之一可能是将网页上的 URL 拖到地址栏中,或者将一些文本拖到 Firefox 的搜索框中。让我们花一些时间来学习如何在 wxPython 中创建这种拖放目标!
import wx
########################################################################
class MyTextDropTarget(wx.TextDropTarget):
#----------------------------------------------------------------------
def __init__(self, textctrl):
wx.TextDropTarget.__init__(self)
self.textctrl = textctrl
#----------------------------------------------------------------------
def OnDropText(self, x, y, text):
self.textctrl.WriteText("(%d, %d)\n%s\n" % (x, y, text))
#----------------------------------------------------------------------
def OnDragOver(self, x, y, d):
return wx.DragCopy
########################################################################
class DnDPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
lbl = wx.StaticText(self, label="Drag some text here:")
self.myTextCtrl = wx.TextCtrl(self,
style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)
text_dt = MyTextDropTarget(self.myTextCtrl)
self.myTextCtrl.SetDropTarget(text_dt)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.myTextCtrl, 1, wx.EXPAND)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def WriteText(self, text):
self.text.WriteText(text)
########################################################################
class DnDFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="DnD Text Tutorial")
panel = DnDPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DnDFrame()
app.MainLoop()
我们必须再次子类化我们的拖放目标类。在这种情况下,我们称之为 MyTextDropTarget 。在那个类中,我们必须覆盖 OnDropText 和 OnDragOver 。我无法找到关于后者的令人满意的文档,但我猜它只是返回了被拖动数据的副本。OnDropText 方法将文本写出到文本控件中。注意,由于我们已经将放置目标直接绑定到文本控件(参见 panel 类),我们必须使用名为 WriteText 的方法来更新文本控件。如果您更改它,您会收到一条错误消息。
带有 PyDropTarget 的自定义 DnD
使用 PyDropTarget 拖放 URL
如果你还没有猜到,这些例子是来自官方 wxPython 演示的 DnD 演示的稍微修改版本。我们将使用一些基于他们的 URLDragAndDrop 演示的代码来解释 PyDropTarget。这个演示的有趣之处在于,您不仅可以创建一个可以接受拖动文本的小部件,还可以将另一个小部件中的一些文本拖回您的浏览器!让我们来看看:
import wx
########################################################################
class MyURLDropTarget(wx.PyDropTarget):
#----------------------------------------------------------------------
def __init__(self, window):
wx.PyDropTarget.__init__(self)
self.window = window
self.data = wx.URLDataObject();
self.SetDataObject(self.data)
#----------------------------------------------------------------------
def OnDragOver(self, x, y, d):
return wx.DragLink
#----------------------------------------------------------------------
def OnData(self, x, y, d):
if not self.GetData():
return wx.DragNone
url = self.data.GetURL()
self.window.AppendText(url + "\n")
return d
#######################################################################
class DnDPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD, False)
# create and setup first set of widgets
lbl = wx.StaticText(self, label="Drag some URLS from your browser here:")
lbl.SetFont(font)
self.dropText = wx.TextCtrl(self, size=(200,200),
style=wx.TE_MULTILINE|wx.HSCROLL|wx.TE_READONLY)
dt = MyURLDropTarget(self.dropText)
self.dropText.SetDropTarget(dt)
firstSizer = self.addWidgetsToSizer([lbl, self.dropText])
# create and setup second set of widgets
lbl = wx.StaticText(self, label="Drag this URL to your browser:")
lbl.SetFont(font)
self.draggableURLText = wx.TextCtrl(self, value="http://www.mousevspython.com")
self.draggableURLText.Bind(wx.EVT_MOTION, self.OnStartDrag)
secondSizer = self.addWidgetsToSizer([lbl, self.draggableURLText])
# Add sizers to main sizer
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(firstSizer, 0, wx.EXPAND)
mainSizer.Add(secondSizer, 0, wx.EXPAND)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def addWidgetsToSizer(self, widgets):
"""
Returns a sizer full of widgets
"""
sizer = wx.BoxSizer(wx.HORIZONTAL)
for widget in widgets:
if isinstance(widget, wx.TextCtrl):
sizer.Add(widget, 1, wx.EXPAND|wx.ALL, 5)
else:
sizer.Add(widget, 0, wx.ALL, 5)
return sizer
#----------------------------------------------------------------------
def OnStartDrag(self, evt):
""""""
if evt.Dragging():
url = self.draggableURLText.GetValue()
data = wx.URLDataObject()
data.SetURL(url)
dropSource = wx.DropSource(self.draggableURLText)
dropSource.SetData(data)
result = dropSource.DoDragDrop()
########################################################################
class DnDFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="DnD URL Tutorial", size=(800,600))
panel = DnDPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = DnDFrame()
app.MainLoop()
第一个类是我们的拖放目标类。这里我们创建一个 wx。URLDataObject 存储我们的 URL 信息。然后在 OnData 方法中,我们提取 URL 并将其添加到绑定的文本控件中。在我们的 panel 类中,我们用与其他两个例子中相同的方式来连接拖放目标,所以我们将跳过它,继续学习新的内容。第二个文本控件是我们需要注意的地方。在这里,我们发现它通过 EVT 运动鼠标运动。在鼠标移动事件处理程序(OnStartDrag)中,我们检查以确保用户正在拖动。如果是,那么我们从文本框中获取值,并将其添加到新创建的 URLDataObject 中。接下来,我们创建一个 DropSource 的实例,并向其传递我们的第二个文本控件,因为它是源。我们将源的数据设置为 URLDataObject。最后,我们在我们的拖放源(文本控件)上调用 DoDragDrop ,它将通过移动、复制、取消或失败做出响应。如果您将 URL 拖到浏览器的地址栏,它会复制。否则很可能行不通。现在让我们利用我们所学的,创造一些原创的东西!
创建自定义拖放应用程序
使用 ObjectListView 拖放
我认为使用文件拖放目标演示程序并把它做成带有 ObjectListView 小部件(一个 ListCtrl 包装器)的东西会很有趣,它可以告诉我们一些关于我们正在放入的文件的信息。我们将显示以下信息:文件名,创建日期,修改日期和文件大小。代码如下:
import os
import stat
import time
import wx
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class MyFileDropTarget(wx.FileDropTarget):
""""""
#----------------------------------------------------------------------
def __init__(self, window):
"""Constructor"""
wx.FileDropTarget.__init__(self)
self.window = window
#----------------------------------------------------------------------
def OnDropFiles(self, x, y, filenames):
"""
When files are dropped, update the display
"""
self.window.updateDisplay(filenames)
########################################################################
class FileInfo(object):
""""""
#----------------------------------------------------------------------
def __init__(self, path, date_created, date_modified, size):
"""Constructor"""
self.name = os.path.basename(path)
self.path = path
self.date_created = date_created
self.date_modified = date_modified
self.size = size
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
self.file_list = []
file_drop_target = MyFileDropTarget(self)
self.olv = ObjectListView(self, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.olv.SetDropTarget(file_drop_target)
self.setFiles()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.olv, 1, wx.EXPAND)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def updateDisplay(self, file_list):
""""""
for path in file_list:
file_stats = os.stat(path)
creation_time = time.strftime("%m/%d/%Y %I:%M %p",
time.localtime(file_stats[stat.ST_CTIME]))
modified_time = time.strftime("%m/%d/%Y %I:%M %p",
time.localtime(file_stats[stat.ST_MTIME]))
file_size = file_stats[stat.ST_SIZE]
if file_size > 1024:
file_size = file_size / 1024.0
file_size = "%.2f KB" % file_size
self.file_list.append(FileInfo(path,
creation_time,
modified_time,
file_size))
self.olv.SetObjects(self.file_list)
#----------------------------------------------------------------------
def setFiles(self):
""""""
self.olv.SetColumns([
ColumnDefn("Name", "left", 220, "name"),
ColumnDefn("Date created", "left", 150, "date_created"),
ColumnDefn("Date modified", "left", 150, "date_modified"),
ColumnDefn("Size", "left", 100, "size")
])
self.olv.SetObjects(self.file_list)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="OLV DnD Tutorial", size=(800,600))
panel = MainPanel(self)
self.Show()
#----------------------------------------------------------------------
def main():
""""""
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
if __name__ == "__main__":
main()
大部分你以前见过的东西。我们有自己的 FileDropTarget 子类,我们将面板连接到它,然后将 ObjectListView 小部件连接到 DropTarget 实例。我们还有一个保存文件相关数据的泛型类。如果你运行这个程序并把文件夹放入其中,你将不会得到正确的文件大小。您可能需要遍历文件夹,并添加其中文件的大小,以使其工作。你可以自己解决这个问题。无论如何,程序的核心是在更新显示方法中。在这里,我们抓取文件的重要统计数据,并将其转换为更可读的格式,因为大多数人不理解从纪元开始以秒为单位的日期。一旦我们对数据稍加处理,我们就显示它。这不是很酷吗?
包扎
至此,您应该知道如何在 wxPython 中进行至少 3 种不同类型的拖放操作。希望您能负责任地使用这些新信息,并在不久的将来创建一些新的开源应用程序。祝你好运!
进一步阅读
- wxPython dragandrop维基页面
- zetcode 的 wxPython DragAndDrop 页面
- DropTarget 文档
- wxPython:使用 ObjectListView 代替 ListCtrl
源代码
wxPython:键盘快捷键(加速器)
原文:https://www.blog.pythonlibrary.org/2010/12/02/wxpython-keyboard-shortcuts-accelerators/
几乎所有电脑超级用户都想使用键盘快捷键(又名:加速器)来完成工作。对我们来说幸运的是,wxPython 提供了一种通过 wx 使用加速器表非常容易地实现这一点的方法。可加速的类。在本文中,我们将通过几个例子来了解这是如何实现的。
入门指南
对于我们的第一个技巧,我们将从一个非常简单的例子开始。查看下面的代码!
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "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()
上面的代码只是一个带有 wx 的面板。AcceleratorTable 对象,其中包含一个快捷键组合,即 CTRL+Q,wxPython 中的快捷键实际上是菜单事件(即 wx。EVT _ 菜单),可能是因为快捷键通常也是一个菜单项。因此,他们依赖于某种身份。如果我们有一个菜单项,我们想给一个快捷方式,我们将使用菜单项的 id。在这里,我们只是创建一个新的 id。注意,我们必须使用 wx。ACCEL_CTRL 来“捕捉”CTRL 键的按下。最后,在创建 wx 之后。AcceleratorTable,我们需要将它添加到 wx 中。调用 Frame 对象的setAcceleratorTable方法并传入 acceleratotable 实例。唷!你都明白了吗?很好!那我们继续吧。
了解多种快捷方式及更多
在这个例子中,我们将学习如何添加多个键盘快捷键,我们还将学习如何创建一个多键快捷键。让我们来看看:
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
# Create a menu
menuBar = wx.MenuBar()
fileMenu = wx.Menu()
refreshMenuItem = fileMenu.Append(wx.NewId(), "Refresh",
"Refresh app")
self.Bind(wx.EVT_MENU, self.onRefresh, refreshMenuItem)
exitMenuItem = fileMenu.Append(wx.NewId(), "E&xit\tCtrl+X", "Exit the program")
self.Bind(wx.EVT_MENU, self.onExit, exitMenuItem)
menuBar.Append(fileMenu, "File")
self.SetMenuBar(menuBar)
# Create an accelerator table
xit_id = wx.NewId()
yit_id = wx.NewId()
self.Bind(wx.EVT_MENU, self.onAltX, id=xit_id)
self.Bind(wx.EVT_MENU, self.onShiftAltY, id=yit_id)
self.accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('R'), refreshMenuItem.GetId()),
(wx.ACCEL_ALT, ord('X'), xit_id),
(wx.ACCEL_SHIFT|wx.ACCEL_ALT, ord('Y'), yit_id)
])
self.SetAcceleratorTable(self.accel_tbl)
#----------------------------------------------------------------------
def onRefresh(self, event):
print "refreshed!"
#----------------------------------------------------------------------
def onAltX(self, event):
""""""
print "You pressed ALT+X!"
#----------------------------------------------------------------------
def onShiftAltY(self, event):
""""""
print "You pressed SHIFT+ALT+Y!"
#----------------------------------------------------------------------
def onExit(self, event):
""""""
self.Close()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
在这个例子中,我们创建了一个只有一个菜单项的超级简单的菜单。正如您所看到的,当我们在加速器表中创建快捷方式时,我们使用了刷新菜单项的 id。我们还将菜单项本身绑定到同一个事件处理程序。加速器表中的其他两个项目以与之前相同的方式绑定。但是,请注意,表中的最后一项是两个标志:wx。ACCEL_SHIFT 和 wx。ACCEL ALT。那是什么意思?这意味着我们必须按 SHIFT+ALT+SomeKey 来触发事件。在这种情况下,我们想要的键是“Y”。
如果你密切注意,你会注意到有一个退出菜单项,声称如果你按下 CTRL+X,应用程序将关闭。但是,我们的加速器表中没有这种快捷方式。幸运的是,wxPython 会自动执行这个快捷方式,因为它被添加到了菜单项中:“\tCtrl+X”。“\t”是一个制表符(以防您不知道),正因为如此,它告诉 wxPython 解析它后面的内容,并将其添加到一个加速表中。这不是很棒吗?
注意:我无法确认 wxPython 是否会将该快捷方式添加到一个单独的快捷键表中,或者是否会将其添加到程序员创建的快捷键表中,但是这两种方式都无关紧要,因为它“只是工作”。
包扎
到目前为止,您应该知道如何在 wxPython 中创建键盘快捷键。恭喜你!您就离创建一个很酷的应用程序更近了!我经常使用键盘快捷键,如果一个应用程序有好的、直观的、容易记住的快捷键,我会非常感激。如果你也是一个键盘迷,那么你会明白我的意思。玩得开心!
进一步阅读
- wxPython:使用菜单、工具栏和加速器
- wxPython: 捕捉键和字符事件
- wx。加速器表文档
- wx。关键事件文档
注意:这段代码在 Windows 上用 Python 2.5.4 和 wxPython 2.8.10.1 进行了测试
wxPython:了解 TreeCtrls
原文:https://www.blog.pythonlibrary.org/2017/05/16/wxpython-learning-about-treectrls/
wxPython GUI 工具包附带了许多小部件。一个常见的控件是树小部件。wxPython 有几个不同的树部件,包括常规的 wx。TreeCtrl ,更新的 DVC_TreeCtrl 和纯 Python 变种,customtreecrl和 HyperTreeList 。在本文中,我们将重点介绍常规的 wx。TreeCtrl 并学习如何创建和使用一个。
创建简单的树
创建 TreeCtrl 实际上很容易。wxPython 演示有一个相当复杂的例子,所以我不能在这里使用它。相反,我最终采用了演示示例,并尽可能多地将其剥离。结果如下:
import wx
class MyTree(wx.TreeCtrl):
def __init__(self, parent, id, pos, size, style):
wx.TreeCtrl.__init__(self, parent, id, pos, size, style)
class TreePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.tree = MyTree(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize,
wx.TR_HAS_BUTTONS)
self.root = self.tree.AddRoot('Something goes here')
self.tree.SetPyData(self.root, ('key', 'value'))
os = self.tree.AppendItem(self.root, 'Operating Systems')
self.tree.Expand(self.root)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.tree, 0, wx.EXPAND)
self.SetSizer(sizer)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title='TreeCtrl Demo')
panel = TreePanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = MainFrame()
app.MainLoop()
在这个例子中,我们创建了 wx 的一个子类。不做任何事情的 TreeCtrl。然后我们创建一个 panel 子类,实例化树并添加一个根和子项。最后,我们创建容纳面板的框架并运行应用程序。您最终应该会看到类似于以下内容的内容:
这是一个相当无聊的例子,所以让我们做一些更有趣的东西。
创建 XML 查看器
一段时间以来,我一直想用 Python 创建一个 XML 编辑器。首先,我在几个周末前写了一些代码,可以将 XML 读入 TreeCtrl 中,以便查看标记元素。对于这个例子,我将使用我在微软的 MSDN 网站上找到的一些 XML 样本:
<book id="bk101"><author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description></book>
<book id="bk102"><author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description></book>
<book id="bk103"><author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
<description>After the collapse of a nanotechnology
society in England, the young survivors lay the
foundation for a new society.</description></book>
<book id="bk104"><author>Corets, Eva</author>
<title>Oberon's Legacy</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-03-10</publish_date>
<description>In post-apocalypse England, the mysterious
agent known only as Oberon helps to create a new life
for the inhabitants of London. Sequel to Maeve
Ascendant.</description></book>
<book id="bk105"><author>Corets, Eva</author>
<title>The Sundered Grail</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-09-10</publish_date>
<description>The two daughters of Maeve, half-sisters,
battle one another for control of England. Sequel to
Oberon's Legacy.</description></book>
<book id="bk106"><author>Randall, Cynthia</author>
<title>Lover Birds</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-09-02</publish_date>
<description>When Carla meets Paul at an ornithology
conference, tempers fly as feathers get ruffled.</description></book>
<book id="bk107"><author>Thurman, Paula</author>
<title>Splish Splash</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-11-02</publish_date>
<description>A deep sea diver finds true love twenty
thousand leagues beneath the sea.</description></book>
<book id="bk108"><author>Knorr, Stefan</author>
<title>Creepy Crawlies</title>
<genre>Horror</genre>
<price>4.95</price>
<publish_date>2000-12-06</publish_date>
<description>An anthology of horror stories about roaches,
centipedes, scorpions and other insects.</description></book>
<book id="bk109"><author>Kress, Peter</author>
<title>Paradox Lost</title>
<genre>Science Fiction</genre>
<price>6.95</price>
<publish_date>2000-11-02</publish_date>
<description>After an inadvertant trip through a Heisenberg
Uncertainty Device, James Salway discovers the problems
of being quantum.</description></book>
<book id="bk110"><author>O'Brien, Tim</author>
<title>Microsoft .NET: The Programming Bible</title>
<genre>Computer</genre>
<price>36.95</price>
<publish_date>2000-12-09</publish_date>
<description>Microsoft's .NET initiative is explored in
detail in this deep programmer's reference.</description></book>
<book id="bk111"><author>O'Brien, Tim</author>
<title>MSXML3: A Comprehensive Guide</title>
<genre>Computer</genre>
<price>36.95</price>
<publish_date>2000-12-01</publish_date>
<description>The Microsoft MSXML3 parser is covered in
detail, with attention to XML DOM interfaces, XSLT processing,
SAX and more.</description></book>
<book id="bk112"><author>Galos, Mike</author>
<title>Visual Studio 7: A Comprehensive Guide</title>
<genre>Computer</genre>
<price>49.95</price>
<publish_date>2001-04-16</publish_date>
<description>Microsoft Visual Studio 7 is explored in depth,
looking at how Visual Basic, Visual C++, C#, and ASP+ are
integrated into a comprehensive development
environment.</description></book>
我们需要决定的第一件事是我们想要使用什么 Python XML 解析器。我个人最喜欢 lxml,但是 Python 自己的 ElementTree 当然是一个可行的选择,而且如果你从 lxml 开始,实际上也很容易转换。但是对于这个例子,我们将使用 lxml。让我们来看看:
import wx
from lxml import etree, objectify
class XmlTree(wx.TreeCtrl):
def __init__(self, parent, id, pos, size, style):
wx.TreeCtrl.__init__(self, parent, id, pos, size, style)
try:
with open(parent.xml_path) as f:
xml = f.read()
except IOError:
print('Bad file')
return
except Exception as e:
print('Really bad error')
print(e)
return
self.xml_root = objectify.fromstring(xml)
root = self.AddRoot(self.xml_root.tag)
self.SetPyData(root, ('key', 'value'))
for top_level_item in self.xml_root.getchildren():
child = self.AppendItem(root, top_level_item.tag)
self.SetItemHasChildren(child)
if top_level_item.attrib:
self.SetPyData(child, top_level_item.attrib)
self.Expand(root)
self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.onItemExpanding)
def onItemExpanding(self, event):
item = event.GetItem()
book_id = self.GetPyData(item)
for top_level_item in self.xml_root.getchildren():
if top_level_item.attrib == book_id:
book = top_level_item
self.SetPyData(item, top_level_item)
self.add_book_elements(item, book)
break
def add_book_elements(self, item, book):
for element in book.getchildren():
child = self.AppendItem(item, element.tag)
if element.getchildren():
self.SetItemHasChildren(child)
if element.attrib:
self.SetPyData(child, element.attrib)
class TreePanel(wx.Panel):
def __init__(self, parent, xml_path):
wx.Panel.__init__(self, parent)
self.xml_path = xml_path
self.tree = XmlTree(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize,
wx.TR_HAS_BUTTONS)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.tree, 0, wx.EXPAND)
self.SetSizer(sizer)
class MainFrame(wx.Frame):
def __init__(self, xml_path):
wx.Frame.__init__(self, parent=None, title='XML Editor')
panel = TreePanel(self, xml_path)
self.Show()
if __name__ == '__main__':
xml_path = 'books.xml'
app = wx.App(redirect=False)
frame = MainFrame(xml_path)
app.MainLoop()
这里的主要变化是在 TreeCtrl 子类中,尽管我们必须在其他类中做一些小的修改来传递 XML 文件路径。不过,让我们关注 TreeCtrl 类。首先,我们从文件中读取 XML,并将其加载到 lxml 的 objectify 模块中。此时,我们有了一个 XML 对象,可以用它来用数据填充 TreeCtrl。所以我们添加了根,然后遍历 XML 中的顶级子元素。对于每个顶级元素,我们向 TreeCtrl 的根添加一个项目。这是非常基本的,因为我们还应该检查每个元素,看看它是否也有子元素。我们没有。相反,我们只是假设它会调用 TreeCtrl 的 SetItemHasChildren() 方法。这将向元素添加一个箭头,以允许扩展元素。
最后,我们扩展根,并将一个事件绑定到EVT _ 树 _ 项目 _ 扩展,这将允许我们在扩展子元素时更新它们。您可以在 onItemExpanding 事件处理程序和事件处理程序调用的 add_book_elements() 中看到这是如何完成的。在这里,我们确实使用 lxml 的 getchildren() 检查元素是否有子元素。如果是,那么我们调用 SetItemHasChildren() 。我想指出的另一件事是所有对 SetPyData() 的调用。SetPyData()方法用于将数据保存到树项目中。在这种情况下,我们将 XML 元素保存到树项目本身,我们可以通过 GetPyData() 再次访问它。如果我们想在 GUI 中添加编辑功能,这将是非常重要的。
其他零碎的东西
wxPython 演示还展示了一些有趣的花絮。比如显示可以添加一个 wx。ImageList 到你的 TreeCtrl。它还显示了您可以绑定到的其他一些特定于树的事件。比如:
- EVT _ 树 _ 项目 _ 折叠
- EVT _ 树 _ 选择 _ 改变
- EVT _ 树 _ 开始 _ 标签 _ 编辑
- END _ 树 _ 结束 _ 标签 _ 编辑
- EVT _ 树 _ 项目 _ 已激活
当然,你也可以绑定到鼠标事件,比如 LEFT _ 左 _ 右 _ 下。
如果您想使树元素可编辑,那么您需要传递 wx。TR_EDIT_LABELS 样式标志。因为我的例子只是一个查看器,所以我觉得没有必要这样做。在演示和文档中还提到了其他一些样式标志,您可能也想看看。
包扎
在这一点上,我想你应该能够开始使用 wxPython 的便捷的 wx.TreeCtrl 了,它非常强大并且易于使用。如果您发现自己需要做一些更加定制的事情,那么我强烈建议您查看 wxPython 的一个备用树控件,比如 CustomTreeCtrl 或 HyperTreeList。
相关阅读
- wxp 上的 wxp 文档。tree ctrl〔t1〕
- TreeCtrl 概述
wxPython:学习专注
原文:https://www.blog.pythonlibrary.org/2009/08/27/wxpython-learning-to-focus/
这周我接到一个请求,要写一个关于 wxPython 中焦点事件的快速教程。幸运的是,在这个主题上没有太多的材料,所以这应该是一个非常快速和肮脏的小帖子。跳完之后再见!
我见过的焦点事件真的只有两个:wx。SET _ 设置 _ 焦点和 wx。EVT _ 杀死 _ 聚焦。当小部件获得焦点时,例如当您单击空白面板或将光标放在 TextCtrl 小部件中时,将触发 EVT_SET_FOCUS 事件。当你点击一个有焦点的小部件时,EVT_KILL_FOCUS 就会被触发。
我在 wxPython 邮件列表中看到的少数几个“陷阱”之一是 wx。只有当而不是有一个可以接受焦点的子部件时,面板才接受焦点。最好的解释方式是用一系列的例子。
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
panel.Bind(wx.EVT_SET_FOCUS, self.onFocus)
def onFocus(self, event):
print "panel received focus!"
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
现在这段代码将显示一个空白面板,上面什么也没有。您会注意到 stdout 立即得到“panel received focus!”打印出来。现在,如果我们添加一个 TextCtrl 或一个按钮,那么它们将获得焦点,而 OnFocus 事件处理程序将不会被触发。试着运行下面的代码来看看这是怎么回事:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1a")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
panel.Bind(wx.EVT_SET_FOCUS, self.onFocus)
txt = wx.TextCtrl(panel, wx.ID_ANY, "")
def onFocus(self, event):
print "panel received focus!"
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
只是为了好玩,试着在那里放一个 StaticText 控件,而不是 TextCtrl。你期待什么会得到关注?如果你猜是 StaticText 控件或面板,那你就错了!事实上,它是接收焦点的框架!Robin Dunn 通过在我的代码中添加一个计时器向我说明了这一点。看看这个:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Finder")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
panel.Bind(wx.EVT_SET_FOCUS, self.onFocus)
txt = wx.StaticText(panel, wx.ID_ANY,
"This label cannot receive focus")
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer)
self.timer.Start(1000)
def onFocus(self, event):
print "panel received focus!"
def onTimer(self, evt):
print 'Focused window:', wx.Window.FindFocus()
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
你可能想知道为什么你会想知道什么时候帧在焦点上。如果您需要知道某人何时点击了您的应用程序或将某个帧放到了前台,这可能会很有帮助。当然,有些人更希望知道鼠标何时进入框架,这些信息可以通过 EVT _ 回车 _ 窗口获得(我不会在这里讨论)。
现在我们来快速看一下 wx。EVT _ 杀死 _ 聚焦。我创建了一个只有两个控件的简单示例。试着猜猜如果你在它们之间切换会发生什么。
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Focus Tutorial 1a")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
txt = wx.TextCtrl(panel, wx.ID_ANY, "")
txt.Bind(wx.EVT_SET_FOCUS, self.onFocus)
txt.Bind(wx.EVT_KILL_FOCUS, self.onKillFocus)
btn = wx.Button(panel, wx.ID_ANY, "Test")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, 0, wx.ALL, 5)
sizer.Add(btn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
def onFocus(self, event):
print "widget received focus!"
def onKillFocus(self, event):
print "widget lost focus!"
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
正如您可能已经猜到的,当您在它们之间切换时,TextCtrl 要么触发一个 kill focus 事件,要么触发一个 set focus 事件。您如何知道哪个小部件触发了这些事件?看看我的绑定方法。只有文本控件绑定到焦点事件。作为一个练习,试着将按钮也绑定到这些处理程序,并打印出哪个小部件触发了什么。
我要提到的最后一个焦点事件是 wx。EVT 儿童对焦,我从来没用过的。该事件用于确定子部件何时获得焦点,并确定它是哪个子部件。根据 Robin Dunn ,wx.lib.scrolledpanel 使用这个事件。我的一个读者告诉我一个关于 wx 的便利用例。EVT _ 儿童 _ 焦点:你可以在框架上使用它,当你点击任何其他子部件时,简单地清除状态栏。这样
当你点击一个不同的子部件时,你就不会有一个旧的“错误”消息或者在状态栏中总结这样的文本。 (hattip @devplayer)
还要注意,一些更复杂的小部件有自己的 focus hokus pokus。有关如何在 wx.grid.Grid 单元格中获得焦点,请参见以下主题:http://www . velocity reviews . com/forums/t 352017-wx grid-and-focus-event . html
我希望本教程能够帮助您更好地理解 wxPython 中焦点事件的工作方式。如果您有问题或其他反馈,请随时通过评论给我留言,或者在邮件列表上询问其他 wxPython 开发人员。
附加信息
下载量
wxPython:学习使用字体
原文:https://www.blog.pythonlibrary.org/2011/04/28/wxpython-learning-to-use-fonts/
你有没有想过如何在 wxPython 中改变你的字体?那么,现在是你的幸运日,因为这正是本教程将要涵盖的内容。我们将查看以下三个项目:
- wxPython 的内置字体
- 字体对话框
- wxPython 演示中的字体枚举器演示
字体是程序的重要组成部分,可以增强程序的可读性。确保你的字体大小和位置合适总是一个好主意。没错,那是常识,但有时候常识在编程中是不会发生的。哲学够了。让我们进入文章的核心部分吧!
内置字体
您可能还没有意识到这一点,但是 wxPython 有自己的一套通用字体!我不确定它们是否在所有平台上都一样,但我敢打赌它们非常接近。让我们来看一个简单的自定义演示,展示这些字体的外观:
import random
import wx
import wx.lib.scrolledpanel as scrolled
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Font Tutorial")
# Add a panel so it looks the correct on all platforms
panel = scrolled.ScrolledPanel(self)
panel.SetAutoLayout(1)
panel.SetupScrolling()
fontSizer = wx.BoxSizer(wx.VERTICAL)
families = {"FONTFAMILY_DECORATIVE":wx.FONTFAMILY_DECORATIVE, # A decorative font
"FONTFAMILY_DEFAULT":wx.FONTFAMILY_DEFAULT,
"FONTFAMILY_MODERN":wx.FONTFAMILY_MODERN, # Usually a fixed pitch font
"FONTFAMILY_ROMAN":wx.FONTFAMILY_ROMAN, # A formal, serif font
"FONTFAMILY_SCRIPT":wx.FONTFAMILY_SCRIPT, # A handwriting font
"FONTFAMILY_SWISS":wx.FONTFAMILY_SWISS, # A sans-serif font
"FONTFAMILY_TELETYPE":wx.FONTFAMILY_TELETYPE # A teletype font
}
weights = {"FONTWEIGHT_BOLD":wx.FONTWEIGHT_BOLD,
"FONTWEIGHT_LIGHT":wx.FONTWEIGHT_LIGHT,
"FONTWEIGHT_NORMAL":wx.FONTWEIGHT_NORMAL
}
styles = {"FONTSTYLE_ITALIC":wx.FONTSTYLE_ITALIC,
"FONTSTYLE_NORMAL":wx.FONTSTYLE_NORMAL,
"FONTSTYLE_SLANT":wx.FONTSTYLE_SLANT
}
sizes = [8, 10, 12, 14]
for family in families.keys():
for weight in weights.keys():
for style in styles.keys():
label = "%s %s %s" % (family, weight, style)
size = random.choice(sizes)
font = wx.Font(size, families[family], styles[style],
weights[weight])
txt = wx.StaticText(panel, label=label)
txt.SetFont(font)
fontSizer.Add(txt, 0, wx.ALL, 5)
panel.SetSizer(fontSizer)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(sizer)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
可以清楚地看到,wxPython 让您应用一个 wx。使用小部件的 SetFont 方法将字体实例添加到小部件中,该方法应用字体。最 wx。字体对象由以下内容组成:大小、字体系列(如瑞士、罗马、正常等)、字体样式(如斜体或正常)和字体粗细(如粗体或正常)。你可以将这三种特质混搭,大部分时候都能得到你想要的。如果你想使用安装在你的系统上的字体,那么,我们将在下一节讨论这个问题。应该注意的是 wx。Font class 也接受下划线、字体和编码参数,但我们不会在本文中涉及这些。有关更多信息,请查看文档。
让我们花点时间看看代码。您会注意到我们有三个 Python 字典,我们将使用它们来控制 StaticText 小部件的外观。这些字典分别包含最常见的系列、重量和样式。我们在一个三重嵌套循环中循环每个系列、粗细和样式,混合和匹配它们,创建 wx 可以用这些集合做的每种类型的标准字体。注意,我们所做的只是创建一个 wx。Font 实例,然后使用 StaticText 的 SetFont 方法将其应用于 static text。代码的其余部分应该很容易理解。很简单,不是吗?
现在我们将了解字体对话框。
字体对话框
对于这个例子,我们将在一个独立的脚本中使用 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()
这段代码非常简单,但还是让我们花点时间来分解它。我们最感兴趣的是 UpdateUI 和 OnSelectFont 方法,但是不要害怕看其他的。在 init 中也有一个很好的部分,在这里我们学习如何获得应用于静态文本的字体并改变它的粗细。提示:查找定义“self.ps”的部分。
无论如何,我们将逆向工作,从 OnSelectFont 方法开始。为什么?因为它调用了另一个有趣的方法!首先,我们需要创建一个 wx。FontData 对象,它将保存与我们稍后在字体对话框中选择的字体相关的信息。然后,我们将对象的当前颜色和字体设置为当前字体的设置。接下来我们创建一个 wx。FontDialog 实例并显示对话框!这里我们取出所有我们需要的字体数据,在这个例子中,是颜色和字体,然后我们调用我们的更新方法。
在 UpdateUI 方法中,我们将一个静态文本控件设置为选择的字体。然后,我们从当前选择的字体中提取各种信息,并在适当的标签中显示这些信息。如您所见,我们选择提取点大小、系列、样式、粗细、字体和原生字体信息。现在我们可以进入最后一个演示了!
FontEnumerator
这个例子也取自 wxPython 演示。这是它对 wx 的评价。FontEnumerator 小工具:
wxFontEnumerator 枚举系统中所有可用的字体,或者只枚举具有给定属性的字体——或者只枚举固定宽度的字体(适用于终端模拟器等程序),或者枚举给定编码中可用的字体。
在我的 Windows XP 机器上,似乎是系统上所有可用的字体。以下是供您阅读的代码:
# fontEnumDemo.py
import wx
########################################################################
class TestPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
e = wx.FontEnumerator()
e.EnumerateFacenames()
elist= e.GetFacenames()
elist.sort()
s1 = wx.StaticText(self, -1, "Face names:")
self.lb1 = wx.ListBox(self, -1, wx.DefaultPosition, (200, 250),
elist, wx.LB_SINGLE)
self.Bind(wx.EVT_LISTBOX, self.OnSelect, id=self.lb1.GetId())
self.txt = wx.StaticText(self, -1, "Sample text...", (285, 50))
row = wx.BoxSizer(wx.HORIZONTAL)
row.Add(s1, 0, wx.ALL, 5)
row.Add(self.lb1, 0, wx.ALL, 5)
row.Add(self.txt, 0, wx.ALL|wx.ADJUST_MINSIZE, 5)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(row, 0, wx.ALL, 30)
self.SetSizer(sizer)
self.Layout()
self.lb1.SetSelection(0)
self.OnSelect(None)
wx.FutureCall(300, self.SetTextSize)
#----------------------------------------------------------------------
def SetTextSize(self):
self.txt.SetSize(self.txt.GetBestSize())
#----------------------------------------------------------------------
def OnSelect(self, evt):
face = self.lb1.GetStringSelection()
font = wx.Font(28, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, face)
self.txt.SetLabel(face)
self.txt.SetFont(font)
if wx.Platform == "__WXMAC__": self.Refresh()
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None,
title="wx.FontEnumerator Tutorial",
size=(800,600))
panel = TestPanel(self)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
好吧,这个代码也很简单。你会问,这是怎么回事?好吧,我们在和 Python 打交道,就是这样!Python 就是摇滚!让我们分解这段代码,看看发生了什么。看起来我们需要做的就是创建一个 wx.FontEnumerator 的实例...至少在演示中没有,文档中也没有提到。接下来我们列举字体名称。最后,我们创建一个人脸名称列表,对它们进行排序,并将列表放在一个 wx 中。列表框。
注意:在最初的演示中,作者使用“list”作为下一行的变量名,但是由于这是 Python 中的一个关键字(通常是非常糟糕的形式),本例中的代码用“elist”替换了“list”。
下一段有趣的代码发生在 OnSelect 方法中,每当用户选择列表框中的一个项目时就会触发该方法。正如您可能已经猜到的那样,我们从 ListBox 中获取了 face name,并使用它来创建一个字体,然后将该字体应用于一个静态文本小部件。这让我们可以看到字体的样子。
包扎
现在你应该知道如何使用 wxPython 的标准字体以及加载到你的机器上的字体。您现在也知道了如何提取关于特定字体的大量信息,这在排除字体不按预期方式运行的故障时可能会派上用场。请记住,除了 StaticText 小部件之外,您还可以将这些字体应用于其他地方。事实上,大多数小部件都有一个 SetFont 方法,所以您可以将您的字体首选项应用到大多数用户界面。
进一步阅读
- wx。FontDialog 文档
- wx。FontEnumerator 文档
- wxPython 的对话框
源代码
wxPython:让面板自毁
原文:https://www.blog.pythonlibrary.org/2012/06/26/wxpython-making-a-panel-self-destruct/
前几天我在 StackOverflow 上看到一个问题,是关于如何在一段时间后动态地销毁和创建面板。我告诉那个家伙,他可以用我的一篇博客文章中的例子,我在那里销毁和创建了按钮,但是那个家伙就是不明白。所以我写了一个简单的例子,其中面板显示倒计时,然后自我销毁,并立即被另一个面板替换。
这是让你享受观赏乐趣的代码:
import wx
########################################################################
class PanelOne(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.countdown = wx.StaticText(self, label="This panel will self-destruct in 10 seconds")
########################################################################
class PanelTwo(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
txt = wx.StaticText(self, label="Panel Two")
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Panel Smacker")
self.panelOne = PanelOne(self)
self.time2die = 10
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
self.timer.Start(1000)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panelOne, 1, wx.EXPAND)
self.SetSizer(self.sizer)
#----------------------------------------------------------------------
def update(self, event):
""""""
if self.time2die < 0:
self.panelOne.Destroy()
self.panelTwo = PanelTwo(self)
self.sizer.Add(self.panelTwo, 1, wx.EXPAND)
self.Layout()
self.timer.Stop()
else:
msg = "This panel will self-destruct in %s seconds" % self.time2die
self.panelOne.countdown.SetLabel(msg)
self.time2die -= 1
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
当您运行这段代码时,您应该会看到类似这样的内容:
它会倒计时 10 秒,然后你会看到这个:
如果你想了解更多关于计时器的知识,我也写了一篇关于计时器的文章。尽情享受吧!
wxPython:使你的框架最大化或全屏
原文:https://www.blog.pythonlibrary.org/2013/07/12/wxpython-making-your-frame-maximize-or-full-screen/
这个话题每年都会不时出现。有人会问如何让他们的应用程序全屏显示,或者如何在第一次加载时最大化。因此,我们将花一点时间向您展示如何完成这些活动。
如何最大化你的框架
有时候,您会希望在第一次加载 wxPython 应用程序时将其最大化。或者您可能想要将子帧最大化。这在 wxPython 中确实很容易做到,但是有一点需要注意。在讨论问题之前,让我们先看看代码:
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
""""""
wx.Frame.__init__(self, None, title="Test Maximize")
panel = MyPanel(self)
self.Show()
self.Maximize(True)
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
这里我们有一个非常标准的设置,使用了两个类,一个是 wx 的子类。Panel,另一个是 wx.Frame 的子类,为了让它最大化,我们只需要调用 Frame 的 Maximize()方法。这就是问题所在。如果在调用 Show 的之前调用 Maximize ,您可能会看到一个小故障。例如,当我在 Windows 7 上调用 Maximize first 时,面板没有正确地伸展到完全覆盖框架。见下面截图:
正如你所看到的,框架在右手边和底部显示了一点点(深灰色)。所以如果你运行上面的代码,面板会像它应该的那样覆盖框架,并且看起来是一致的。有时候,你可能需要调用你的框架的 Raise()方法,让它显示在顶部,或者至少让任务栏闪烁一下,以引起用户的注意。
让 wxPython 全屏显示
就我个人而言,我还没有发现任何全屏(即覆盖整个屏幕)的好用例,除了可能是屏幕保存类型的应用程序或者可能是照片浏览器。但不管怎样,完成这项任务的通常方法如下:
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.Bind(wx.EVT_KEY_DOWN, self.onKey)
#----------------------------------------------------------------------
def onKey(self, event):
"""
Check for ESC key press and exit is ESC is pressed
"""
key_code = event.GetKeyCode()
if key_code == wx.WXK_ESCAPE:
self.GetParent().Close()
else:
event.Skip()
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Test FullScreen")
panel = MyPanel(self)
self.ShowFullScreen(True)
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
注意,因为应用程序是全屏的,有一个带有关闭按钮的标题栏,所以没有好的方法来关闭应用程序。因此,我为按键事件添加了一个事件处理程序,这样用户就可以按 ESC 键关闭应用程序。
包扎
现在你应该熟悉如何让自己的 wxPython 应用程序进入最大化状态甚至全屏。祝你好运,编码快乐!
额外资源
- 全屏标志上的 wxWidgets 文档
- Nabble - wx。框架-最大化()未完全最大化
- wxPython Wiki - 使用框架。全屏显示
- StackOverflow: wxPython:退出全屏
wxPython:摆弄鼠标光标
原文:https://www.blog.pythonlibrary.org/2008/08/02/wxpython-messing-with-mouse-cursors/
最近在 wxPython 邮件列表上,关于改变鼠标图标有相当多的流量。在本文中,我将描述用 wxPython 操作光标的不同方法。为了跟进,我建议您下载 Python 2.4 或更高版本和 wxPython 2.8.x 。
wxPython(以及一般的 Python)的一个很酷的地方是它“包括电池”。在这种情况下,wxPython 在 wxPython 文档中提供了可用股票光标的列表。以下是设定股票图标的方法:
myCursor= wx.StockCursor(wx.CURSOR_POINT_LEFT)
myFrame.SetCursor(myCursor)
当然,有时您可能希望使用自定义光标。如果是这种情况,那么您可以使用 wx 创建一个游标对象。光标()。以下是我在 Windows 中的做法。您必须根据操作系统的需要进行调整。
# wx.Cursor(path\to\file, wx.BITMAP_TYPE* constant)
myCursor= wx.Cursor(r"C:\WINDOWS\Cursors\3dgarro.cur",
wx.BITMAP_TYPE_CUR)
# self is a wx.Frame in my example
myFrame.SetCursor(myCursor)
请注意,您可能无法在 Mac 上使用自定义光标。至少,罗宾·邓恩在他的博客上是这么说的。这就是你真正需要知道的使这个工作。如果你有问题,一定要发邮件给我,地址是“python library . org 的 mike”或 wxPython 用户组。
wxPython:在 ObjectListView 中移动项目
原文:https://www.blog.pythonlibrary.org/2017/11/16/wxpython-moving-items-in-objectlistview/
最近有人问我如何在一个 wx 中实现项目的拖放。ListCtrl 或 in ObjectListView 。不幸的是,这两个控件都没有内置这个功能,尽管我在 wxPython wiki 上找到了一篇文章,演示了一种在 ListCtrl 中拖放项目的方法。
然而,我确实认为实现一些按钮来移动 ObjectListView 小部件中的项目应该很容易实现。所以这就是这篇文章将要关注的。
更改项目顺序
如果您没有安装 wxPython 和 ObjectListView,那么您将希望使用 pip 来安装它们:
pip install wxPython objectlistview
一旦完成,打开你最喜欢的文本编辑器或 IDE,输入下面的代码:
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
def __repr__(self):
return "".format(title=self.title)
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.current_selection = None
self.products = [Book("wxPython in Action", "Robin Dunn",
"1932394621", "Manning"),
Book("Hello World", "Warren and Carter Sande",
"1933988495", "Manning"),
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")
]
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 up and down buttons
up_btn = wx.Button(self, wx.ID_ANY, "Up")
up_btn.Bind(wx.EVT_BUTTON, self.move_up)
down_btn = wx.Button(self, wx.ID_ANY, "Down")
down_btn.Bind(wx.EVT_BUTTON, self.move_down)
# Create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(up_btn, 0, wx.ALL|wx.CENTER, 5)
mainSizer.Add(down_btn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
def move_up(self, event):
"""
Move an item up the list
"""
self.current_selection = self.dataOlv.GetSelectedObject()
data = self.dataOlv.GetObjects()
if self.current_selection:
index = data.index(self.current_selection)
if index > 0:
new_index = index - 1
else:
new_index = len(data)-1
data.insert(new_index, data.pop(index))
self.products = data
self.setBooks()
self.dataOlv.Select(new_index)
def move_down(self, event):
"""
Move an item down the list
"""
self.current_selection = self.dataOlv.GetSelectedObject()
data = self.dataOlv.GetObjects()
if self.current_selection:
index = data.index(self.current_selection)
if index < len(data) - 1:
new_index = index + 1
else:
new_index = 0
data.insert(new_index, data.pop(index))
self.products = data
self.setBooks()
self.dataOlv.Select(new_index)
def setBooks(self):
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)
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)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
本例中我们最关心的代码是 move_up() 和 move_down() 方法。这些方法中的每一个都将检查您是否在 ObjectListView 小部件中选择了一个项目。它还将获取小部件的当前内容。如果您选择了一个项目,那么它将从 ObjectListView 小部件的数据中获取该项目的索引,这些数据是我们在调用 GetObjects() 时获取的。然后,我们可以使用该索引来确定是否应该根据我们按下的按钮来增加(move_down)或减少(move_up)它的索引。
在我们用改变的位置更新列表之后,我们更新 self.products ,这是我们在 setBooks() 中使用的类变量,用于更新我们的 ObjectListView 小部件。最后,我们实际上调用了 setBooks() ,并且我们重置了选择,因为我们最初的选择已经移动了。
包扎
我认为这是一个整洁的小项目,不需要很长时间就可以完成。我会注意到这个实现至少有一个问题,那就是当你在控件中选择多个项目时,它不能正常工作。您可能可以通过禁用 ObjectListView 小部件中的多重选择或者通过找出逻辑使其适用于多重选择来解决这个问题。但是我会让读者自己去解决。祝您编码愉快!
wxPython:宣布了新的小部件:XLSGrid
原文:https://www.blog.pythonlibrary.org/2011/08/20/wxpython-new-widget-announced-xlsgrid/
最近,wxPython 代码库中 agw 库的开发人员 Andrea Gavana 发布了他的最新小部件:XLSGrid。它的目的是忠实地再现 Microsoft Excel 电子表格的外观(XLSGrid 的每个实例一个工作表)。这个小部件基于 wx.grid.PyGridTableBase 和 wx.grid.PyGridCellRenderer,需要 xlrd 。安德里亚还建议使用马克·哈蒙德的 PyWin32 模块,否则这个小工具的格式化能力将非常有限。如果你想阅读完整的公告,请点击这里。
如果您从 wxPython 组获取下载,您将得到三个文件:
- 示例 1.xls
- xlsgrid.py
- XLSGridDemo.py
第一个是示例 Microsoft Excel 文件,第二个是小部件文件本身,第三个是方便的演示。如果您运行演示程序并在命令窗口中注意到以下错误,那么您需要从 wxPython SVN 资源库下载最新的 agw 内容:
Traceback (most recent call last):
File "C:\Users\Mike\Desktop\xls\xlsgrid.py", line 1657, in OnMouseMotion
self.tip_window = TransientPopup(window, comment, wx.GetMousePosition())
File "C:\Users\Mike\Desktop\xls\xlsgrid.py", line 1853, in __init__
self.DoShowNow()
AttributeError: 'TransientPopup' object has no attribute 'DoShowNow'
现在,我们将花一点时间创建一个简单的 Excel 文件和我们自己的小演示。让我们开始编码吧!
注意:你可以下载这篇文章末尾的 Excel 文件。
import wx
import xlrd
import xlsgrid as XG
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
filename = "demo.xls"
book = xlrd.open_workbook(filename, formatting_info=1)
sheetname = "Sheet1"
sheet = book.sheet_by_name(sheetname)
rows, cols = sheet.nrows, sheet.ncols
comments, texts = XG.ReadExcelCOM(filename, sheetname, rows, cols)
xlsGrid = XG.XLSGrid(panel)
xlsGrid.PopulateGrid(book, sheet, texts, comments)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(xlsGrid, 1, wx.EXPAND, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
如果您运行上面的代码,您应该会看到类似这样的内容:
当我运行它时,我遇到的唯一问题是,如果 Excel 文件没有任何注释,我会得到下面的回溯:
pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, 'Microsoft Excel', 'No cells were found.', 'C:\\Program Files\\Microsoft Office\\Office10\\1033\\xlmain10.chm', 0, -2146827284), None)
File "E:\My Documents\My Dropbox\Scripts\wx tutorials\XLSGrid-tut\mvp_xlsDemo.py", line 32, in frame = MyForm().Show()
File "E:\My Documents\My Dropbox\Scripts\wx tutorials\XLSGrid-tut\mvp_xlsDemo.py", line 19, in __init__
comments, texts = XG.ReadExcelCOM(filename, sheetname, rows, cols)
File "E:\My Documents\My Dropbox\Scripts\wx tutorials\XLSGrid-tut\xlsgrid.py", line 475, in ReadExcelCOM
comm_range = workbook.GetCommentsRange()
File "E:\My Documents\My Dropbox\Scripts\wx tutorials\XLSGrid-tut\xlsgrid.py", line 535, in GetCommentsRange
return self.sheet.Cells.SpecialCells(-4144)
File "L:\Python25\Lib\site-packages\win32com\client\dynamic.py", line 3, in SpecialCells
因此,从版本 0.2 开始,这个小部件目前似乎需要至少一个注释才能工作。除此之外,它工作得很好。如果您需要在代码中读取和显示 Microsoft Excel 文件(或者您只是想学习一些简洁的 wx.grid 技巧),您应该去下载这个很酷的新部件!
下载
wxPython: ObjectListview -如何双击项目
原文:https://www.blog.pythonlibrary.org/2013/12/12/wxpython-objectlistview-double-click-items/
前几天我在做一个项目,在这个项目中我使用了精彩的 ObjectListView 小部件(wx 的包装器)。我想添加双击控件中的一个项目来打开 PDF 的功能。我知道我在网上的某个地方读到过如何做这类事情,但是再一次发现这些信息是一种拖累。所以现在知道了,这次决定分享一下。作为奖励,我还将向您展示如何在 Windows 上打开 PDF 文件!
深入研究代码
使用 ObjectListView 小部件非常容易。我在过去谈论过它,所以如果你愿意,你可以去看看那些以前的文章,我会在这篇文章的结尾链接到它们。无论如何,我总是发现显示代码然后解释发生了什么更容易,所以让我们在这里也这样做:
import glob
import os
import subprocess
import wx
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class File(object):
"""
Model of the file object
"""
#----------------------------------------------------------------------
def __init__(self, path):
"""Constructor"""
self.filename = os.path.basename(path)
self.path = path
########################################################################
class UIPanel(wx.Panel):
"""
Panel class
"""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.base_path = os.path.dirname(os.path.abspath(__file__))
self.data = []
# -----------------------------------------------
# create the widgets
# add the data viewing control
self.pdfOlv = ObjectListView(self,
style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.pdfOlv.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onDoubleClick)
self.pdfOlv.SetEmptyListMsg("No PDFs Found!")
self.updateDisplay()
browseBtn = wx.Button(self, label="Browse")
browseBtn.Bind(wx.EVT_BUTTON, self.getPdfs)
# -----------------------------------------------
# layout the widgets
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.pdfOlv, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(browseBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def getPdfs(self, event):
"""
Attempts to load PDFs into objectlistview
"""
self.data = []
dlg = wx.DirDialog(self, "Choose a directory:",
style=wx.DD_DEFAULT_STYLE)
res = dlg.ShowModal()
if res != wx.ID_OK:
return
path = dlg.GetPath()
dlg.Destroy()
pdfs = glob.glob(path + "/*.pdf")
if pdfs:
for pdf in pdfs:
self.data.append(File(pdf))
self.updateDisplay()
#----------------------------------------------------------------------
def onDoubleClick(self, event):
"""
Opens the PDF that is double-clicked
"""
obj = self.pdfOlv.GetSelectedObject()
print "You just double-clicked on %s" % obj.path
cmd = os.getenv("comspec")
acrobat = "acrord32.exe"
pdf = obj.path
cmds = [cmd, "/c", "start", acrobat, "/s", pdf]
subprocess.Popen(cmds)
#----------------------------------------------------------------------
def updateDisplay(self):
"""
Updates the object list view control
"""
self.pdfOlv.SetColumns([
ColumnDefn("File", "left", 700, "filename")
])
self.pdfOlv.SetObjects(self.data)
########################################################################
class MainFrame(wx.Frame):
"""
Main frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="DoubleClick OLV Demo",
size=(800,600))
panel = UIPanel(self)
self.createMenu()
self.Show()
#----------------------------------------------------------------------
def createMenu(self):
"""
Create the menus
"""
menubar = wx.MenuBar()
fileMenu = wx.Menu()
closeMenuItem = fileMenu.Append(wx.NewId(), "Close",
"Close the application")
self.Bind(wx.EVT_MENU, self.onClose, closeMenuItem)
menubar.Append(fileMenu, "&File")
self.SetMenuBar(menubar)
#----------------------------------------------------------------------
def onClose(self, event):
"""
Close the application
"""
self.Close()
#----------------------------------------------------------------------
def main():
"""
Run the application
"""
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
#----------------------------------------------------------------------
if __name__ == "__main__":
main()
ObjectListView (OLV)小部件可以处理对象和字典。在这个例子中,我们创建了一个文件类,它将被提供给我们的 OLV 小部件。然后在 panel 类中,我们创建了带有浏览按钮的 OLV 小部件。为了获得双击效果,我们将 OLV 小部件绑定到 wx。EVT _ 列表 _ 项目 _ 激活。这不是一个非常直观的命名事件,但它确实可以捕捉双击。要实际使用这个脚本,您需要浏览到一个包含 pdf 的文件夹。一旦选择了文件夹,就会调用get pdf方法。
在所述方法中,我们使用 Python 的 glob 模块来查找 pdf。如果它返回一些,那么我们通过向它附加 File 类的实例来更新我们的数据列表。现在你应该有了类似于你在文章开头看到的截图的东西。现在,当你双击项目时,它会打印出 PDF 的路径,然后尝试使用 Python 的 os 模块获取窗口的cmd.exe路径。然后,它将尝试使用 Python 的子进程模块调用带有几个标志和 PDF 路径的 Adobe Acrobat Reader 32 位版本。如果每个人都正常工作,您应该看到 Acrobat 加载了您选择的 PDF。
注意:当双击事件处理程序触发时,我收到一个 wxPyDeprecationWarning。我不完全确定为什么会发生这种情况,因为它谈论的是关键事件,但我只是认为我的读者应该知道他们可以忽略这一点,因为我不认为这将对他们产生任何影响。
我在 Windows 7 上使用 ObjectListView 1.2、wxPython 2.9.4.0(经典)和 Python 2.7.3 测试了这段代码。
wxPython Phoenix Alpha 版本
原文:https://www.blog.pythonlibrary.org/2017/04/17/wxpython-phoenix-alpha-release/
wxPython 项目在周末发布了一个重要公告,向 Python 打包索引(PyPI)发布了新的 wxPython“Phoenix”包的 alpha 版本。wxPython 是一个主要的用于 Python 的跨平台桌面图形用户界面工具包。它包装了 wxWidgets,是 PyQt 的主要竞争对手之一。wxPython 的所有新版本都将在未来发布到 PyPI。您可以在此处直接获得副本:
还应该注意的是,wxPython 现在以 Python wheel 和 tarball 的形式发布。这意味着您现在可以安装带有 pip 的 wxPython:
pip install wxPython
如果您想保持领先地位并使用每日快照构建,那么您可以执行以下操作:
pip install --pre --find-links http://wxpython.org/Phoenix/snapshot-builds/ wxPython
我已经使用 wxPython 的 Phoenix 版本一年多了,到目前为止,它工作得非常好!你可以在这里阅读更多关于它和 Classic 的区别:
wxPython Project Phoenix 文档测试版发布
原文:https://www.blog.pythonlibrary.org/2011/12/14/wxpython-phoenix-documentation-beta-released/
wxPython 项目的 Phoenix 版本正在慢慢推出。Phoenix 是新 wxPython 的代号,它将支持 Python 2.x 和 3.x。无论如何,Andrea Gavana 在 Doxygen 的基础上使用 Sphinx 整理了一些自动生成的文档。你可以在 wxPython 邮件列表上阅读包括任何已知问题的公告。
对于那些勇敢的人来说,你也可以在这里阅读凤凰的快照或者在这里下载 T2。注意:这些 tarballs 仅适用于 Mac 和 Window 用户,它们并不完全有效。
对于那些对凤凰计划一无所知的人,你可以在 wxPython wiki 上阅读。
wxPython:在面板上放置背景图像
原文:https://www.blog.pythonlibrary.org/2010/03/18/wxpython-putting-a-background-image-on-a-panel/
昨天,我收到一个请求,要求用 Tkinter 或 wxPython 创建一个 GUI,它的背景是一个图像,顶部是按钮。看了一下 Tkinter,发现它的 PhotoImage widget 只支持 gif 和 pgm 两种格式(除非我安装了 Python 图像库)。因此,我决定尝试一下 wxPython。这是我发现的。
使用我的一些 Google-Fu,我在 daniweb 上发现了一个线程,看起来它可能会工作。我将在这里重现这个例子:
# create a background image on a wxPython panel
# and show a button on top of the image
import wx
class Panel1(wx.Panel):
"""class Panel1 creates a panel with an image on it, inherits wx.Panel"""
def __init__(self, parent, id):
# create the panel
wx.Panel.__init__(self, parent, id)
try:
# pick an image file you have in the working
# folder you can load .jpg .png .bmp or
# .gif files
image_file = 'roses.jpg'
bmp1 = wx.Image(
image_file,
wx.BITMAP_TYPE_ANY).ConvertToBitmap()
# image's upper left corner anchors at panel
# coordinates (0, 0)
self.bitmap1 = wx.StaticBitmap(
self, -1, bmp1, (0, 0))
# show some image details
str1 = "%s %dx%d" % (image_file, bmp1.GetWidth(),
bmp1.GetHeight())
parent.SetTitle(str1)
except IOError:
print "Image file %s not found" % imageFile
raise SystemExit
# button goes on the image --> self.bitmap1 is the
# parent
self.button1 = wx.Button(
self.bitmap1, label='Button1',
pos=(8, 8))
app = wx.App(False)
# create a window/frame, no parent, -1 is default ID
# change the size of the frame to fit the backgound images
frame1 = wx.Frame(None, -1, "An image on a panel",
size=(350, 400))
# create the class instance
panel1 = Panel1(frame1, -1)
frame1.Show(True)
app.MainLoop()
当我看到这个的时候,我的第一个想法是:“这可能是坏的”。我为什么会这么想?发布这个的人用的是 wx。按钮父级的 StaticBitmap。StaticBitmap 小部件不像 Panel 或 Frame 那样是一个容器小部件,所以我认为这可能不是一个好主意。因此,我在#wxPython IRC 频道上询问了罗宾·邓恩(Robin Dunn)的看法。他说如果我像上面的例子那样做,我可能会遇到 tab 遍历的问题,他建议我使用 EVT _ 擦除 _ 背景事件来做一些自定义绘制。由于 Robin Dunn 是 wxPython 的创建者,我最终走上了这条路,下面是我根据他的建议编写的代码:
import wx
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.frame = parent
sizer = wx.BoxSizer(wx.VERTICAL)
hSizer = wx.BoxSizer(wx.HORIZONTAL)
for num in range(4):
label = "Button %s" % num
btn = wx.Button(self, label=label)
sizer.Add(btn, 0, wx.ALL, 5)
hSizer.Add((1,1), 1, wx.EXPAND)
hSizer.Add(sizer, 0, wx.TOP, 100)
hSizer.Add((1,1), 0, wx.ALL, 75)
self.SetSizer(hSizer)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
#----------------------------------------------------------------------
def OnEraseBackground(self, evt):
"""
Add a picture to the background
"""
# yanked from ColourDB.py
dc = evt.GetDC()
if not dc:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
bmp = wx.Bitmap("butterfly.jpg")
dc.DrawBitmap(bmp, 0, 0)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, size=(600,450))
panel = MainPanel(self)
self.Center()
########################################################################
class Main(wx.App):
""""""
#----------------------------------------------------------------------
def __init__(self, redirect=False, filename=None):
"""Constructor"""
wx.App.__init__(self, redirect, filename)
dlg = MainFrame()
dlg.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = Main()
app.MainLoop()
下面是一个截图例子,我用一张有趣的蝴蝶图片作为背景图片,这张图片是我在夏天拍摄的:
需要关注的主要代码如下:
def OnEraseBackground(self, evt):
"""
Add a picture to the background
"""
# yanked from ColourDB.py
dc = evt.GetDC()
if not dc:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
bmp = wx.Bitmap("butterfly.jpg")
dc.DrawBitmap(bmp, 0, 0)
我从 wxPython 演示中的 ColourDB.py 演示中复制了它,并对它进行了一些编辑,以使它适用于我的应用程序。基本上,你只需将面板绑定到 EVT _ 擦除 _ 背景,在那个处理程序中,你获取设备上下文(DC),在这个例子中就是面板(我想)。我称它为 Clear 方法,主要是因为在我的实际应用中,我使用了一个透明的图像,它让背景渗透进来。通过清理伤口,我止住了出血。无论如何,条件检查以查看 dc 是否为空(我不太确定是哪一个),如果不是,它更新该区域(或脏区——它是应用程序中被移动另一个窗口“损坏”的任何部分)。然后,我抓取我的图像,并使用 DrawBitmap 将其应用于背景。这有点奇怪,我也不完全明白发生了什么,但它确实有效。
我还发现了另一个我在这篇博客中没有尝试过的方法:http://www . 5et demi . com/blog/archives/2006/06/making-a-panel-with-a-background-in-wxpython/
请随意尝试这两种方法,看看哪一种最适合您。这有点像 Robin Dunn 的方法,因为它也使用 DCs,但与我使用的类型不同。
2014 年 1 月 15 日更新:本文中的代码最初是使用 wxPython 2.8.12 测试的。已经发现在 2.9+中,您将需要删除以下行:
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
否则,图像不会正确显示。根据这个 StackOverflow 回答,原因是因为风格,wx。BG_STYLE_CUSTOM,防止背景被擦除。
也可以将背景样式改为 wx。BG_STYLE_ERASE 也可以。
wxPython: PyPlot -使用 Python 绘制图形
原文:https://www.blog.pythonlibrary.org/2010/09/27/wxpython-pyplot-graphs-with-python/
有些人通过做来学习,有些人更擅长视觉刺激。至少,这是我们被告知的。因此,本着我们所学的精神,我们将看看等式的视觉部分,看看我们如何用 wxPython 制作图形。您可能不知道这一点,但是 wxPython 包含了一个专门用于此目的的小部件。它的名字叫 PyPlot。PyPlot 非常擅长绘制简单的图形,而且速度也非常快!如果你需要怪异或复杂的绘图,那么你会想用 matplotlib 代替。幸运的是,wxPython 和 matplotlib 配合得很好,但是我们不会在本文中讨论 matplotlib。
入门(带条形图!)
如果您查看 wxPython 发行版中的 plot.py 文件,您会发现 PyPlot 需要 Numeric、numarray 或 numpy(以相反的顺序),因此请确保您已经安装了其中一个以便能够使用这个小部件。当然,wxPython 也是必需的,但您知道这一点,对吗?
总之,在 Python 文件的底部,有一个简单的演示,展示了如何用 PyPlot 绘制各种图形。让我们取一些代码,看看我们是否能弄明白。
import wx
from wx.lib.plot import PolyLine, PlotCanvas, PlotGraphics
#----------------------------------------------------------------------
def drawBarGraph():
# Bar graph
points1=[(1,0), (1,10)]
line1 = PolyLine(points1, colour='green', legend='Feb.', width=10)
points1g=[(2,0), (2,4)]
line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10)
points1b=[(3,0), (3,6)]
line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10)
points2=[(4,0), (4,12)]
line2 = PolyLine(points2, colour='Yellow', legend='May', width=10)
points2g=[(5,0), (5,8)]
line2g = PolyLine(points2g, colour='orange', legend='June', width=10)
points2b=[(6,0), (6,4)]
line2b = PolyLine(points2b, colour='brown', legend='July', width=10)
return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
"Bar Graph - (Turn on Grid, Legend)", "Months",
"Number of Students")
########################################################################
class MyGraph(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
'My First Plot (to take over the world!)')
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
checkSizer = wx.BoxSizer(wx.HORIZONTAL)
# create the widgets
self.canvas = PlotCanvas(panel)
self.canvas.Draw(drawBarGraph())
toggleGrid = wx.CheckBox(panel, label="Show Grid")
toggleGrid.Bind(wx.EVT_CHECKBOX, self.onToggleGrid)
toggleLegend = wx.CheckBox(panel, label="Show Legend")
toggleLegend.Bind(wx.EVT_CHECKBOX, self.onToggleLegend)
# layout the widgets
mainSizer.Add(self.canvas, 1, wx.EXPAND)
checkSizer.Add(toggleGrid, 0, wx.ALL, 5)
checkSizer.Add(toggleLegend, 0, wx.ALL, 5)
mainSizer.Add(checkSizer)
panel.SetSizer(mainSizer)
#----------------------------------------------------------------------
def onToggleGrid(self, event):
""""""
self.canvas.SetEnableGrid(event.IsChecked())
#----------------------------------------------------------------------
def onToggleLegend(self, event):
""""""
self.canvas.SetEnableLegend(event.IsChecked())
if __name__ == '__main__':
app = wx.App(False)
frame = MyGraph()
frame.Show()
app.MainLoop()
drawBarGraph 函数直接来自前面提到的 plot.py 文件。在本例中,函数名从“_draw6Objects”改为“drawBarGraph ”,以使代码更容易理解。让我们来看看吧。点就是图上的点:[(x1,y1),(x2,y2)]。他们通过折线方法告诉 PyPlot 在哪里绘图。正如您所看到的,PolyLine 采用了一个图表点元组的列表,并可选地采用了颜色、图例、宽度和样式(未显示)。我们创建一系列折线,然后将它们添加到一个 PlotGraphics 实例中。PlotGraphics first 方法是折线(或其他 PolyXXX 对象)、标题、xLabel 和 yLabel 的列表。我们将 PlotGraphics 对象返回给 wxPython 类中的调用者。
现在我们把注意力转向那个类,它有一个平淡无奇的名字 MyGraph。如果您以前使用过 wxPython,那么前几行非常熟悉,所以让我们跳过它们,直接跳到小部件创建部分。在这里,我们看到如何创建一个只有普通 wx 的绘图画布。面板作为其父面板。为了绘制条形图,我们调用画布对象的 Draw 方法,传入从 drawBarGraph 函数返回的 PlotGraphics 对象。在继续之前,请根据需要反复阅读,以了解发生了什么。
你准备好了吗?那我们继续吧!在我们绘制了条形图之后,我们创建了两个复选框来切换图表的网格和图例。然后我们在框架上布置小部件。复选框的方法是不言自明的,所以你可以自己解决。提示:IsChecked()返回一个布尔值。
使用保存的数据绘图
通常你会想从保存的文件、数据库或网络服务中读取数据,而不是使用硬编码的数据。在这里,我们将看看如何使用一些保存的数据来创建一个图表。以下是我们将使用的数据(您可能想要下载文章底部的档案):
# http://www.wunderground.com/history/airport/KMIW/2010/9/22/WeeklyHistory.html?format=1 CDT,Max TemperatureF,Mean TemperatureF,Min TemperatureF,Max Dew PointF,MeanDew PointF,Min DewpointF,Max Humidity, Mean Humidity, Min Humidity, Max Sea Level PressureIn, Mean Sea Level PressureIn, Min Sea Level PressureIn, Max VisibilityMiles, Mean VisibilityMiles, Min VisibilityMiles, Max Wind SpeedMPH, Mean Wind SpeedMPH, Max Gust SpeedMPH,PrecipitationIn, CloudCover, Events 2010-9-19,56,52,47,55,49,44,100,97,93,30.21,30.17,30.11,10,5,2,14,9,20,0.34,8,Rain-Thunderstorm 2010-9-20,88,72,56,71,62,55,100,73,46,30.10,29.94,29.77,10,6,0,25,12,32,T,4,Fog-Rain 2010-9-21,75,70,64,66,64,63,93,83,73,29.89,29.83,29.75,10,7,0,22,7,30,1.79,5,Fog-Rain-Thunderstorm 2010-9-22,75,70,64,68,64,63,100,93,69,30.00,29.96,29.86,10,5,1,15,4,,0.26,8,Rain
第一行是网站,第二行告诉我们后面用逗号分隔的行是什么。最后四行是普通数据,每行末尾有一些垃圾 HTML。最后一行也是我们想要忽略的。让我们创建一些代码来实际绘制这些数据!
import wx
from wx.lib.plot import PolyLine, PlotCanvas, PlotGraphics
class MyGraph(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
'Plotting File Data')
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.canvas = PlotCanvas(panel)
self.canvas.Draw(self.createPlotGraphics())
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, 1, wx.EXPAND)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def readFile(self):
""""""
# normally you would want to pass a file path in, NOT hard code it!
f = open("data.txt")
# skip the first two lines of text in the file
data = f.readlines()[2:-1]
temps = []
for line in data:
parts = line.split(",")
date = parts[0].split("-")
day = date[2]
points = [(day, parts[3]), (day, parts[1])]
temps.append(points)
return temps
#----------------------------------------------------------------------
def createPlotGraphics(self):
""""""
temps = self.readFile()
lines = []
for temp in temps:
tempInt = int(temp[1][1])
if tempInt < 60:
color = "blue"
elif tempInt >=60 and tempInt <= 75:
color = "orange"
else:
color = "red"
lines.append(PolyLine(temp, colour=color, width=10))
return PlotGraphics(lines, "Bar Graph of Temperatures",
"Days", "Temperatures")
if __name__ == '__main__':
app = wx.App(False)
frame = MyGraph()
frame.Show()
app.MainLoop()
你能解开这个密码吗?好吧,如果你不能(或不想)那么你现在就可以阅读它的全部内容!就像我们前面的例子一样,我们导入一些东西并创建一个 wx。带有面板和绘图画布的框架。我们也有一个简单的 readFile 方法和一个 createPlotGraphics 方法。这两种方法是我们将要重点介绍的。
readFile 方法由 createPlotGraphics 方法调用。它所做的只是读取一个文件。对于这个例子,我们有硬编码的文件的“路径”。你通常想做的是使用某种文件浏览器来加载文件,但我们走的是超级简单的路线。当我们读取文件中的行时,我们使用下面的语法跳过前两行:
data = f.readlines()[2:-1]
这样做是跳过文件中的前两行,读到末尾,减去一行。通过这样做,我们在开头和结尾都跳过了垃圾。Python 是不是很酷?接下来,我们创建一个简单的“for 循环”来提取我们想要的数据,这只是一天,低温和高温。剩下的我们就扔了。
在 createPlotGraphics 方法中,我们获取从 readFile 方法返回的 temps 列表,并对这些列表进行循环,从而创建一个新的折线列表。我们使用一些“if 语句”来决定条形图中每个条形的颜色。最后,我们将所有折线放入一个 PlotGraphics 实例中,并将其返回给 init 方法中调用的。这就是全部了!
具有数千个点的点图
现在我们来看看如何创建一个有 25,000 个点的点图!这一张也是来自演示。代码如下:
import numpy.oldnumeric as _Numeric
import wx
from wx.lib.plot import PlotCanvas, PlotGraphics, PolyLine, PolyMarker
#----------------------------------------------------------------------
def drawLinePlot():
# 25,000 point line
data1 = _Numeric.arange(5e5,1e6,10)
data1.shape = (25000, 2)
line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5)
# A few more points...
markers2 = PolyMarker(data1, legend='Square', colour='blue',
marker='square')
return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "")
########################################################################
class MyGraph(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
'It Looks Like a Line Graph!')
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
checkSizer = wx.BoxSizer(wx.HORIZONTAL)
# create the widgets
self.canvas = PlotCanvas(panel)
self.canvas.Draw(drawLinePlot())
toggleGrid = wx.CheckBox(panel, label="Show Grid")
toggleGrid.Bind(wx.EVT_CHECKBOX, self.onToggleGrid)
toggleLegend = wx.CheckBox(panel, label="Show Legend")
toggleLegend.Bind(wx.EVT_CHECKBOX, self.onToggleLegend)
# layout the widgets
mainSizer.Add(self.canvas, 1, wx.EXPAND)
checkSizer.Add(toggleGrid, 0, wx.ALL, 5)
checkSizer.Add(toggleLegend, 0, wx.ALL, 5)
mainSizer.Add(checkSizer)
panel.SetSizer(mainSizer)
#----------------------------------------------------------------------
def onToggleGrid(self, event):
""""""
self.canvas.SetEnableGrid(event.IsChecked())
#----------------------------------------------------------------------
def onToggleLegend(self, event):
""""""
self.canvas.SetEnableLegend(event.IsChecked())
if __name__ == '__main__':
app = wx.App(False)
frame = MyGraph()
frame.Show()
app.MainLoop()
我们重用了我们在原始示例中看到的大部分 wxPython 代码,在这里只调用了一个不同的函数。 drawLinePlot 函数非常简单。对于本例,我们使用 numpy 创建 25,000 个地块点,然后用它们创建一条多段线。如果你放大,你会看到一些点是方形的,而不是圆形的。这就是 PolyMarker 类的用途。它设置“标记”的样式。现在我们准备看下一个例子了!
创建正弦/余弦图形
这个例子向你展示了如何获取正弦和余弦,并把它们画出来。它看起来有点像水平双螺旋。总之,代码如下:
import numpy.oldnumeric as _Numeric
import wx
from wx.lib.plot import PlotCanvas, PlotGraphics, PolyLine, PolyMarker
def drawSinCosWaves():
# 100 points sin function, plotted as green circles
data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
data1.shape = (100, 2)
data1[:,1] = _Numeric.sin(data1[:,0])
markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1)
# 50 points cos function, plotted as red line
data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
data1.shape = (50,2)
data1[:,1] = _Numeric.cos(data1[:,0])
lines = PolyLine(data1, legend= 'Red Line', colour='red')
# A few more points...
pi = _Numeric.pi
markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
(3.*pi/4., -1)], legend='Cross Legend', colour='blue',
marker='cross')
return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis")
########################################################################
class MyGraph(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
'Sin / Cos Plot')
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
checkSizer = wx.BoxSizer(wx.HORIZONTAL)
# create the widgets
self.canvas = PlotCanvas(panel)
self.canvas.Draw(drawSinCosWaves())
toggleGrid = wx.CheckBox(panel, label="Show Grid")
toggleGrid.Bind(wx.EVT_CHECKBOX, self.onToggleGrid)
toggleLegend = wx.CheckBox(panel, label="Show Legend")
toggleLegend.Bind(wx.EVT_CHECKBOX, self.onToggleLegend)
# layout the widgets
mainSizer.Add(self.canvas, 1, wx.EXPAND)
checkSizer.Add(toggleGrid, 0, wx.ALL, 5)
checkSizer.Add(toggleLegend, 0, wx.ALL, 5)
mainSizer.Add(checkSizer)
panel.SetSizer(mainSizer)
#----------------------------------------------------------------------
def onToggleGrid(self, event):
""""""
self.canvas.SetEnableGrid(event.IsChecked())
#----------------------------------------------------------------------
def onToggleLegend(self, event):
""""""
self.canvas.SetEnableLegend(event.IsChecked())
if __name__ == '__main__':
app = wx.App(False)
frame = MyGraph()
frame.Show()
app.MainLoop()
这个例子是为数学爱好者准备的。我很久没学过三角学和几何学了,所以这里就不解释方程了。你可以用你最喜欢的搜索引擎来查找这类信息。此示例使用一条折线和两个聚合标记来创建图形。虽然它和其他例子很像,所以真的没什么好说的。
包扎
到目前为止,您应该已经准备好用 wxPython 自己绘制图形了。如果您遇到困难,plot.py 文件中还有其他几个例子,wxPython 邮件列表成员非常友好,如果您友好地提出请求,他们可能会帮助您。让我知道你是否创造了任何酷的东西!
注意:本文中的代码已经在 Windows XP、Python 2.5、wxPython 2.8.10.1 上测试过了
进一步阅读
- PyPlot 的官方文档
下载
wxPython 食谱图书大赛
原文:https://www.blog.pythonlibrary.org/2018/01/11/wxpython-recipes-book-contest/
最近,我有一本自己出版的书,“wxPython Cookbook”被一家出版社选中,并作为 wxPython 食谱重新出版。因为他们给了我一些免费的平装本,我决定做一个小竞赛。
规则
- 发表评论,告诉我你为什么想要一本
- 最聪明或最真诚的评论者将由我选出
比赛将从现在开始,持续到美国中部时间 1 月 15 日星期一晚上 11:59。
获胜者将由你的真实联系,我将在书上签名并把它运送到你想让我去的任何地方。
对于那些想买这本书的人,Apress 给了我一张蹩脚的八折优惠券,你可以在他们的网站上购买电子书或平装本
wxPython 食谱图书发布
原文:https://www.blog.pythonlibrary.org/2017/12/19/wxpython-recipes-book-release/
今年早些时候,一家出版社联系我,希望以他们的品牌重新出版我的书 wxPython Cookbook 。我想看看我能从出版商那里学到什么可能会很有趣,所以我和他们一起去了,因为我过去喜欢过他们的几本书。这本书最大的变化是,我最终把食谱分成了几章,而不是每一个食谱都是独立的一章。我还添加了一些新的食谱,以帮助在一些章节不容易归类时进行补充。
总之,前几天出版了这本书:
你可以在亚马逊或者 T2 出版社的网站上找到这本书。你也可以在谷歌上看到这本书的预览。
您可以使用以下代码从press开始享受图书八折优惠: wx20 。在 2018 年 6 月之前,这本书的平装本和电子书版本的代码都是有效的。
**这本书的代码存放在 Apress 的 Github 账户上。我在 Github 上也有一份拷贝。
不管怎样,请随意查看。如果你已经买了一本 wxPython 食谱,那么你不需要再买一本,因为它基本上是一样的,只是多了一些修饰和一些新的食谱。我计划在 2018 年自助出版一些其他书籍,所以请关注博客上的相关新闻!**
wxPython -重定向 stdout / stderr
原文:https://www.blog.pythonlibrary.org/2009/01/01/wxpython-redirecting-stdout-stderr/
每周都有新的程序员开始使用 Python 和 wxPython。因此,每隔几个月,我就会看到有人询问如何将 stdout 重定向到 wx。comp.lang.python 上的 TextCtrl 或 wxPython 邮件列表。既然这是一个如此普遍的问题,我想我应该写一篇关于它的文章。普通读者会知道我在之前的一篇文章中提到过这个概念。
更新于 2015-10-06
最初我认为我们需要创建一个类,它可以 duck-type 编写 wx.TextCtrl 的 API。注意,我使用了所谓的“新风格”类,它子类化了对象(参见下面的代码)。
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
注意,这个类中只有一个方法(当然,除了初始化方法)。它允许我们将文本从 stdout 或 stderr 写入文本控件。应该注意的是, write 方法不是线程安全的。如果您想要重定向线程中的文本,请将 write 语句更改如下:
def write(self, string):
wx.CallAfter(self.out.WriteText, string)
现在让我们深入研究我们需要的 wxPython 代码:
import sys
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "wxPython Redirect Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100),
style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
btn = wx.Button(panel, wx.ID_ANY, 'Push me!')
self.Bind(wx.EVT_BUTTON, self.onButton, btn)
# Add widgets to a sizer
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
# redirect text here
redir=RedirectText(log)
sys.stdout=redir
def onButton(self, event):
print "You pressed the button!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
在上面的代码中,我创建了一个只读的多行文本控件和一个按钮,其唯一目的是将一些文本打印到 stdout。我将它们添加到一个 BoxSizer 中,以防止小部件相互堆叠,并更好地处理框架的大小调整。接下来,我通过向它传递我的文本控件的实例来实例化 RedirectText 类。最后,我将 stdout 设置为 RediectText 实例, redir (即 sys.stdout=redir)。
如果您还想重定向 stderr,那么只需在“sys.stdout=redir”之后添加以下内容:sys.stderr=redir
可以对此进行改进,即颜色编码(或预挂起)哪些消息来自 stdout,哪些来自 stderr,但我将把它留给读者作为练习。
最近有人向我指出,我不应该需要经历所有这些困难。相反,您可以将 stdout 直接重定向到 TextCtrl 小部件,因为它有自己的 write 方法。这里有一个例子:
import sys
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None,
title="wxPython Redirect Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL
log = wx.TextCtrl(panel, wx.ID_ANY, size=(300,100),
style=style)
btn = wx.Button(panel, wx.ID_ANY, 'Push me!')
self.Bind(wx.EVT_BUTTON, self.onButton, btn)
# Add widgets to a sizer
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(log, 1, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
# redirect text here
sys.stdout=log
def onButton(self, event):
print "You pressed the button!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
你会注意到上面的代码不再引用 RedirectText 类,因为我们不需要它。我很确定,如果你想使用线程,这样做是不安全的。为了安全起见,您需要以前面提到的类似方式覆盖 TextCtrl 的 write 方法。特别感谢 carandraug 为我指出了这一点。
相关阅读
- Python: 运行 Ping、Traceroute 等
下载源代码
wxPython:重构您的程序
原文:https://www.blog.pythonlibrary.org/2009/11/11/wxpython-refactoring-your-program/
世界上有很多糟糕的代码。我在本文中的目标是帮助 wxPython 程序员学习如何使他们的应用程序更容易维护和修改。需要注意的是,本文中的内容并不一定是重构程序的所谓“最佳”方式;相反,以下是我从自己的经历中学到的一些东西,并得到了罗宾·邓恩的书 wxPython in Action 和 wxPython 社区的一些帮助。
为了便于说明,我创建了一个流行的计算器程序的 GUI 框架,计算机科学教授喜欢用它来吸引毫无戒心的新生。代码只创建用户界面。它实际上不会进行任何计算。然而,我在这个网站的上找到了一些代码,应该很容易适应这个程序。我将把它留给读者作为练习。让我们来看一段粗略的、未重构的代码:
import wx
class PyCalc(wx.App):
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
def OnInit(self):
# create frame here
self.frame = wx.Frame(None, wx.ID_ANY, title="Calculator")
panel = wx.Panel(self.frame, wx.ID_ANY)
self.displayTxt = wx.TextCtrl(panel, wx.ID_ANY, "0",
size=(155,-1),
style=wx.TE_RIGHT|wx.TE_READONLY)
size=(35, 35)
zeroBtn = wx.Button(panel, wx.ID_ANY, "0", size=size)
oneBtn = wx.Button(panel, wx.ID_ANY, "1", size=size)
twoBtn = wx.Button(panel, wx.ID_ANY, "2", size=size)
threeBtn = wx.Button(panel, wx.ID_ANY, "3", size=size)
fourBtn = wx.Button(panel, wx.ID_ANY, "4", size=size)
fiveBtn = wx.Button(panel, wx.ID_ANY, "5", size=size)
sixBtn = wx.Button(panel, wx.ID_ANY, "6", size=size)
sevenBtn = wx.Button(panel, wx.ID_ANY, "7", size=size)
eightBtn = wx.Button(panel, wx.ID_ANY, "8", size=size)
nineBtn = wx.Button(panel, wx.ID_ANY, "9", size=size)
zeroBtn.Bind(wx.EVT_BUTTON, self.method1)
oneBtn.Bind(wx.EVT_BUTTON, self.method2)
twoBtn.Bind(wx.EVT_BUTTON, self.method3)
threeBtn.Bind(wx.EVT_BUTTON, self.method4)
fourBtn.Bind(wx.EVT_BUTTON, self.method5)
fiveBtn.Bind(wx.EVT_BUTTON, self.method6)
sixBtn.Bind(wx.EVT_BUTTON, self.method7)
sevenBtn.Bind(wx.EVT_BUTTON, self.method8)
eightBtn.Bind(wx.EVT_BUTTON, self.method9)
nineBtn.Bind(wx.EVT_BUTTON, self.method10)
divBtn = wx.Button(panel, wx.ID_ANY, "/", size=size)
multiBtn = wx.Button(panel, wx.ID_ANY, "*", size=size)
subBtn = wx.Button(panel, wx.ID_ANY, "-", size=size)
addBtn = wx.Button(panel, wx.ID_ANY, "+", size=(35,100))
equalsBtn = wx.Button(panel, wx.ID_ANY, "Enter", size=(35,100))
divBtn.Bind(wx.EVT_BUTTON, self.method11)
multiBtn.Bind(wx.EVT_BUTTON, self.method12)
addBtn.Bind(wx.EVT_BUTTON, self.method13)
subBtn.Bind(wx.EVT_BUTTON, self.method14)
equalsBtn.Bind(wx.EVT_BUTTON, self.method15)
mainSizer = wx.BoxSizer(wx.VERTICAL)
masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
vBtnSizer = wx.BoxSizer(wx.VERTICAL)
numSizer = wx.GridBagSizer(hgap=5, vgap=5)
numSizer.Add(divBtn, pos=(0,0), flag=wx.CENTER)
numSizer.Add(multiBtn, pos=(0,1), flag=wx.CENTER)
numSizer.Add(subBtn, pos=(0,2), flag=wx.CENTER)
numSizer.Add(sevenBtn, pos=(1,0), flag=wx.CENTER)
numSizer.Add(eightBtn, pos=(1,1), flag=wx.CENTER)
numSizer.Add(nineBtn, pos=(1,2), flag=wx.CENTER)
numSizer.Add(fourBtn, pos=(2,0), flag=wx.CENTER)
numSizer.Add(fiveBtn, pos=(2,1), flag=wx.CENTER)
numSizer.Add(sixBtn, pos=(2,2), flag=wx.CENTER)
numSizer.Add(oneBtn, pos=(3,0), flag=wx.CENTER)
numSizer.Add(twoBtn, pos=(3,1), flag=wx.CENTER)
numSizer.Add(threeBtn, pos=(3,2), flag=wx.CENTER)
numSizer.Add(zeroBtn, pos=(4,1), flag=wx.CENTER)
vBtnSizer.Add(addBtn, 0)
vBtnSizer.Add(equalsBtn, 0)
masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
mainSizer.Add(masterBtnSizer)
panel.SetSizer(mainSizer)
mainSizer.Fit(self.frame)
self.frame.Show()
return True
def method1(self, event):
pass
def method2(self, event):
pass
def method3(self, event):
pass
def method4(self, event):
pass
def method5(self, event):
pass
def method6(self, event):
pass
def method7(self, event):
pass
def method8(self, event):
pass
def method9(self, event):
pass
def method10(self, event):
pass
def method13(self, event):
pass
def method14(self, event):
pass
def method12(self, event):
pass
def method11(self, event):
pass
def method15(self, event):
pass
def main():
app = PyCalc()
app.MainLoop()
if __name__ == "__main__":
main()
我把这段代码建立在一些非常讨厌的 VBA 代码的基础上,在过去的几年里,我不得不维护这些代码。这种代码很可能来自为程序员自动生成代码的程序,如 Visual Studio 或 Microsoft Office 中的宏生成器。请注意,函数只是编号,而不是描述性的,许多代码看起来都是一样的。当你看到两行或多行代码看起来相同或似乎有相同的目的时,它们通常符合重构的条件。这种现象的一个术语是“复制粘贴”或意大利面代码(不要与其他与意大利面相关的代码委婉语混淆)。是的,复制粘贴代码是邪恶的!当您需要进行更改时,您需要找到复制代码的每个实例,并对其进行更改。
有鉴于此,让我们开始重构这个烂摊子吧!我认为将框架、应用程序和面板对象分开会使代码更容易处理,所以这是我们首先要做的。通过查看小部件的父控件,我们看到文本控件和所有按钮都使用 panel 作为它们的父控件,所以让我们把它们放在一个类中。我还将把实际的小部件创建和布局放到一个函数中,这个函数可以从 panel 类的 init 中调用(见下文)。
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.parent = parent
self.formula = []
self.currentVal = "0"
self.previousVal = "0"
self.operator = None
self.operatorFlag = False
self.createAndlayoutWidgets()
def createAndlayoutWidgets(self):
mainSizer = wx.BoxSizer(wx.VERTICAL)
masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
vBtnSizer = wx.BoxSizer(wx.VERTICAL)
numSizer = wx.GridBagSizer(hgap=5, vgap=5)
self.displayTxt = wx.TextCtrl(self, wx.ID_ANY, "0",
size=(155,-1),
style=wx.TE_RIGHT|wx.TE_READONLY)
# number buttons
size=(45, 45)
zeroBtn = wx.Button(self, wx.ID_ANY, "0", size=size)
oneBtn = wx.Button(self, wx.ID_ANY, "1", size=size)
twoBtn = wx.Button(self, wx.ID_ANY, "2", size=size)
threeBtn = wx.Button(self, wx.ID_ANY, "3", size=size)
fourBtn = wx.Button(self, wx.ID_ANY, "4", size=size)
fiveBtn = wx.Button(self, wx.ID_ANY, "5", size=size)
sixBtn = wx.Button(self, wx.ID_ANY, "6", size=size)
sevenBtn = wx.Button(self, wx.ID_ANY, "7", size=size)
eightBtn = wx.Button(self, wx.ID_ANY, "8", size=size)
nineBtn = wx.Button(self, wx.ID_ANY, "9", size=size)
numBtnLst = [zeroBtn, oneBtn, twoBtn, threeBtn, fourBtn, fiveBtn,
sixBtn, sevenBtn, eightBtn, nineBtn]
for button in numBtnLst:
button.Bind(wx.EVT_BUTTON, self.onButton)
# operator buttons
divBtn = wx.Button(self, wx.ID_ANY, "/", size=size)
multiBtn = wx.Button(self, wx.ID_ANY, "*", size=size)
subBtn = wx.Button(self, wx.ID_ANY, "-", size=size)
addBtn = wx.Button(self, wx.ID_ANY, "+", size=(45,100))
equalsBtn = wx.Button(self, wx.ID_ANY, "Enter", size=(45,100))
equalsBtn.Bind(wx.EVT_BUTTON, self.onCalculate)
opBtnLst = [divBtn, multiBtn, subBtn, addBtn]
for button in opBtnLst:
button.Bind(wx.EVT_BUTTON, self.onOperation)
numSizer.Add(divBtn, pos=(0,0), flag=wx.CENTER)
numSizer.Add(multiBtn, pos=(0,1), flag=wx.CENTER)
numSizer.Add(subBtn, pos=(0,2), flag=wx.CENTER)
numSizer.Add(sevenBtn, pos=(1,0), flag=wx.CENTER)
numSizer.Add(eightBtn, pos=(1,1), flag=wx.CENTER)
numSizer.Add(nineBtn, pos=(1,2), flag=wx.CENTER)
numSizer.Add(fourBtn, pos=(2,0), flag=wx.CENTER)
numSizer.Add(fiveBtn, pos=(2,1), flag=wx.CENTER)
numSizer.Add(sixBtn, pos=(2,2), flag=wx.CENTER)
numSizer.Add(oneBtn, pos=(3,0), flag=wx.CENTER)
numSizer.Add(twoBtn, pos=(3,1), flag=wx.CENTER)
numSizer.Add(threeBtn, pos=(3,2), flag=wx.CENTER)
numSizer.Add(zeroBtn, pos=(4,1), flag=wx.CENTER)
vBtnSizer.Add(addBtn, 0)
vBtnSizer.Add(equalsBtn, 0)
masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
mainSizer.Add(masterBtnSizer)
self.SetSizer(mainSizer)
mainSizer.Fit(self.parent)
您会注意到添加了一个函数和一些空白使这部分代码看起来更好。它还使我们能够将这些代码放到一个单独的文件中,如果我们愿意的话,我们可以导入这个文件。这有助于提升整个 MVC 做事方式。如果您注意的话,您会看到我已经将数字按钮绑定到一个处理程序,将操作按钮绑定到另一个处理程序。以下是我采用的方法:
def onOperation(self, event):
"""
Add an operator to the equation
"""
print "onOperation handler fired"
def onButton(self, event):
"""
Keeps the display up to date
"""
# Get the button object
buttonObj = event.GetEventObject()
# Get the label of the button object
buttonLbl = buttonObj.GetLabel()
def onCalculate(self):
"""
Calculate the total
"""
print 'in onCalculate'
我在 onButton 事件处理程序中添加了一些代码,以便您可以看到如何获得调用它的按钮对象的句柄。否则,这个方法实际上没有任何作用。其他方法只是存根。现在让我们看看框架和应用程序对象代码:
class PyCalcFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
title="Calculator")
panel = MainPanel(self)
class PyCalc(wx.App):
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
def OnInit(self):
# create frame here
frame = PyCalcFrame()
frame.Show()
return True
def main():
app = PyCalc()
app.MainLoop()
if __name__ == "__main__":
main()
正如你所看到的,它们非常简短扼要。如果你赶时间,那么这一点点重构可能就足够了。然而,我认为我们可以做得更好。向上滚动,再次查看重构后的 panel 类。看到有十几个基本相同的按钮创建行了吗?“sizer 也有很多。把“台词也加进去。那些将是我们的下一个目标!
在今年春天(2009 年)的 wxPython 邮件列表上,有一个关于这个主题的大讨论。我看到了许多有趣的解决方案,但流传最广的是创建某种小部件构建方法。这就是我要告诉你怎么做的。以下是我的限量版:
def onWidgetSetup(self, widget, event, handler, sizer, pos=None, flags=[]):
"""
Accepts a widget, the widget's default event and its handler,
the sizer for the widget, the position of the widget inside
the sizer (if applicable) and the sizer flags (if applicable)
"""
widget.Bind(event, handler)
if not pos:
sizer.Add(widget, 0, wx.ALL, 5)
elif pos and flags:
sizer.Add(widget, pos=pos, flag=wx.CENTER)
else:
sizer.Add(widget, pos=pos)
return widget
这是一段非常简单的代码,它获取一个小部件,将其绑定到一个事件,并将结果组合添加到一个 sizer 中。这个脚本可以扩展做额外的绑定,嵌套大小,设置字体等等。发挥你的想象力。现在我们可以看看这是如何改变面板类代码的:
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.parent = parent
self.formula = []
self.currentVal = "0"
self.previousVal = "0"
self.operator = None
self.operatorFlag = False
self.createAndlayoutWidgets()
def createAndlayoutWidgets(self):
mainSizer = wx.BoxSizer(wx.VERTICAL)
masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
vBtnSizer = wx.BoxSizer(wx.VERTICAL)
numSizer = wx.GridBagSizer(hgap=5, vgap=5)
self.displayTxt = wx.TextCtrl(self, wx.ID_ANY, "0",
size=(155,-1),
style=wx.TE_RIGHT|wx.TE_READONLY)
# number buttons
size=(45, 45)
zeroBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "0", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(4,1))
oneBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "1", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(3,0))
twoBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "2", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(3,1))
threeBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "3", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(3,2))
fourBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "4", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(2,0))
fiveBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "5", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(2,1))
sixBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "6", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(2,2))
sevenBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "7", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(1,0))
eightBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "8", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(1,1))
nineBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "9", size=size),
wx.EVT_BUTTON, self.onButton, numSizer,
pos=(1,2))
# operator buttons
divBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "/", size=size),
wx.EVT_BUTTON, self.onOperation, numSizer,
pos=(0,0), flags=wx.CENTER)
multiBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "*", size=size),
wx.EVT_BUTTON, self.onOperation, numSizer,
pos=(0,1), flags=wx.CENTER)
subBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "-", size=size),
wx.EVT_BUTTON, self.onOperation, numSizer,
pos=(0,2), flags=wx.CENTER)
addBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "+", size=(45,100)),
wx.EVT_BUTTON, self.onOperation, vBtnSizer)
equalsBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "Enter", size=(45,100)),
wx.EVT_BUTTON, self.onCalculate, vBtnSizer)
masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
mainSizer.Add(masterBtnSizer)
self.SetSizer(mainSizer)
mainSizer.Fit(self.parent)
def onWidgetSetup(self, widget, event, handler, sizer, pos=None, flags=[]):
"""
Accepts a widget, the widget's default event and its handler,
the sizer for the widget, the position of the widget inside
the sizer (if applicable) and the sizer flags (if applicable)
"""
widget.Bind(event, handler)
if not pos:
sizer.Add(widget, 0, wx.ALL, 5)
elif pos and flags:
sizer.Add(widget, pos=pos, flag=wx.CENTER)
else:
sizer.Add(widget, pos=pos)
return widget
这段代码最棒的地方在于,它把所有按钮创建的东西都放在了一个方法中,所以我们不必编写“wx。Button()”一遍又一遍。这次迭代还删除了大部分“sizer”。添加“通话。当然,在它的位置上,我们有许多“self.onWidgetSetup()”方法调用。看起来这个还是可以重构的,但是怎么重构呢!?对于我的下一个技巧,我浏览了罗宾·邓恩书中的重构部分,并得出结论,他的想法值得一试。(他毕竟是 wxPython 的创造者。)
在他的书中,他有一个按钮构建器方法,看起来类似于我的小部件构建器,尽管他的要简单得多。他还有一个只返回按钮数据的方法。我已经采纳了这些想法,并将其应用到这个程序中,正如你在我最终的面板代码中看到的:
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.parent = parent
self.formula = []
self.currentVal = "0"
self.previousVal = "0"
self.operator = None
self.operatorFlag = False
self.createDisplay()
def createDisplay(self):
"""
Create the calculator display
"""
mainSizer = wx.BoxSizer(wx.VERTICAL)
masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
vBtnSizer = wx.BoxSizer(wx.VERTICAL)
numSizer = wx.GridBagSizer(hgap=5, vgap=5)
self.displayTxt = wx.TextCtrl(self, wx.ID_ANY, "0",
size=(155,-1),
style=wx.TE_RIGHT|wx.TE_READONLY)
for eachLabel, eachSize, eachHandler, eachPos in self.buttonData():
button = self.buildButton(eachLabel, eachSize, eachHandler)
if eachPos:
numSizer.Add(button, pos=eachPos, flag=wx.CENTER)
else:
vBtnSizer.Add(button)
masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
mainSizer.Add(masterBtnSizer)
self.SetSizer(mainSizer)
mainSizer.Fit(self.parent)
def buttonData(self):
size=(45, 45)
return (("0", size, self.onButton, (4,1)),
("1", size, self.onButton, (3,0)),
("2", size, self.onButton, (3,1)),
("3", size, self.onButton, (3,2)),
("4", size, self.onButton, (2,0)),
("5", size, self.onButton, (2,1)),
("6", size, self.onButton, (2,2)),
("7", size, self.onButton, (1,0)),
("8", size, self.onButton, (1,1)),
("9", size, self.onButton, (1,2)),
("/", size, self.onOperation, (0,0)),
("*", size, self.onOperation, (0,1)),
("-", size, self.onOperation, (0,2)),
("+", (45,100), self.onOperation, None),
("Enter", (45,100), self.onCalculate, None))
def buildButton(self, label, size, handler):
"""
Builds a button and binds it to an event handler.
Returns the button object
"""
button = wx.Button(self, wx.ID_ANY, label, size=size)
self.Bind(wx.EVT_BUTTON, handler, button)
return button
在这一点上,我们应该退一步,看看这给我们带来了什么。原始代码是 132 行,第一次重构将行数减少到 128 行,第二次将行数增加到 144 行,最后一次将行数减少到 120 行。愤世嫉俗的人可能会说我们只保存了 12 行代码。我不同意。我们最终得到的(不管它是否比原始代码多)是一个更容易维护的代码库。这可以修改,并保持清洁比原来容易得多。
我希望这篇文章已经帮助你看到了如何将你的代码重构为类和方法可以使你的程序更易读,更容易维护——并且与其他贡献者分享,毫无羞耻!
下载量
延伸阅读
wxPython:重置背景颜色
原文:https://www.blog.pythonlibrary.org/2009/09/03/wxpython-resetting-the-background-color/
在过去的几周里,我看到很多人询问关于将一个小部件的颜色重新设置回它最初的“默认”颜色。在 wxPython 的邮件列表中至少有一个人和另外一个人在他们的 IRC 频道上请求关于这个话题的信息。当我第一次查找这个问题时,是为了列表中的一位程序员,他想重置面板的背景颜色。在我的寻找中,我认为我找到了完美的解决方案:
color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
panel.SetBackgroundColour(color)
不幸的是,这并不适用于所有情况。相反,罗宾·邓恩推荐使用 wx。取而代之的是 NullColor(你可以在这里读取完整线程)。按照邓恩先生的说法,原因是(wx。NullColor)将告诉 wx 小部件没有特定的颜色集,因此它将使用平台想要使用的任何颜色,这可能由活动主题控制,可能根本不是纯色。这与使用系统设置颜色有一点不同,因为 wx 会像设置了一个自定义颜色一样工作,它不在乎它是否与系统颜色相同。
因此,我创建了一个演示应用程序,向您展示如何重置颜色:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Background Reset Tutorial")
# Add a panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
self.txt = wx.TextCtrl(self.panel)
self.txt.SetBackgroundColour("Yellow")
blueBtn = wx.Button(self.panel,
label="Change Background Color")
blueBtn.Bind(wx.EVT_BUTTON, self.onChangeBackground)
resetBtn = wx.Button(self.panel, label="Reset")
resetBtn.Bind(wx.EVT_BUTTON, self.onReset)
topSizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add(blueBtn, 0, wx.ALL|wx.CENTER, 5)
btnSizer.Add(resetBtn, 0, wx.ALL|wx.CENTER, 5)
topSizer.Add(self.txt, 0, wx.ALL, 5)
topSizer.Add(btnSizer, 0, wx.CENTER)
self.panel.SetSizer(topSizer)
def onChangeBackground(self, event):
"""
Change the background color of the panel
"""
self.panel.SetBackgroundColour("Blue")
self.panel.Refresh()
def onReset(self, event):
"""
Reset the color of the panel to the default color
"""
self.panel.SetBackgroundColour(wx.NullColor)
self.txt.SetBackgroundColour(wx.NullColor)
self.panel.Refresh()
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm()
frame.Show()
app.MainLoop()
在这段代码中,您会注意到我将文本控件的初始背景颜色设置为黄色,并且允许用户通过按钮事件处理程序更改面板的背景。用户也可以通过按下“重置”按钮来“重置”两个窗口小部件的背景颜色。
这是一张使用前和使用后的图片:
重置前
复位后
真的没什么好说的了。现在你也知道诀窍了,所以如果你看到一个新手在这方面挣扎,你也可以告诉他们该怎么做!
此代码在以下位置进行了测试:
- Windows XP、wxPython 2.8.10.1(MSW-unicode)、Python 2.5.2
下载量
wxPython:设置框架在哪个显示器上
原文:https://www.blog.pythonlibrary.org/2018/06/13/wxpython-set-which-display-the-frame-is-on/
前几天在 wxPython IRC 频道看到一个有趣的问题。他们询问是否有办法设置他们的应用程序将出现在哪个显示器上。wxPython 的创建者 Robin Dunn 给了提问者一些提示,但我决定继续写一篇关于这个主题的快速教程。
wxPython 工具包实际上包含了这类事情所需的所有部分。第一步是获得组合屏幕尺寸。我的意思是问 wxPython 它认为屏幕的总尺寸是多少。这将是所有显示器的总宽度和高度的总和。你可以通过调用 wx 得到这个。DisplaySize() ,返回一个元组。如果你想获得单独的显示分辨率,那么你必须调用 wx。显示并传入显示器的索引。因此,如果您有两台显示器,那么第一台显示器的分辨率可以这样获得:
index = 0
display = wx.Display(index)
geo = display.GetGeometry()
让我们写一个快速的小应用程序,它有一个按钮,可以切换应用程序的显示。
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.frame = parent
mainsizer = wx.BoxSizer(wx.VERTICAL)
sizer = wx.BoxSizer(wx.HORIZONTAL)
btn = wx.Button(self, label='Switch Display')
btn.Bind(wx.EVT_BUTTON, self.switch_displays)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
mainsizer.AddStretchSpacer(prop=1)
mainsizer.Add(sizer, 0, wx.ALL|wx.CENTER, 5)
mainsizer.AddStretchSpacer(prop=1)
self.SetSizer(mainsizer)
def switch_displays(self, event):
combined_screen_size = wx.DisplaySize()
for index in range(wx.Display.GetCount()):
display = wx.Display(index)
geo = display.GetGeometry()
print(geo)
current_w, current_h = self.frame.GetPosition()
screen_one = wx.Display(0)
_, _, screen_one_w, screen_one_h = screen_one.GetGeometry()
screen_two = wx.Display(1)
_, _, screen_two_w, screen_two_h = screen_two.GetGeometry()
if current_w > combined_screen_size[0] / 2:
# probably on second screen
self.frame.SetPosition((int(screen_one_w / 2),
int(screen_one_h / 2)))
else:
self.frame.SetPosition((int(screen_one_w + (screen_two_w / 2)),
int(screen_two_h / 2)))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Display Change')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
这里我们创建了两个类。第一个包含了几乎所有的代码,并定义了按钮及其事件处理程序。另一个类用于创建框架本身。不过,事件处理程序才是最有趣的地方,所以让我们来看看。作为背景,我碰巧有两台相同品牌、型号和方向的显示器。
def switch_displays(self, event):
combined_screen_size = wx.DisplaySize()
for index in range(wx.Display.GetCount()):
display = wx.Display(index)
geo = display.GetGeometry()
print(geo)
x, y = self.frame.GetPosition()
screen_one = wx.Display(0)
_, _, screen_one_w, screen_one_h = screen_one.GetGeometry()
screen_two = wx.Display(1)
_, _, screen_two_w, screen_two_h = screen_two.GetGeometry()
if x > combined_screen_size[0] / 2:
# probably on second screen
self.frame.SetPosition((int(screen_one_w / 2),
int(screen_one_h / 2)))
else:
self.frame.SetPosition((int(screen_one_w + (screen_two_w / 2)),
int(screen_two_h / 2)))
这里我们得出两个显示器的总分辨率。然后为了演示的目的,我们循环显示并打印出它们的几何图形。您可以将这些行注释掉,因为它们除了有助于调试之外什么也不做。
然后,我们通过调用它的 GetPosition 方法来获取框架的当前位置。接下来,我们通过调用每个显示对象的 GetGeometry 方法来提取两个显示器的分辨率。接下来,我们检查框架的 X 坐标是否大于显示器的组合宽度除以 2。因为我知道我的两个显示器都是相同的分辨率和方向,我知道这将工作。无论如何,如果它更大,那么我们通过调用 SetPosition 来尝试将应用程序移动到对面的监视器。
包扎
您应该尝试一下这个代码,看看它是否能在您的多显示器设置上工作。如果没有,你可能需要调整一下算法,或者试着找出你的操作系统认为你的显示器在哪里,这样你就可以相应地修改代码。
附加阅读
- wx 上的 wxPython 文档页面。显示
wxPython:在 wx 中显示 2 种文件类型。文件对话框
原文:https://www.blog.pythonlibrary.org/2011/02/10/wxpython-showing-2-filetypes-in-wx-filedialog/
前几天在 freenode 上的 wxPython IRC 频道上,一个成员问是否有一种方法可以制作 wx。FileDialog 一次显示多个文件类型。在内心深处,我认为我已经看到了一个微软的产品可以做到这一点,但我从来没有见过任何 wxPython 的例子。在这个简短的教程中,你将学会如何做这个简单的技巧!
以下是您需要的代码:
import wx
wildcard = "Python source (*.py; *.pyc)|*.py;*.pyc|" \
"All files (*.*)|*.*"
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Multi-file type wx.FileDialog Tutorial")
panel = wx.Panel(self, wx.ID_ANY)
btn = wx.Button(panel, label="Open File Dialog")
btn.Bind(wx.EVT_BUTTON, self.onOpenFile)
#----------------------------------------------------------------------
def onOpenFile(self, event):
"""
Create and show the Open FileDialog
"""
dlg = wx.FileDialog(
self, message="Choose a file",
defaultFile="",
wildcard=wildcard,
style=wx.OPEN | wx.MULTIPLE | wx.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()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
这段代码中的关键字在通配符变量中。仔细看,你会注意到里面有一些分号。第一个字符串后半部分的分号才是我们关心的。它告诉对话框只显示。py 和。pyc 文件。是的,就是这么简单。前半部分可以是您想要的任何内容,但是建议您告诉您的用户他们可以期望它返回什么文件类型。
这就是全部了。当你创建自己的文件对话框时,一定要记住这个技巧。你可能只是需要它!
wxPython Sizers 教程:使用 GridBagSizer
原文:https://www.blog.pythonlibrary.org/2008/05/22/wxpython-sizers-tutorial-using-a-gridbagsizer/
在本教程中,我将从前几天编写的 GridSizer 教程中提取代码,并对其进行大量修改,以在 GridBagSizer 中显示形状奇怪的小部件。GridBagSizer 是最复杂的筛分机。它是 FlexGridSizer 的子类,因此您可以使用它的所有父方法以及 GridBagSizer 添加的方法。参见文档或使用 Python 的 help()功能了解更多关于所述方法的信息。
下面是如何实例化 GridBagSizer 的方法:
bagSizer = wx.GridBagSizer(hgap=5, vgap=5)
您会注意到,在创建 GridBagSizer 的实例时,不需要指定行或列。当您使用 Add()函数时,您会注意到它添加了 span 参数。此参数允许您指定小部件是否可以指定多行、多列或两者都指定。在我真正开始做之前,我将检查一下我们将要添加到行中的奇怪形状的部件。
我要添加的第一个是无线电盒。首先,您需要为单选按钮创建一个值列表。这是这个有趣的小部件的设置:
rb = wx.RadioBox(parent, id, label, pos, size, choices, majorDimension,
style, validator, name)
我认为这里只有两个参数需要额外的解释:选择和主要维度。“选择”参数是指您创建的值列表。majorDimension 参数用于表示一维的大小,可以是行或列,这取决于您使用的样式标志(wx。RA_SPECIFY_COLS 或 wx。RA_SPECIFY_ROWS)。它默认为列。如果你想要相反的效果,那么使用 wx。RA_SPECIFY_ROWS 标志。有关更多信息,请参见“wxPython in Action”一书,第 215 页。
我将使用的另一个小部件是多行 TextCtrl。要使 TextCtrl 变成多行,您需要做的就是添加样式标志 wx。TE_MULTILINE。一旦我们添加了这些内容,并且正确的标志就位,您应该会看到类似这样的内容:
好的。让我们看看代码:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, 'My Form')
# Add a panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD)
titleIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
title = wx.StaticText(self.panel, wx.ID_ANY, 'My Title')
title.SetFont(font)
# 1st row of widgets
bmp = wx.ArtProvider.GetBitmap(wx.ART_TIP, wx.ART_OTHER, (16, 16))
inputOneIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelOne = wx.StaticText(self.panel, wx.ID_ANY, 'Name')
inputTxtOne = wx.TextCtrl(self.panel, wx.ID_ANY,'')
sampleList = ['zero', 'one', 'two', 'three', 'four', 'five',
'six', 'seven', 'eight']
rb = wx.RadioBox(
self.panel, wx.ID_ANY, "wx.RadioBox", wx.DefaultPosition,
wx.DefaultSize, sampleList, 2, wx.RA_SPECIFY_COLS
)
# 2nd row of widgets
multiTxt = wx.TextCtrl(self.panel, wx.ID_ANY, '',
size=(200,100),
style=wx.TE_MULTILINE)
sampleList = ['one', 'two', 'three', 'four']
combo = wx.ComboBox(self.panel, wx.ID_ANY, 'Default', wx.DefaultPosition,
(100,-1), sampleList, wx.CB_DROPDOWN)
# Create the sizers
topSizer = wx.BoxSizer(wx.VERTICAL)
titleSizer = wx.BoxSizer(wx.HORIZONTAL)
bagSizer = wx.GridBagSizer(hgap=5, vgap=5)
# Add widgets to sizers
titleSizer.Add(titleIco, 0, wx.ALL, 5)
titleSizer.Add(title, 0, wx.ALL, 5)
bagSizer.Add(inputOneIco, pos=(0,0),
flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL,
border=5)
bagSizer.Add(labelOne, pos=(0,1),
flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL,
border=5)
bagSizer.Add(inputTxtOne, pos=(0,2),
flag=wx.EXPAND|wx.ALL,
border=10)
bagSizer.AddGrowableCol(2, 0)
bagSizer.Add(rb, pos=(0,3), span=(3,2))
bagSizer.Add(multiTxt, pos=(1,0),
flag=wx.ALL,
border=5)
bagSizer.Add(combo, pos=(1,1),
flag=wx.ALL,
border=5)
# Add sub-sizers to topSizer
topSizer.Add(titleSizer, 0, wx.CENTER)
topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(bagSizer, 0, wx.ALL|wx.EXPAND, 5)
self.panel.SetSizer(topSizer)
# SetSizeHints(minW, minH, maxW, maxH)
self.SetSizeHints(250,200,700,300)
topSizer.Fit(self)
# Run the program
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
您首先会注意到的一件事是,当我将图标和标签添加到 GridBagSizer 时,我确保传递了 wx。ALIGN_CENTER_VERTICAL 标志,使它们与第一个文本控件居中。您还会注意到“pos”参数。这告诉 GridBagSizer 将小部件放在哪里。所以 pos=(0,0)意味着将小部件放在第 0 行第 0 列。
其他需要注意的事项:
- 第一个文本控件的边框为 10,所以不会“太大”(太高)
- 要允许文本控件调整大小,需要使用 GridBagSizer 的 AddGrowableCol()方法使列“可增长”。
- 注意我给了哪些小部件 span 属性。记住,span(row,col)从它所应用的小部件所在的地方开始。因此,如果小部件在(0,0)处,您告诉它跨越(2,0),它将跨越 2 行,但不跨越 0 列。
在本文中,我没有使用 GridBagSizer 的所有方法。我鼓励读者查看 SetFlexibleDirection()和 SetNonFlexibleGrowMode()方法,因为它们有助于控制行或列的拉伸方向。
下载
代码
延伸阅读
维基上的 GridBagSizers
zet code
wxPython Sizers 教程:使用 GridSizer
原文:https://www.blog.pythonlibrary.org/2008/05/19/wxpython-sizers-tutorial-using-a-gridsizer/
在我上一篇文章中,我只用 wx 在 wxPython 中创建了一个通用表单。用于自动调整我的部件大小的 BoxSizers。这一次,我将使用一个 wx 来添加到我之前的示例中。GridSizer 显示以下内容:
- 如何右对齐图标和标签
- 如何将标签与文本控件垂直对齐
- 无论标签有多长,如何保持文本控件对齐?
首先,我要感谢来自 wxPython 用户组的 Malcolm,他向我提出了改进上一个例子的想法,我还需要感谢 Karsten 告诉我一个解决 Malcolm 右对齐困境的好方法。如你所知,这不是唯一的方法,可能有更好或更简单的方法。任何愚蠢的代码都是我的错。
现在,继续表演!首先要注意的是 wx。我用的 GridSizer。让我们来看看引擎盖下:
gridSizer = wx.GridSizer(rows=4, cols=2, hgap=5, vgap=5)
这告诉我们,我正在创建一个小部件,它将有 4 行 2 列,垂直和水平的空白间隔为 5 个像素。您以从右向左的方式向其中添加小部件,这样当您添加的小部件数量等于列数时,它会自动“换行”到下一行。换句话说,如果我添加两个小部件,这就构成了一整行,我添加的下一个小部件将自动转到第二行(以此类推)。
在我们给 wx 添加任何东西之前。不过,我们需要看看我是如何改变我的 wx.BoxSizers 的。比例为 1 的 BoxSizer。这意味着垫片将占据 sizer 中所有剩余的空间。我还为每个 wx 添加了图标和标签。BoxSizer,然后添加 wx。BoxSizer 作为 wx.GridSizer 中的第一项。
inputOneSizer.Add((20,20), proportion=1)
在这种情况下,间隔符只是一个元组。你实际上可以使用任何尺寸。元组的第一个元素是宽度,第二个元素是高度。如果我没有将比例设置为 1,那么我可以改变宽度的大小来放置图标和标签。如果您想使用默认高度,请使用-1。这告诉 wxPython 使用缺省值。
你会注意到我已经添加了 wx。当我把我的标签添加到 wx.BoxSizer 时,ALIGN_CENTER_VERTICAL 标志。这是为了强制标签垂直居中,因此看起来它也相对于文本控件居中。在我的一些真实的程序中,我只是把字体放大了一些,以达到一种非常相似的效果。
现在我们添加 wx。BoxSizer 到我的 wx。GridSizer:
gridSizer.Add(inputOneSizer, 0, wx.ALIGN_RIGHT)
这里我设置了 wx。ALIGN_RIGHT 使所有的项都“推”向文本控件旁边单元格的不可见墙。当我添加文本控件时,我确保我传递了 wx。扩展标志,使其在调整窗口大小时扩展。
最后,我加上 wx。GridSizer 到我的 topSizer,这是一个垂直 wx。盒子尺寸:
topSizer.Add(gridSizer, 0, wx.ALL|wx.EXPAND, 5)
在这一点上,你应该注意到我告诉它也要扩展。如果我不这样做,那么 topSizer 将阻止它所包含的 Sizer 膨胀。试着拿出那面旗子自己看看。
下面是完整的代码:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, title='My Form')
# Add a panel so it looks correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, (16, 16))
titleIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
title = wx.StaticText(self.panel, wx.ID_ANY, 'My Title')
lblSize = (50, -1)
bmp = wx.ArtProvider.GetBitmap(wx.ART_TIP, wx.ART_OTHER, (16, 16))
inputOneIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelOne = wx.StaticText(self.panel, wx.ID_ANY, 'Name')
inputTxtOne = wx.TextCtrl(self.panel, wx.ID_ANY,'')
inputTwoIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelTwo = wx.StaticText(self.panel, wx.ID_ANY, 'Address')
inputTxtTwo = wx.TextCtrl(self.panel, wx.ID_ANY,'')
inputThreeIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelThree = wx.StaticText(self.panel, wx.ID_ANY, 'Email')
inputTxtThree = wx.TextCtrl(self.panel, wx.ID_ANY, '')
inputFourIco = wx.StaticBitmap(self.panel, wx.ID_ANY, bmp)
labelFour = wx.StaticText(self.panel, wx.ID_ANY, 'Phone')
inputTxtFour = wx.TextCtrl(self.panel, wx.ID_ANY, '')
okBtn = wx.Button(self.panel, wx.ID_ANY, 'OK')
cancelBtn = wx.Button(self.panel, wx.ID_ANY, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.onOK, okBtn)
self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn)
topSizer = wx.BoxSizer(wx.VERTICAL)
titleSizer = wx.BoxSizer(wx.HORIZONTAL)
gridSizer = wx.GridSizer(rows=4, cols=2, hgap=5, vgap=5)
inputOneSizer = wx.BoxSizer(wx.HORIZONTAL)
inputTwoSizer = wx.BoxSizer(wx.HORIZONTAL)
inputThreeSizer = wx.BoxSizer(wx.HORIZONTAL)
inputFourSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
titleSizer.Add(titleIco, 0, wx.ALL, 5)
titleSizer.Add(title, 0, wx.ALL, 5)
# each input sizer will contain 3 items
# A spacer (proportion=1),
# A bitmap (proportion=0),
# and a label (proportion=0)
inputOneSizer.Add((20,-1), proportion=1) # this is a spacer
inputOneSizer.Add(inputOneIco, 0, wx.ALL, 5)
inputOneSizer.Add(labelOne, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
inputTwoSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
inputTwoSizer.Add(inputTwoIco, 0, wx.ALL, 5)
inputTwoSizer.Add(labelTwo, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
inputThreeSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
inputThreeSizer.Add(inputThreeIco, 0, wx.ALL, 5)
inputThreeSizer.Add(labelThree, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
inputFourSizer.Add((20,20), 1, wx.EXPAND) # this is a spacer
inputFourSizer.Add(inputFourIco, 0, wx.ALL, 5)
inputFourSizer.Add(labelFour, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
# Add the 3-item sizer to the gridsizer and
# Right align the labels and icons
gridSizer.Add(inputOneSizer, 0, wx.ALIGN_RIGHT)
# Set the TextCtrl to expand on resize
gridSizer.Add(inputTxtOne, 0, wx.EXPAND)
gridSizer.Add(inputTwoSizer, 0, wx.ALIGN_RIGHT)
gridSizer.Add(inputTxtTwo, 0, wx.EXPAND)
gridSizer.Add(inputThreeSizer, 0, wx.ALIGN_RIGHT)
gridSizer.Add(inputTxtThree, 0, wx.EXPAND)
gridSizer.Add(inputFourSizer, 0, wx.ALIGN_RIGHT)
gridSizer.Add(inputTxtFour, 0, wx.EXPAND)
btnSizer.Add(okBtn, 0, wx.ALL, 5)
btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
topSizer.Add(titleSizer, 0, wx.CENTER)
topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(gridSizer, 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 5)
topSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
# SetSizeHints(minW, minH, maxW, maxH)
self.SetSizeHints(250,300,500,400)
self.panel.SetSizer(topSizer)
topSizer.Fit(self)
def onOK(self, event):
# Do something
print 'onOK handler'
def onCancel(self, event):
self.closeProgram()
def closeProgram(self):
self.Close()
# Run the program
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
今天到此为止。尽情享受吧!
下载
wxPython Sizer 教程 2
wxPython:在组合框或列表框小部件中存储对象
本周早些时候,wxPython IRC 频道上有一个关于如何在 wx.ListBox 中存储对象的讨论。然后在那天晚些时候, StackOverflow 上有一个关于同样事情的问题,但与 wx.ComboBox 有关。幸运的是,这两个小部件都继承了 wx。ItemContainer 并包含 Append 方法,该方法允许您将对象与这些小部件中的项目相关联。在本文中,您将了解这是如何做到的。
向 wx 添加对象。列表框
我们先从列表框开始。让我们直接进入代码,因为我认为这样你会学得更快。
import wx
class Car:
""""""
def __init__(self, id, model, make, year):
"""Constructor"""
self.id = id
self.model = model
self.make = make
self.year = year
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)
cars = [Car(0, "Ford", "F-150", "2008"),
Car(1, "Chevrolet", "Camaro", "2010"),
Car(2, "Nissan", "370Z", "2005")]
sampleList = []
self.cb = wx.ComboBox(panel,
size=wx.DefaultSize,
choices=sampleList)
self.widgetMaker(self.cb, cars)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.cb, 0, wx.ALL, 5)
panel.SetSizer(sizer)
def widgetMaker(self, widget, objects):
""""""
for obj in objects:
widget.Append(obj.make, obj)
widget.Bind(wx.EVT_COMBOBOX, self.onSelect)
def onSelect(self, event):
""""""
print("You selected: " + self.cb.GetStringSelection())
obj = self.cb.GetClientData(self.cb.GetSelection())
text = """
The object's attributes are:
%s %s %s %s
""" % (obj.id, obj.make, obj.model, obj.year)
print(text)
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
现在,这是如何工作的?让我们花点时间来解开这个例子。首先,我们创建了一个非常简单的汽车类,其中定义了四个属性:id、型号、品牌和年份。然后我们创建一个带有面板和列表框小部件的简单框架。如您所见,我们使用 ListBox 继承的 Append 方法添加每个 Car 对象的“make”字符串,然后添加对象本身。这允许我们将列表框中的每一项与一个对象相关联。最后,我们将列表框绑定到 EVT 列表框,这样我们就可以知道当我们从小部件中选择一个项目时如何访问该对象。
要了解这是如何完成的,请查看 onSelect 方法。这里我们可以看到,我们需要调用 ListBox 的 GetClientData 方法,并将当前选择传递给它。这将返回我们之前关联的对象。现在我们可以访问该方法的每个属性。在本例中,我们只是将所有内容输出到 stdout。现在让我们看看如何使用 wx.ComboBox 来完成这个任务。
向 wx 添加对象。组合框
wx 的代码。ComboBox 实际上是相同的,所以为了好玩,我们将做一点重构。看一看:
import wx
class Car:
""""""
def __init__(self, id, model, make, year):
"""Constructor"""
self.id = id
self.model = model
self.make = make
self.year = year
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)
cars = [Car(0, "Ford", "F-150", "2008"),
Car(1, "Chevrolet", "Camaro", "2010"),
Car(2, "Nissan", "370Z", "2005")]
sampleList = []
self.cb = wx.ComboBox(panel,
size=wx.DefaultSize,
choices=sampleList)
self.widgetMaker(self.cb, cars)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.cb, 0, wx.ALL, 5)
panel.SetSizer(sizer)
def widgetMaker(self, widget, objects):
""""""
for obj in objects:
widget.Append(obj.make, obj)
widget.Bind(wx.EVT_COMBOBOX, self.onSelect)
def onSelect(self, event):
""""""
print("You selected: " + self.cb.GetStringSelection())
obj = self.cb.GetClientData(self.cb.GetSelection())
text = """
The object's attributes are:
%s %s %s %s
""" % (obj.id, obj.make, obj.model, obj.year)
print(text)
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
在本例中,步骤完全相同。但是我们有多个组合框,为什么要做这种事情呢?那会有很多多余的代码。因此,我们编写了一个名为 widgetMaker 的简单助手方法,它将为我们完成追加和事件绑定。我们可以让它构建小部件,将它添加到 sizer 和其他东西中,但在这个例子中我们将保持简单。无论如何,为了让它工作,我们传入 ComboBox 小部件以及我们想要添加到小部件的对象列表。widgetMaker 将为我们把这些对象追加到 ComboBox 中。代码的其余部分是相同的,除了我们需要绑定到的稍微不同的事件。
包扎
如您所见,这是一个非常简单的小练习,但是它使您的 GUI 更加健壮。对于数据库应用程序,您可能会这样做。我可以想象自己将它用于 SqlAlchemy 结果集。发挥创造力,我相信你也会发现它的用处。
附加阅读
wxPython:用 Esky 更新应用程序
原文:https://www.blog.pythonlibrary.org/2013/07/12/wxpython-updating-your-application-with-esky/
今天我们将学习 wxPython 的一个新特性:wx.lib.softwareupdate。它实际上是几年前添加的。这允许你做的是给你的软件增加更新能力。据我所知,这个 mixin 只允许提示更新,不允许静默更新。
入门指南
它内置于 wxPython 2.9 中,所以如果您想继续学习,您将需要它。软件更新功能实际上使用了 Esky 项目。如果你用的是 Windows,你还需要 py2exe 。如果你在 Mac 上,那么你将需要 py2app 。在这次演示中,我们将使用我以前的一篇文章中的代码。我创建了两个版本的图像查看器,所以你也想获得这些代码。请注意,我们将只展示如何在 Windows 上做到这一点!
一旦获得了本文中的代码,就应该将每个文件放在单独的目录中。我建议这样做:
TopFolder
--> imageViewer0.0.1
--> imageViewer0.1.0
然后,确保在第二个目录中将 image_viewer2.py 重命名为 image_viewer.py,以便脚本名称匹配。现在我们已经准备好检查代码了。
向初始版本添加更新代码
我们的初始版本将基于下面的代码。我已经添加了软件更新位,我们将在代码之后查看:
import os
import wx
from wx.lib.softwareupdate import SoftwareUpdate
########################################################################
class PhotoCtrl(wx.App, SoftwareUpdate):
"""
The Photo Viewer App Class
"""
#----------------------------------------------------------------------
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
BASEURL = "http://127.0.0.1:8000"
self.InitUpdates(BASEURL,
BASEURL + "/" + 'ChangeLog.txt')
self.SetAppDisplayName('Image Viewer')
self.CheckForUpdate()
self.frame = wx.Frame(None, title='Photo Control')
self.panel = wx.Panel(self.frame)
self.PhotoMaxSize = 500
self.createWidgets()
self.frame.Show()
#----------------------------------------------------------------------
def createWidgets(self):
instructions = 'Browse for an image'
img = wx.EmptyImage(240,240)
self.imageCtrl = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.BitmapFromImage(img))
instructLbl = wx.StaticText(self.panel, label=instructions)
self.photoTxt = wx.TextCtrl(self.panel, size=(200,-1))
browseBtn = wx.Button(self.panel, label='Browse')
browseBtn.Bind(wx.EVT_BUTTON, self.onBrowse)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY),
0, wx.ALL|wx.EXPAND, 5)
self.mainSizer.Add(instructLbl, 0, wx.ALL, 5)
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
self.sizer.Add(self.photoTxt, 0, wx.ALL, 5)
self.sizer.Add(browseBtn, 0, wx.ALL, 5)
self.mainSizer.Add(self.sizer, 0, wx.ALL, 5)
self.panel.SetSizer(self.mainSizer)
self.mainSizer.Fit(self.frame)
self.panel.Layout()
#----------------------------------------------------------------------
def onBrowse(self, event):
"""
Browse for file
"""
wildcard = "JPEG files (*.jpg)|*.jpg"
dialog = wx.FileDialog(None, "Choose a file",
wildcard=wildcard,
style=wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
self.photoTxt.SetValue(dialog.GetPath())
dialog.Destroy()
self.onView()
#----------------------------------------------------------------------
def onView(self):
"""
Attempts to load the image and display it
"""
filepath = self.photoTxt.GetValue()
img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.PhotoMaxSize
NewH = self.PhotoMaxSize * H / W
else:
NewH = self.PhotoMaxSize
NewW = self.PhotoMaxSize * W / H
img = img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
self.panel.Refresh()
self.mainSizer.Fit(self.frame)
#----------------------------------------------------------------------
if __name__ == '__main__':
app = PhotoCtrl()
app.MainLoop()
要让这段代码正常工作,只需要做几处修改。首先我们从 wx.lib.softwareupdate 中导入 SoftwareUpdate 。接下来,我们需要创建两个 wx 的子类。应用和软件更新。是的,Python 支持多重继承。然后在 init 构造函数中,我们需要用我们选择的 URL 加上与 ChangeLog.txt 连接的相同 URL 来调用 InitUpdates 。我们设置应用程序的显示名称,最后我们调用 CheckForUpdate 。就是这样!现在我们只需要把这个打包。
您需要创建一个 setup.py 脚本,其中包含以下内容,并将其放在与初始发布脚本相同的目录中:
#---------------------------------------------------------------------------
# This setup file serves as a model for how to structure your
# distutils setup files for making self-updating applications using
# Esky. When you run this script use
#
# python setup.py bdist_esky
#
# Esky will then use py2app or py2exe as appropriate to create the
# bundled application and also its own shell that will help manage
# doing the updates. See wx.lib.softwareupdate for the class you can
# use to add self-updates to your applications, and you can see how
# that code is used here in the superdoodle.py module.
#---------------------------------------------------------------------------
import sys, os
from esky import bdist_esky
from setuptools import setup
import version
# platform specific settings for Windows/py2exe
if sys.platform == "win32":
import py2exe
FREEZER = 'py2exe'
FREEZER_OPTIONS = dict(compressed = 0,
optimize = 0,
bundle_files = 3,
dll_excludes = ['MSVCP90.dll',
'mswsock.dll',
'powrprof.dll',
'USP10.dll',],
)
exeICON = 'mondrian.ico'
# platform specific settings for Mac/py2app
elif sys.platform == "darwin":
import py2app
FREEZER = 'py2app'
FREEZER_OPTIONS = dict(argv_emulation = False,
iconfile = 'mondrian.icns',
)
exeICON = None
# Common settings
NAME = "wxImageViewer"
APP = [bdist_esky.Executable("image_viewer.py",
gui_only=True,
icon=exeICON,
)]
DATA_FILES = [ 'mondrian.ico' ]
ESKY_OPTIONS = dict( freezer_module = FREEZER,
freezer_options = FREEZER_OPTIONS,
enable_appdata_dir = True,
bundle_msvcrt = True,
)
# Build the app and the esky bundle
setup( name = NAME,
scripts = APP,
version = version.VERSION,
data_files = DATA_FILES,
options = dict(bdist_esky=ESKY_OPTIONS),
)
您还需要一个包含以下内容的 version.py 文件:
VERSION='0.0.1'
现在您已经准备好实际创建可执行文件了。进入命令行并导航到存放这些文件的文件夹。我还在我的文件夹中放了几个图标文件,您可以在本文末尾的下载部分找到它们。您会希望 setup.py 脚本能够找到它们。好,现在我们需要创建分布。在命令 shell 中键入以下内容:
python setup.py bdist_esky
这假设您的路径中有 Python。如果你没有,你会想谷歌如何做到这一点。运行这个命令后,您会看到一大堆输出。如果一切顺利,您将得到两个新的子文件夹:build 和 dist。我们并不真正关心构建文件夹。dist 文件夹中应该有一个文件,命名如下:wxImageViewer-0 . 0 . 1 . win32 . zip
为了简单起见,您应该创建一个 downloads 文件夹来复制它。现在我们只需要对新版本做同样的事情。我们接下来会谈到这一点。
准备新版本
下面是新版本的代码:
# ----------------------------------------
# image_viewer2.py
#
# Created 03-20-2010
#
# Author: Mike Driscoll
# ----------------------------------------
import glob
import os
import wx
from wx.lib.pubsub import Publisher
from wx.lib.softwareupdate import SoftwareUpdate
########################################################################
class ViewerPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
width, height = wx.DisplaySize()
self.picPaths = []
self.currentPicture = 0
self.totalPictures = 0
self.photoMaxSize = height - 200
Publisher().subscribe(self.updateImages, ("update images"))
self.slideTimer = wx.Timer(None)
self.slideTimer.Bind(wx.EVT_TIMER, self.update)
self.layout()
#----------------------------------------------------------------------
def layout(self):
"""
Layout the widgets on the panel
"""
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
img = wx.EmptyImage(self.photoMaxSize,self.photoMaxSize)
self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY,
wx.BitmapFromImage(img))
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL|wx.CENTER, 5)
self.imageLabel = wx.StaticText(self, label="")
self.mainSizer.Add(self.imageLabel, 0, wx.ALL|wx.CENTER, 5)
btnData = [("Previous", btnSizer, self.onPrevious),
("Slide Show", btnSizer, self.onSlideShow),
("Next", btnSizer, self.onNext)]
for data in btnData:
label, sizer, handler = data
self.btnBuilder(label, sizer, handler)
self.mainSizer.Add(btnSizer, 0, wx.CENTER)
self.SetSizer(self.mainSizer)
#----------------------------------------------------------------------
def btnBuilder(self, label, sizer, handler):
"""
Builds a button, binds it to an event handler and adds it to a sizer
"""
btn = wx.Button(self, label=label)
btn.Bind(wx.EVT_BUTTON, handler)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
#----------------------------------------------------------------------
def loadImage(self, image):
""""""
image_name = os.path.basename(image)
img = wx.Image(image, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.photoMaxSize
NewH = self.photoMaxSize * H / W
else:
NewH = self.photoMaxSize
NewW = self.photoMaxSize * W / H
img = img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.BitmapFromImage(img))
self.imageLabel.SetLabel(image_name)
self.Refresh()
Publisher().sendMessage("resize", "")
#----------------------------------------------------------------------
def nextPicture(self):
"""
Loads the next picture in the directory
"""
if self.currentPicture == self.totalPictures-1:
self.currentPicture = 0
else:
self.currentPicture += 1
self.loadImage(self.picPaths[self.currentPicture])
#----------------------------------------------------------------------
def previousPicture(self):
"""
Displays the previous picture in the directory
"""
if self.currentPicture == 0:
self.currentPicture = self.totalPictures - 1
else:
self.currentPicture -= 1
self.loadImage(self.picPaths[self.currentPicture])
#----------------------------------------------------------------------
def update(self, event):
"""
Called when the slideTimer's timer event fires. Loads the next
picture from the folder by calling th nextPicture method
"""
self.nextPicture()
#----------------------------------------------------------------------
def updateImages(self, msg):
"""
Updates the picPaths list to contain the current folder's images
"""
self.picPaths = msg.data
self.totalPictures = len(self.picPaths)
self.loadImage(self.picPaths[0])
#----------------------------------------------------------------------
def onNext(self, event):
"""
Calls the nextPicture method
"""
self.nextPicture()
#----------------------------------------------------------------------
def onPrevious(self, event):
"""
Calls the previousPicture method
"""
self.previousPicture()
#----------------------------------------------------------------------
def onSlideShow(self, event):
"""
Starts and stops the slideshow
"""
btn = event.GetEventObject()
label = btn.GetLabel()
if label == "Slide Show":
self.slideTimer.Start(3000)
btn.SetLabel("Stop")
else:
self.slideTimer.Stop()
btn.SetLabel("Slide Show")
########################################################################
class ViewerFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Image Viewer")
panel = ViewerPanel(self)
self.folderPath = ""
Publisher().subscribe(self.resizeFrame, ("resize"))
self.initToolbar()
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.Show()
self.sizer.Fit(self)
self.Center()
#----------------------------------------------------------------------
def initToolbar(self):
"""
Initialize the toolbar
"""
self.toolbar = self.CreateToolBar()
self.toolbar.SetToolBitmapSize((16,16))
open_ico = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16,16))
openTool = self.toolbar.AddSimpleTool(wx.ID_ANY, open_ico, "Open", "Open an Image Directory")
self.Bind(wx.EVT_MENU, self.onOpenDirectory, openTool)
self.toolbar.Realize()
#----------------------------------------------------------------------
def onOpenDirectory(self, event):
"""
Opens a DirDialog to allow the user to open a folder with pictures
"""
dlg = wx.DirDialog(self, "Choose a directory",
style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
self.folderPath = dlg.GetPath()
print self.folderPath
picPaths = glob.glob(self.folderPath + "\\*.jpg")
print picPaths
Publisher().sendMessage("update images", picPaths)
#----------------------------------------------------------------------
def resizeFrame(self, msg):
""""""
self.sizer.Fit(self)
########################################################################
class ImageApp(wx.App, SoftwareUpdate):
""""""
#----------------------------------------------------------------------
def OnInit(self):
"""Constructor"""
BASEURL = "http://127.0.0.1:8000"
self.InitUpdates(BASEURL,
BASEURL + 'ChangeLog.txt')
self.CheckForUpdate()
frame = ViewerFrame()
self.SetTopWindow(frame)
self.SetAppDisplayName('Image Viewer')
return True
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = ViewerFrame()
app.MainLoop()
这里主要看的是最后一个类, ImageApp 。在这里,我们做了与上一个例子几乎完全相同的事情,除了这次我们使用了 wx。App 的 OnInit()方法,而不是 init。这两个版本之间没有太大的区别,但是我想你可能需要看两个版本,因为你肯定在野外生活得很好。
我们现在需要看一下这个版本的 setup.py,因为它有一点不同:
#---------------------------------------------------------------------------
# This setup file serves as a model for how to structure your
# distutils setup files for making self-updating applications using
# Esky. When you run this script use
#
# python setup.py bdist_esky
#
# Esky will then use py2app or py2exe as appropriate to create the
# bundled application and also its own shell that will help manage
# doing the updates. See wx.lib.softwareupdate for the class you can
# use to add self-updates to your applications, and you can see how
# that code is used here in the superdoodle.py module.
#---------------------------------------------------------------------------
import sys, os
from esky import bdist_esky
from setuptools import setup
import version
# platform specific settings for Windows/py2exe
if sys.platform == "win32":
import py2exe
includes = ["wx.lib.pubsub.*",
"wx.lib.pubsub.core.*",
"wx.lib.pubsub.core.kwargs.*"]
FREEZER = 'py2exe'
FREEZER_OPTIONS = dict(compressed = 0,
optimize = 0,
bundle_files = 3,
dll_excludes = ['MSVCP90.dll',
'mswsock.dll',
'powrprof.dll',
'USP10.dll',],
includes = includes
)
exeICON = 'mondrian.ico'
# platform specific settings for Mac/py2app
elif sys.platform == "darwin":
import py2app
FREEZER = 'py2app'
FREEZER_OPTIONS = dict(argv_emulation = False,
iconfile = 'mondrian.icns',
)
exeICON = None
# Common settings
NAME = "wxImageViewer"
APP = [bdist_esky.Executable("image_viewer.py",
gui_only=True,
icon=exeICON,
)]
DATA_FILES = [ 'mondrian.ico' ]
ESKY_OPTIONS = dict( freezer_module = FREEZER,
freezer_options = FREEZER_OPTIONS,
enable_appdata_dir = True,
bundle_msvcrt = True,
)
# Build the app and the esky bundle
setup( name = NAME,
scripts = APP,
version = version.VERSION,
data_files = DATA_FILES,
options = dict(bdist_esky=ESKY_OPTIONS)
)
第二个脚本使用 wxPython 的 pubsub。然而,py2exe 不会自己处理这个问题,所以您必须显式地告诉它来获取 pubsub 部分。您可以在脚本顶部附近的 includes 部分执行此操作。
不要忘记确保你的 version.py 文件比原始版本有更高的版本值,否则我们将无法更新。这是我放在我的里面的:
VERSION='0.1.0'
现在像以前一样使用相同的命令行,只是这次是在更新后的发布目录中使用:python setup.py bdist_esky
将 zip 文件复制到您的下载文件夹中。现在我们只需要在您计算机的本地主机上提供这些文件。为此,通过命令行导航到您的下载文件夹,并运行以下命令:
python -m SimpleHTTPServer
Python 现在将运行一个小型 HTTP 服务器来服务这些文件。如果你在你的网络浏览器中进入 http://127.0.0.1:8000 ,你会亲眼看到。现在我们已经准备好进行升级了!
更新您的程序
请确保您在计算机上的某个地方解压缩了第一个版本的图像查看器。然后运行名为 image_viewer.exe 的文件。如果一切按计划进行,您将看到以下内容:
继续应用更新,您将被要求重新启动应用程序:
它应该重新启动,你会得到新的图像浏览器界面。我注意到当我关闭应用程序时,我收到了一个错误,结果是一个弃用警告。您可以忽略它,或者如果您想做些什么,您可以导入警告模块并取消它。
包扎
此时,你已经准备好迎接重要时刻了。你也可以使用 AutoCheckForUpdate 来代替 CheckForUpdate,并传递检查之间的天数,这样你就不会每次打开应用程序都打电话回家。或者您可能希望将 CheckForUpdate 函数放入用户触发的事件处理程序中。很多应用程序都是这样做的,用户必须进入菜单系统,按下“检查更新”菜单项。发挥你的想象力,开始黑吧!还有另一个项目叫做 goodasnew ,似乎是 Esky 的竞争对手,你可能想看看。它现在还没有集成到 wxPython 中,但是它可能是一个可行的选择。
最后,如果您想查看这方面的另一个示例,请查看文档和演示包的 wxPython 2.9 版本。在那里你会找到一个样本文件夹,在里面你会看到一个涂鸦文件夹。这是另一个软件更新的例子。祝你好运!
附加阅读
下载
wxPython:使用 ObjectListView 而不是 ListCtrl
原文:https://www.blog.pythonlibrary.org/2009/12/23/wxpython-using-objectlistview-instead-of-a-listctrl/
wxPython ListCtrl 是一个非常方便的小部件。不幸的是,使用起来也很痛苦。这一发现促使 Phillip Piper 、莫桑比克传教士编写了 ObjectListView ,它是 wx.ListCtrl 的包装器。ObjectListView 实际上增加了功能,因为它使用对象来创建它的行,因此,它使得从多列获取信息更加容易。Piper 先生还添加了许多其他便利功能,使添加自定义编辑器变得更加容易,可以改变行的颜色,自动对行进行排序,等等。本文将帮助您学习使用 ObjectListView 的一些基础知识,以便您能够在将来的项目中使用它。这并不意味着对控件的详尽查看,因为它实际上已经很好地记录了。
应该注意的是,ObjectListView 不是标准列表控件的直接替代品。设置有很大的不同。另外,这是一个第三方库,不包含在 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)
#----------------------------------------------------------------------
def updateControl(self, event):
"""
"""
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"}
]
data = self.products + product_dict
self.dataOlv.SetObjects(data)
#----------------------------------------------------------------------
def setBooks(self, data=None):
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)
########################################################################
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()
如果您运行这段代码,您应该会看到类似这样的内容:
现在让我们来看看这一切都做了什么。首先,我用一些属性创建了一个通用的“Book”类:isbn、作者、制造商和标题。我们将使用该类在 ObjectListView 中创建行。接下来,我们创建一个标准面板,并在其上放置一个 ObjectListView 和一个 button 小部件。您还会注意到有一个简短的“书”对象列表。使用 LC_REPORT 样式标志将 ObjectListView 设置为报告模式。它也有其他模式,但我不会涵盖这些。报告模式看起来最像 Windows 资源管理器中的详细信息模式。
下一个片段有点奇怪:
self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
这段代码告诉我们的小部件允许通过双击来编辑行中的所有单元格(除了第一个)。我不知道为什么它被设计成这样,因为它看起来像所有你应该做的就是单击一次。甚至文档都说一次点击应该就够了。也许是 Windows 的限制。无论如何,要编辑任何行的第一个单元格,只需选择它并按 F2。
初始化方法的最后几行只是将小部件放入 sizers 中。下一段有趣的代码在 updateControl 方法中,在这里我们实际上更新了 ObjectListView 的内容。我在这里展示了两种不同的更新方式。第一种方法是只使用 Book 对象的产品列表,并使用传入的列表调用 ObjectListView 的 SetObjects 方法。第二种方法是使用字典。字典的键必须匹配 ColumnDefn 的 valueGetter 名称(我们将在 setBooks 方法中看到)。字典值可以是您想要的任何值。在我的例子中,我实际上组合了 Book 对象列表和字典列表,并对结果调用 SetObjects。
在 setBooks 方法中,我们定义了 ObjectListView 的列。这是通过将 ColumnDefn 对象的列表传递给 ObjectListView 的 SetColumns 方法来实现的。ColumnDefn 有许多参数,但是我们只讨论前四个。参数一是列的标题;参数二是列的整体对齐方式;参数三是列的宽度;第四个参数是 valueGetter 名称。这个名称必须匹配上面提到的 dictionary 方法中的键或者您使用的类的属性(在本例中是 my Book 类)。否则,一些数据将不会出现在微件中。
如果您想了解如何访问一些 row 对象的数据,那么向该应用程序添加另一个按钮,并将其绑定到以下函数:
def getRowInfo(self, event):
""""""
rowObj = self.dataOlv.GetSelectedObject()
print rowObj.author
print rowObj.title
现在您可以选择一行,并使用 ObjectListView 的 GetSelectedObject 方法来获取 row 对象。一旦你有了它,你就可以访问对象的属性,比如作者和标题以及你定义的任何东西。这比 ListCtrl 简单得多,在 list ctrl 中,您必须获取列和行来查找每个项目的信息。
这涵盖了使用 ObjectListCtrl 的基础知识。一定要下载源代码,因为它有很多有趣的演示,包括一个允许用户用自己绘制的组合框编辑一些单元格的演示!我将很快写另一篇使用 ObjectListView 的文章。它将向您展示如何保存和恢复数据,并创建一个有趣的小应用程序!
使用 Windows Vista、wxPython 2.8.10.1(unicode)和 Python 2.5 测试了该代码。
延伸阅读
下载量
wxPython:使用 PyDispatcher 而不是 Pubsub
原文:https://www.blog.pythonlibrary.org/2013/09/06/wxpython-using-pydispatcher-instead-of-pubsub/
前几天,我为 wxPython 2.9 写了一篇 wxPython pubsub 文章的更新版本,并意识到我从未尝试过 PyDispatcher 来看看它与 pubsub 有何不同。我仍然不确定它在内部有什么不同,但我认为将上一篇文章中的 pubsub 代码“移植”到 PyDispatcher 会很有趣。看看变化有多大!
入门指南
首先,您需要获取 PyDispatcher 并将其安装在您的系统上。如果安装了 pip,您可以执行以下操作:
pip install PyDispatcher
否则,转到项目的 sourceforge 页面并从那里下载。在 wxPython 中使用 pubsub 的好处之一是它已经包含在标准的 wxPython 发行版中。但是,如果您想在 wxPython 之外使用 pubsub,您必须下载它的独立代码库并安装它。我只是觉得我应该提一下。大多数开发者不喜欢在其他包之上下载额外的包。
无论如何,现在我们有了 PyDispatcher,让我们移植代码,看看我们最终会得到什么!
import wx
from pydispatch import dispatcher
########################################################################
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()
dispatcher.send("panelListener", message=msg)
dispatcher.send("panelListener", message="test2", arg2="2nd argument!")
self.Close()
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
dispatcher.connect(self.myListener, signal="panelListener",
sender=dispatcher.Any)
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()
这应该不会花太长时间来解释。所以我们从 pydispatch 导入dispatch。然后我们编辑另一个框架的 onSendAndClose 方法,这样它就会向我们的面板监听器发送消息。怎么会?通过执行以下操作:
dispatcher.send("panelListener", message=msg)
dispatcher.send("panelListener", message="test2", arg2="2nd argument!")
然后在 MyPanel 类中,我们像这样设置一个侦听器:
dispatcher.connect(self.myListener, signal="panelListener",
sender=dispatcher.Any)
这个代码告诉 pydispatcher 监听任何有panel ener信号的发送者。如果它有那个信号,那么它将调用面板的 myListener 方法。这就是我们从 pubsub 到 pydispatcher 的所有工作。那不是很容易吗?
wxPython: Windows 样式和事件猎人
原文:https://www.blog.pythonlibrary.org/2011/07/25/wxpython-windows-styles-and-events-hunter/
如果你是这个博客的长期读者,那么你会知道我真的很喜欢 wxPython GUI 工具包。让 wxPython 新用户感到困惑的是如何找出每个小部件的样式和事件。学习文档如何工作确实需要一些练习,但是 Andrea Gavana 有一个很好的小程序,他称之为 Windows 样式和事件猎人。它在几年前发布在的邮件列表上,但我不认为它很受欢迎。我用它来回答 wxPython 邮件列表或 IRC 频道上的问题,所以我知道它非常方便。
这是它实际运行的样子:
让我们来看看,试一试吧!
使用 Windows 样式和事件猎人
一旦下载了应用程序,运行它所需要的就是 wxPython。我想你已经有了,否则你为什么要读这个?该程序的唯一其他要求是一个开放的互联网连接,您可能必须允许它通过您的防火墙。为什么?这个应用程序被设计成按需从 wxPython 文档中下载信息。一旦它被下载,它就被缓存在硬盘上的一个 pickle 文件中,所以从技术上来说,你可以下载所有的信息,你不再需要互联网连接。
无论如何,要使用它,您需要运行应用程序,然后双击感兴趣的小部件。猎人将出去下载或加载信息到三个标签:风格,额外的风格和事件。只需点击每个选项卡,查看那里有什么。这就是全部了!这个程序真的很简单!几乎就像是苹果什么的设计出来的。
包扎
我将很快撰写另一篇关于另一个伟大的小工具的文章,您可以使用它来学习 wxPython 并更好地理解它。同时,看看这个。我在这里上传了 Windows 风格和事件猎人,所以你可以直接从这个博客下载。
[计] 下载
- EventsInStyle.zip
- 或者点击获取最新版本
wxPython:使用菜单、工具栏和加速器
原文:https://www.blog.pythonlibrary.org/2008/07/02/wxpython-working-with-menus-toolbars-and-accelerators/
除了那些只能从命令行运行的程序之外,菜单和工具栏几乎出现在所有现代程序中。在这篇文章中,我们将学习如何使用 wxPython 工具包来创建它们。以下是您需要遵循的内容:
创建菜单
首先,我们来看看菜单的组成部分。wxPython 中菜单系统的基础是 wx。菜单栏对象。顾名思义,它创建了一个栏来放置你的菜单。拼图的下一块是 wx。菜单对象。这些基本上都是你在菜单栏里看到的词,比如“文件”、“编辑”。wx 下的项目。菜单对象是使用您创建的菜单对象的 Append 方法创建的。请参见以下示例:
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "wx.Menu Tutorial")
# Add a panel so it looks the correct on all platforms
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.SetMenuBar(menuBar)
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
您的应用程序现在应该看起来像这样:
请注意创建菜单项的三个参数:
fileMenu.Append(wx.NewId(), "Exit", "Exit the application")
你需要一个 id,一个标签,和一个描述。如果您已经创建了一个描述,它将出现在状态栏中。
最近 wxPython 邮件列表上有很多人想要禁用菜单中的项目或整个菜单。为此,我们必须使用反直觉命名的方法,分别是 Enable()和 EnableTop()。Enable()方法是菜单项方法的一部分,而 EnableTop()必须由 MenuBar 对象调用。现在要小心,因为在禁用整个菜单之前,需要将菜单栏附加到框架上。
让我们来看看一些代码。根据我上面的来源,你可以这样做来禁用“文件”菜单中的“退出”项:
exitMenuItem.Enable(False)
如果您想禁用整个“文件”菜单,您可以这样做:
self.menuBar.EnableTop(0, False)
如您所见,用 wxPython 创建菜单非常简单直接。现在,让我们将一个事件处理程序绑定到“退出”菜单项,否则它不会做任何事情。将事件绑定到菜单项与将事件绑定到 wxPython 中的任何其他项目非常相似。我将向您展示最简单的方法以及演示的方式。
最快的方法是绑定到 EVT 菜单事件,就像这样:
self.Bind(wx.EVT_MENU, self.onExit, exitMenuItem)
然而,如果你有很多菜单项,这很快就会变得乏味。因此,我喜欢在 wxPython 演示中找到的方法,它创建了一个嵌套方法来为我做这件事。一般的方法是将下面的代码放在您的一个方法中:
def doBind(item, handler):
''' Create menu events. '''
self.Bind(wx.EVT_MENU, handler, item)
我通常在我从 init 构造函数调用的它们自己的函数中创建我的菜单。如果您这样做,那么您的代码应该看起来像下面的代码片段:
def createMenu(self):
""" Create the menu bar. """
def doBind(item, handler):
""" Create menu events. """
self.Bind(wx.EVT_MENU, handler, item)
doBind( fileMenu.Append(wx.ID_ANY, "&Exit\tAlt+F4", "ExitProgram"),
self.onExit)
最后,您可能希望用一条水平线来分隔菜单中的一些项目。要添加一个,请调用菜单的 AppendSeparator()方法:
fileMenu.AppendSeparator()
创建工具栏
使用 wxPython 的工具栏功能也非常简单。要初始化工具栏,你需要做的就是调用 wx。CreateToolBar()。我还使用 SetToolBitmapSize()方法设置了工具栏的图标大小。工具栏也可以有分隔符,但是使用工具栏的 AddSeparator()而不是 AppendSeparator()。
让我们来看一些代码,这样您可以更好地了解这一点。下面是我的 createToolbar 方法:
def createToolbar(self):
"""
Create a toolbar.
"""
self.toolbar = self.CreateToolBar()
self.toolbar.SetToolBitmapSize((16,16)) # sets icon size
# Use wx.ArtProvider for default icons
save_ico = wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, (16,16))
saveTool = self.toolbar.AddSimpleTool(wx.ID_ANY, save_ico, "Save", "Saves the Current Worksheet")
self.Bind(wx.EVT_MENU, self.onSave, saveTool)
self.toolbar.AddSeparator()
print_ico = wx.ArtProvider.GetBitmap(wx.ART_PRINT, wx.ART_TOOLBAR, (16,16))
printTool = self.toolbar.AddSimpleTool(wx.ID_ANY, print_ico, "Print", "Sends Timesheet to Default Printer")
self.Bind(wx.EVT_MENU, self.onPrint, printTool)
delete_ico = wx.ArtProvider.GetBitmap(wx.ART_DELETE, wx.ART_TOOLBAR, (16,16))
deleteTool = self.toolbar.AddSimpleTool(wx.ID_ANY, delete_ico, "Delete", "Delete contents of cell")
self.Bind(wx.EVT_MENU, self.onDelete, deleteTool)
undo_ico = wx.ArtProvider.GetBitmap(wx.ART_UNDO, wx.ART_TOOLBAR, (16,16))
self.undoTool = self.toolbar.AddSimpleTool(wx.ID_UNDO, undo_ico, "Undo", "")
self.toolbar.EnableTool(wx.ID_UNDO, False)
self.Bind(wx.EVT_TOOL, self.onUndo, self.undoTool)
redo_ico = wx.ArtProvider.GetBitmap(wx.ART_REDO, wx.ART_TOOLBAR, (16,16))
self.redoTool = self.toolbar.AddSimpleTool(wx.ID_REDO, redo_ico, "Redo", "")
self.toolbar.EnableTool(wx.ID_REDO, False)
self.Bind(wx.EVT_TOOL, self.onRedo, self.redoTool)
# This basically shows the toolbar
self.toolbar.Realize()
注意,我已经通过调用 EnableTool(wx)禁用了几个工具栏按钮。ID_UNDO,False)。正如您所看到的,这个方法有两个参数:工具栏按钮的 id 和一个 bool。为了更好地理解这一点,我附上了下面的截图:
创建加速器
大多数高级用户更喜欢使用键盘快捷键,而不是在错综复杂的菜单中挖掘。幸运的是,wxPython 提供了一种方法来做到这一点;它被称为加速表。这些表格通常与菜单系统相关联,但是您不需要菜单来使用表格。不过,我将对这两种方法进行讨论。
首先,我们将讨论如何使用带菜单的桌子。首先,我们需要创建一个 wx.AcceleratorTable 的实例。ACCEL CTRL,组合键,和一个 id。
对于这个例子,我想映射 CTRL+Q 来使应用程序退出。使用 wxPython 的方法是这样的:
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('Q'), exitMenuItem.GetId()) ])
self.frame.SetAcceleratorTable(accel_tbl)
现在让我们不使用菜单项。您会注意到,在创建表之前,我创建了一个新的 id 并绑定到一个事件处理程序。除此之外,真的差不了多少。
exitId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onExit, id=exitId )
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('Q'), exitId )])
self.SetAcceleratorTable(accel_tbl)
现在我们已经介绍了菜单、工具栏和加速器创建的基础知识。我希望这对你有所帮助。向 python library . org 的 mike 发送评论/问题。
更新 我已经忘记了这一点,但罗宾·邓恩提醒我,你还可以使用 wxUpdateUIEvent/EVT _ 更新 _UI 来更新菜单和工具栏,以及它们是启用还是禁用。
附加资源
注: 本帖所有照片均来自 Windows XP 盒子
wxPython:使用状态栏
原文:https://www.blog.pythonlibrary.org/2017/06/06/wxpython-working-with-status-bars/
大多数应用程序都有状态栏。状态栏是您每天使用的大多数应用程序底部的小部件。它们会告诉您正在文本编辑器中编辑哪一行,或者您上次保存的时间。在 wxPython 中,您可以使用 wx 在框架中添加一个状态栏。StatusBar 类。在本文中,我们将学习如何在 wxPython 中使用状态栏。
没有状态栏
从头开始总是好的。因此,我们将从一些示例代码开始我们的旅程,这些代码展示了没有状态栏的框架是什么样子的:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='No Statusbars')
panel = wx.Panel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
当您运行这段代码时,您应该会看到如下所示的内容:
这很简单。让我们来看看如何添加一个状态栏!
添加状态栏
当然,不添加任何东西非常简单。但是您很快就会发现,在 wxPython 中添加一个简单的单字段状态栏也非常容易。其实真的只是一行字的改动!然而,为了让它更有趣一点,我们也将设置状态栏的文本。让我们来看看:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='')
panel = wx.Panel(self)
self.statusbar = self.CreateStatusBar(1)
self.statusbar.SetStatusText('This goes in your statusbar')
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
我将在 Windows 7 上运行这段代码,因为我认为 Windows 有一个最容易看到的状态栏。当您运行它时,您应该会看到类似这样的内容:
您会注意到,当我们创建状态栏时,我们必须调用框架的 CreateStatusBar() 方法。我们传入的参数告诉状态栏,我们只需要状态栏中的一个字段。
创建多字段状态栏
许多应用程序可以在应用程序的状态栏中向用户显示多条信息。微软的 Word 就是一个很好的例子,因为它会在状态栏的不同部分列出页面信息、字数等。你也可以用 wxPython 展示这种东西。让我们看一个例子:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Statusbars')
panel = wx.Panel(self)
self.statusbar = self.CreateStatusBar(2)
self.statusbar.SetStatusText('This goes field one')
self.statusbar.SetStatusText('Field 2 here!', 1)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
在这个例子中,我们将数字 2 传递给 CreateStatusBar 以在状态栏中创建两个大小相等的部分。然后我们调用 SetStatusText() 。您会注意到,第一次调用没有指定将文本放在哪个部分。那是因为默认值是零。要将文本放在非零字段中,我们需要更加明确。因此,在第二次调用 SetStatusText() 时,我们向它传递一个 1,告诉 wxPython 将文本放在状态栏的第二部分。
更改截面宽度
您可以通过状态栏的 SetStatusWidths() 方法指定部分宽度,该方法接受 Python 列表。您可以在状态栏中设置固定宽度或可变宽度。对于 fixed,您只需传递一个 Python 整数列表,其中每个整数以像素为单位表示字段的大小。如果你更喜欢可变宽度,那么你将使用一个包含负数的列表。例如,如果您有[-2,-1],第一个字段将占用 66%的空间,而第二个字段将占用剩余的 33%。也可以混合固定和可变,像这样:[-2,-1,50]。在这种情况下,您告诉 wxPython 在第一个场中占据 66%的剩余空间,在第二个场中占据 33%的剩余空间,在第三个场中占据 50 个像素。
这个视觉上可能更容易看出来,我们来看一个例子!
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Statusbars')
panel = wx.Panel(self)
self.statusbar = self.CreateStatusBar(2)
self.statusbar.SetStatusWidths([100, 300])
self.statusbar.SetStatusText('This goes field one')
self.statusbar.SetStatusText('Field 2 here!', 1)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
在本例中,我们希望第一个字段的宽度为 100 像素,第二个字段的宽度为 300 像素。当您运行代码时,您应该会看到类似这样的内容:
请注意,我调整了窗口的大小,以演示当框架宽于 400 像素时,状态栏看起来有点奇怪。这是使用固定宽度时的常见问题。如果您使用小部件的绝对定位而不是 sizers,您将会遇到类似的问题。让我们看看是否可以通过混合使用固定宽度和可变宽度来解决这个问题。下面是对代码的更改:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Statusbars')
panel = wx.Panel(self)
self.statusbar = self.CreateStatusBar(2)
self.statusbar.SetStatusWidths([100, -1])
self.statusbar.SetStatusText('This goes field one')
self.statusbar.SetStatusText('Field 2 here!', 1)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
您会注意到,我们用-1 替换了 300 个像素,这意味着第二个字段应该占据前 100 个像素之后的所有空间。下面是截图:
这一次状态栏看起来不那么奇怪了。现在,您可以根据需要调整状态栏的宽度。
获取状态
还可以使用 status bar 的 GetStatusText() 方法来获取状态栏的状态。让我们来看看:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Statusbars')
panel = wx.Panel(self)
status_btn = wx.Button(panel, label='Get Status')
status_btn.Bind(wx.EVT_BUTTON, self.on_status)
self.statusbar = self.CreateStatusBar(2)
self.statusbar.SetStatusText('This goes in field one')
self.statusbar.SetStatusText('Field 2 here!', 1)
self.Show()
def on_status(self, event):
print self.statusbar.GetStatusText()
print self.statusbar.GetStatusText(1)
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
您会注意到,为了获取第二个字段的文本,我们必须通过向它传递一个值来显式地告诉 GetStatusText。
更改状态文本
我们已经看过用 SetStatusText() 改变状态栏的文本。不过,还有两个方法值得一看: PushStatusText() 和 PopStatusText() 。它们使用一个堆栈,因此当您调用 PushStatusText()时,它会将当前状态放入堆栈的内存中,并显示您传递给它的字符串。当您调用 PopStatusText()时,它将恢复以前存储的文本。但是,如果在 push 和 pop 之间调用 SetStatusText(),那么内存将被擦除,pop 将不会恢复状态字符串。
让我们看一个例子:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Statusbars')
panel = wx.Panel(self)
status_btn = wx.Button(panel, label='Set Temp Status')
status_btn.Bind(wx.EVT_BUTTON, self.set_temp_status)
restore_btn = wx.Button(panel, label='Restore Status')
restore_btn.Bind(wx.EVT_BUTTON, self.restore_status)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(status_btn, 0, wx.ALL, 5)
sizer.Add(restore_btn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.statusbar = self.CreateStatusBar(2)
self.statusbar.SetStatusText('This goes in field one')
self.statusbar.SetStatusText('Field 2 here!', 1)
self.Show()
def set_temp_status(self, event):
self.statusbar.PushStatusText('This is a temporary status')
def restore_status(self, event):
self.statusbar.PopStatusText()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
请注意,PushStatusText()和 PopStatusText()默认推送和弹出第一个字段。如果需要的话,您需要指定一个不同的字段来推送和弹出。试试这段代码,看看会发生什么!
包扎
这篇文章涵盖了很多材料。您学习了如何为您的框架创建 StatusBar 小部件。您学习了如何将状态栏分割成多个宽度不同的字段。您还学习了如何从中获取文本并修改文本。我还会注意到,当你创建一个 menubar 时,你可以用一个字符串来设置每个菜单,当你把鼠标放在菜单项上时,这个字符串就会出现在状态栏(如果你有的话)中。我将把它作为一个练习留给读者去尝试。
相关阅读
- 关于 StatusBar 类的 wxPython 文档
- wxPython: 关于菜单的一切
- wxPython: 使用菜单、工具栏和加速器
wxPython:用 WrapSizer 包装小部件
原文:https://www.blog.pythonlibrary.org/2014/01/22/wxpython-wrap-widgets-with-wrapsizer/
wxPython 2.9 向世界介绍了一种新型的 sizer,它可以在您调整框架大小时自动让小部件“环绕”。这种尺码仪被称为 wx。包装尺寸器。由于某种原因,它相对来说不太为人所知,所以在本文中,我们将花几分钟来讨论如何使用它。
要跟随本教程,您需要安装 wxPython 2.9 或更高版本。一旦你明白了,我们就可以继续了。
使用包装尺寸
wx。WrapSizer 小部件的工作方式与 wx 非常相似。BoxSizer 。要使用它,您需要做的就是实例化它并向它添加小部件。让我们来看一个简单的程序:
import random
import wx
from wx.lib.buttons import GenButton
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
text = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
sizer = wx.WrapSizer()
for letter in text:
btn = GenButton(self, label=letter)
r = random.randint(128, 255)
g = random.randint(128, 255)
b = random.randint(128, 255)
btn.SetBackgroundColour(wx.Colour(r,g,b))
btn.Refresh()
sizer.Add(btn, 0, wx.ALL, 5)
self.SetSizer(sizer)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="WrapSizers", size=(400,500))
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
这里我们创建一个 sizer 的实例,然后遍历字母表中的字母,为每个字母创建一个按钮。我们还改变了每个按钮的背景颜色,增加了一点变化。如果您还没有猜到,这个例子是基于 wxPython 演示例子的。您会注意到,当您调整框架大小时,按钮会尽可能地重新排列。有时,它们甚至会改变一点大小。让我们了解更多关于这个 sizer 的信息!
wx。WrapSizer 可以被告知它的方向,您可以在实例化时向它传递标志。方向标志是 wx。水平和 wx.VERTICAL。默认为水平。根据文档“flags 参数可以是值 EXTEND_LAST_ON_EACH_LINE 和 REMOVE_LEADING_SPACES 的组合,EXTEND _ LAST _ ON _ EACH _ LINE 将导致每一行的最后一项使用该行的任何剩余空间,REMOVE _ LEADING _ SPACES 将从行的开头删除任何间隔元素。”除了普通的 wx 之外,WrapSizer 还有四个附加方法。Sizer 方法: CalcMin (计算最小尺寸)、 InformFirstDirection (似乎不被使用)、 IsSpaceItem (可用于将一些普通项目作为间隔符处理)和recalcasesizes(实现计算一个 box sizer 的尺寸,然后设置其子元素的尺寸)。
这就包含了这个小部件的所有信息。希望您能在自己的项目中发现这个相对未知的 sizer 的许多好用途。
注意:这段代码是在 Windows 7 上使用 wxPython 2.9.3 (classic)和 Python 2.7.3 测试的。
wxPython: wx。ListCtrl 提示和技巧
原文:https://www.blog.pythonlibrary.org/2011/01/04/wxpython-wx-listctrl-tips-and-tricks/
去年,我们介绍了网格控制的一些技巧和窍门。在这篇文章中,我们将回顾 wx 的一些提示和技巧。处于“报告”模式时的 ListCtrl 小部件。看看下面的提示:
- 如何创建一个简单的 ListCtrl
- 如何对列表中的行进行排序
- 如何使 ListCtrl 单元格可就地编辑
- 将对象与 clistctrl 行相关联
- 替换 ListCtrl 的行颜色
如何创建一个简单的 ListCtrl
简单的 clistctrl
列表控件是一个非常常见的小部件。在 Windows 中,您将在 Windows 资源管理器中看到列表控件。它有四种模式:图标、小图标、列表和报告。它们分别与 Windows 资源管理器中的图标、平铺、列表和详细信息视图大致匹配。我们将把重点放在报表模式下的 ListCtrl 上,因为这是大多数开发人员使用它的模式。下面是一个如何创建列表控件的简单示例:
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.index = 0
self.list_ctrl = wx.ListCtrl(panel, size=(-1,100),
style=wx.LC_REPORT
|wx.BORDER_SUNKEN
)
self.list_ctrl.InsertColumn(0, 'Subject')
self.list_ctrl.InsertColumn(1, 'Due')
self.list_ctrl.InsertColumn(2, 'Location', width=125)
btn = wx.Button(panel, label="Add Line")
btn.Bind(wx.EVT_BUTTON, self.add_line)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def add_line(self, event):
line = "Line %s" % self.index
self.list_ctrl.InsertStringItem(self.index, line)
self.list_ctrl.SetStringItem(self.index, 1, "01/19/2010")
self.list_ctrl.SetStringItem(self.index, 2, "USA")
self.index += 1
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
从上面的代码可以看出,创建 ListCtrl 实例非常容易。注意,我们使用 wx 将样式设置为报告模式。LC_REPORT 标志。为了添加列标题,我们调用 ListCtrl 的 InsertColumn 方法,并传递一个整数来告诉 ListCtrl 哪一列是哪一列,并传递一个字符串以方便用户。是的,这些列是从零开始的,所以第一列是数字 0,第二列是数字 1,依此类推。
下一个重要部分包含在按钮的事件处理程序中, add_line ,在这里我们学习如何向 ListCtrl 添加数据行。使用的典型方法是 InsertStringItem 方法。如果您还想在每一行中添加一个图像,那么您可以使用一个更复杂的方法,比如 InsertColumnInfo 和 InsertImageStringItem 方法。您可以在 wxPython 演示中看到如何使用它们。在这篇文章中,我们坚持使用简单的东西。
无论如何,当你调用 InsertStringItem 时,你给它正确的行索引和一个字符串。您使用 SetStringItem 方法为该行的其他列设置数据。请注意,SetStringItem 方法需要三个参数:行索引、列索引和一个字符串。最后,我们增加行索引,这样我们就不会覆盖任何内容。现在你可以走出去,做你自己的!让我们继续,看看如何对行进行排序!
如何对列表中的行进行排序
可排序的 clistctrl
ListCtrl 小部件编写了一些额外的脚本,为小部件添加功能。这些脚本被称为 mixins。你可以在这里阅读它们。对于这个食谱,我们将使用列排序器混合信息混合信息。下面的代码是一个 wxPython 演示示例的精简版本。
import wx
import wx.lib.mixins.listctrl as listmix
musicdata = {
0 : ("Bad English", "The Price Of Love", "Rock"),
1 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"),
2 : ("George Michael", "Praying For Time", "Rock"),
3 : ("Gloria Estefan", "Here We Are", "Rock"),
4 : ("Linda Ronstadt", "Don't Know Much", "Rock"),
5 : ("Michael Bolton", "How Am I Supposed To Live Without You", "Blues"),
6 : ("Paul Young", "Oh Girl", "Rock"),
}
########################################################################
class TestListCtrl(wx.ListCtrl):
#----------------------------------------------------------------------
def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
########################################################################
class TestListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin):
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
self.list_ctrl = TestListCtrl(self, size=(-1,100),
style=wx.LC_REPORT
|wx.BORDER_SUNKEN
|wx.LC_SORT_ASCENDING
)
self.list_ctrl.InsertColumn(0, "Artist")
self.list_ctrl.InsertColumn(1, "Title", wx.LIST_FORMAT_RIGHT)
self.list_ctrl.InsertColumn(2, "Genre")
items = musicdata.items()
index = 0
for key, data in items:
self.list_ctrl.InsertStringItem(index, data[0])
self.list_ctrl.SetStringItem(index, 1, data[1])
self.list_ctrl.SetStringItem(index, 2, data[2])
self.list_ctrl.SetItemData(index, key)
index += 1
# Now that the list exists we can init the other base class,
# see wx/lib/mixins/listctrl.py
self.itemDataMap = musicdata
listmix.ColumnSorterMixin.__init__(self, 3)
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list_ctrl)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
# Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
def GetListCtrl(self):
return self.list_ctrl
#----------------------------------------------------------------------
def OnColClick(self, event):
print "column clicked"
event.Skip()
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
# Add a panel so it looks the correct on all platforms
panel = TestListCtrlPanel(self)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
这段代码有点奇怪,因为我们继承了 wx 中的 mixin。基于面板的类,而不是 wx。ListCtrl 类。只要你正确地重新排列代码,你可以用任何一种方式来做。无论如何,我们将聚焦于这个例子和前一个例子之间的主要区别。最重要的第一个区别是在我们插入列表控件数据的循环结构中。这里我们包含了列表控件的 SetItemData 方法,以包含允许排序发生的必要内部工作。正如您可能已经猜到的,这个方法将行索引与音乐数据字典的键相关联。
接下来我们实例化 ColumnSorterMixin 并告诉它列表控件中有多少列。我们可以让EVT _ 列表 _ 列 _ 点击绑定这个例子,因为它与行的实际排序无关,但是为了增加你的知识,它被留了下来。它所做的只是向您展示如何捕捉用户的列点击事件。代码的其余部分不言自明。如果您想了解这个 mixin 的要求,特别是当您的行中有图像时,请查看源代码中的相关部分(即 listctrl.py)。这不是很容易吗?让我们继续我们的旅程,并找出如何使单元格可编辑!
如何使 ListCtrl 单元格可就地编辑
可编辑的 clistctrl
有时,程序员会希望允许用户点击单元格并在适当的位置编辑它。这是一个轻量级版本的 wx.grid.Grid 控件。这里有一个例子:
import wx
import wx.lib.mixins.listctrl as listmix
########################################################################
class EditableListCtrl(wx.ListCtrl, listmix.TextEditMixin):
''' TextEditMixin allows any column to be edited. '''
#----------------------------------------------------------------------
def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0):
"""Constructor"""
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.TextEditMixin.__init__(self)
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
rows = [("Ford", "Taurus", "1996", "Blue"),
("Nissan", "370Z", "2010", "Green"),
("Porche", "911", "2009", "Red")
]
self.list_ctrl = EditableListCtrl(self, style=wx.LC_REPORT)
self.list_ctrl.InsertColumn(0, "Make")
self.list_ctrl.InsertColumn(1, "Model")
self.list_ctrl.InsertColumn(2, "Year")
self.list_ctrl.InsertColumn(3, "Color")
index = 0
for row in rows:
self.list_ctrl.InsertStringItem(index, row[0])
self.list_ctrl.SetStringItem(index, 1, row[1])
self.list_ctrl.SetStringItem(index, 2, row[2])
self.list_ctrl.SetStringItem(index, 3, row[3])
index += 1
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY, "Editable List Control")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
在这个脚本中,我们将 TextEditMixin 放在 wx 中。ListCtrl 类而不是我们的 wx。面板,这与前面的示例相反。mixin 本身承担了所有繁重的工作。同样,你必须检查 mixin 的源代码才能真正理解它是如何工作的。
将对象与 ListCtrl 行相关联
这个主题出现了很多次:如何将数据(即对象)与 ListCtrl 的行关联起来?好吧,我们将通过下面的代码找出具体的方法:
import wx
########################################################################
class Car(object):
""""""
#----------------------------------------------------------------------
def __init__(self, make, model, year, color="Blue"):
"""Constructor"""
self.make = make
self.model = model
self.year = year
self.color = color
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
rows = [Car("Ford", "Taurus", "1996"),
Car("Nissan", "370Z", "2010"),
Car("Porche", "911", "2009", "Red")
]
self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
style=wx.LC_REPORT
|wx.BORDER_SUNKEN
)
self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
self.list_ctrl.InsertColumn(0, "Make")
self.list_ctrl.InsertColumn(1, "Model")
self.list_ctrl.InsertColumn(2, "Year")
self.list_ctrl.InsertColumn(3, "Color")
index = 0
self.myRowDict = {}
for row in rows:
self.list_ctrl.InsertStringItem(index, row.make)
self.list_ctrl.SetStringItem(index, 1, row.model)
self.list_ctrl.SetStringItem(index, 2, row.year)
self.list_ctrl.SetStringItem(index, 3, row.color)
self.myRowDict[index] = row
index += 1
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def onItemSelected(self, event):
""""""
currentItem = event.m_itemIndex
car = self.myRowDict[currentItem]
print car.make
print car.model
print car.color
print car.year
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
列表控件小部件实际上没有内置的方法来完成这一壮举。如果您想这样做,那么您会想要检查一下 ObjectListView 小部件,它包装了 ListCtrl 并赋予它更多的功能。与此同时,我们将花一分钟时间检查上面的代码。第一件只是一辆普通的车类,有四个属性。然后在 MyPanel 类中,我们创建一个汽车对象列表,我们将使用它作为 ListCtrl 的数据。
为了将数据添加到 ListCtrl 中,我们使用一个 for 循环来遍历列表。我们还使用 Python 字典将每一行与一个汽车对象相关联。我们使用行的索引作为键,字典的值最终成为 Car 对象。这允许我们稍后在 onItemSelected 方法中访问所有 Car/row 对象的数据。我们去看看!
在 onItemSelected 中,我们用下面的小技巧获取行的索引: event.m_itemIndex 。然后,我们使用该值作为字典的键,这样我们就可以访问与该行相关联的 Car 对象。此时,我们只是打印出汽车对象的所有属性,但是您可以在这里做任何您想做的事情。这个基本思想可以很容易地扩展到使用来自 ListCtrl 数据的 SqlAlchemy 查询的结果集。希望你能大致了解。
现在,如果你像 Robin Dunn(wxPython 的创建者)一样密切关注,那么你可能会注意到这段代码中一些非常愚蠢的逻辑错误。你找到他们了吗?除非你对行进行排序,删除或插入一行,否则你看不到它。你现在明白了吗?是的,我愚蠢地将字典中的“unique”键基于行的位置,如果发生任何事件,行的位置就会改变。让我们来看一个更好的例子:
import wx
########################################################################
class Car(object):
""""""
#----------------------------------------------------------------------
def __init__(self, make, model, year, color="Blue"):
"""Constructor"""
self.id = id(self)
self.make = make
self.model = model
self.year = year
self.color = color
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
rows = [Car("Ford", "Taurus", "1996"),
Car("Nissan", "370Z", "2010"),
Car("Porche", "911", "2009", "Red")
]
self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
style=wx.LC_REPORT
|wx.BORDER_SUNKEN
)
self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
self.list_ctrl.InsertColumn(0, "Make")
self.list_ctrl.InsertColumn(1, "Model")
self.list_ctrl.InsertColumn(2, "Year")
self.list_ctrl.InsertColumn(3, "Color")
index = 0
self.myRowDict = {}
for row in rows:
self.list_ctrl.InsertStringItem(index, row.make)
self.list_ctrl.SetStringItem(index, 1, row.model)
self.list_ctrl.SetStringItem(index, 2, row.year)
self.list_ctrl.SetStringItem(index, 3, row.color)
self.list_ctrl.SetItemData(index, row.id)
self.myRowDict[row.id] = row
index += 1
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
#----------------------------------------------------------------------
def onItemSelected(self, event):
""""""
currentItem = event.m_itemIndex
car = self.myRowDict[self.list_ctrl.GetItemData(currentItem)]
print car.make
print car.model
print car.color
print car.year
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
在这个例子中,我们向 Car 类添加了一个新的属性,为每个实例创建一个惟一的 id,这个实例是使用 Python 的便利的 id 内建的。然后在我们向列表控件添加数据的循环中,我们调用小部件的 SetItemData 方法,并给它行索引和汽车实例的唯一 id。现在,行在哪里结束并不重要,因为它已经附加了惟一的 id。最后,我们必须修改 onItemSelected 来获得正确的对象。神奇的事情发生在这段代码中:
# this code was helpfully provided by Robin Dunn
car = self.myRowDict[self.list_ctrl.GetItemData(currentItem)]
很酷吧。我们的最后一个例子将涵盖如何交替行颜色,所以让我们看看!
替换 ListCtrl 的行颜色
具有交替行颜色的 ListCtrl
正如这一节的标题所暗示的,我们将看看如何改变 ListCtrl 的行的颜色。代码如下:
import wx
import wx.lib.mixins.listctrl as listmix
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
rows = [("Ford", "Taurus", "1996", "Blue"),
("Nissan", "370Z", "2010", "Green"),
("Porche", "911", "2009", "Red")
]
self.list_ctrl = wx.ListCtrl(self, style=wx.LC_REPORT)
self.list_ctrl.InsertColumn(0, "Make")
self.list_ctrl.InsertColumn(1, "Model")
self.list_ctrl.InsertColumn(2, "Year")
self.list_ctrl.InsertColumn(3, "Color")
index = 0
for row in rows:
self.list_ctrl.InsertStringItem(index, row[0])
self.list_ctrl.SetStringItem(index, 1, row[1])
self.list_ctrl.SetStringItem(index, 2, row[2])
self.list_ctrl.SetStringItem(index, 3, row[3])
if index % 2:
self.list_ctrl.SetItemBackgroundColour(index, "white")
else:
self.list_ctrl.SetItemBackgroundColour(index, "yellow")
index += 1
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, wx.ID_ANY,
"List Control w/ Alternate Colors")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
上面的代码将改变每一行的背景颜色。因此,您应该会看到黄色和白色的行。我们通过调用 ListCtrl 实例的 SetItemBackgroundColour 方法来实现这一点。如果你正在使用一个虚拟列表控件,那么你会想要覆盖 OnGetItemAttr 方法。要查看后一种方法的示例,请打开您的 wxPython 演示副本;里面有一个。
包扎
我们已经讨论了很多问题。你现在应该可以用你的 wx 做更多的事情了。当然,假设你是使用它的新手。欢迎在评论中提问或建议未来的食谱。我希望这对你有所帮助!
注意:所有例子都是在 Windows XP 上用 Python 2.5 和 wxPython 2.8.10.1 测试的。他们还在使用 Python 2.6 的 Windows 7 Professional 上进行了测试
附加阅读
- 官方 wxPython wx。列表控件文档
- 列表控件维基页面
- ListCtrl 工具提示维基页面
- 对象列表视图网站
- UltimateListCtrl ,一个纯 Python 实现,现在包含在 wxPython 中
源代码
wxPython 的上下文管理器
原文:https://www.blog.pythonlibrary.org/2015/10/22/wxpythons-context-managers/
几年前,wxPython 工具包在其代码库中添加了上下文管理器,但是由于某种原因,您并没有看到很多使用它们的例子。在本文中,我们将看看 wxPython 中上下文管理器的三个例子。一个 wxPython 用户是第一个建议在 wxPython 的邮件列表中使用上下文管理器的人。我们将从使用我们自己的上下文管理器开始,然后看几个 wxPython 中内置上下文管理器的例子。
创建自己的 wxPython 上下文管理器
在 wxPython 中创建自己的上下文管理器非常容易。我们将使用 wx。FileDialog 是我们的上下文管理器的例子。
import os
import wx
########################################################################
class ContextFileDialog(wx.FileDialog):
""""""
#----------------------------------------------------------------------
def __enter__(self):
""""""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.Destroy()
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Open File')
btn.Bind(wx.EVT_BUTTON, self.onOpenFile)
#----------------------------------------------------------------------
def onOpenFile(self, event):
""""""
wildcard = "Python source (*.py)|*.py|" \
"All files (*.*)|*.*"
kwargs = {'message':"Choose a file",
'defaultDir':os.path.dirname(os.path.abspath( __file__ )),
'defaultFile':"",
'wildcard':wildcard,
'style':wx.FD_OPEN | wx.FD_MULTIPLE | wx.FD_CHANGE_DIR
}
with ContextFileDialog(self, **kwargs) as dlg:
if dlg.ShowModal() == wx.ID_OK:
paths = dlg.GetPaths()
print "You chose the following file(s):"
for path in paths:
print path
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title='wxPython Contexts')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
在这个例子中,我们子类化了 wx。FileDialog 和我们所做的就是覆盖 enter 和 exit 方法。当我们使用 python和语句调用 FileDialog 实例时,它将变成一个上下文管理器。您可以在 MyPanel 类中的 onOpenFile 事件处理程序中看到这一点。现在让我们继续看一些 wxPython 的内置例子!
wxPython 的上下文管理器
wxPython 包支持任何子类化 wx 的上下文管理器。对话框以及以下小部件:
- wx.BusyInfo
- wx。忙碌光标
- wx。windows 禁用程序
- wx.LogNull
- wx。DCTextColourChanger
- wx。DCPenChanger
- wx(地名)。DCBrushChanger
- wx。DCClipper
- wx。定格/ wx。融雪
可能还有更多小部件,但这是我在撰写本文时唯一能找到的清单。让我们看几个例子:
import time
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.frame = parent
main_sizer = wx.BoxSizer(wx.VERTICAL)
dlg_btn = wx.Button(self, label='Open ColorDialog')
dlg_btn.Bind(wx.EVT_BUTTON, self.onOpenColorDialog)
main_sizer.Add(dlg_btn, 0, wx.ALL|wx.CENTER)
busy_btn = wx.Button(self, label='Open BusyInfo')
busy_btn.Bind(wx.EVT_BUTTON, self.onOpenBusyInfo)
main_sizer.Add(busy_btn,0, wx.ALL|wx.CENTER)
self.SetSizer(main_sizer)
#----------------------------------------------------------------------
def onOpenColorDialog(self, event):
"""
Creates and opens the wx.ColourDialog
"""
with wx.ColourDialog(self) as dlg:
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetColourData()
color = str(data.GetColour().Get())
print 'You selected: %s\n' % color
#----------------------------------------------------------------------
def onOpenBusyInfo(self, event):
"""
Creates and opens an instance of BusyInfo
"""
msg = 'This app is busy right now!'
self.frame.Hide()
with wx.BusyInfo(msg) as busy:
time.sleep(5)
self.frame.Show()
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title='Context Managers')
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == '__main__':
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
在上面的代码中,我们有两个 wxPython 的上下文管理器的例子。第一个是在 onOpenColorDialog 事件处理程序中。这里我们创建一个 wx 的实例。ColourDialog 然后如果用户按下 OK 按钮,抓取所选颜色。第二个例子稍微复杂一点,因为它在显示 BusyInfo 实例之前隐藏了框架。坦率地说,我认为这个例子可以通过将框架的隐藏和显示放入上下文管理器本身来改进,但是我将把它作为一个练习留给读者去尝试。
包扎
wxPython 的上下文管理器非常方便,使用起来也很有趣。我希望你很快会在自己的代码中使用它们。请务必尝试 wxPython 中的其他一些上下文管理器,看看它们是否适合您的代码库,或者只是让您的代码更整洁一些。
wxPython:将图像拖放到您的应用程序中
原文:https://www.blog.pythonlibrary.org/2017/10/25/wx_drag_and_drop_images/
我最近在 StackOverflow 上遇到一个问题,用户想知道如何在 wxPython 中将图像拖动到他们的图像控件上,并将拖动的图像调整为缩略图。这激起了我的兴趣,我决定弄清楚如何去做。
我知道你可以用 Python 中的 Pillow 包创建一个缩略图。因此,如果您想继续学习,您需要安装 Pillow 和 wxPython with pip:
pip install Pillow wxPython
现在我们有了我们需要的包的最新版本,我们可以写一些代码了。我们来看看:
注意:您可能希望使用 wxPython 4 来确保它正常工作
import os
import wx
import wx.lib.statbmp as SB
from PIL import Image
from wx.lib.pubsub import pub
PhotoMaxSize = 240
class DropTarget(wx.FileDropTarget):
def __init__(self, widget):
wx.FileDropTarget.__init__(self)
self.widget = widget
def OnDropFiles(self, x, y, filenames):
image = Image.open(filenames[0])
image.thumbnail((PhotoMaxSize, PhotoMaxSize))
image.save('thumbnail.png')
pub.sendMessage('dnd', filepath='thumbnail.png')
return True
class PhotoCtrl(wx.App):
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
self.frame = wx.Frame(None, title='Photo Control')
self.panel = wx.Panel(self.frame)
pub.subscribe(self.update_image_on_dnd, 'dnd')
self.createWidgets()
self.frame.Show()
def createWidgets(self):
instructions = 'Browse for an image or Drag and Drop'
img = wx.Image(240,240)
self.imageCtrl = SB.GenStaticBitmap(self.panel, wx.ID_ANY,
wx.Bitmap(img))
filedroptarget = DropTarget(self)
self.imageCtrl.SetDropTarget(filedroptarget)
instructLbl = wx.StaticText(self.panel, label=instructions)
self.photoTxt = wx.TextCtrl(self.panel, size=(200,-1))
browseBtn = wx.Button(self.panel, label='Browse')
browseBtn.Bind(wx.EVT_BUTTON, self.on_browse)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY),
0, wx.ALL|wx.EXPAND, 5)
self.mainSizer.Add(instructLbl, 0, wx.ALL, 5)
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
self.sizer.Add(self.photoTxt, 0, wx.ALL, 5)
self.sizer.Add(browseBtn, 0, wx.ALL, 5)
self.mainSizer.Add(self.sizer, 0, wx.ALL, 5)
self.panel.SetSizer(self.mainSizer)
self.mainSizer.Fit(self.frame)
self.panel.Layout()
def on_browse(self, event):
"""
Browse for file
"""
wildcard = "JPEG files (*.jpg)|*.jpg"
dialog = wx.FileDialog(None, "Choose a file",
wildcard=wildcard,
style=wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
self.photoTxt.SetValue(dialog.GetPath())
dialog.Destroy()
self.on_view()
def update_image_on_dnd(self, filepath):
self.on_view(filepath=filepath)
def on_view(self, filepath=None):
if not filepath:
filepath = self.photoTxt.GetValue()
img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = PhotoMaxSize
NewH = PhotoMaxSize * H / W
else:
NewH = PhotoMaxSize
NewW = PhotoMaxSize * W / H
img = img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.Bitmap(img))
self.panel.Refresh()
if __name__ == '__main__':
app = PhotoCtrl()
app.MainLoop()
当您运行这段代码时,您应该会看到类似这样的内容:
第一个类子类的 wx。FileDropTarget 和缩略图创建魔术。你会注意到我们覆盖了 OnDropFiles() 方法,并使用 PhotoMaxSize 变量创建了一个缩略图。然后我们使用 pubsub 告诉我们的 wxPython 应用程序进行自我更新。另一个类实际上是 wx 的子类。App 并且包含了我们显示图像所需的所有零碎东西。坦白地说,我们不需要继承 wx.App。我们可以很容易地从 wx 继承。这个例子的框架。
无论如何,我们在这段代码中所做的只是创建一个 frame 和一个 panel 对象,用 pubsub 订阅一个特定的订阅,以便它侦听来自 DropTarget 类的消息,然后更新显示。您会注意到,当应用程序对象通过 pubsub 接收消息时,它将调用 update_image_on_dnd() 方法,该方法又将调用 onView() 方法。这段代码基本上只是按比例缩小传递给它的图像,使它适合我们的 wx。静态位图控件。当然,由于我们已经传入了一个应该合适的图像,我们可以给这个方法添加一些逻辑,在某些情况下跳过缩放。但是我把它放在那里是为了当用户使用浏览按钮打开图像时使用。
无论如何,一旦我们按照我们想要的方式缩放了图像,我们就调用 SetBitmap 并刷新面板。试一试,看看你能做些什么来改进这个例子!
XKCD 艺人被 PyCon 封杀
原文:https://www.blog.pythonlibrary.org/2009/02/11/xkcd-artist-banned-from-pycon/
上周, PyCon 官员在 PyCon 上封杀了网络漫画家兰道尔·门罗, XKCD 。在为去年的会议授权了他的一部漫画后,这是一个奇怪的举动。当然,如果你看看 PyCon 邮件列表的档案,你会很快意识到这是一个宣传噱头,他们正努力让他来。有趣的东西!
是的,还有一本 wxPython 的书
原文:https://www.blog.pythonlibrary.org/2010/12/09/yes-theres-another-wxpython-book/
早在 2006 年,曼宁发布了 Noel Rappin 和 Robin Dunn(wxPython 的创造者)的优秀的《wxPython 在行动中》一书。这仍然是一本很棒的书,我全心全意地将它推荐给那些想要增加他们对 wxPython 的理解的人。然而,Packt Publishing 刚刚发布了四年多来的第一本 wxPython 新书。它被 Cody Precord(Editra 的创造者)称为 wxPython 2.8 应用程序开发食谱。这些书在各自的出版商网站上都有电子书版本。
我打算读完 Precord 的书后再评论一下。他在 wxPython 邮件列表上非常活跃,帮助人们(包括我)了解 wxPython 的来龙去脉。如果你和我一样喜欢 wx,我希望你能通过购买这本书来支持他和 wxPython 社区。Packt 将这本书的部分购买价格返还给它所涉及的项目,这非常酷。他们还在 Packt 网站上进行 Python 书籍的销售(向罗宾·邓恩致敬)。
另一个 Python 日期时间的替代品:Pendulum
原文:https://www.blog.pythonlibrary.org/2016/07/13/yet-another-python-datetime-replacement-pendulum/
我偶然发现了另一个新的库,据称它比 Python 的 datetime 模块更好。它叫做钟摆。根据它的文档,钟摆受到 PHP 的碳的严重影响。
这些库总是很有趣,尽管我不确定是什么让这个库比 Arrow 或 Delorean 更好。在 Reddit 上有一些评论,钟摆的创造者说他发现 Arrow 在某些情况下表现奇怪。
无论如何,本文不是要比较这些相互竞争的库。只是为了看看钟摆本身是如何工作的。这些不同库的主要目的之一是创建一个具有更“Pythonic 式”API 和时区意识的 datetime 模块。
让我们安装它,以便我们可以检查它:
pip install pendulum
如果您不喜欢将它直接安装到您的主 Python 安装中,那么您可以随意将其安装到 virtualenv 中。无论您将它安装在哪里,您都会看到它安装了一些自己的依赖项,如 tzlocal、python-translate、codegen 和 polib。
现在已经安装好了,让我们打开 Python 的解释器,试一试:
>>> import pendulum
>>> pendulum.now('Japan')
>>> pendulum.now()
这里我们获取我所在位置的当前时间,然后我们询问日本的当前时间。你会注意到钟摆自动检测你的时区。如您所料,您也可以从日期创建类似日期时间的对象:
>>> from pendulum import Pendulum
>>> Pendulum.create_from_date(2016, 7, 8, 'US/Eastern')
>>> Pendulum.create_from_date(2016, 7, 8, 'US/Central')
>>> Pendulum.create_from_date(2016, 7, 8, 'US/Mountain')
这个例子演示了如何通过指定不同的时区来获得各种类似 datetime 的对象。然而,我喜欢钟摆的一点是它如何根据时间段计算日期:
>>> today = pendulum.now()
>>> today
>>> tomorrow = pendulum.now().add(days=1)
>>> tomorrow
>>> last_week = pendulum.now().subtract(weeks=1)
>>> last_week
我发现它加减时间段的方式非常直观。钟摆还有一个有趣的间隔概念:
>>> interval = pendulum.interval(days=365)
>>> interval.weeks
52
>>> interval.years
1
>>> interval.days
365
>>> interval.for_humans
>>> interval.for_humans()
'52 weeks 1 day'
这里我们创建了一个 365 天的时间间隔。然后,我们可以询问它关于这段时间的各种信息,例如它包含多少天、多少周和多少年。您可以做的另一件有趣的事情是创建一个日期时间,然后向它询问与创建日期相关的其他日期:
>>> dt = pendulum.create(2012, 1, 31, 12, 0, 0)
>>> dt.start_of('decade')
>>> dt.start_of('century')
>>> dt
>>> dt.end_of('month')
>>> dt
在早期版本的 Pendulum 中,这些调用实际上会改变 dt 对象的位置。这将意味着我的 dt 对象将不是 2012-1-31,而是更改为十年的开始,然后是世纪,最后在月末结束,也就是 2001-01-30。在撰写本文时,此问题已得到解决,以与您在上面看到的内容相匹配。
包扎
钟摆是一个非常有趣的图书馆。在过去的一周左右的时间里,已经有了很多更新,因为作者正在根据用户的反馈修改 API。我发现我在写这篇文章时试图使用的函数在我准备发表时已经改变了。我确实认为这个库值得一看,但是当至少有另外两个有价值的竞争者已经存在一段时间时,我们将会看到它是否存在。一定要检查文档,因为它变化很大,这篇文章可能会过时。
相关项目
相关文章
- python "与 Delorean 共度时光
- arrow "Python 的新日期/时间包
你可以在皮肯演讲
原文:https://www.blog.pythonlibrary.org/2010/09/24/you-could-speak-at-pycon/
杰西·诺勒刚刚发布了 2011 年 PyCon 的提案。这意味着你有机会给一大群讨厌的 python 爱好者做演讲!拿出你的思维帽,戴上那些吸盘!如果我今年去,我想看一些令人惊讶和有趣的演讲,所以你们(和女孩)快去吧!要了解完整的 monty,请查看此页面: