introduction
On the road of innovation and exploration of tkinter, I have written two articles on tkinter's use of browser web components:
- Use Internet Explorer Application
- Using minilink
In addition, other methods of realizing web page components have been summarized:
- TkHtml3 | backward
- cef | bulky
- Nested external exe | uncontrollable
It is precisely because of the early technology accumulation and tkinter itself does not support native web page components that various solutions appear one after another.
However, Microsoft's support for IE interface is coming to an end, and Miniblink can't meet the needs of general HTML browsing and display. Therefore, tkinter web page components nested based on WebView2 were born!!!
Write in front
Catch up
According to the comments and feedback, the implementation methods of my other two tkinter web components are no longer applicable to the needs of all tkinter enthusiasts, and the functions are really weak.
Originally, I wanted to rewrite pywebview, but it became a dependency because it was "in a hurry".
And this article will continue to be updated.
I have uploaded the project to PYPI and can install tkwebview2 through pip.
Dependency Library
Python net, which may need to install the VS compiler. The final compilation volume is very small.
Python net, the core library, is not going to be simplified due to time problems.
lazy
This article is very long because it is complex. If you don't want to read it, you can turn to the end to see the introduction to tkwebview2.
Create class
class WebView2(Frame): #Note: to use this component, set the__ init__.py to a new file in the same directory def __init__(self,parent,width:int,height:int,url:str='',**kw): ''' parent::Parent component width::width height::height url::Web page displayed at startup '''
Create embedded function
Because pywebview does not provide a handle acquisition method, we need to obtain the window handle through the window title:
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
Override the binding of pywebview
There are two problems with the window startup of pywebview in tkinter:
- Single thread startup prevents tkinter window from running.
- Each initialization will activate all browser windows, resulting in redundant windows and affecting dynamic creation.
In this regard, I changed some fragments of the initialization file and rewritten them in bind Py.
Change as follows:
''' 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()
Start only the latest window.
And:
''' guilib.create_window(windows[-1]) ''' Thread(target=lambda:guilib.create_window(windows[-1])).start()
Start the window as a thread.
Embed webview
In order to avoid the repetition of the title, we use the handle of the Frame as the window title, which is unique.
In addition, according to the previous experience of embedding WinForms components, the component size can be dynamically bound.
class WebView2(Frame): #Note: to use this component, set the__ init__.py to a new file in the same directory 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): #Embed 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): #Bind each item 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)
override method
class WebView2(Frame): #... def get_url(self): #Returns the current url. If not, it is null return self.web.get_current_url() def evaluate_js(self,script): #Execute the javascript code and return the final result return self.web.evaluate_js(script) def load_css(self,css): #Load css self.web.load_css(css) def load_html(self,content,base_uri=None): #Load HTML code #content=HTML content #base_uri = Basic URL, the default is the directory where the program starts if base_uri==None: self.web.load_html(content) else: self.web.load_html(content,base_uri) def load_url(self,url): #Load new URL self.web.load_url(url) def none(self): pass
be accomplished.
Using tkwebview2
Use 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()
effect
epilogue
This is the best method I have found for pure embedded WebView2. Of course, you can also explore other methods, such as using WebView2 directly WinForms instead of borrowing the mediation pywebview.
For more extended usage, please refer to the relevant materials and change the WebView2 binding of pywebview.
☀ tkinter innovation ☀