1

I am trying to make a GUI with Tkinter that allows you to enter either a URL or an ID.

Example URL: https://www.fanfiction.net/s/10030860/1/The-Final-Battle Example ID: 10030860

As you can see, the ID is embedded in the URL. I want to have two entries, one for entering a URL and one for entering an ID. If a user fills in the URL box, the ID is automatically generated. (If I put the example URL in the URL entry box, I want the example ID in the ID box automatically generated and vice versa) If a user fills in the ID box, the URL is automatically generated.

More examples: (The brackets are pretending to entry boxes)

URL: [https://www.fanfiction.net/s/10030860/1/The-Final-Battle] <-- If I fill this in
ID:  [10030860] <-- Python fills this in for me
URL: [https://www.fanfiction.net/s/10030860/1/The-Final-Battle] <-- Python fills this in for me
ID:  [10030860] <-- If I fill this in

Here is my code so far:

import tkinter as tk
from tkinter import ttk

# Define a function to autofill in the URL and ID entries
def autofill_id_url():
    fanfic_url.set("https://www.fanfiction.net/s/" + fanfic_id.get() + "/1/")
    root.after(100, autofill_id_url)

# Root window
root = tk.Tk()

# Define the labeled frame where we input stuff
input_frame = tk.LabelFrame(master=root, text="Input")
input_frame.grid(row=0, column=0, padx=1, pady=1, rowspan=2, sticky=tk.NS)

# Label for entering URL
ttk.Label(master=input_frame, text="URL:").grid(row=0, column=0, padx=1, pady=1)
# Entry field for URL
fanfic_url = tk.StringVar()
url_entry = ttk.Entry(master=input_frame, textvariable=fanfic_url)
url_entry.grid(row=0, column=1, padx=1, pady=1)

# Label for entering ID
ttk.Label(master=input_frame, text="ID:").grid(row=1, column=0, padx=1, pady=1)
# Entry field for ID
fanfic_id = tk.StringVar()
id_entry = ttk.Entry(master=input_frame, textvariable=fanfic_id)
id_entry.grid(row=1, column=1, padx=1, pady=1)

# Start callback functions
autofill_id_url()

# Start GUI event loop
root.mainloop()

I have the part when you fill in the ID, the URL is generated automatically. But I have no idea how to make it when you fill in the URL box, you get the ID box filled in for you.

Thanks in advance.

Unsigned_Arduino
  • 357
  • 2
  • 16

2 Answers2

2

Better do the auto fill when Enter key is pressed in the entry box instead of using .after().

baseurl = 'https://www.fanfiction.net/s/'

def autofill_url_id(_):
    try:
        # extract the id from the url
        url = fanfic_url.get().strip()
        if url.startswith(baseurl):
            id = url.split('/')[4]
            fanfic_id.set(id)
    except IndexError:
        print('failed to extract id from the url')

def autofill_id_url(_):
    id = fanfic_id.get().strip()
    if id:
        fanfic_url.set(baseurl+id+'/1/')
...
# Start callback functions
#autofill_id_url()
url_entry.bind('<Return>', autofill_url_id)
id_entry.bind('<Return>', autofill_id_url)
acw1668
  • 40,144
  • 5
  • 22
  • 34
2

You can use the trace_variable method of StringVar to do autofill. The following code will do so but this is just a basic code to get started as it will require some more work to implement autofill perfectly.

after_ids = {}

def get_url(id_):
    """returns url from id."""
    url = 'https://www.fanfiction.net/s/{}/1/The-Final-Battle'
    return url.format(id_)

def get_id(url):
    """returns id from the url."""
    l = url.split('/')
    return l[4] if len(l) > 4 else ''

def autofill_entry(mode, dalay=1000):
    """Auto-fills Url/ID."""
    for v in after_ids.values():
        root.after_cancel(v)
    if mode == 'url':  
        id_ = get_id(fanfic_url.get())
        after_ids[0] = root.after(dalay, lambda: fanfic_id.set(id_))
    elif mode == 'id':
        url = get_url(fanfic_id.get())
        after_ids[1] = root.after(dalay, lambda: fanfic_url.set(url))

Now assign the function autofill_entry to the entry widgets StringVars.

fanfic_url.trace_variable('w', lambda *a: autofill_entry('url'))
fanfic_id.trace_variable('w', lambda *a: autofill_entry('id'))

Also, I would recommend you use from urllib.parse import urlparse, parse_qs to join URL and fetch id from the URL.

Saad
  • 3,340
  • 2
  • 10
  • 32
  • Although it could work, but I don't recommend to use the code `[root.after_cancel(v) for v in after_ids.values()]`. It is not necessary to increase the memory usage in order to reduce the lines of code. – jizhihaoSAMA Jun 18 '20 at 07:34
  • @jizhihaoSAMA: if you mean `for v in after_ids.values(): root.after_cancel(v)` then I don't see a difference as I'm not creating any variable to store `[root.after_cancel(v) for v in after_ids.values()]`. *(Feel free to correct me if i'm wrong :) )* – Saad Jun 18 '20 at 07:58
  • 2
    Yes ,both of them could work.Your code created a list but didn't assign it to a variable.When python evaluate this code, it would waste unnecessary memory overhead.(Not a big problem but I think it is not a good practice). – jizhihaoSAMA Jun 18 '20 at 08:04
  • Thank you so much for taking the time to answer my question! I already have code that fetches URLS with `requests` and use `bs4` to parse and it works quite well for someone who just started learning to web scrap stuff. May I ask, why do you use this syntax: `lambda *a: ` Is the `*` necessary? – Unsigned_Arduino Jun 18 '20 at 12:17
  • I'm just not that familiar with `lambda` I know that define a function on the fly like you have done here: `lambda: fanfic_id.set(id_)`. But is the `*` significant? – Unsigned_Arduino Jun 18 '20 at 12:25
  • 1
    @Unsigned_Arduino: By default, a variable trace method passes 3 arguments to the callback function so [`*a`](https://www.geeksforgeeks.org/args-kwargs-python/) parameter will take all those 3 arguments passed by `trace_variable`. You can read this [page](https://www.geeksforgeeks.org/tracing-tkinter-variables-in-python/) and scroll down to the code to understand those 3 arguments. – Saad Jun 18 '20 at 12:35
  • 1
    You can read this [answer](https://stackoverflow.com/a/5771787/10364425) on `lambda` function as a command in Tkinter to get a better understanding. – Saad Jun 18 '20 at 12:38
  • So it passes these three arguments `var, indx, mode` like from the code? – Unsigned_Arduino Jun 18 '20 at 12:38
  • I also do know that you can pass in `lambda` to `tkinter` so that you can pass in parameter to the function you want to execute. (I really do need to use theme more though) EDIT: Like `ttk.Button(master=root, text="Hi", command=lambda: print("Hi"))` – Unsigned_Arduino Jun 18 '20 at 12:40
  • 1
    @Unsigned_Arduino: Yes, but as we are not using these parameters, so I decide to make *one autofill function for both the traces* and used lambda to define a difference with parameter `mode`. – Saad Jun 18 '20 at 12:42
  • Ah, I get it now. Thanks! (Ok, now SO is telling to move this discussion to the chat) – Unsigned_Arduino Jun 18 '20 at 12:45