【问题标题】:Is it possible to render HTML in Tkinter? [closed]是否可以在 Tkinter 中呈现 HTML? [关闭]
【发布时间】:2021-01-14 15:46:48
【问题描述】:

我有一张地图,它保存为html 文件。

有什么方法可以在使用 tkinter 的应用程序中显示它?

我看到一些提到 tkhtml 的答案,但我几乎没有找到关于它的信息。如果有人可以请给我一个亮点,并了解如何以及在哪里瞄准我的代码......

谢谢

【问题讨论】:

    标签: python html tkinter


    【解决方案1】:

    不,没有在 tkinter 应用程序中显示 html 的好方法。

    自 2008 年以来,Tkhtml 没有看到任何重大发展,并且从未走出早期的 alpha 阶段。

    据我所知,tkhtml 的官方源代码在这个fossil 存储库中:http://tkhtml.tcl.tk/fossil/timeline?y=ci&n=50

    【讨论】:

    【解决方案2】:

    我已经设法使用@j_4321 提供的link 呈现简单的html 标签

    只需pip3 install tkinterhtml

    并且,来自包example

    from tkinterhtml import HtmlFrame
    
    import tkinter as tk
    
    root = tk.Tk()
    
    frame = HtmlFrame(root, horizontal_scrollbar="auto")
     
    frame.set_content("<html></html>")
     
    

    如果您将内容保存在文件中,或手动输入。或者:

    frame.set_content(urllib.request.urlopen("http://thonny.cs.ut.ee").read().decode())
    

    请求并呈现它。

    希望对你有帮助:)

    【讨论】:

    • 什么是root
    • 你的 tk 根 root = Tk()
    • Tkinterhtml 多年未见发展,不支持样式、图像甚至超链接。不过,Tkinterweb 确实支持这些功能。可以通过pip3 install tkinterweb安装。
    • 当我使用这个库时,我的应用程序只是崩溃而没有任何错误。
    • @YılmazAlpaslan 在能够渲染它们之前,您可能会进入生成元素的循环。从一个非常简单的 UI 开始,然后从那里构建以识别您的错误线。
    【解决方案3】:

    是的,您可以在 tkinter 中嵌入 HTML 并打开完整的网页(甚至使用 CSS 和 javascript)。使用cefpython module,您可以在 tk 窗口中嵌入成熟的 Chromium 浏览器。下面是一个显示本地 HTML 文件的工作示例(在 #todo 注释的行更改 HTML 文件位置)

    2020 年 8 月更新:这适用于 Python 2.7 / 3.4 / 3.5 / 3.6 / 3.7

    2021 年 3 月更新现在还支持 Python 3.8 和 3.9 https://github.com/cztomczak/cefpython/releases/tag/v66.1

    # Example of embedding CEF Python browser using Tkinter toolkit.
    # This example has two widgets: a navigation bar and a browser.
    #
    # NOTE: This example often crashes on Mac (Python 2.7, Tk 8.5/8.6)
    #       during initial app loading with such message:
    #       "Segmentation fault: 11". Reported as Issue #309.
    #
    # Tested configurations:
    # - Tk 8.5 on Windows/Mac
    # - Tk 8.6 on Linux
    # - CEF Python v55.3+
    #
    # Known issue on Linux: When typing url, mouse must be over url
    # entry widget otherwise keyboard focus is lost (Issue #255
    # and Issue #284).
    
    from cefpython3 import cefpython as cef
    import ctypes
    try:
        import tkinter as tk
    except ImportError:
        import Tkinter as tk
    import sys
    import os
    import platform
    import logging as _logging
    
    # Fix for PyCharm hints warnings
    WindowUtils = cef.WindowUtils()
    
    # Platforms
    WINDOWS = (platform.system() == "Windows")
    LINUX = (platform.system() == "Linux")
    MAC = (platform.system() == "Darwin")
    
    # Globals
    logger = _logging.getLogger("tkinter_.py")
    
    # Constants
    # Tk 8.5 doesn't support png images
    IMAGE_EXT = ".png" if tk.TkVersion > 8.5 else ".gif"
    
    
    class MainFrame(tk.Frame):
    
        def __init__(self, root):
            self.browser_frame = None
            self.navigation_bar = None
    
            # Root
            root.geometry("900x640")
            tk.Grid.rowconfigure(root, 0, weight=1)
            tk.Grid.columnconfigure(root, 0, weight=1)
    
            # MainFrame
            tk.Frame.__init__(self, root)
            self.master.title("Tkinter example")
            self.master.protocol("WM_DELETE_WINDOW", self.on_close)
            self.master.bind("<Configure>", self.on_root_configure)
            self.setup_icon()
            self.bind("<Configure>", self.on_configure)
            self.bind("<FocusIn>", self.on_focus_in)
            self.bind("<FocusOut>", self.on_focus_out)
    
            # NavigationBar
            self.navigation_bar = NavigationBar(self)
            self.navigation_bar.grid(row=0, column=0,
                                     sticky=(tk.N + tk.S + tk.E + tk.W))
            tk.Grid.rowconfigure(self, 0, weight=0)
            tk.Grid.columnconfigure(self, 0, weight=0)
    
            # BrowserFrame
            self.browser_frame = BrowserFrame(self, self.navigation_bar)
            self.browser_frame.grid(row=1, column=0,
                                    sticky=(tk.N + tk.S + tk.E + tk.W))
            tk.Grid.rowconfigure(self, 1, weight=1)
            tk.Grid.columnconfigure(self, 0, weight=1)
    
            # Pack MainFrame
            self.pack(fill=tk.BOTH, expand=tk.YES)
    
        def on_root_configure(self, _):
            logger.debug("MainFrame.on_root_configure")
            if self.browser_frame:
                self.browser_frame.on_root_configure()
    
        def on_configure(self, event):
            logger.debug("MainFrame.on_configure")
            if self.browser_frame:
                width = event.width
                height = event.height
                if self.navigation_bar:
                    height = height - self.navigation_bar.winfo_height()
                self.browser_frame.on_mainframe_configure(width, height)
    
        def on_focus_in(self, _):
            logger.debug("MainFrame.on_focus_in")
    
        def on_focus_out(self, _):
            logger.debug("MainFrame.on_focus_out")
    
        def on_close(self):
            if self.browser_frame:
                self.browser_frame.on_root_close()
            self.master.destroy()
    
        def get_browser(self):
            if self.browser_frame:
                return self.browser_frame.browser
            return None
    
        def get_browser_frame(self):
            if self.browser_frame:
                return self.browser_frame
            return None
    
        def setup_icon(self):
            resources = os.path.join(os.path.dirname(__file__), "resources")
            icon_path = os.path.join(resources, "tkinter"+IMAGE_EXT)
            if os.path.exists(icon_path):
                self.icon = tk.PhotoImage(file=icon_path)
                # noinspection PyProtectedMember
                self.master.call("wm", "iconphoto", self.master._w, self.icon)
    
    
    class BrowserFrame(tk.Frame):
    
        def __init__(self, master, navigation_bar=None):
            self.navigation_bar = navigation_bar
            self.closing = False
            self.browser = None
            tk.Frame.__init__(self, master)
            self.bind("<FocusIn>", self.on_focus_in)
            self.bind("<FocusOut>", self.on_focus_out)
            self.bind("<Configure>", self.on_configure)
            self.focus_set()
    
        def embed_browser(self):
            window_info = cef.WindowInfo()
            rect = [0, 0, self.winfo_width(), self.winfo_height()]
            window_info.SetAsChild(self.get_window_handle(), rect)
            self.browser = cef.CreateBrowserSync(window_info,
                                                 url="file:///J:\q.htm") #todo
            assert self.browser
            self.browser.SetClientHandler(LoadHandler(self))
            self.browser.SetClientHandler(FocusHandler(self))
            self.message_loop_work()
    
        def get_window_handle(self):
            if self.winfo_id() > 0:
                return self.winfo_id()
            elif MAC:
                # On Mac window id is an invalid negative value (Issue #308).
                # This is kind of a dirty hack to get window handle using
                # PyObjC package. If you change structure of windows then you
                # need to do modifications here as well.
                # noinspection PyUnresolvedReferences
                from AppKit import NSApp
                # noinspection PyUnresolvedReferences
                import objc
                # Sometimes there is more than one window, when application
                # didn't close cleanly last time Python displays an NSAlert
                # window asking whether to Reopen that window.
                # noinspection PyUnresolvedReferences
                return objc.pyobjc_id(NSApp.windows()[-1].contentView())
            else:
                raise Exception("Couldn't obtain window handle")
    
        def message_loop_work(self):
            cef.MessageLoopWork()
            self.after(10, self.message_loop_work)
    
        def on_configure(self, _):
            if not self.browser:
                self.embed_browser()
    
        def on_root_configure(self):
            # Root <Configure> event will be called when top window is moved
            if self.browser:
                self.browser.NotifyMoveOrResizeStarted()
    
        def on_mainframe_configure(self, width, height):
            if self.browser:
                if WINDOWS:
                    ctypes.windll.user32.SetWindowPos(
                        self.browser.GetWindowHandle(), 0,
                        0, 0, width, height, 0x0002)
                elif LINUX:
                    self.browser.SetBounds(0, 0, width, height)
                self.browser.NotifyMoveOrResizeStarted()
    
        def on_focus_in(self, _):
            logger.debug("BrowserFrame.on_focus_in")
            if self.browser:
                self.browser.SetFocus(True)
    
        def on_focus_out(self, _):
            logger.debug("BrowserFrame.on_focus_out")
            if self.browser:
                self.browser.SetFocus(False)
    
        def on_root_close(self):
            if self.browser:
                self.browser.CloseBrowser(True)
                self.clear_browser_references()
            self.destroy()
    
        def clear_browser_references(self):
            # Clear browser references that you keep anywhere in your
            # code. All references must be cleared for CEF to shutdown cleanly.
            self.browser = None
    
    
    class LoadHandler(object):
    
        def __init__(self, browser_frame):
            self.browser_frame = browser_frame
    
        def OnLoadStart(self, browser, **_):
            if self.browser_frame.master.navigation_bar:
                self.browser_frame.master.navigation_bar.set_url(browser.GetUrl())
    
    
    class FocusHandler(object):
    
        def __init__(self, browser_frame):
            self.browser_frame = browser_frame
    
        def OnTakeFocus(self, next_component, **_):
            logger.debug("FocusHandler.OnTakeFocus, next={next}"
                         .format(next=next_component))
    
        def OnSetFocus(self, source, **_):
            logger.debug("FocusHandler.OnSetFocus, source={source}"
                         .format(source=source))
            return False
    
        def OnGotFocus(self, **_):
            """Fix CEF focus issues (#255). Call browser frame's focus_set
               to get rid of type cursor in url entry widget."""
            logger.debug("FocusHandler.OnGotFocus")
            self.browser_frame.focus_set()
    
    
    class NavigationBar(tk.Frame):
        def __init__(self, master):
            self.back_state = tk.NONE
            self.forward_state = tk.NONE
            self.back_image = None
            self.forward_image = None
            self.reload_image = None
    
            tk.Frame.__init__(self, master)
            resources = os.path.join(os.path.dirname(__file__), "resources")
    
            # Back button
            back_png = os.path.join(resources, "back"+IMAGE_EXT)
            if os.path.exists(back_png):
                self.back_image = tk.PhotoImage(file=back_png)
            self.back_button = tk.Button(self, image=self.back_image,
                                         command=self.go_back)
            self.back_button.grid(row=0, column=0)
    
            # Forward button
            forward_png = os.path.join(resources, "forward"+IMAGE_EXT)
            if os.path.exists(forward_png):
                self.forward_image = tk.PhotoImage(file=forward_png)
            self.forward_button = tk.Button(self, image=self.forward_image,
                                            command=self.go_forward)
            self.forward_button.grid(row=0, column=1)
    
            # Reload button
            reload_png = os.path.join(resources, "reload"+IMAGE_EXT)
            if os.path.exists(reload_png):
                self.reload_image = tk.PhotoImage(file=reload_png)
            self.reload_button = tk.Button(self, image=self.reload_image,
                                           command=self.reload)
            self.reload_button.grid(row=0, column=2)
    
            # Url entry
            self.url_entry = tk.Entry(self)
            self.url_entry.bind("<FocusIn>", self.on_url_focus_in)
            self.url_entry.bind("<FocusOut>", self.on_url_focus_out)
            self.url_entry.bind("<Return>", self.on_load_url)
            self.url_entry.bind("<Button-1>", self.on_button1)
            self.url_entry.grid(row=0, column=3,
                                sticky=(tk.N + tk.S + tk.E + tk.W))
            tk.Grid.rowconfigure(self, 0, weight=100)
            tk.Grid.columnconfigure(self, 3, weight=100)
    
            # Update state of buttons
            self.update_state()
    
        def go_back(self):
            if self.master.get_browser():
                self.master.get_browser().GoBack()
    
        def go_forward(self):
            if self.master.get_browser():
                self.master.get_browser().GoForward()
    
        def reload(self):
            if self.master.get_browser():
                self.master.get_browser().Reload()
    
        def set_url(self, url):
            self.url_entry.delete(0, tk.END)
            self.url_entry.insert(0, url)
    
        def on_url_focus_in(self, _):
            logger.debug("NavigationBar.on_url_focus_in")
    
        def on_url_focus_out(self, _):
            logger.debug("NavigationBar.on_url_focus_out")
    
        def on_load_url(self, _):
            if self.master.get_browser():
                self.master.get_browser().StopLoad()
                self.master.get_browser().LoadUrl(self.url_entry.get())
    
        def on_button1(self, _):
            """Fix CEF focus issues (#255). See also FocusHandler.OnGotFocus."""
            logger.debug("NavigationBar.on_button1")
            self.master.master.focus_force()
    
        def update_state(self):
            browser = self.master.get_browser()
            if not browser:
                if self.back_state != tk.DISABLED:
                    self.back_button.config(state=tk.DISABLED)
                    self.back_state = tk.DISABLED
                if self.forward_state != tk.DISABLED:
                    self.forward_button.config(state=tk.DISABLED)
                    self.forward_state = tk.DISABLED
                self.after(100, self.update_state)
                return
            if browser.CanGoBack():
                if self.back_state != tk.NORMAL:
                    self.back_button.config(state=tk.NORMAL)
                    self.back_state = tk.NORMAL
            else:
                if self.back_state != tk.DISABLED:
                    self.back_button.config(state=tk.DISABLED)
                    self.back_state = tk.DISABLED
            if browser.CanGoForward():
                if self.forward_state != tk.NORMAL:
                    self.forward_button.config(state=tk.NORMAL)
                    self.forward_state = tk.NORMAL
            else:
                if self.forward_state != tk.DISABLED:
                    self.forward_button.config(state=tk.DISABLED)
                    self.forward_state = tk.DISABLED
            self.after(100, self.update_state)
    
    
    if __name__ == '__main__':
        logger.setLevel(_logging.INFO)
        stream_handler = _logging.StreamHandler()
        formatter = _logging.Formatter("[%(filename)s] %(message)s")
        stream_handler.setFormatter(formatter)
        logger.addHandler(stream_handler)
        logger.info("CEF Python {ver}".format(ver=cef.__version__))
        logger.info("Python {ver} {arch}".format(
                ver=platform.python_version(), arch=platform.architecture()[0]))
        logger.info("Tk {ver}".format(ver=tk.Tcl().eval('info patchlevel')))
        assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this"
        sys.excepthook = cef.ExceptHook  # To shutdown all CEF processes on error
        root = tk.Tk()
        app = MainFrame(root)
        # Tk must be initialized before CEF otherwise fatal error (Issue #306)
        cef.Initialize()
        
        app.mainloop()
        cef.Shutdown()
    

    【讨论】:

    • 在 Linux 中编写代码并编译为 .exe/.deb 后,它会在 Windows 中运行吗?
    • @NoahJ.Standerson 没有尝试过。最好自己试试
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-17
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多