Welcome, guest | Sign In | My Account | Store | Cart
# Version: 1.2
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print "Author's email: ", "61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex")

try:
    from Tkinter import Frame, Text
    from Tkconstants import *
    import tkFont
except ImportError:
    from tkinter import Frame, Text
    from tkinter.constants import *
    from tkinter import font as tkFont

class AutoResizedText(Frame):
    def __init__(self, master, width=0, height=0, family=None, size=None,*args, **kwargs):
        
        Frame.__init__(self, master, width = width, height= height)
        self.pack_propagate(False)

        self._min_width = width
        self._min_height = height

        self._textarea = Text(self, *args, **kwargs)
        self._textarea.pack(expand=True, fill='both')

        if family != None and size != None:
            self._font = tkFont.Font(family=family,size=size)
        else:
            self._font = tkFont.Font(family=self._textarea.cget("font"))

        self._textarea.config(font=self._font)

        # I want to insert a tag just in front of the class tag
        # It's not necesseary to guive to this tag extra priority including it at the beginning
        # For this reason I am making this search
        self._autoresize_text_tag = "autoresize_text_"+str(id(self))
        list_of_bind_tags = list(self._textarea.bindtags())
        list_of_bind_tags.insert(list_of_bind_tags.index('Text'), self._autoresize_text_tag)

        self._textarea.bindtags(tuple(list_of_bind_tags))
        self._textarea.bind_class(self._autoresize_text_tag, "<KeyPress>",self._on_keypress)

    def _on_keypress(self, event):
        self._textarea.focus_set()
        
        if event.keysym == 'BackSpace':
            self._textarea.delete("%s-1c" % INSERT)
            new_text = self._textarea.get("1.0", END)
        elif event.keysym == 'Delete':
            self._textarea.delete("%s" % INSERT)
            new_text = self._textarea.get("1.0", END)
        # We check whether it is a punctuation or normal key
        elif len(event.char) == 1:
            if event.keysym == 'Return':
                # In this situation ord(event.char)=13, which is the CARRIAGE RETURN character
                # We want instead the new line character with ASCII code 10
                new_char = '\n'
            else:
                new_char = event.char


            old_text = self._textarea.get("1.0", END)
            new_text = self._insert_character_into_message(old_text, self._textarea.index(INSERT), new_char)

        else:
            # If it is a special key, we continue the binding chain
            return
    
        # Tk Text widget always adds a newline at the end of a line
        # This last character is also important for the Text coordinate system
        new_text = new_text[:-1]

        self._fit_to_size_of_text(new_text)

        # Finally we insert the new character
        if event.keysym != 'BackSpace' and event.keysym != 'Delete':
            self._textarea.insert(INSERT, new_char)
        
        return "break"

    def _insert_character_into_message(self, message, coordinate, char):
        target_row, target_column = map( int, coordinate.split('.'))

        this_row = 1
        this_column = 0
        index = 0

        for ch in message:
            if this_row == target_row and this_column == target_column:
                message = message[:index] + char + message[index:]
                return message

            index += 1

            if ch == '\n':
                this_row += 1
                this_column = 0
            else:
                this_column += 1

    def _fit_to_size_of_text(self, text):
        number_of_lines = 0
        widget_width = 0

        for line in text.split("\n"):
            widget_width = max(widget_width,self._font.measure(line))
            number_of_lines += 1

        # We need to add this extra space to calculate the correct width
        widget_width += 2*self._textarea['bd'] + 2*self._textarea['padx'] + self._textarea['insertwidth']

        if widget_width < self._min_width:
            widget_width = self._min_width

        self._textarea.configure(height=number_of_lines)
        widget_height = max(self._textarea.winfo_reqheight(), self._min_height)

        self.config(width=widget_width, height=widget_height)

        # If we don't call update_idletasks, the window won't be resized before an insertion
        self.update_idletasks()

    @property
    def tag(self):
        return self._autoresize_text_tag

    def focus(self):
        self._textarea.focus()
        
    def bind(self, event, handler, add=None):
        self._textarea.bind(event, handler, add)

    def get(self, start, end=None):
        return self._textarea.get(start, end)

    def update(self, text):
        self._textarea.delete('1.0', 'end')        
        self._fit_to_size_of_text(text)
        self._textarea.insert('1.0', text)

if __name__ == '__main__':
    try:
        from Tkinter import Tk
    except ImportError:
        from tkinter import Tk

    root = Tk()
    auto_text = AutoResizedText(root, family="Arial",size=15, width = 100, height = 50)
    auto_text.pack()
    auto_text.focus()
    root.mainloop()

Diff to Previous Revision

--- revision 13 2016-11-20 00:34:11
+++ revision 14 2016-12-10 15:40:25
@@ -1,51 +1,55 @@
-# Version: 1.1
+# Version: 1.2
 # Author: Miguel Martinez Lopez
 # Uncomment the next line to see my email
 # print "Author's email: ", "61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex")
 
 try:
-    import Tkinter as Tk
+    from Tkinter import Frame, Text
+    from Tkconstants import *
     import tkFont
 except ImportError:
-    import tkinter as Tk
+    from tkinter import Frame, Text
+    from tkinter.constants import *
     from tkinter import font as tkFont
 
-class AutoResizedText(Tk.Frame):
+class AutoResizedText(Frame):
     def __init__(self, master, width=0, height=0, family=None, size=None,*args, **kwargs):
-
-        Tk.Frame.__init__(self, master, width = width, height= height)
+        
+        Frame.__init__(self, master, width = width, height= height)
         self.pack_propagate(False)
 
-        self.min_width = width
-        self.min_height = height
+        self._min_width = width
+        self._min_height = height
 
-        self.Text_widget = Tk.Text(self, *args, **kwargs)
-        self.Text_widget.pack(expand=True, fill='both')
+        self._textarea = Text(self, *args, **kwargs)
+        self._textarea.pack(expand=True, fill='both')
 
         if family != None and size != None:
-            self.font = tkFont.Font(family=family,size=size)
+            self._font = tkFont.Font(family=family,size=size)
         else:
-            self.font = tkFont.Font(family=self.Text_widget.cget("font"))
+            self._font = tkFont.Font(family=self._textarea.cget("font"))
 
-        self.Text_widget.config(font=self.font)
+        self._textarea.config(font=self._font)
 
         # I want to insert a tag just in front of the class tag
         # It's not necesseary to guive to this tag extra priority including it at the beginning
         # For this reason I am making this search
-        autoresizetext_tag = "autoresizetext_"+str(self)
-        list_of_bind_tags = list(self.Text_widget.bindtags())
-        list_of_bind_tags.insert(list_of_bind_tags.index('Text'), autoresizetext_tag)
+        self._autoresize_text_tag = "autoresize_text_"+str(id(self))
+        list_of_bind_tags = list(self._textarea.bindtags())
+        list_of_bind_tags.insert(list_of_bind_tags.index('Text'), self._autoresize_text_tag)
 
-        self.Text_widget.bindtags( tuple(list_of_bind_tags) )
-        self.Text_widget.bind_class(autoresizetext_tag, "<KeyPress>",self.update_textbox)
-    def update_textbox(self, event):
+        self._textarea.bindtags(tuple(list_of_bind_tags))
+        self._textarea.bind_class(self._autoresize_text_tag, "<KeyPress>",self._on_keypress)
 
+    def _on_keypress(self, event):
+        self._textarea.focus_set()
+        
         if event.keysym == 'BackSpace':
-            self.Text_widget.delete("%s-1c" % Tk.INSERT)
-            new_text = self.Text_widget.get("1.0", Tk.END)
+            self._textarea.delete("%s-1c" % INSERT)
+            new_text = self._textarea.get("1.0", END)
         elif event.keysym == 'Delete':
-            self.Text_widget.delete("%s" % Tk.INSERT)
-            new_text = self.Text_widget.get("1.0", Tk.END)
+            self._textarea.delete("%s" % INSERT)
+            new_text = self._textarea.get("1.0", END)
         # We check whether it is a punctuation or normal key
         elif len(event.char) == 1:
             if event.keysym == 'Return':
@@ -56,45 +60,26 @@
                 new_char = event.char
 
 
-            old_text = self.Text_widget.get("1.0", Tk.END)
-            new_text = self.insert_character_into_message(old_text, self.Text_widget.index(Tk.INSERT), new_char)
+            old_text = self._textarea.get("1.0", END)
+            new_text = self._insert_character_into_message(old_text, self._textarea.index(INSERT), new_char)
 
         else:
             # If it is a special key, we continue the binding chain
             return
-
+    
         # Tk Text widget always adds a newline at the end of a line
         # This last character is also important for the Text coordinate system
         new_text = new_text[:-1]
 
-        number_of_lines = 0
-        widget_width = 0
-
-        for line in new_text.split("\n"):
-            widget_width = max(widget_width,self.font.measure(line))
-            number_of_lines += 1
-
-        # We need to add this extra space to calculate the correct width
-        widget_width += 2*self.Text_widget['bd'] + 2*self.Text_widget['padx'] + self.Text_widget['insertwidth']
-
-        if widget_width < self.min_width:
-            widget_width = self.min_width
-
-        self.Text_widget.configure(height=number_of_lines)
-        widget_height = max(self.Text_widget.winfo_reqheight(), self.min_height)
-
-        self.config(width=widget_width, height=widget_height)
-
-        # If we don't update_idletasks(), the window won't be resized before the insertion
-        self.update_idletasks()
+        self._fit_to_size_of_text(new_text)
 
         # Finally we insert the new character
         if event.keysym != 'BackSpace' and event.keysym != 'Delete':
-            self.Text_widget.insert(Tk.INSERT, new_char)
-
+            self._textarea.insert(INSERT, new_char)
+        
         return "break"
 
-    def insert_character_into_message(self,message, coordinate, char):
+    def _insert_character_into_message(self, message, coordinate, char):
         target_row, target_column = map( int, coordinate.split('.'))
 
         this_row = 1
@@ -114,12 +99,54 @@
             else:
                 this_column += 1
 
+    def _fit_to_size_of_text(self, text):
+        number_of_lines = 0
+        widget_width = 0
+
+        for line in text.split("\n"):
+            widget_width = max(widget_width,self._font.measure(line))
+            number_of_lines += 1
+
+        # We need to add this extra space to calculate the correct width
+        widget_width += 2*self._textarea['bd'] + 2*self._textarea['padx'] + self._textarea['insertwidth']
+
+        if widget_width < self._min_width:
+            widget_width = self._min_width
+
+        self._textarea.configure(height=number_of_lines)
+        widget_height = max(self._textarea.winfo_reqheight(), self._min_height)
+
+        self.config(width=widget_width, height=widget_height)
+
+        # If we don't call update_idletasks, the window won't be resized before an insertion
+        self.update_idletasks()
+
+    @property
+    def tag(self):
+        return self._autoresize_text_tag
+
     def focus(self):
-        self.Text_widget.focus()
+        self._textarea.focus()
+        
+    def bind(self, event, handler, add=None):
+        self._textarea.bind(event, handler, add)
+
+    def get(self, start, end=None):
+        return self._textarea.get(start, end)
+
+    def update(self, text):
+        self._textarea.delete('1.0', 'end')        
+        self._fit_to_size_of_text(text)
+        self._textarea.insert('1.0', text)
 
 if __name__ == '__main__':
-    root = Tk.Tk()
+    try:
+        from Tkinter import Tk
+    except ImportError:
+        from tkinter import Tk
+
+    root = Tk()
     auto_text = AutoResizedText(root, family="Arial",size=15, width = 100, height = 50)
     auto_text.pack()
-    auto_text.Text_widget.focus()
+    auto_text.focus()
     root.mainloop()

History