Tkinter: print (.txt) filepath to text widget AND (.txt) file content to scrolledtext widget in the same gui with one filedialogue access

Question:

As a follow-up question to this:
Python tkinter – how to send StringVar() content to Scrolled Text box using grid() and using procedural code

  1. I know how to print the filepath to a text widget field GUI.
  2. I know how to put the contents of a text file into a scrolledtext widget field in a GUI.
  3. I don’t know how to do them at the same time…

I can browse twice, in the same programme, to achieve the goal; but that’s not really achieving the goal.

  1. This is someone else’s code that I tried out for size and modified:
def browsefunc(): #browse button to search for files
    filename = filedialog.askopenfilename()
    infile = open(filename, 'r')
    content = infile.read()
    pathadd = os.path.dirname(filename)+filename

    pathlabel.delete(0, END)
    pathlabel.insert(0, pathadd)

    return content

Button errors in Tkinter – Selecting files, adding their paths
I also did this way, which is a bit different:

def fd_getfile():
    filetypes = (
        ('sys list file', '*.txt'),
        ('any', '*.*')
    )
    filename_file1 = fd.askopenfilename(
        title='add file1',
        initialdir='src',
        filetypes=filetypes)
file1_entry.insert(tk.END, filename_file1)
    showinfo(
        title='file1 selected',
        message=filename_file1,
    )

def process(*args):
    try:
        # COMMENT: Reset output
        file1.set('')

        # COMMENT: Load files
        with open(file1path.get()) as file1:

  1. This is how I got this to work… I haven’t seen any example of this online anywhere.
def forkscroll():
    mylabelframe = tk.LabelFrame(myframe, text='My Long Text:').pack()
    scroll_w = 30
    scroll_h = 10
    myscrolledtext = scrolledtext.ScrolledText(mylabelframe, width=scroll_w, height=scroll_h, wrap=tk.WORD)
    myscrolledtext.vbar.config(width=20)
    myscrolledtext.grid(sticky='news', row=6, column=0, rowspan=1, columnspan=1, padx=5, pady=5)

# Open a dialogue; select and load a text file; read the text file directly into the scrolledtext widget (box)
    fork = open(filedialog.askopenfilename(), 'r') # it's the same as "infile" above; but it's not a string
    myscrolledtext.insert(tk.END, fork.read()) ### This reads the content of "fork" into the scrolledtext widget
  1. …can’t do it. As I say, I can run both processes in parallel… browsing twice for files, but each function does something different with what looks like similar code.
  forkit = StringVar()
    forkit.set(fork.get())
    mynormaltext = Text(mylabelframe, width = 10, height = 1, wrap=tk.WORD)
    mynormaltext.grid(sticky='news', row=5, column=0, rowspan=1, columnspan=1, padx=5, pady=5)
    mynormaltext.insert(tk.END, forkit.get())

# Even though it does the same thing in one line as it does in three, the outcome is not the same for scrolledtext
#    forkname = filedialog.askopenfilename()
#    forkfile = open(forkname, 'r')
#    fork = forkfile.read()

# above... "fork" is a textIO, but Text can read it...

I looked at those three lines of forkname, forkfile, and fork, and saw that it looked like a shorthand for fork=open(filedialog.askopenfilename(), ‘r’).read()
and that’s how I got the scrolledtext to finally work… but this "fork" is a different filetype in each case… in one it’s a string, in another it’s a "TextIO" or "FilePath" object. The latter works for scrolledtext, but the former doesn’t. The former works for insert to Text, but the latter doesn’t. They can’t coexist in the same function, and the code doesn’t seem to like them in the same .py file, so I guess they should be in separate classes… but I don’t know how to convert this code into classes.

Any ideas about how to achieve the intended functionality please?

The intended functionality is:

  1. present 2 labels with 2 buttons each with a field: one as a text widget field; and scrolledtext widget field.
  2. to click a button to browse to a text file, and read it into memory, and print its path into the text widget field
  3. to click the other button, and print the contents of the same file into the scrolledtext field, and have it present within the frame, with a nice long scrollbar.

the closest I can get, so far...

Asked By: Reginald Lloyd

||

Answers:

How about this:

from tkinter import *
from tkinter import filedialog
from tkinter import scrolledtext
import os

def browsefunc():
    global content
    filename = filedialog.askopenfilename()
    infile = open(filename, 'r')
    content = infile.read()
    pathadd = filename

    pathtext.delete(0.0, END)
    pathtext.insert(0.0, pathadd)

    contenttext.delete(0.0, END)
    contenttext.insert(0.0, content)

w = Tk()
w.geometry('600x370')

lb1 = Label(text="Select text file")
lb1.place(x=0, y=0)

bt1 = Button(text="Process", command=browsefunc)
bt1.place(x=0, y=40)

pathtext = Text()
pathtext.place(x=0, y=85, width=450, height=20)

contenttext = scrolledtext.ScrolledText()
contenttext.place(x=0, y=120, width=450)

After reading the file your data will be inside content variable.

Answered By: Live Forever

After looking at the debugger, and seeing that the TextIO variable "fork", comprises two parts: "<_io.Textwrapper" and "name = C:blahblahblah"

enter image description here

I got it to 99% work like this:

#import os
#import io
from tkinter import *
#from tkinter import ttk
from tkinter import filedialog
from tkinter import scrolledtext
import tkinter as tk
from pathlib import Path
from typing import TextIO


def forkscroll():
    mylabelframe = tk.LabelFrame(myframe, text='My Long Text:').pack()
    scroll_w = 30
    scroll_h = 10
    myscrolledtext = scrolledtext.ScrolledText(mylabelframe, width=scroll_w, height=scroll_h, wrap=tk.WORD)
    myscrolledtext.vbar.config(width=20)
    myscrolledtext.grid(sticky='news', row=6, column=0, rowspan=1, columnspan=1, padx=5, pady=5)

# Open a dialogue; select and load a text file; read the text file directly into the scrolledtext widget (box)
    fork: TextIO = open(filedialog.askopenfilename(), 'r') 
    myscrolledtext.insert(tk.END, fork.read()) ### This reads the content of "fork" into the scrolledtext widget

    forkname = fork.name # works
    mypathtext = Text(mylabelframe, width=10, height=1, wrap=tk.WORD)
    mypathtext.grid(sticky='news', row=5, column=0, rowspan=1, columnspan=1, padx=5, pady=5)
    mypathtext.insert(tk.END, forkname)  # works

#GUI

root = Tk()
root.title('Root Title')
root.geometry('600x300')

myframe = tk.Frame(root, width=300, height=300)
myframe.winfo_toplevel().title('MyFrame Title')
myframe.grid(column=0, row=0, sticky='news')

myforkpath = StringVar()

f1 = Frame(myframe, width=900, height=150) #file1
f1.pack(fill=X, side =TOP)
f2 = Frame(myframe, width=900, height=150) #file2
f2.pack(fill=X, side=BOTTOM)

Label(f1, text="Select text file").grid(row=0, column=0, sticky='e')  # select file button label
run_button = Button(myframe, text="Process", command=forkscroll).pack()
Label(f2, text="put the content of the file in the text scroll box below").grid(row=1, column=0, sticky='s')

myframe.mainloop()

…with one problem… that 1%

My laptop is named "Crappy Laptop" (not really, but it has a space in the name in that part of the path) and so the path in name that is printed to the Text widget field is:

"C://Users/Crappy"

…I don’t get the whole path… which should be
"C://Users/Crappy Laptop/Desktop/My/Python/Code/mylongtextfile.txt"

in fact I get forward slashes, and not backslashes… in the Pycharm console window it displays the paths with slashes in each direction! Like this:
C:……python.exe C://…/…/myfile.py

How to handles the space?

Well I’ve done this so far…

    forkname = fork.name # works
# fix the "path with spaces problem"
    newforkname1 = forkname.replace(' ', '_')
    print(newforkname1)
    newforkname2 = newforkname1.replace('/', '\')
    print(newforkname2)

    mypathtext = Text(mylabelframe, width=10, height=1, wrap=tk.WORD)
    mypathtext.grid(sticky='news', row=5, column=0, rowspan=1, columnspan=1, padx=5, pady=5)
    #mypathtext.insert(tk.END, forkname)  # works
    mypathtext.insert(tk.END, newforkname2)  # works

haven’t sussed out how to display a space instead of an underscore yet.

This doesn’t quite do it…

    newforkname1 = forkname.replace(' ', ' '.join(forkname))
Answered By: Reginald Lloyd

For second part of your question

The problem with the incomplete path appears because of wrap setting. To solve this just change this string:

mypathtext = Text(mylabelframe, width=10, height=1)

A feature with backslashes and forward slashes you can fix by using .replace() method.

Answered By: Live Forever

This is a separate answer because I fixed a problem from a previous question and this question, but created a new problem 😀

I was able to use a StringVar to pass a string content from a text file out of one function and into another function…

So I add new StringVars at the top of the code

reflines_sv = StringVar()
cfglines_sv = StringVar()

Then inside a function I open the text file, get the content, and pass it into a string, and thence into a stringvar:

def getref():
    # COMMENT: Get reference file
    Label(frame_input, anchor='w', width=80, text='Reference filepath:', background='#abdbe3').pack(side=TOP, fill=Y)
    ref: TextIO = open(filedialog.askopenfilename(title='add Reference file', initialdir='src', ), 'r')
    reflines = ref.readlines()
    reflines_sv.set(reflines)

    # COMMENT: Get reference file path
    refname = ref.name
    refname_new = refname.replace('/', '\')
    pathtext_ref = Text(frame_input, width=80, height=1, bg='#f0f0f0')
    pathtext_ref.pack(anchor='n', side=TOP, fill=Y, ipadx=5, ipady=5)
    pathtext_ref.configure(font=("Arial", 9, "italic",))
    pathtext_ref.insert(tk.END, refname_new)
    return reflines_sv

Then in the next function, I’m able to retrieve the content and use it.

def process():
    try:
        # COMMENT: Reset output
        rdiff.set('')
        tdiff.set('')
        rtmiss.set('')

        reflines = reflines_sv.get()
        cfglines = cfglines_sv.get()

        # COMMENT: Comparing lines
        refhash = dict()
        cfghash = dict()
        different = False

etc...

The benefit of this is that I can now have a wizard with separate buttons for separate stages of the handling of a text file.

There’s probably a better way to do this, but I haven’t found it yet.

The new problem, is that now the content of the text file is not outputted properly.

etc… = below code

        for i, value in enumerate(reflines):
            refhash[i] = value
        for i, value in enumerate(cfglines):
            cfghash[i] = value
            contains = value in refhash.values()
            different = different or not contains
            if not contains:
                tdiff.set(tdiff.get() + 'problem line ' + str(i) + ': ' + value) ### <- new problem!
        for i, value in enumerate(reflines):
            contains = value in cfghash.values()
            different = different or not contains
            if not contains:
                rdiff.set(rdiff.get() + 'line ' + str(i) + ': ' + value)
        if not different:
            errmsg.set('no differences')

        if len(reflines) == len(cfglines) and different:
            for i, _ in enumerate(reflines):
                if cfglines[i] != reflines[i]:
                    rtmiss.set(rtmiss.get() + 'line ' + str(i) + 'n')
        else:
            rtmiss.set('files have different number of lines')

        scroll_w = 15
        scroll_h = 5
        myscrolledtext1 = scrolledtext.ScrolledText(frame_input, width=scroll_w, height=scroll_h, wrap=tk.WORD,background='#bbffe6')
        myscrolledtext1.vbar.config(width=20)
        myscrolledtext1.pack(anchor='s', fill=X, side=LEFT, ipadx=5,ipady=5, expand=True)
        myscrolledtext1.insert(tk.END, rdiff.get())

        myscrolledtext2 = scrolledtext.ScrolledText(frame_input, width=scroll_w, height=scroll_h, wrap=tk.WORD, background='#bbffe6')
        myscrolledtext2.vbar.config(width=20)
        myscrolledtext2.pack(anchor='s', fill=X, side=LEFT, ipadx=5,ipady=5, expand=True)
        myscrolledtext2.insert(tk.END, tdiff.get())

        myscrolledtext3 = scrolledtext.ScrolledText(frame_input, width=scroll_w, height=scroll_h, wrap=tk.WORD,background='#bbffe6')
        myscrolledtext3.vbar.config(width=20)
        myscrolledtext3.pack(anchor='s', fill=X, side=LEFT, ipadx=5,ipady=5, expand=True)
        myscrolledtext3.insert(tk.END, rtmiss.get())

    except OSError:
        errmsg.set('failed to load both files')


# COMMENT: Buttons
button_ref = Button(frame_button, text="Browse for ref file", font='50', command=getref).pack(anchor='s', side=BOTTOM, pady=10, ipadx=5, ipady=20)
button_cfg = Button(frame_button, text="Browse for test file", font='50', command=getcfg).pack(anchor='s', side=BOTTOM, pady=10, ipadx=5, ipady=20)

button_process = Button(frame_button, text="Process", font='50', command=process).pack(anchor='s', side=BOTTOM, pady=10, ipadx=5, ipady=20)

It didn’t do this before, when I had it all in one function, with one button.

Looking in the debugger I saw what was happening to the string in each function.

#       cfglines in get getcfg() prints ['BINGn,'BONGn', etc..
#       cfglines in process() prints as ('BING\n','BONG\n', etc...

so I need to remove and replace the bottom line to make it look like the top line, and then it all works.

the problem is, that the original variable created by using readlines() is an array of strings, and when it’s passed into a stringvar, it becomes a tuple, and bringing it out again into another function means it can become a single useless string, or a tuple that you can convery into a useless array of all separated characters, because you can’t split it into an array if you don’t know what the content of the file is.

Hence, the process of passing the content of the text file from one function to another, needs a better solution.

Answered By: Reginald Lloyd