首先,这个思路有点偏,大概有点奇技淫巧的嫌疑。由于行文混乱,大家可以根据加粗字体定位到需要具体了解的内容。
容我先吐槽一下Electron 我们知道用html写桌面端程序好说啊,拿Electron或者NW任何一个都可以,也可以单纯用NodeJS或Vue,都没什么问题,码起来速度快不说,界面也漂亮,遇到问题网上也有一大堆解决方法(一大堆重复的解决方法,天朝网友威武)。我用了一段时间Electron,要说开发效率是高,可是一个Hello World打包就150M左右(打包了一个小型操作系统),尝试用UPX压缩一下,是小了一半,但执行速度就要呵呵了(有点不爽)。也许有人会说一定有办法减小体积的,你没找到而已,我真找不到了。然后我就回头看了一下自己之前做的python废品小程序,打包成exe文件一共30多M,当时一看30多M,太大了吧,很不爽。现在看起来自己当时还是太年轻。
先贴一张鼓捣半天做的初始界面(wxPython的Frame下放一个WebView(wx.html2.WebView),然后加载html文件),丑哭了,所以会美颜很重要(Element-UI+CSS+VueJS)。
现在聊一聊目前这个思路 当时没用wxPython因为界面做起来不漂亮,虽然当时使用了里面的html2.WebView控件+Bootstrap4来编辑内容,但布局仍然有问题(能力有限,不要砸我),而且主要功能还是通过python处理的,主要问题是获取WebView中的内容并解析有些麻烦,虽然最后能用了,但感觉瑕疵太大,就放弃了。
最近接触了Electron,进而知道了NodeJS,VueJS,又发现了Element-UI(当然还有iView和HeyUI,但综合对比后还是感觉Element-UI比较灵活,而且也规范,iView在非Template模式下连规范的命名都没有,一会儿驼峰大小写一会又用横杠分割,刚入门就把人整晕了)。然后就想在WebView中加载已经写好的html文件,进而进行各种操作。 经过一番摸索,终于弄明白怎么直接在html中使用Vue和Element-ui(不需要安装NodeJS也不需要Vue组件化)布局,用Element-UI的Container组件也遇到了一些问题,网上有很多答案,但能解决问题的没几个(抄来抄去真没意思,网友真闲哈),最后还是通过_奔跑的蜗牛的博客并结合官网的例子自己尝试出来一个布局。
布局问题基本上解决了(还有一些小问题不好处理,例如菜单弹出和收起时会闪一下)。
接下来就是最主要的问题:操作文件和文件夹。虽然是个本地应用,但WebView就是个浏览器,因为它调用的是系统浏览器内核:Windows下是Trident(IE),MacOS下是Webkit,而Linux下好像是GTK(这个给忘了)。这样如果通过JS直接操作本地文件或文件夹,只有IE可以实现,因为它有一个被广大网友诟病的ActiveX插件可以静悄悄访问你的本地文件。。。即使现在HTML5提供了File相关的接口,也只是在用户手动选择文件后才能读取文件,而不能直接操作本地文件或文件夹(安全问题)。既然js无法直接操作文件和文件夹(虽然windows下可以,但万一我想玩企鹅,万一想吃苹果呢),那么只好通过python来处理了。
这样就涉及到一个问题,WebView控件怎么和它之外的代码交换它内部的数据? WebView只提供了有限的几个方法,GetPageText,GetPageSource,SetPage以及RunScript。GetPageText只能得到纯文本,Html标签全部被剔除,GetPageSource只能得到源码,对于input标签无法获取用户的输入(为了解决这个问题,我之前只能使用textarea)。好在RunScript可以运行js,从而将数据通过js传入WebView内部(RunScript中的js代码相当于是WebView内html中的js代码),而WebView的EVT_WEBVIEW_TITLE_CHANGED事件可以监测页面标题是否变化,这样我们可以修改标题来通知WebView外部的代码:起来工作了,数据放在了Title中。
上面提到,将WebView中的html数据放在Title中从而让外部代码使用,但这样存在一个问题,Title改变内容时会重复发通知,这样就不好了。再想一想,GetPageSource只是无法获取Input等有限控件输入的内容,但其他标签及其内容仍然可以很方便的被找到,我在WebView内部通过js将数据处理好后将其放在特定的div标签内(当然需要通过hidden属性隐藏起来),然后修改Title通知外部代码去取数据。 js有强大的社区环境,大部分功能都能够实现,例如markdown格式转换等。
另外,关于WebView中获取文件数据想到另一个方法,可以通过在页面上放一个隐藏的iframe来获取本地文件内容。思路来源是我们都知道浏览器可以直接访问并打开本地文件(部分文件,例如txt,md等纯文本文件),那么WebView中的iframe当然也可以读取了(通过动态修改src属性来获取或者将超链接的target属性设置为目标iframe),而且iframe获取到的内容还是包含在当前页面的,这样就可以通过js直接提取了(纯文本的内容会在iframe标签内用pre标签包裹起来,html文件的内容会用div包裹起来,图片会直接呈现),如果只读取纯文本(docx等会自动下载,无法读取),这应该是个不错的思路。
最后,本来想将程序中Windows自带的标题栏去掉,这样就可以在Html页面上重新设计标题栏(就像现在大部分软件一样),但目前还没想到去掉标题栏后怎么通过WebView内部的html标题栏来拖动窗口,只好作罢。
好了,瞎说一通后就这么结束吧。写这篇文章只是想为大家提供一个解决问题的思路,毕竟也许有不少人也对Electron的体积感到难受(谁不想漂亮的同时也瘦一点呢),又想做一个可以跨平台的桌面小程序,这个思路应该还是有些帮助的吧。
如果有时间,后续会再写一些文章把开发过程中遇到的问题和解决方法列出来,希望对大家有所帮助。
PS:真诚呼吁广大网友不要来回抄答案了,浪费自己的时间不说,重复的答案还未必解决问题,最主要的是给真正需要解决方案的人带来很多不必要的麻烦。
2018最后一天,补充:悲催了,突然发现上面想要通过GetPageSource()获取WebView内容的方法无法实现,调用GetPageSource或GetPageText时程序总是崩溃,而我之前做的小应用则没有发生这个问题。反复测试后确定这个问题是因为html中引用了外部js和css文件引起的(之前的应用中我是直接在python中将js和css文件读出来然后使用字符串拼接的方法放在html页面中,因此没有问题)。这样的话就不能通过GetPageSource等法法获取所需数据了。网上搜索了一下,除了wxPython外PyQt也能实现这个功能,而且提供了Python代码和浏览器控件内部通信的方法,感觉很不错,后来测试了一下,打包后200多M(比Electron还大哈),因为PyQt5中浏览器控件使用也是谷歌浏览器内核chromium。虽然呈现效果非常好,但考虑到体积问题只好放弃了。
因为始终不想使用字符串拼接的方式处理这个问题,就google看看歪果仁有没有解决方法(百度:“成妾做不到啊”),果然找到一个方法。比我使用Title获取内容好很多(Title改变的事件在加载页面时可能会重复触发,存在iframe的情况,而且每次都需要改变title的值,否则不能触发),这个方法就是监控浏览器的navigating事件EVT_WEBVIEW_NAVIGATING 这里是问题和解决方法,这个事件会在event参数中提供一个url,可以将我们需要的数据放在这个url中,并尝试导航,python后来捕获这个事件后从url中提取数据,然后阻止导航事件。
下面是部分代码,直接粘贴的,格式很乱,主要是看一下里面是怎样获取url的。
self.Bind(wx.html2.EVT_WEB_VIEW_NAVIGATING, self.OnNavigate,self.browser)
CATCH_STRING = "catch-this-click:"
def OnNavigate(self,evt):
targetUrl=evt.GetURL()
#print "Navigate Target: ",targetUrl
if self.CATCH_STRING in targetUrl:
passed_data = targetUrl.split(self.CATCH_STRING,2)[1]
print "Click Caught! Data = ",passed_data
evt.Veto() # prevent actual navigation.
else:
pass # let it do the normal thing...