1

I'm trying to do something with classes in Python (I come from a procedural languages background). Trying to create a version of tkinter's Label widget supporting a couple of new methods to manipulate the text of the label.

My problem is that I can't get the label to actually be visible on the screen.

Here's the code:

from tkinter import *

DEFAULT_BG = '#f0f0f0'


class cngMsg(Label):
    """Message Display label"""
    def __init__(self, parent, w, h):
        """Init the Message Label"""
        self.parent = parent
        Label.__init__(self, parent)
        self.msgText = "Hello World"
        self.msgLabel = Label(parent, text=self.msgText)
        self.msgLabel.config(height=h, width=w, bg=DEFAULT_BG)

    def clear(self):
        self.msgText = ""
        print(len(self.msgText))
        self.msgLabel.config(text=self.msgText)

    def newMessage(self, message):
        print("about to display <" + message + ">")
        self.msgText = message
        print(len(self.msgText))
        self.msgLabel.config(text=self.msgText)

    def show(self, message, sameLine=None):
        if (not sameLine) and len(self.msgText) > 0:
            self.msgText += '/n'
        print("about to show: <" + message + ">")
        self.msgText = self.msgText + message
        print(len(self.msgText))
        self.msgLabel.config(text=self.msgText)


#Root Stuff

if __name__ == "__main__":

    app = Tk()
    app.title("Message Test")

    # this is the start of the application
    print("initialise the Message Test")

    gMsg = cngMsg(app, 60, 20)
    gMsg.pack()
    gMsg.newMessage("new message")
    gMsg.show("this is a test")
    gMsg.show("second test")

    app.mainloop()

The debug print messages appear on the console but the application window doesn't display the Label.

martineau
  • 119,623
  • 25
  • 170
  • 301
LeeS
  • 13
  • 4
  • Each `cngMsg` is a Label that contains no text (or any other attributes, for that matter). Instead, it *contains* another Label with actual text, but you never call `.pack()` on that Label to make it visible. Basically, every reference to `self.msgLabel` should simply be `self`; there's no point in making your object a subclass of Label otherwise. – jasonharper Jan 15 '21 at 16:58
  • thanks to Bryan Oakley and martineau for speedy and clear answers. I can see what I did wrong now thanks. Aslo appreciate the references for further reading – LeeS Jan 16 '21 at 00:32

2 Answers2

0

GUI programming requires using a non-procedural paradigm since they are user-input driven. The question Tkinter — executing functions over time discusses this and has some sample code.

Personally I have often found it useful when creating GUI apps to think of them as FSMs (Finite State Machines) in which user inputs cause them to change their state.

Here's how to do something similar to what I think you were trying to in your sample code which is based on the @Bryan Oakley's answer to the linked question (updated to Python 3). It also shows the proper way to subclass tkinter classes. In addition it mostly follows the PEP 8 - Style Guide for Python Code guideline, which I strongly suggest your read and start following.

from tkinter import *

DEFAULT_BG = '#f0f0f0'
DEFAULT_MSG_TEXT = "Hello World"
DELAY = 1000  # Milliseconds.


class CngMsg(Label):
    """Message Display label"""
    def __init__(self, parent, w, h):
        # Intialize with default text and background color.
        super().__init__(parent, text=DEFAULT_MSG_TEXT, height=h, width=w, bg=DEFAULT_BG)

    def clear(self):
        self.config(text='')

    def newMessage(self, message):
        self.config(text=message)

    def show(self, message, same_line=False):
        text = self.cget('text')  # Get value of current option.
        if not same_line and text:
            text += '\n'
        text += message
        self.config(text=text)


class MyApp(Tk):
    def __init__(self):
        super().__init__()
        self.frame = Frame(self)
        self.frame.pack()

        self.test_msg = CngMsg(self.frame, 60, 20)
        self.test_msg.pack()

        self.state = 0
        self.do_test()

    def do_test(self):
        if self.state == 0:
            self.test_msg.newMessage("start message")
            self.state = 1
        elif self.state == 1:
            self.test_msg.show("this is a test")
            self.state = 2
        elif self.state == 2:
            self.test_msg.show("second test")
            self.state = 3
        elif self.state == 3:
            self.test_msg.clear()
            self.test_msg.show("TEST COMPLETED")
            self.state = -1  # Enter final state.
        elif self.state != -1:
            self.quit()  # Stop mainloop.
            raise RuntimeError("Unknown state encountered")

        if self.state != -1: # Not final state?
            self.after(DELAY, self.do_test)  # Schedule another call.


if __name__ == "__main__":
    root = MyApp()
    root.title("Message Test")
    root.mainloop()

martineau
  • 119,623
  • 25
  • 170
  • 301
  • @matrineau - should there be a test around the `self.after(DELAY, self.do_test)` once the `self.state` reaches -1? – LeeS Jan 16 '21 at 11:07
0

Your cngMsg class is creating two labels. The first is the instance of that class itself. The second is created when you do self.msgLabel = Label(parent, text=self.msgText). This second label is a child of the cngMsg label. Since you do not call pack, place, or grid on that second label it will not appear anywhere.

In your newMessage method you are updating the text in the invisible label instead of the actual label.

You don't need this second label, and in newMessage you should configure itself like this:

def newMessage(self, message):
    self.msgText = msgText
    self.configure(text=self.msgText)

Similarly, show and clear should be defined in a similar way:

def clear(self):
    self.msgText = ""
    self.config(text=self.msgText)

def show(self, message, sameLine=None):
    if (not sameLine) and len(self.msgText) > 0:
        self.msgText += '/n'
    self.msgText = self.msgText + message
    self.config(text=self.msgText)
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685