tkinter使用WebView2网页组件

引言

在对tkinter的一番创新和探索道路上,我自己已经写了两篇关于tkinter使用浏览器网页组件的文章:

  • 使用InternetExplorer.Application
  • 使用miniblink

而且,还总结过其它实现网页组件的方法:

  • TkHtml3 | 落后
  • cef | 体积庞大
  • 嵌套外部exe | 不可控

而正因为前期的技术积累,以及tkinter自身不支持原生网页组件,所以才让各种解决方案相继出现。

但是,微软对IE接口的支持即将“寿终正寝”,Miniblink无法满足通用HTML浏览和显示的需求。所以,基于WebView2嵌套的tkinter网页组件孕育而生!!!


写在前面

赶工

因为根据评论反馈,我另外两篇tkinter网页组件的实现方法已不再是适用于所有tkinter爱好者的需求,而且功能实在是弱爆了。

本来想重写pywebview,但是因为“赶稿”,就让其成为依赖项了。

而且这篇文章还会继续更新。

我已经将项目上传到PYPI,可通过pip安装tkwebview2

依赖库

pythonnet,这个可能需要安装VS编译器,最终编译体积很小。

pythonnet,核心库,目前因为时间问题不打算简化。

懒惰

这一篇文章很长,因为比较复杂,如果不想看,可以直接翻到最后查看tkwebview2的使用介绍。


创建类

class WebView2(Frame):
    #说明,若要使用这个组件,请将pywebview的__init__.py改为同目录内新的文件
    
    def __init__(self,parent,width:int,height:int,url:str='',**kw):
        '''
        parent::父组件
        width::宽度
        height::高度
        url::启动时显示的网页
        '''

创建嵌入函数

因为pywebview没有提供句柄获取方法,因此我们需要通过窗口标题获取窗口句柄:

enumWindows = ctypes.windll.user32.EnumWindows
enumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
getWindowText = ctypes.windll.user32.GetWindowTextW
getWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
isWindowVisible = ctypes.windll.user32.IsWindowVisible
SetParent=ctypes.windll.user32.SetParent
MoveWindow=ctypes.windll.user32.MoveWindow
GetWindowLong=ctypes.windll.user32.GetWindowLongA
SetWindowLong=ctypes.windll.user32.SetWindowLongA
def _getAllTitles():
    titles=[]
    def foreach_window(hWnd, lParam):
        if isWindowVisible(hWnd):
            length = getWindowTextLength(hWnd)
            buff = ctypes.create_unicode_buffer(length + 1)
            getWindowText(hWnd, buff, length + 1)
            titles.append((hWnd, buff.value))
        return True
    enumWindows(enumWindowsProc(foreach_window),0)
    return titles
def getWindowsWithTitle(title):
    hWndsAndTitles = _getAllTitles()
    windowObjs = []
    for hWnd, winTitle in hWndsAndTitles:
        if title.upper() in winTitle.upper():
            windowObjs.append(hWnd)
    return windowObjs

重写pywebview的绑定

pywebview的窗口启动在tkinter使用中有两个问题:

  1. 单线程启动,阻碍tkinter窗口运行。
  2. 每一次初始化会将所有浏览器窗口激活一遍,造成窗口多余,影响动态创建。

对此,我更改了初始化文件的部分片段,并重新写在了bind.py中。

更改如下:

'''
for window in windows:
	windows[-1]._initialize(guilib, multiprocessing, http_server)

if len(windows) > 1:
    t = Thread(target=_create_children, args=(windows[1:],))
    t.start()
'''
#for window in windows:
windows[-1]._initialize(guilib, multiprocessing, http_server)

#if len(windows) > 1:
#    t = Thread(target=_create_children, args=(windows[1:],))
#    t.start()

只启动最新的窗口。

以及:

'''
guilib.create_window(windows[-1])
'''
Thread(target=lambda:guilib.create_window(windows[-1])).start()

以线程启动窗口。

嵌入webview

为了避免标题重复,我们使用Frame的句柄作为窗口标题,具有唯一性。

此外,再根据以往嵌入WinForms组件的经验,动态绑定组件尺寸即可。

class WebView2(Frame):
    #说明,若要使用这个组件,请将pywebview的__init__.py改为同目录内新的文件
    
    def __init__(self,parent,width:int,height:int,url:str='',**kw):
        Frame.__init__(self,parent,width=width,height=height,**kw)
        self.fid=self.winfo_id()
        self.width=width
        self.height=height
        self.title=str(self.fid)
        self.parent=parent
        if url=='':
            self.web=webview.create_window(self.title,width=width,height=height,frameless=True,text_select=True)
        else:
            self.web=webview.create_window(self.title,url,width=width,height=height,frameless=True,text_select=True)
        webview.start(self.__in_frame)

    def __in_frame(self):
        #嵌入WebView2
        wid=getWindowsWithTitle(self.title)
        while wid==[]:
            wid=getWindowsWithTitle(self.title)
        wid=wid[0]
        SetParent(wid,self.fid)
        MoveWindow(wid,0,0,self.width,self.height,True)
        self.wid=wid
        self.__go_bind()

    def __go_bind(self):
        #绑定各个项目
        self.bind('<Destroy>',lambda event:self.web.destroy())
        self.bind('<Configure>',self.__resize_webview)
    def __resize_webview(self,event):
        MoveWindow(self.wid,0,0,self.winfo_width(),self.winfo_height(),True)

重写方法

class WebView2(Frame):
    #...
    def get_url(self):
        #返回当前url,若果没有则为空
        return self.web.get_current_url()

    def evaluate_js(self,script):
        #执行javascript代码,并返回最终结果
        return self.web.evaluate_js(script)

    def load_css(self,css):
        #加载css
        self.web.load_css(css)

    def load_html(self,content,base_uri=None):
        #加载HTML代码
        #content=HTML内容
        #base_uri=基本URL,默认为启动程序的目录
        if base_uri==None:
            self.web.load_html(content)
        else:
            self.web.load_html(content,base_uri)

    def load_url(self,url):
        #加载全新的URL
        self.web.load_url(url)

    def none(self):
        pass

大功告成。


使用tkwebview2

使用pip install tkwebview2

from tkinter import Frame,Tk
from tkwebview2.tkwebview2 import WebView2

if __name__=='__main__':
    root=Tk()
    root.title('pywebview for tkinter test')
    root.geometry('1200x600+5+5')

    frame=WebView2(root,500,500)
    frame.load_html('<h1>hi hi hi</h1>')
    frame.pack(side='left')
    
    frame2=WebView2(root,500,500)
    frame2.load_url('https://smart-space.com.cn/')
    frame2.pack(side='right',fill='x',expand=True)

    root.mainloop()


效果

在这里插入图片描述


更新内容

2022-4-10更新:可判断并下载runtime

有些电脑不存在webview2 runtime,也有可能版本过低。对此,tkwebview2提供了两个方法:have_runtime, install_runtime

具体用法如下:

from tkwebview2.tkwebview2 import have_runtime, install_runtime

if not have_runtime():#不存在webview2 runtime或版本过低
    install_runtime()#下载并安装runtime

注意,这个操作或暂停主程序运行,直到使用者手动关闭安装指引。

2022-6-11更新:全新创建

本次更新点: 使用pywebview提供的EdgeChrome,而不是直接嵌套窗口
带来的好处:

  1. tkinter窗口不会失去焦点
  2. 减少运行消耗
  3. 不直接使用高gdi渲染
  4. 可以直接操控WebView2(tkwebview2没有直接提供)
    具体见本文章续篇

结语

这已经是目前我找到最好的纯嵌入式WebView2的方法了,当然,大家还可以探讨其它方法,比如直接使用WebView2.WinForms,而不是借用pywebview这个中介。

关于更多的拓展用法,请自行参阅相关资料并更改pywebview的WebView2绑定。

☀tkinter创新☀

  • 10
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 80
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 80
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值