2

I have been trying to design a little help tool for the game master of our DnD sessions that records the players' leveling up progress of stats and skills. I would like for these stats to be displayed with a progress bar, containing a percentage inside, underneath them and I am perfectly able to do that using a regular progress bar without any percentage in it:

progress bars without percentage

but as soon as I try to follow the examples I have found here for a progress bar with a percentage inside, they somehow connect to each other adding the percentages together and displaying the total in each progress bar

progress bars with percentage

The console output is as follows:

"Smith's tools | Progress: 1253/2000
62.64999999999999
Dragonchess set | Progress: 24/2000
1.2"

and when added together it adds up to 63.84999 which results in the rounded out total percentage of 63.85 in the image above.

These get created in a function I call for each character that is saved, originally I had copied the code as it was from this post and that resulted into all progress bars summing up together, while with the current code it displays the total percentage separate for each character, then adds that to the percentage of the next character and all progress bars get filled to the highest percentage.

highest percentage filled

This is my first ever post on here so if you need any more code or details let me know. The function that produces these weird fill results is as follows:

def buildbasicobjectinfo(elems, charfield, charname):
pbnum = 0
for elem in elems:
    try:

        style.layout('TProgressbar.'+str(charname)+'.'+str(pbnum), 
         [('Progressbar.trough',
           {'children': [('Horizontal.Progressbar.pbar',
                          {'side': 'left', 'sticky': 'ns'})],
            'sticky': 'nswe'}), 
          ('Progressbar.label', {'sticky': ''})])

        t = elem.childNodes[0].firstChild.data+" | Progress: "+elem.childNodes[2].firstChild.data+"/"+elem.childNodes[1].firstChild.data
        print(t)
        progress = tk.Label(charfield, text=t)
        progress.pack(anchor="w")
        pbar = ttk.Progressbar(charfield, style='TProgressbar.'+str(charname)+'.'+str(pbnum), variable=variable)
        print("variable="+str(variable))
        
        percentage = float(float(elem.childNodes[2].firstChild.data)/float(elem.childNodes[1].firstChild.data)*100)
        print(percentage)
        pbar.step(percentage)
        pbar.pack(anchor="w")
        
        style.configure('TProgressbar.'+str(charname)+'.'+str(pbnum), text='{:g} %'.format(variable.get()))
        pbnum = pbnum+1
    except AttributeError:
        print(elem, elem.childNodes[0])

Here is the code for the same function working perfectly fine without the style part:

working progress bars image

def buildbasicobjectinfo(elems, charfield, charname):
    for elem in elems:
        try:
            percentage = round(float(float(elem.childNodes[2].firstChild.data)/float(elem.childNodes[1].firstChild.data)*100), 2) #calculate the percentage
            print(percentage) #print percentage in console

            t = elem.childNodes[0].firstChild.data+" | Progress: "+elem.childNodes[2].firstChild.data+"/"+elem.childNodes[1].firstChild.data+" | Percentage: "+str(percentage)+"%" #format necessary data
            print(t) #print data in console

            #create label for data in text format
            progress = tk.Label(charfield, text=t)
            progress.pack(anchor="w")

            #create progress bar
            pbar = ttk.Progressbar(charfield, orient="horizontal", mode="determinate", length=300)
            pbar.step(percentage) #fill progress bar with compelted percentage
            pbar.pack(anchor="w")
        except AttributeError:
            print(elem, elem.childNodes[0])

For those interested in a small program that demonstrates the problem this should do it, on the left it will show 2 progress bars, made the normal way and displaying correctly, on the right it will show 2 progress bars created in the same way but with the added style to add the percentage visually to the progress bar itself and how they for some reason add to each other, above each bar it will display the percentage they should be at:

import tkinter as tk
import tkinter.ttk as ttk

#function to divide the centered frame evenly, replicates distribution of character placement
def sidebysidedisplay(data, frame):
    npbframe = tk.LabelFrame(frame, text="normal bars", bd=0, font=(0, 12, 'bold'))
    npbframe.grid(row=1, column=0, sticky="nesw", padx=(0,20))

    spbframe = tk.LabelFrame(frame, text="styled bars", bd=0, font=(0, 12, 'bold'))
    spbframe.grid(row=1, column=1, sticky="nesw", padx=(20,0))

    normalprogressbar(data, npbframe)
    styledprogressbar(data, spbframe)


def normalprogressbar(items, frame):
    print("Normal progress bars")
    for item in items:
        percentage = round(float(float(item[0])/float(item[1])*100),2)
        print("Item: "+str(item)+" | Percentage: "+str(percentage))
        progress = tk.Label(frame, text=percentage)
        progress.pack(anchor="w")

        pbar = ttk.Progressbar(frame, orient="horizontal", mode="determinate", length=400)
        pbar.step(percentage)
        pbar.pack(anchor="w")

def styledprogressbar(items, frame):
    print("Styled progress bars")
    for item in items:
        percentage = round(float(float(item[0])/float(item[1])*100),2)
        print("Item: "+str(item)+" | Percentage: "+str(percentage))
        style.layout('text.Horizontal.TProgressbar.'+str(item), 
             [('Horizontal.Progressbar.trough',
               {'children': [('Horizontal.Progressbar.pbar',
                              {'side': 'left', 'sticky': 'ns'})],
                'sticky': 'nswe'}), 
              ('Horizontal.Progressbar.label', {'sticky': ''})])

        progress = tk.Label(frame, text=percentage)
        progress.pack(anchor="w")

        pbar = ttk.Progressbar(frame, style='text.Horizontal.TProgressbar.'+str(item), variable=variable, length=400)
        pbar.step(percentage)
        pbar.pack(anchor="w")
        style.configure('text.Horizontal.TProgressbar.'+str(item), 
                    text='{:g} %'.format(variable.get()))  # update label

#====================================================================================================#

root = tk.Tk()
root.state("zoomed")
data = ((1253, 2000),(24,2000)) #mock data to recreate data read from xml file

style = ttk.Style(root)
variable = tk.DoubleVar(root)

#create centered frame to display data in the center of the screen
centerframe = tk.LabelFrame(root, bd=0)
centerframe.place(relx=.5, rely=.5, anchor="c")


sidebysidedisplay(data, centerframe)

root.mainloop()
tripleee
  • 175,061
  • 34
  • 275
  • 318
1kili2
  • 23
  • 4
  • first: if you create many objects then you should keep them on list - it would be simpler to access them. – furas Jun 20 '21 at 08:26
  • better create minimal working code (without styles) so we could run it and see problem. – furas Jun 20 '21 at 08:29
  • @furas ive added the code that works without the style, as for using a list since, since I only need to set and show them I didn't think that was necessary since I wont be accessing them again – 1kili2 Jun 20 '21 at 08:52
  • you put minimal but not working code - we can't copy it and run it because it is incomplete. – furas Jun 20 '21 at 15:17
  • @furas im sorry i misunderstood code for part of code instead of full functional program, i'll work on it and update this with the necessary stuff to run in the morning – 1kili2 Jun 20 '21 at 15:50
  • @furas alright ive posted a small fully executionable program you can copy paste, i hope that helps give some more insight to get to the bottom of what is happening – 1kili2 Jun 21 '21 at 03:13
  • 1
    `root .state("zoomed")` doesn't load on Linux. Replaced with `root .attributes( '-zoomed', True )` – Doyousketch2 Jun 21 '21 at 04:35

1 Answers1

1

After running code I see problem makes variable = tk.DoubleVar(root)

You use one variable for two Progressbars and pbar.step(percentage) adds value to variable so finally you get sum 63.83%.

It makes also other problem. If you change value in variable then it changes length in both Progressbar and you see the same length in both widgets.

You should remove variable

pbar = ttk.Progressbar(frame, length=400, 
                       style='text.Horizontal.TProgressbar.'+str(item))

as use percentage to display text

style.configure('text.Horizontal.TProgressbar.'+str(item), 
                text='{:g} %'.format(percentage) ) # update label

Or you will have to create two variables for two Progressbars


EDIT:

You may also create own widget - you can use Frame to create widget with Label, and Progressbar and variable.


Working code with using variable

import tkinter as tk
import tkinter.ttk as ttk

#function to divide the centered frame evenly, replicates distribution of character placement
def sidebysidedisplay(data, frame):
    npbframe = tk.LabelFrame(frame, text="normal bars", bd=0, font=(0, 12, 'bold'))
    npbframe.grid(row=1, column=0, sticky="nesw", padx=(0,20))

    spbframe = tk.LabelFrame(frame, text="styled bars", bd=0, font=(0, 12, 'bold'))
    spbframe.grid(row=1, column=1, sticky="nesw", padx=(20,0))

    normalprogressbar(data, npbframe)
    styledprogressbar(data, spbframe)


def normalprogressbar(items, frame):
    print("Normal progress bars")
    for item in items:
        percentage = round(float(float(item[0])/float(item[1])*100),2)
        print("Item: "+str(item)+" | Percentage: "+str(percentage))
        progress = tk.Label(frame, text=percentage)
        progress.pack(anchor="w")

        pbar = ttk.Progressbar(frame, orient="horizontal", mode="determinate", length=400)
        pbar.step(percentage)
        pbar.pack(anchor="w")

def styledprogressbar(items, frame):
    print("Styled progress bars")
    for item in items:
        percentage = round(float(float(item[0])/float(item[1])*100),2)
        print("Item: "+str(item)+" | Percentage: "+str(percentage))
        style.layout('text.Horizontal.TProgressbar.'+str(item), 
             [('Horizontal.Progressbar.trough',
               {'children': [('Horizontal.Progressbar.pbar',
                              {'side': 'left', 'sticky': 'ns'})],
                'sticky': 'nswe'}), 
              ('Horizontal.Progressbar.label', {'sticky': ''})])

        progress = tk.Label(frame, text=percentage)
        progress.pack(anchor="w")

        #pbar = ttk.Progressbar(frame, style='text.Horizontal.TProgressbar.'+str(item), variable=variable, length=400)
        pbar = ttk.Progressbar(frame, style='text.Horizontal.TProgressbar.'+str(item), length=400)
        pbar.step(percentage)
        pbar.pack(anchor="w")
        
        #style.configure('text.Horizontal.TProgressbar.'+str(item), text='{:g} %'.format(variable.get()))  # update label
        style.configure('text.Horizontal.TProgressbar.'+str(item), text='{:g} %'.format(percentage))  # update label

#====================================================================================================#

root = tk.Tk()
#root.state("zoomed")
data = ((1253, 2000),(24,2000)) #mock data to recreate data read from xml file

style = ttk.Style(root)
variable = tk.DoubleVar(root)

#create centered frame to display data in the center of the screen
centerframe = tk.LabelFrame(root, bd=0)
centerframe.place(relx=.5, rely=.5, anchor="c")


sidebysidedisplay(data, centerframe)

root.mainloop()

EDIT:

Version with own widget

import tkinter as tk
import tkinter.ttk as ttk

class MyWidget(tk.Frame):
    
    def __init__(self, parent, item, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)

        self.item = item
        
        self.percentage = round(100*item[0]/item[1], 2)

        print("[MyWidget] Item: {} | Percentage: {}".format(self.item, self.percentage))
        
        style_name = 'text.Horizontal.TProgressbar.'+str(item)

        style.layout(style_name, 
             [('Horizontal.Progressbar.trough',
               {'children': [('Horizontal.Progressbar.pbar',
                              {'side': 'left', 'sticky': 'ns'})],
                'sticky': 'nswe'}), 
              ('Horizontal.Progressbar.label', {'sticky': ''})])

            
        self.label = tk.Label(self, text=self.percentage)
        self.label.pack(anchor="w")

        self.pbar = ttk.Progressbar(self, style=style_name, length=400)
        self.pbar.step(self.percentage)
        self.pbar.pack(anchor="w")
        
        style.configure(style_name, text='{:g} %'.format(self.percentage))
            
#function to divide the centered frame evenly, replicates distribution of character placement
def sidebysidedisplay(data, frame):
    npbframe = tk.LabelFrame(frame, text="normal bars", bd=0, font=(0, 12, 'bold'))
    npbframe.grid(row=1, column=0, sticky="nesw", padx=(0,20))

    spbframe = tk.LabelFrame(frame, text="styled bars", bd=0, font=(0, 12, 'bold'))
    spbframe.grid(row=1, column=1, sticky="nesw", padx=(20,0))

    normalprogressbar(data, npbframe)
    styledprogressbar(data, spbframe)

    widgetsframe = tk.LabelFrame(frame, text="widgets", bd=0, font=(0, 12, 'bold'))
    widgetsframe.grid(row=1, column=2, sticky="nesw", padx=(20,0))

    for item in data:
        w = MyWidget(widgetsframe, item)
        w.pack()

def normalprogressbar(items, frame):
    print("Normal progress bars")
    for item in items:
        percentage = round(float(float(item[0])/float(item[1])*100),2)
        print("Item: "+str(item)+" | Percentage: "+str(percentage))
        progress = tk.Label(frame, text=percentage)
        progress.pack(anchor="w")

        pbar = ttk.Progressbar(frame, orient="horizontal", mode="determinate", length=400)
        pbar.step(percentage)
        pbar.pack(anchor="w")

def styledprogressbar(items, frame):
    print("Styled progress bars")
    for item in items:
        
        percentage = round(item[0]/item[1]*100, 2)
        print("Item: {} | Percentage: {}".format(item, percentage))
        
        style_name = 'text.Horizontal.TProgressbar.'+str(item)
        
        style.layout(style_name, 
             [('Horizontal.Progressbar.trough',
               {'children': [('Horizontal.Progressbar.pbar',
                              {'side': 'left', 'sticky': 'ns'})],
                'sticky': 'nswe'}), 
              ('Horizontal.Progressbar.label', {'sticky': ''})])

        progress = tk.Label(frame, text=percentage)
        progress.pack(anchor="w")

        pbar = ttk.Progressbar(frame, style=style_name, length=400)
        pbar.step(percentage)
        pbar.pack(anchor="w")
        
        style.configure(style_name, text='{:g} %'.format(percentage))

#====================================================================================================#

root = tk.Tk()
#root.state("zoomed")
data = ((1253, 2000),(24,2000)) #mock data to recreate data read from xml file

style = ttk.Style(root)
variable = tk.DoubleVar(root)

#create centered frame to display data in the center of the screen
centerframe = tk.LabelFrame(root, bd=0)
centerframe.place(relx=.5, rely=.5, anchor="c")

sidebysidedisplay(data, centerframe)

root.mainloop()
furas
  • 134,197
  • 12
  • 106
  • 148