tkwebview-tkinter的web视图
tkwebview-tkinter的web视图
- 引言
- 添加尺寸填充方法
- 封装
- 核心类
- TkWebview
- 示例
- 关于跨平台
引言
我又来倒腾tkinter的web视图了😭。
很久以前实现了tkwebview2,在Windows平台上使用WebView2,这需要pywebview
和pythonnet
,大动干戈,而且在python层面中还写了很多底层方法的调用,到头来也没弄清pywebview
的js_api
怎么搞😭。
那干脆用c/c++封装完,在python里写统一的接口就得了。
感谢webview/webview和HIllya51/webviewpy,巨人的肩膀。
添加尺寸填充方法
在原版的webview中,set_size
只对独立窗口模式有效,我们期望tkwebview基于Frame,显然是用不了这个函数的。虽然webviewpy给出了在tkinter中使用的示例,但是使用了user32.dll,为了给跨平台留出可能,并且得到一个简单易用的webview控件,有必要将尺寸填充写进webview中,而在python层面封装为一个控件即可。
我单独维护了一个基于webview的c++库Smart-Space/webview,并在里面添加我需要的函数。
其实这一步很简单,直接调用resize_widget
,我就不说明了,详见源码。
接下来就是编译,由于条件限制,我只实现了Windows平台下的尺寸填充方法,并在tkwebview中提供了动态链接库(32位未测试,但理论上和64位一样的)。
封装
核心类
这部分来自webviewpy,我只额外添加了webview_resize
方法,不过这个函数只在内部用到。
TkWebview
一个真正的tkinter控件。
class TkWebview(Frame):def __init__(self, master=None, **kwargs):Frame.__init__(self, master, bg='black', **kwargs)self.update()self.webview = Webview(debug=False, window=self.winfo_id())self.bind('<Configure>', self.on_configure)def on_configure(self, event):self.webview.resize()def resolve(self, id, status, result):return self.webview.resolve(id, status, result)def bindjs(self, name, fn, is_async_return=False):return self.webview.bind(name, fn, is_async_return)def dispatch(self, fn):return self.webview.dispatch(fn)def unbindjs(self, name):return self.webview.unbind(name)def eval(self, js):return self.webview.eval(js)def navigate(self, url):return self.webview.navigate(url)def init(self, js):return self.webview.init(js)def set_html(self, html):return self.webview.set_html(html)def version(self):return self.webview.version()
其含义见Smart-Space/tkwebview: tkinter webview control。
Win11前需要安装WebView2 Runtime,这个自行想办法判断吧😵。
示例
pip install tkwebview
tkwebview不是比tkwebview2低一等,而是TkWebView和Tk-WebView2,两个东西,虽然在Windows上用的WebView2。
以下为示例代码:
from tkinter import Tk, Entry
from tkwebview import TkWebview as Webview
import ctypes
from threading import Thread
from time import sleep
ScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0)/100
ctypes.windll.shcore.SetProcessDpiAwareness(1)def get_url(event):# 需要以协议(https, http, file等)开头url=e.get()web.navigate(url)def count(req):global count_numcount_num += reqreturn str(count_num)def compute(returner, num1, num2):def _(_1, _2):sleep(1)returner(_1 * _2)Thread(target=_,args=(num1, num2),).start()a=Tk()
a.tk.call('tk', 'scaling', ScaleFactor)
a.geometry('800x400')e=Entry(a,font='微软雅黑 18', relief='solid')
e.pack(fill='x', padx=10, pady=5)
e.bind('<Return>', get_url)html="""
<div><button id=\"increment\">+</button><button id=\"decrement\">−</button><span>Counter: <span id=\"counterResult\">0</span></span>
</div>
<hr />
<div><button id=\"compute\">Compute</button><span>Result: <span id=\"computeResult\">(not started)</span></span>
</div>
<script type=\"module\">const getElements = ids => Object.assign({}, ...ids.map(id => ({ [id]: document.getElementById(id) })));const ui = getElements([\"increment\", \"decrement\", \"counterResult\", \"compute\",\"computeResult\"]);ui.increment.addEventListener(\"click\", async () => {ui.counterResult.textContent = await window.count(1);});ui.decrement.addEventListener(\"click\", async () => {ui.counterResult.textContent = await window.count(-1);});ui.compute.addEventListener(\"click\", async () => {ui.compute.disabled = true;ui.computeResult.textContent = \"(pending)\";ui.computeResult.textContent = await window.compute(6, 7);ui.compute.disabled = false;});
</script>"""count_num = 0web=Webview(a)
web.pack(fill='both', expand=True)
# web.navigate('https://www.baidu.com/')
web.set_html(html)
web.bindjs('count', count)
web.bindjs('compute', compute, is_async_return=True)a.bind_all("<Button-1>", lambda e: e.widget.focus_force())
# 如果某个控件同样需要<Button-1>,可自行改写或者使用add参数添加a.mainloop()
关于焦点问题,这里的注释和项目简介里都有说明。
效果(这次支持从JS中调用python代码了😋):
关于跨平台
首先,webview是跨平台的,webviewpy也支持跨平台判断,理论上tkwebview是可以做到跨平台的,唯一的难点就是实现跨平台的webview_resize
,这个也很简单,因为webview本身提供了实现这个功能的方法。但是,条件限制,我无法编译和测试其他平台下的tkwebview。
以上代码库均为MIT协议开源,完全可以放到本地自行编写合适的webview方法,包括现在还不支持的事件回调(因为我不确定其它webkit支持情况如何)。
Linux下好像不需要实现
webview_resize
,GTK自己会处理,如果tkinter是用GTK实现的话,但好像不是,是X11,那就不好搞了,不过反正我不需要😵😭🤣。
在本文初次发布之后的一个小时里,我又看了Mac上的源码,如果基于Cocoa的tkinter,也不用单独实现webview_resize
,因为使用了NSViewWidthSizable | NSViewHeightSizable
,基于X11也难办🤣😭😵。
那我之后就看看Windows下有什么好玩的吧。