用Python中的wxPython实现最基本的浏览器功能
通常,大多数应用程序通过保持 HTML 简单来解决大多数浏览器问题 ― 或者说,根据最低共同特性来编写。然而,即便如此,也仍然存在字体和布局的问题,发行新浏览器和升级现有浏览器时,也免不了测试应用程序的痛苦。替代方案 ― 只支持一种浏览器 ― 通常不是一种用户友好的解决方案。
明显的解决方案是在应用程序中嵌入自己的表现 HTML 的窗口构件。当然,从头开始编写这样的窗口构件工作量很大,因此,求助于预先封装的解决方案好象是合理的。
商界有许多选择及几个开放源码软件包。本文将向您显示如何以 Python 作为绑定的语言选择(也支持 C++、Perl 和其它语言)使用作为 wxWindows 软件包一部分分发的 wxHtml 窗口构件。
虽然没有任何 wxPython 经验而熟谙 Python 的开发人员应该能够从头开始,但本文还是假定您具有基本的 wxPython 知识。在本文中,我们将创建一个独立的浏览器应用程序,同时,保持体系结构足够简单以致将浏览器功能迁移到现有的应用程序中是一项简单的任务。
世界上最基本的浏览器
第一步是组装支持使用 wxHtml 窗口构件的应用程序所必需的最少代码。下列代码实现用 wxHtml 窗口构件作为其主窗口内容的基本 wxPython 应用程序。
清单 1. 基本示例浏览器代码
from wxPython.wx import * from wxPython.html import * import os,sys class exHtmlWindow(wxHtmlWindow): def __init__(self, parent, id, frame): wxHtmlWindow.__init__(self,parent,id) class exHtmlPanel(wxPanel): def __init__(self, parent, id, frame): wxPanel.__init__(self,parent,-1) self.html = exHtmlWindow(self, -1, frame) self.box = wxBoxSizer(wxVERTICAL) self.box.Add(self.html, 1, wxGROW) self.SetSizer(self.box) self.SetAutoLayout(true) class exFrame (wxFrame): def __init__(self, parent, ID, title): wxFrame.__init__(self,parent,ID,title,wxDefaultPosition,wxSize(600,750)) panel = exHtmlPanel(self, -1, self) class exApp(wxApp): def OnInit(self): frame = exFrame(NULL, -1, "Example Browser") frame.Show(true) self.SetTopWindow(frame) return true app = exApp(0) app.MainLoop()
假定您已正确安装 wxPython,那么在 Python 解释器中运行上述代码将产生一个具有空的白面板(wxHtml 窗口构件)的大窗口。如果出现任何语法错误,请检查空格问题 ― 尤其是如果您将代码剪切粘贴到解释器或编辑器的情况。如果 Python 解释器显示无法导入 wxPython,请检查安装以确保安装正确。
当然,一启动该浏览器,立刻出现的是:我们缺少某些东西 ... 例如装入页面的机制。对于某些应用程序,这一非常基本的设置实际上可能已经够了 — 如果您已知道您要交付什么,那么用户就无需选择自己的页面。简单的更改是向 exHtmlPanel 传递额外的参数,那就是您想访问的页面:
清单 2. 修改 exHtmlPanel 以装入页面
class exHtmlPanel(wxPanel): + def __init__(self, parent, id, frame, file): wxPanel.__init__(self, parent, -1) self.html = exHtmlWindow(self, -1, frame) self.box = wxBoxSizer(wxVERTICAL) self.box.Add(self.html, 1, wxGROW) self.SetSizer(self.box) self.SetAutoLayout(true) + self.html.LoadPage(file)
为了使之更独立也为了使之更象浏览器,我们将扩展 ttHtmlPanel 类以添加一些执行标准浏览器任务的按钮。当然,如果您实际上是计划构建一个真正的浏览器应用程序,那么在 GUI 设计和可用性方面您可能要考虑的比我们这里做的更多。
清单 3. 修改 ttHtmlPanel 以添加按钮
class ttHtmlPanel(wxPanel): def __init__(self, parent, id, frame): wxPanel.__init__(self, parent, -1) self.frame = frame self.cwd = os.path.split(sys.argv[0])[0] if not self.cwd: self.cwd = os.getcwd self.html = ttHtmlWindow(self, -1, self.frame) self.box = wxBoxSizer(wxVERTICAL) self.box.Add(self.html, 1, wxGROW) subbox = wxBoxSizer(wxHORIZONTAL) btn = wxButton(self, 1202, "Load File") EVT_BUTTON(self, 1202, self.OnLoadFile) subbox.Add(btn, 1, wxGROW | wxALL, 2) btn = wxButton(self, 1203, "Load Page") EVT_BUTTON(self, 1203, self.OnLoadPage) subbox.Add(btn, 1, wxGROW | wxALL, 2) btn = wxButton(self, 1204, "Back") EVT_BUTTON(self, 1204, self.OnBack) subbox.Add(btn, 1, wxGROW | wxALL, 2) btn = wxButton(self, 1205, "Forward") EVT_BUTTON(self, 1205, self.OnForward) subbox.Add(btn, 1, wxGROW | wxALL, 2) self.box.Add(subbox, 0, wxGROW) self.SetSizer(self.box) self.SetAutoLayout(true) def OnLoadPage(self, event): dlg = wxTextEntryDialog(self, 'Location:') if dlg.ShowModal() == wxID_OK: self.destination = dlg.GetValue() dlg.Destroy() self.html.LoadPage(self.destination) def OnLoadFile(self, event): dlg = wxFileDialog(self, wildcard = '*.htm*', style=wxOPEN) if dlg.ShowModal(): path = dlg.GetPath() self.html.LoadPage(path) dlg.Destroy() def OnBack(self, event): if not self.html.HistoryBack(): wxMessageBox("No more items in history!") def OnForward(self, event): if not self.html.HistoryForward(): wxMessageBox("No more items in history!")
如果您以前使用过 wxPython 或任何其它 Python 图形工具箱,那么您可以发现我们做的所有事情就是向面板添加另一个容器并将四个按钮置于其中,带有对 exHtmlPanel 类中所添加的方法的回调函数。基础 wxHtml 类巧妙地为我们管理历史,因此, OnBack 和 OnForward 仅仅是对基础方法的调用。
假定读到这些时您已一直在使用 Python 解释器,那么您可能注意到:如果关闭应用程序,它从不将控制返回给控制台。这个问题解决起来很简单,但我们可能应该添加一个菜单栏来提供具有退出选项的文件菜单:
清单 4. 修改 exFrame 以添加带有退出的文件菜单
class exFrame(wxFrame): def __init__(self, parent, ID, title): wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(600,750)) panel = exHtmlPanel (self, -1, self) mnu_file = wxMenu() mnu_file.Append(101, "E&xit", "Exit the browser") menuBar = wxMenuBar() menuBar.Append(mnu_file, "F&ile") self.SetMenuBar(menuBar) EVT_MENU(self, 101, self.Exit) def Exit(self, event): self.Close(true)
当我们没有试图将它变为一个真正的浏览器的时候,我们在结尾处发现少了两个添加项:大多数浏览器都有状态栏,并且您可能注意到了没有绘制任何图像。下列对 exApp 、 exFrame 和 exHtmlPanel 的修改添加了一个状态栏以及所有来自 wxPython 的内置图像支持:
清单 5. 添加状态栏及图像支持
class exApp(wxApp): def OnInit(self): + wxInitAllImageHandlers() frame = exFrame(NULL, -1, "Example Browser") frame.Show(true) self.SetTopWindow(frame) return true class exHtmlPanel(wxPanel): def __init__(self, parent, id, frame): wxPanel.__init__(self, parent, -1) self.frame = frame self.cwd = os.path.split(sys.argv[0])[0] if not self.cwd: self.cwd = os.getcwd self.html = exHtmlWindow(self, -1, self.frame) + self.html.SetRelatedFrame(self.frame, "%s") + self.html.SetRelatedStatusBar(0) ... class exFrame(wxFrame): def __init__(self, parent, ID, title): wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(600,750)) panel = exHtmlPanel (self, -1, self) + self.CreateStatusBar() + self.SetStatusText("Default status bar") ...
现在,基本浏览器的功能应该齐全了。wxPython 的高级特性允许您创建自己的标记,可以通过定制代码来处理这些标记以执行您选择的任何操作。对您自己的可定制嵌入式浏览器的控制为增强的报表生成及联机帮助提供了无限的可能性。
这些代码本身就可以轻易为任意数目的应用程序提供基础,并且 ― 没有理由将您限制在仅仅提供联机帮助上。请*使用这些类,看看能让它们发生什么有趣的行为。:-)