ddaniels
ddaniels

Reputation: 250

Python 3.3 Tkinter change LabelFrame Position

I am trying to put a labelframe next to a few other widgets but I cannot figure it out at all. Currently it displays underneath all the other widgets when I use the .pack() method. When I try to configure the position using .grid(column=x, row=y) then the program runs but hangs and no GUI comes up.

Here is the relevent code

def createWidgets(self):
    # Create entrybox and align to grid
    self.send_entry = tk.Entry(self)
    self.send_entry.grid(row=0,column=0)
    # Create button,allign to grid, get xml
    self.change_sub = tk.Button(self,text='Change Subreddit',padx=5, pady=5, command=lambda :self.getXML(self.send_entry.get())).grid(row=0 , column=3)
    # Create scrollbar on Y-Axis
    self.lb_scrollY = tk.Scrollbar(self,orient=tk.VERTICAL)
    # On grid next to Listbox(sticky means fill whole row
    self.lb_scrollY.grid(row=1,column=4,sticky=tk.NS,rowspan=6)
    # Create Listbox and get Y from scrollbar
    self.thread_lb = tk.Listbox(self,yscrollcommand=self.lb_scrollY.set)
    # Calls function whenever a new item is selected
    self.thread_lb.bind('<<ListboxSelect>>',self.updateSelected)
    # scrolly will change the view of listbox
    self.lb_scrollY['command']=self.thread_lb.yview
    self.thread_lb.grid(row=1,column=0,sticky=tk.NS+tk.EW,columnspan=4,rowspan=3)
    self.threadFrame = tk.LabelFrame(main,text='Reddit')

    self.threadLabelTitle = tk.Label(self.threadFrame,textvariable=self.threadTitle,wraplength=200).grid(row=1,column=2,sticky= tk.EW)
    self.threadLabelAuth = tk.Label(self.threadFrame, textvariable=self.threadAuth,wraplength=200).grid(row=2,column=2,sticky = tk.EW)
    self.threadLabelPub = tk.Label(self.threadFrame, textvariable=self.threadPub,wraplength=200).grid(row=3,column=2,sticky = tk.EW)
    self.threadLabelArtLink = tk.Label(self.threadFrame, textvariable=self.threadArtLink,wraplength=200).grid(row=4,column=2,sticky = tk.EW)
    self.threadLabelThreadLink = tk.Label(self.threadFrame, textvariable=self.threadLink,wraplength=200).grid(row=5,column=2,sticky = tk.EW)
    self.threadImgLink = tk.Label(self.threadFrame, textvariable=self.threadImg,wraplength=200).grid(row=6,column=2,sticky = tk.EW)
    # self.columnconfigure(2,minsize=200)
    # self.rowconfigure(1,minsize=175)
    self.threadFrame.pack()
    self.QUIT = tk.Button(self, text="QUIT", fg="red", command=main.destroy,padx=5, pady=5).grid(row=7)

And here is the full program so you can run it if you like.

import xml.etree.ElementTree as ET
import webbrowser,time,urllib.request,re
import tkinter as tk
import urllib

main = tk.Tk()
class Application(tk.Frame):


    def __init__(self, master=None):
        self.threadTitle = tk.StringVar()
        self.threadAuth = tk.StringVar()
        self.threadPub = tk.StringVar()
        self.threadArtLink = tk.StringVar()
        self.threadLink = tk.StringVar()
        self.threadImg = tk.StringVar()
        self.threadArtLink.set('Click something to display thread info')

        # Intializes tkinter gui framework
        tk.Frame.__init__(self, master)
        # Packs widgets needed
        self.pack()
        # Creates the widgets functions
        self.createWidgets()
        # Intializes the man rss.xml
        self.initial()
        # self.threadLabelArtLink = None
        # self.threadLabelTitle = None
        # self.threadLabelThreadLink = None
        # self.threadLabelArtLink = None
        # self.threadImgLink = None

    def createWidgets(self):
        # Create entrybox and align to grid
        self.send_entry = tk.Entry(self)
        self.send_entry.grid(row=0,column=0)
        # Create button,allign to grid, get xml
        self.change_sub = tk.Button(self,text='Change Subreddit',padx=5, pady=5, command=lambda :self.getXML(self.send_entry.get())).grid(row=0 , column=3)
        # Create scrollbar on Y-Axis
        self.lb_scrollY = tk.Scrollbar(self,orient=tk.VERTICAL)
        # On grid next to Listbox(sticky means fill whole row
        self.lb_scrollY.grid(row=1,column=4,sticky=tk.NS,rowspan=6)
        # Create Listbox and get Y from scrollbar
        self.thread_lb = tk.Listbox(self,yscrollcommand=self.lb_scrollY.set)
        # Calls function whenever a new item is selected
        self.thread_lb.bind('<<ListboxSelect>>',self.updateSelected)
        # scrolly will change the view of listbox
        self.lb_scrollY['command']=self.thread_lb.yview
        self.thread_lb.grid(row=1,column=0,sticky=tk.NS+tk.EW,columnspan=4,rowspan=3)
        self.threadFrame = tk.LabelFrame(main,text='Reddit')

        self.threadLabelTitle = tk.Label(self.threadFrame,textvariable=self.threadTitle,wraplength=200).grid(row=1,column=2,sticky= tk.EW)
        self.threadLabelAuth = tk.Label(self.threadFrame, textvariable=self.threadAuth,wraplength=200).grid(row=2,column=2,sticky = tk.EW)
        self.threadLabelPub = tk.Label(self.threadFrame, textvariable=self.threadPub,wraplength=200).grid(row=3,column=2,sticky = tk.EW)
        self.threadLabelArtLink = tk.Label(self.threadFrame, textvariable=self.threadArtLink,wraplength=200).grid(row=4,column=2,sticky = tk.EW)
        self.threadLabelThreadLink = tk.Label(self.threadFrame, textvariable=self.threadLink,wraplength=200).grid(row=5,column=2,sticky = tk.EW)
        self.threadImgLink = tk.Label(self.threadFrame, textvariable=self.threadImg,wraplength=200).grid(row=6,column=2,sticky = tk.EW)
        # self.columnconfigure(2,minsize=200)
        # self.rowconfigure(1,minsize=175)
        self.threadFrame.pack()
        self.QUIT = tk.Button(self, text="QUIT", fg="red", command=main.destroy,padx=5, pady=5).grid(row=7)


    def updateSelected(self, event):
        # getting selected listbox item
        i=self.thread_lb.curselection()
        # Returns tuple that must be split
        x,y,z = re.split("\D+",str(i))
        y=int(y)
        print(self.threadTitleList[y])
        print(self.threadPubDateList[y])
        print(self.threadLinkList[y])
        print(self.threadDescList[y])
        self.threadTitle.set(self.threadTitleList[y])
        self.threadAuth.set('Will have poster here')
        self.threadPub.set(self.threadPubDateList[y])
        self.threadArtLink.set(self.threadLinkList[y])
        self.threadLink.set(self.threadDescList[y])
        self.threadImg.set('Will put image here')

        # # threadTitle = self.threadTitleList[y]
        # print(self.threadLabelTitle["text"])
        # # self.threadLabelTitle['text']=threadTitle
        # self.threadLabelAutPub['text']=self.threadPubDateList[y]
        # self.threadImgLink['text']=self.threadLinkList[y]
        # self.threadLabelThreadLink['text']=self.threadDescList[y]
        # main.update()

    def descStripper(self,desc):
        # Intialize values
        l1,l2,l2Start = 0,0,0
        t1,t2,t2start = 0,0,0
        link = ""
        thread = ""

        # Where to start looking for each in description element
        l1=int(desc.find('<br/> <a href="'))
        t1=int(desc.find('</a> <a href="'))

        # If both of the tags are found then continue
        if l1 != -1 and t1 != -1:
            # Start looking for end of quotes 16 characters from beginning of tag
            l2Start = l1+16
            l2=int(desc.find('"',l2Start))
            # Link is created from what is in the quotes
            link = desc[l1+15:l2]

            # Same as above but to find thread link
            t2start = t1+15
            t2=int(desc.find('"',t2start))
            thread = desc[t1+14:t2]
            return link,thread
        else:
            # If it can't find one it will return an error
            link = "Couldn't find the stuff :("
            thread = "Couldn't find the thread link :("
            return link, thread

    def lbPopulator(self,title,pub,link):
        # Delete old entries from listbox
        self.thread_lb.delete(0,tk.END)
        # Iterate through all the items and append them to the listbox
        for item in title:
            self.thread_lb.insert(tk.END,item)

    def getXmlData(self):
        # Intialize lists
        self.threadPubDateList = []
        self.threadTitleList = []
        self.threadLinkList = []
        self.threadDescList = []
        self.threadThumbNailList = []
        # Use the downloaded rss.xml for XML parsing
        tree=ET.parse('rss.xml')
        # define root as the base of the XML parsing tree
        root=tree.getroot()
        for channel in root:
            # Iterate through all the channels
            for SubChannel in channel:
                # Iterate through all the items in the channel
                if SubChannel.tag == 'item':
                    # If the SubChannel is called item then search for the items below
                    for threadInfo in SubChannel:
                        # iterate through all the items in the 'item'
                        if threadInfo.tag == 'title':
                            # append the tag from the title to the list
                            self.threadTitleList.append(threadInfo.text)
                        if threadInfo.tag == 'pubDate':
                            #  Append the pubdate info to the list but remove excess characters
                            self.threadPubDateList.append(threadInfo.text[:-6])
                        if threadInfo.tag == 'description':
                            # Pass all the information from the description to the stripper to get the useful
                            # information and links
                            link,thread = self.descStripper(threadInfo.text)
                            self.threadLinkList.append(link)
                            self.threadDescList.append(thread)
            # Populate the listbox with the newly generated lists
        self.lbPopulator(self.threadTitleList,self.threadPubDateList,self.threadLinkList)

    def getXML(self,subreddit):
        try:
            # Try to download the xml file using the user input subreddit
            url = 'http://www.reddit.com'+subreddit+'.rss'
            source = urllib.request.urlretrieve(url,'rss.xml')
            self.getXmlData()
        except urllib.error.HTTPError as err:
            # Error caused by reddit API limiting connections
            print('Too many requests-Try again')

    def initial(self):
        try:
            # Same as above but downloads the front page
            source = urllib.request.urlretrieve('http://www.reddit.com/.rss','rss.xml')
            self.getXmlData()
        except urllib.error.HTTPError as err:
            print('Too many requests-Trying again 3')
            # If error occurs program waits 3 seconds and then restarts
            time.sleep(3)
            self.__init__()

# main.geometry("350x400")
app = Application(master=main)
# Begins the applications GUI loop

app.mainloop()

Upvotes: 1

Views: 5533

Answers (2)

Bryan Oakley
Bryan Oakley

Reputation: 386362

There isn't enough information in your question to know exactly what you want, so I'm guessing you want the labelframe with the text "Reddit" to be to the right of all of the other widgets. is that right?

If so, the problem is because of these two lines:

self.pack()
...
self.threadFrame.pack()

Without any options, pack places things at the top of any empty space in the containing widget. So, self fills the top part of the window, leaving empty space below it. self.threadFrame is placed at the top of the empty space, which is why these two widgets appear stacked.

If you tried switching just the second statement to use grid, the GUI will hang just like you report, because you can't use both grid and pack when the two widgets have the same parent.

To get them side-by-side, a simple fix is to be explicit when you are packing:

self.pack(side="left", fill="both", expand=False)
...
self.threadFrame.pack(side="right", fill="both", expand=True)

My use of expand will cause the threadFrame to grow and shrink as you resize the window. I don't know if that's the behavior you want or not. You can play around with the expand options to see how they affect the window when you resize it.

You could just as easily use grid to get the exact same result, as long as you use grid for both:

self.grid(row=0, column=0, sticky="nsew")
...
self.threadFrame.grid(row=0, column=1, stick="nsew")
...
main.grid_columnconfigure(1, weight=1)
main.grid_rowconfigure(0, weight=1)

As you can see, with grid you have to do a little extra work to get the same resize behavior. This is why it's good to use both grid and pack in different parts of your application: pack is great when you want to put a couple of widgets side-by-side or in a vertical stack. grid is great when you have a more complex layout that can be specified in rows and columns. As I said earlier, however, just be sure you don't mix grid and pack for two or more widgets with the same parent.

Upvotes: 0

hamidfzm
hamidfzm

Reputation: 4695

Try this code and use place method instead of pack:

from tkinter import *
root=Tk()
mylabel = Label(root,text="This is my lablel.")
mylabel.place(x=30,y=30,height=20,width=100)

Upvotes: 3

Related Questions