1

In my tkinter application I would like the user to be able to click on a button or menu option in order to be given more information. I would like to be able to open another window on which would be about an A4 page of detailed information.

Ideally I would like this to be nicely formatted, with headings, sub-headings, bulleted lists, numbered lists and bold & italic etc.

I naïvely thought that this would simply be a case of producing a markdown or RTF document and then finding the appropriate tk widget to display it. Unless I'm missing something, it seems that it's not that easy.

From what I've found out so far (for example from this thread) I think my main options are either to laboriously create tk labels with each different type of formatting; or to use some sort of python / HTML such as tk-html-widgets. I'm not a big fan of HTML (or at least of writing it), but if that's the only way...

Before I go to the pain of either of these approaches I thought I would ask here first. Is this indeed the only way of achieving a nicely formatted page of text? Is there a better way?

Looking forward to some suggestions!

Thanks...

ArthurDent
  • 149
  • 2
  • 10
  • does it have to be texts itself? What about image? the user wont be able to select and copy tho – Delrius Euphoria Jul 26 '20 at 10:49
  • I guess not, but you would need to write the code for everything you like. In this post he trys to create a text editor, for something like a word-document you would need indeed much more code and time. https://stackoverflow.com/questions/62978589/how-to-get-tab-id-to-set-it-as-active-tab – Thingamabobs Jul 26 '20 at 10:52
  • Also note that tkinter and python works perfectly fine with unicode symbols. – Thingamabobs Jul 26 '20 at 10:55
  • 1
    @CoolCloud, thanks. I did consider using an image. The problem is that the document could (potentially) be quite long and it's difficult to capture (and them would be difficult to display properly on a small screen and might need to scroll). Editing the text (if required) would be a pain and, as you point out, the user won't be able to select text (although I don't think this would be a major problem). – ArthurDent Jul 26 '20 at 11:18
  • @Atlas435, thanks. With regard to your first comment, I don't need the user to be able to edit the text - I just want to display a page or so of nicely formatted text that the user can read easily. However... I hadn't considered unicode. I am going to have a play with this. I know you can do bold, italic etc., but don't know how easy it is to do lists etc..... Thanks though... – ArthurDent Jul 26 '20 at 11:21
  • Then I would start with a canvas – Thingamabobs Jul 26 '20 at 11:22
  • Do you need precise placement of text (ie: exact pixel locations)? If so, the canvas as the ability to add text at any location. If you don't need pixel-precise layout, a text widget would be much easier. – Bryan Oakley Jul 26 '20 at 14:00
  • @BryanOakley No. I certainly don't need exact pixel locations. All I want to do is to display some (reasonably pretty) text. I had hoped that I could take a Word / Markdown / RTF document (about a page or so) and display it i somehow in a window... Is there a better way of doing this? – ArthurDent Jul 26 '20 at 14:21

2 Answers2

9

Tkinter doesn't directly support markdown or rst or any other rich format. However, the text widget has the ability to apply formatting to regions of text via tags. Tags are remarkably powerful, allowing you to apply fonts, colors, margins, spacing, and other attributes to a range of characters.

The text widget doesn't directly support bulleted lists, but they are easy to create with just a few lines of code and some custom tags.

Here's a quick example that shows how to configure and use a few tags, and how to create a method that creates bulleted items.

import tkinter as tk
from tkinter import font as tkFont

class RichText(tk.Text):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        default_font = tkFont.nametofont(self.cget("font"))

        em = default_font.measure("m")
        default_size = default_font.cget("size")
        bold_font = tkFont.Font(**default_font.configure())
        italic_font = tkFont.Font(**default_font.configure())
        h1_font = tkFont.Font(**default_font.configure())

        bold_font.configure(weight="bold")
        italic_font.configure(slant="italic")
        h1_font.configure(size=int(default_size*2), weight="bold")

        self.tag_configure("bold", font=bold_font)
        self.tag_configure("italic", font=italic_font)
        self.tag_configure("h1", font=h1_font, spacing3=default_size)

        lmargin2 = em + default_font.measure("\u2022 ")
        self.tag_configure("bullet", lmargin1=em, lmargin2=lmargin2)

    def insert_bullet(self, index, text):
        self.insert(index, f"\u2022 {text}", "bullet")

if __name__ == "__main__":
    root = tk.Tk()
    text = RichText(root, width=40, height=15)
    text.pack(fill="both", expand=True)

    text.insert("end", "Rich Text Example\n", "h1")
    text.insert("end", "Hello, world\n\n")
    text.insert_bullet("end", "Item 1\n")
    text.insert_bullet("end", "Item 2\n")

    text.insert("end", "\n")
    text.insert("end", "This line is bold\n", "bold")
    text.insert("end", "This line is italicized\n", "italic")

    root.mainloop()

screenshot

For an example of a text widget that has a function for applying formatting to words that match a regular expression see this answer: https://stackoverflow.com/a/3781773/7432

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Well, @Bryan Oakley, Thank you!. With the greatest of respect to atlas435, whose answer was extremely helpful, this is far closer to what I had hoped for. It is still not quite the "my_widget = tk.display_that_ready_made_document_you_already_had("myBeautifulDocument.md")" that I would have liked to find; but, now that I have it set up, it's far easier than the alternatives. Thank you again! – ArthurDent Jul 27 '20 at 11:10
  • It's fairly the better answer to your Question @ArthurDent . I wasn't working much with the text_widget, so I learned something new with the tag option too. – Thingamabobs Jul 27 '20 at 13:36
2

I would start like in the code below, there you can draw your text and place it how you like.

import tkinter as tk
from tkinter import font as tkfont

root = tk.Tk()

class MyDocument(tk.Canvas):
    def __init__(self, master):
        tk.Canvas.__init__(self,master)
        self.master = master
        
        self.configure(width=500, height=500, relief='sunken', bd=2,
                       background='white')

        self.Headline(100,100, self, "I'm the headline")
        self.txt_listed(50,150, self, 'Hi',"I'm",'listed')
        self.mixed(50,250,self, normal = 'This bit is ',
                   bold = 'really',
                   normal2 = 'important')

    class Headline(object):
        def __init__(self,x,y,canvas, text):
            self.font = tkfont.Font(family="Helvetica", size=20,
                                    weight="bold",underline=1)
            canvas.create_text(x,y, text=text,anchor='nw',
                               font=self.font, fill='black',
                               activefill='green')
    class txt_listed(object):
        def __init__(self,x,y,canvas,*text):
            self.font = tkfont.Font(family="Arial", size=11,underline=1)
            add=20
            for txt in text:
                canvas.create_text(x,y+add, text=f"•  {txt}",anchor='nw')
                add = add+20
    
    class mixed(object):
        def __init__(self,x,y,canvas, **text):
            
            forward = x
            self.bold_font = tkfont.Font(family="Arial", size=10,
                                         weight="bold")
            self.normal_font = tkfont.Font(family="Arial", size=10)
            
            for key,value in text.items():
                if 'bold' in str(key):
                    key = canvas.create_text(forward,y,text=value, font=self.bold_font)
                    forward = canvas.bbox(key)[2]+30
                if 'normal' in str(key):
                    key = canvas.create_text(forward,y,text=value, font=self.normal_font)
                    forward = canvas.bbox(key)[2]+15        

        


text_me = MyDocument(root)
text_me.pack()

root.mainloop()

the mixed class dosent work stabel but just to give you the idea what is possible. Maybe its easier to make italic and bold classes and place them as you like instead of the math. Cause keep in mind that charackters arent in pixels, so even if you find something with another font it will be messed up.

Have fun with it.

Thingamabobs
  • 7,274
  • 5
  • 21
  • 54
  • 1
    Wow. Thanks! I like this approach because I can create a module where I can define classes for each element (Heading, subheading, body text etc.) and then import them when needed. I'm not sure however, how I would do bulleted lists, indented paragraphs etc. and I don't don't know how I could make the text flow when the window gets resized. But it's a great start. Also - how would you alter just some words in the text (e.g "This bit is **really** important!")? Thanks again. – ArthurDent Jul 26 '20 at 12:00