Pysimplegui: Question/Demo: Using Metadata with Combo, Listbox, etc.

Created on 16 Sep 2020  路  11Comments  路  Source: PySimpleGUI/PySimpleGUI

Type of Issues

QUESTION

Operating System

Linux Mint 19.3 MATE

Python version

3.6.9

PySimpleGUI Port and Version


4.29.0.9 Unreleased
Added shink parameter to pin, added variable Window.maximized, added main_sdk_help_window function, theme DarkGrey10 added, no longer setting highlight thickness to 0 for buttons so that focus can be seen, new themes DarkGrey11 DarkGrey12 DarkGrey13 DarkGrey14, new user_settings APIs, added text parameter to Radio.update, echo_stdout_stderr parm added to Multiline and Output elements, added DarkBrown7 theme
8.6.8
3.6.9 (default, Jul 17 2020, 12:50:27)
[GCC 8.4.0]

Your Experience Levels In Months or Years

2 months: Python programming experience
Decades: Programming experience overall
Tkinter barely: Have used another Python GUI Framework (tkinter, Qt, etc) previously (yes/no is fine)?

You have completed these steps:

  • [x] Read instructions on how to file an Issue
  • [x] Searched through main docs http://www.PySimpleGUI.org for your problem
  • [x] Searched through the readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
  • [x] Looked for Demo Programs that are similar to your goal http://www.PySimpleGUI.com
  • [x] Note that there are also Demo Programs under each port on GitHub
  • [x] Run your program outside of your debugger (from a command line)
  • [x] Searched through Issues (open and closed) to see if already reported
  • [x] Try again by upgrading your PySimpleGUI.py file to use the current one on GitHub. Your problem may have already been fixed but is not yet on PyPI.

Description of Problem / Question / Details

[EDIT 9-16-2020: As Jason pointed out in a comment, my code sample does not actually use metadata! That is my error. So as you read this please realize that metadata is not exemplified here. In fact, the metadata parameter can be removed from the code altogether and it will still work. [/EDIT]

My purpose with this post is two fold:

  1. Show others who may be laboring with this how to get a value other than the displayed value from a combo box, listbox, etc. This is not the same as getting the index position in the list of values display, rather it is returning a completely separate value that is not displayed. Key point being: "not displayed."

For example, in a database application you may need to retrieve the primary key field for a user selected record but not want to display that field to the user, but instead display the name field (or whatever) of that record (e.g. [(100, "Shoes"),(102, "Shirts"),(203,"Ties")]). It SEEMS that this cannot be done with combos, listboxes, and other display components because they display everything given in the values parameter. But, there is hope!

I spent about half a day today figuring out a workaround as I'm pretty new to Python, but after I figured out how to do it with code I realized the mysterious metadata parameter can be used for it instead!! Regardless, I'm happy I went through the process of figuring out a workaround.

Further, there is no occurrence of the word "metadata" in any of the Demo Programs. And, the Call Reference does not provide code examples. So this brings me to the second purpose of this post.

  1. A few Demo Programs showing how to use the metadata parameter really need to be added. I have built one, and for the sake of shortening the code I've rendered it down to one combo box example in the code below.

QUESTION: Who posts the Demo Programs to the PSG Github? Can anyone do it? Does it need to go through a review? I'd like to know so I can contribute in the future.

ONE LAST TIP: While this is useful for combo, listbox, etc. I have not needed the metadata trick for Tables because it is much easier to get what I need with it since it has a visible_column_map parameter and supports multiple columns. Just hide what you don't want displayed and read it all back with the indexed value in the list. Easy peasy. Maybe someday combos and listboxes can have a visible column map and support two fields instead of one!

Code To Duplicate

## Paste your code here
import PySimpleGUI as sg

combo_showdata = ["Shoes","Shirts","Jackets","Pants","Socks","Ties"]
combo_returndata = [1000,1100,1200,1300,1400,1500]

layout = [ [sg.T('Combo test with metadata'),],
            [sg.Combo(values=combo_showdata,
                size=(40,5),
                readonly=True,
                enable_events=True,
                key='-COMBO-',
                metadata=combo_returndata,)],
            [sg.B('QUIT'),] ]

window = sg.Window('METADATA EXAMPLE', layout)

print(f'combo_showdata: {combo_showdata}')
print(f'combo_returndata: {combo_returndata}')
while True:
    event, values = window.read()
    print(event, values)
    if event in (sg.WIN_CLOSED, 'QUIT'):
        if sg.popup_yes_no('Really Quit?', title='CONFIRM', modal=True) in ('Yes'):
            print('Quitting')
            break
    if event == '-COMBO-':
        combo_position = combo_showdata.index(values['-COMBO-'])
        combo_returned = combo_returndata[combo_position]
        sg.popup(f'Combo return field (metadata) = {combo_returned}')

window.close()

All 11 comments

As my opinions here,

  1. It seems that option 'metadata' not used in PSG source code.
  2. Event sg.WIN_CLOSED couldn't be skipped to not close window if you click 'No'.
  3. To give another value for the selection in Combo, Listbox, for example, an index, it should be by programmer. like
import PySimpleGUI as sg

data = {"Shoes":1000, "Shirts":1100, "Jackets":1200, "Pants":1300, "Socks":1400, "Ties":1500}

layout = [ [sg.T('Combo test with metadata'),],
            [sg.Combo(values=list(data.keys()),
                size=(40,5),
                readonly=True,
                enable_events=True,
                key='-COMBO-')],
            [sg.B('QUIT'),] ]

window = sg.Window('METADATA EXAMPLE', layout)

while True:
    event, values = window.read()
    print(event, values)
    if event in (sg.WIN_CLOSED, 'QUIT'):
        break
    if event == '-COMBO-':
        sg.popup(f'Combo return field (metadata) = {data[values[event]]}')

window.close()
  1. Demo should be managed by PySimpleGUI/Mr. Mike.

Another way of to give a combo a way of selecting database type data is to use a class that has a __str__() method as the list object as in the following

import PySimpleGUI as sg

class Person():
    def __init__(self, record_number, given_name, family_name, date_of_birth, gender):
        self.record_number = record_number
        self.given_name = given_name
        self.family_name = family_name
        self.date_of_birth = date_of_birth
        self.gender = gender

    def __str__(self):
        return self.given_name + ' ' + self.family_name

    def record(self):
        return str(self.record_number) + ' ' + \
               self.given_name + ' ' + \
               self.family_name + ' ' + \
               self.date_of_birth + ' ' + \
               self.gender

people = []
people.append(Person(1, 'Fred', 'Blogs', '1987-05-23', 'Male'))
people.append(Person(2, 'Deborah', 'Goggins', '1972-08-02', 'Female'))
people.append(Person(3, 'Bill', 'Dukes', '1984-04-15', 'Male'))
people.append(Person(4, 'Carol', 'Lewis', '1999-06-27', 'Female'))

layout = [ [sg.Combo(people, key = '-PERSON-', enable_events = True, readonly = True)],
           [sg.Button('Quit')] ]

window = sg.Window('People', layout)

while True:
    event, values = window.read()
    if event in (sg.WIN_CLOSED, 'Quit'):
        break
    elif event == '-PERSON-':
        print(values['-PERSON-'].record())

window.close()

Really surprised at how barren the information is on the metadata parameter. I thought I documented it and used it in a demo or two, but clearly not there. I used it in the Theme Maker code. In that code I used a Radio Button's metadata to store the value that it represented. In that example, the radio buttons represented button colors, so I put the colors that they represented in the metadata field. It was a way to associate data in the layout itself and not use a lookup table that mapped from Radio Button key to a button color.

The project doesn't take pull requests. The contributing.md file and main documentation explain this.

There is a section for User Submitted Programs but it's not very recent. I would be happy to post a demo there from you if you have something to share.

I'm in the process of reworking the readme, building a gallery and creating more structure around the demo programs so that there's some amount of grouping documented so that you know which program to check out for a particular feature. I want to rework the section on other user's projects as well. GitHub reports over 1,000 repos that have the tkinter version of PySimpleGUI shown in the requirements.txt file which is what I used to scrape images for a gallery. How this can be organized for user to user sharing is still a TBD in my head.

There are a zillion uses for a metadata variable/parameter, especially since it can be anything. You can store functions in there to call when a button is clicked. Or source data of some kind used to create an element. I really had no particular usage in mind when I created it other than wanting it to be a very broad capability. It's great to see someone looking at ways to use it. 馃憤馃徎

As my opinions here,

1. It seems that option 'metadata' not used in PSG source code.

2. Event sg.WIN_CLOSED couldn't be skipped to not close window if you click 'No'.

3. To give another value for the selection in Combo, Listbox, for example, an index, it should be by programmer. like

1. Demo should be managed by PySimpleGUI/Mr. Mike.

Jason - you are correct! I didn't actually use metadata and didn't realize it. Was very late when I made that code and post. After reviewing the code I cannot figure out how to actually use metadata. How is it referenced or accessed by code?

You're right about the close confirmation too. I fixed it by only applying the confirmation to the Quit button. Can't do much about the direct window close though. Oh well.

Regarding your keys approach, thank you for that. I learned something new! However, unlike my demo code in this post, the data I'm using are from SQLite queries and the Python driver for it returns a list of tuples so that's what I need to work with. Converting it to a keys dictionary seems to be inefficient regarding memory. I'm using a very small function to do the task of creating a list of values from the query result to display in a combo or listbox.

Another way of to give a combo a way of selecting database type data is to use a class that has a __str__() method as the list object as in the following

import PySimpleGUI as sg

class Person():
    def __init__(self, record_number, given_name, family_name, date_of_birth, gender):
        self.record_number = record_number
        self.given_name = given_name
        self.family_name = family_name
        self.date_of_birth = date_of_birth
        self.gender = gender

    def __str__(self):
        return self.given_name + ' ' + self.family_name

    def record(self):
        return str(self.record_number) + ' ' + \
               self.given_name + ' ' + \
               self.family_name + ' ' + \
               self.date_of_birth + ' ' + \
               self.gender

people = []
people.append(Person(1, 'Fred', 'Blogs', '1987-05-23', 'Male'))
people.append(Person(2, 'Deborah', 'Goggins', '1972-08-02', 'Female'))
people.append(Person(3, 'Bill', 'Dukes', '1984-04-15', 'Male'))
people.append(Person(4, 'Carol', 'Lewis', '1999-06-27', 'Female'))

layout = [ [sg.Combo(people, key = '-PERSON-', enable_events = True, readonly = True)],
           [sg.Button('Quit')] ]

window = sg.Window('People', layout)

while True:
    event, values = window.read()
    if event in (sg.WIN_CLOSED, 'Quit'):
        break
    elif event == '-PERSON-':
        print(values['-PERSON-'].record())

window.close()

aajshaw - AWESOME solution there. Being new to Python I would never have approached it like this without more experience. This one is going into my keep folder :)

In addition to the Demo Programs for PSG I have found many of the code samples in the Issues to be quite helpful. Yours is one!

Whoa wait.... I'm not quite done with this one. Wanting to make sure I totally get all this material and follow-through. I don't feel like I've properly absorbed it all and internalized so that I can take some kind of useful action. There's some good stuff here. Let's keep it around for just a bit longer.

No worries. Just remember I made a pretty big error in that my code sample did not use metadata at all.

Right, that's why I want to spend more time on it this weekend. That gives me a clue about what to write when explaining it.

Right, that's why I want to spend more time on it this weekend. That gives me a clue about what to write when explaining it.

Mike - I really appreciate your dedication to PSG and especially the support area here. Very inspiring.

Very inspiring.

Well Mark, it's a bi-directional thing. It's not like it's one-way thing.

I too get inspiration here. I'm inspired by PySimpleGUI users of all kinds. I get just as much of a thrill from witnessing a beginner's program working as seeing a complex, multi-windowed complex application working. That shared success, however you go about defining that, is why I work on the project and provide support.

I've been working on the same thing in a database project using PySimpleGUI - how to display some data and use the gui to manipulate other data. I came up with the following. I don't think using the metadata parameter is necessary in this example, but it is being used.

from collections import namedtuple
import PySimpleGUI as sg

sg.theme('Dark Blue 3')

Produce = namedtuple('Produce', ['Name', 'SKU', 'Price'])

banana = Produce('Banana', 4419, 0.89)
organic_banana = Produce('Banana', 99419, 0.89)  # this is a copy of banana with a different SKU only
apple = Produce('Gala Apple', 4012, 0.69)
beans = Produce('Black Beans', 7559, 1.29)
soy_cake = Produce('Tofu', 88682, 4.25)

shelf = [banana, apple, beans, soy_cake, apple, apple, organic_banana]
# the "shelf" list index is hardcoded to the items so that we can see the exact items that are placed in the bag:
indexed_shelf = list(enumerate(shelf))
gui_shelf = [(item[0], item[2]) for item in shelf]  # a subset of data for a pretty display

bag = []  # empty bag to fill at checkout

layout = [[sg.T("Shelf", size=(37, 0), justification='left'), sg.T("Basket", size=(37, 0), justification='left')],
        [sg.Listbox(values=gui_shelf, size=(40, 10), select_mode='multiple', key='SELECTION', enable_events=True, \
        metadata=()),
        sg.Listbox(values=['Empty Basket'], size=(40, 10), key='BASKET')],
        [sg.B("View Metadata", key='-META-'), sg.B("Checkout", key='CHECKOUT')]
        ]

window = sg.Window(layout=layout, title='Go shopping!', element_padding=((10, 10), (5, 5)))

while True:
    event, value = window.read()

    if event is None:
        window.close()
        break

    if event == 'SELECTION':
        # replace existing metadata with a tuple of the indexes of highlighted items in the Listbox:
        window.Element('SELECTION').metadata=window.Element('SELECTION').GetIndexes()
        # copy the pretty display of items in a second Listbox:
        window.Element('BASKET').Update(value['SELECTION'])

    if event == '-META-':
        print("metadata: ", window.Element('SELECTION').metadata)

    if event == 'CHECKOUT':
        for item in indexed_shelf:
            if indexed_shelf.index(item) in window.Element('SELECTION').metadata:
                bag.append(item)
        print("metadata: ", window.Element('SELECTION').metadata)
        print("bag: ", bag)
        bag = []  # get a new empty bag
Was this page helpful?
0 / 5 - 0 ratings

Related issues

mozesa picture mozesa  路  5Comments

xuguojun168 picture xuguojun168  路  3Comments

MikeTheWatchGuy picture MikeTheWatchGuy  路  6Comments

lucasea777 picture lucasea777  路  3Comments

OndoyManing picture OndoyManing  路  4Comments