R4PH43L
R4PH43L

Reputation: 2200

Selection of Object not working with context Menu

I have created a little MessageBox that shows messages like an email-client. Just like in an email-client the user should be able to mark messages as read or unread using a context menu. Building that one up is not a problem. Displaying messages in the messages window based on selection neither.

But when i use the context menu to mark messages as read or unread, it is always the last message that is highlighted.

I was unable to find a solution for that so far. Couldn't find and answer or hint neither on ddg nor on SO.

A small overview for the program flow: - messages are displayed / represented via MessageObjects. - they are stored inside a list called self.__messages inside the InBox-Object - selection is done by left-clicking a message object, context menu is opened using right-click. - the selection is done inside the InBox object in order to allow only single selection - the context menu is registered and run only inside the MessageObject.

Unfortunately i was not able to strip the example below more to get a still working code.

Any help, hints or ideas are very welcome as i cannot figure out where the error comes from.

    from sys import hexversion, modules

    if hexversion>0x03000000:
        import Tkinter
        modules['Tkinter'] = tkinter
        modules['ttk']     = tkinter.ttk

    import Tkinter as tk
    import ttk

    __DEBUG__=2

    class MessageObject(ttk.Frame):
        def __init__(self, master=None, **options):
            ttk.Frame.__init__(self, master, **options)
            self.pack(expand=True, fill=tk.BOTH)

            self.master=master
            self.takefocus=True

            self.read=tk.BooleanVar()
            self.sender=tk.StringVar()
            self.date=tk.StringVar()
            self.text=tk.StringVar()
            self.subject=tk.StringVar()
            self.mID=tk.IntVar()

            self.__loadUI()

        def __loadUI(self, event=None):
            self.grid_columnconfigure(0, weight=1)
            self.grid_columnconfigure(0, weight=1)

            self.Labels=[]

            self.Labels.append(ttk.Label(self, text="Subject:", justify=tk.LEFT))
            self.Labels.append(ttk.Label(self, text="Sender:", justify=tk.LEFT))

            self.Labels.append(ttk.Label(self, textvariable=self.subject, justify=tk.RIGHT))
            self.Labels.append(ttk.Label(self, textvariable=self.sender, justify=tk.RIGHT))

            self.Labels[0].grid(row=0, column=0, sticky=tk.N+tk.SW)
            self.Labels[1].grid(row=1, column=0, sticky=tk.N+tk.SW)

            self.Labels[2].grid(row=0, column=1, sticky=tk.N+tk.SE)
            self.Labels[3].grid(row=1, column=1, sticky=tk.N+tk.SE)

            #Context Menu
            self.__cMenu=tk.Menu(self, tearoff=0)
            self.__cMenu.add_command(label="Mark as read", command=lambda: self.Read(True))
            self.__cMenu.add_command(label="Mark as unread", command=lambda: self.Read(False))

            self.bind_all("", self.__showContextMenu)

        def __showContextMenu(self, event=None):
            self.__cMenu.post(event.x_root, event.y_root)

        def Read(self, value=False):
            self.read.set(value)
            if value:
                self.config(style='Read.TFrame')
                for Label in self.Labels:
                    Label.config(style='Read.TLabel')
            else:
                self.config(style='Unread.TFrame')
                for Label in self.Labels:
                    Label.config(style='Unread.TLabel')

        def Selected(self, value=False):
            #TODO: keep Font, only change for- and background
            if value:
                self.config(style='Selected.TFrame')
                for Label in self.Labels:
                    Label.config(style='Selected.TLabel')
            else:
                for Label in self.Labels:
                    if self.read.get():
                        Label.config(style='Read.TLabel')
                        self.config(style='Read.TFrame')
                    else:
                        Label.config(style='Unread.TLabel')
                        self.config(style='Unread.TFrame')

    class InBox(ttk.Frame):
        def __init__(self, master=None, **options):
            ttk.Frame.__init__(self, master, **options)
            self.pack(expand=True, fill=tk.BOTH)

            self.master=master

            self.grid_columnconfigure(0, weight=2)
            self.grid_columnconfigure(1, weight=0)
            self.grid_columnconfigure(2, weight=0)
            self.grid_columnconfigure(3, weight=0)

            self.grid_rowconfigure(0, weight=0)
            self.grid_rowconfigure(1, weight=1)
            self.grid_rowconfigure(2, weight=1)
            self.grid_rowconfigure(3, weight=1)

            self.__text=tk.StringVar()
            self.__currentSubject=tk.StringVar()
            self.__currentTimestamp=tk.StringVar()

            self.__itemCount=0
            self.__messages=[]

            self.__loadUI()

        def __loadUI(self):
            ttk.Label(self, text="Inbox:", anchor=tk.W).grid(row=0, column=0, sticky=tk.NW+tk.SE)
            ttk.Label(self, text="Selected Message:", anchor=tk.W).grid(row=0, column=1, sticky=tk.NW+tk.SE)

            ttk.Label(self, textvariable=self.__currentSubject).grid(row=0, column=2, sticky=tk.NW)
            ttk.Label(self, textvariable=self.__currentTimestamp).grid(row=0, column=3, sticky=tk.NW)

            self.InBoxList=ttk.Frame(self)
            self.InBoxList.grid(row=1, rowspan=3, column=0, sticky=tk.NW+tk.SE)

            self.TextDisplay=tk.Text(self)
            self.TextDisplay.grid(row=1, rowspan=3, column=1, columnspan=3, sticky=tk.NW+tk.SE)

            self.__messages=[]

        def insert(self,index=0, mID=0, sender=None, subject=None, message=None, date=None, Read=False):
            if sender!=None and subject!=None and  message!=None:
                self.InBoxList.grid_rowconfigure(self.__itemCount, weight=0)

                self.__messages.append(MessageObject(self.InBoxList))
                self.__messages[-1].mID.set(mID)
                self.__messages[-1].text.set(message)
                self.__messages[-1].sender.set(sender)
                self.__messages[-1].subject.set(subject)
                self.__messages[-1].date.set(date)
                self.__messages[-1].Read(Read)
                self.__messages[-1].grid(row=self.__itemCount, column=0, sticky=tk.NE+tk.SW)
                self.__messages[-1].bind_all("", self.__selectionChanged)

                self.__itemCount=self.__itemCount+1
                return 0
            else:
                return 1

        def Messages(self):
            return self.__messages

        def __selectionChanged(self, event):
            try:
                item=event.widget

                #Empty Message Display
                self.TextDisplay.delete(1.0, tk.END)
                self.__currentSubject.set("")
                self.__currentTimestamp.set("")

                #Deactivate all but the selected one (the one calling)
                for i in range(0,len(self.__messages)):
                    print("EventItem: %s"%item)
                    print("Message:   %s"%self.__messages[i])
                    found=False

                    if "%s"%self.__messages[i] in "%s"%item :
                        self.__messages[i].Selected(True)

                        #Refresh Message Display
                        self.TextDisplay.insert(tk.END, self.__messages[i].text.get())
                        self.__currentSubject.set(self.__messages[i].subject.get())
                        self.__currentTimestamp.set(self.__messages[i].date.get())
                    else:
                        self.__messages[i].Selected(False)

            except Exception as ex:
                #Not in clickable Area?
                if __DEBUG__>1: print("Got click but cannot link it to item - %s"%ex)
                pass

        def delete(self, index=0, messageObject=None):
            self.__logger.debug("")
            if messageObject!=None:
                for i in range(len(self.__itemCount)):
                    if self.__messages[i]==messageObject:
                        self.__messages[i].destroy()
            else:
                self.__messages[index].destroy()
            self.__itemCount=self.__itemCount-1

    if __name__=="__main__":
       # Styling
        __msg_style=ttk.Style()

        __msg_style.configure('Read.TFrame', background='#ffffff')
        __msg_style.configure('Read.TFrame', foreground='#000000')
        __msg_style.configure('Read.TFrame', font='Segue\ UI 8 normal')

        __msg_style.configure('Unread.TFrame', background='#fafafa')
        __msg_style.configure('Unread.TFrame', foreground='#0000ff')
        __msg_style.configure('Unread.TFrame', font='Segue\ UI 8 bold')

        __msg_style.configure('Selected.TFrame', background='#8888ff')
        __msg_style.configure('Selected.TFrame', foreground='#ffffff')
       #__msg_style.configure('Selected.TFrame', font='Segue\ UI 8 italic')

        __msg_style.configure('Read.TLabel', background='#ffffff')
        __msg_style.configure('Read.TLabel', foreground='#000000')
        __msg_style.configure('Read.TLabel', font='Segue\ UI 8 normal')

        __msg_style.configure('Unread.TLabel', background='#fafafa')
        __msg_style.configure('Unread.TLabel', foreground='#0000ff')
        __msg_style.configure('Unread.TLabel', font='Segue\ UI 8 bold')

        __msg_style.configure('Selected.TLabel', background='#8888ff')
        __msg_style.configure('Selected.TLabel', foreground='#ffffff')
       #__msg_style.configure('Selected.TLabel', font='Segue\ UI 8 italic')

        app=InBox()
        app.insert(sender="Hans Dampf", subject="Read Subject", message="Lorem ipsum Read", date="14/01/2013", Read=True)
        app.insert(sender="Test Name", subject="UnRead Subject", message="Lorem ipsum UnRead", date="14/01/2016", Read=False)

        app.bind_all("", app.destroy)
        app.mainloop()

Upvotes: 0

Views: 81

Answers (1)

R4PH43L
R4PH43L

Reputation: 2200

I managed making it working. It seems I did not get the documentation properly.

The widget that raised the Event was not the instance of the widget MessageObject but the whole application as bind_all was used.

But Tkinter also allows you to create bindings on the class and application level; in fact, you can create bindings on four different levels:

  • the widget instance, using bind.

  • the widget’s toplevel window (Toplevel or root), also using bind.

  • the widget class, using bind_class (this is used by Tkinter to provide standard bindings).

  • the whole application, using bind_all.

I slightly adapted the code to:

self.bind("<Button-3>", self.__showContextMenu)
for label in self.Labels:
    label.bind("<Button-3>", self.__showContextMenu)

Now the instance of the widget calls the handler and everything works.

Upvotes: 1

Related Questions