About building GUI using TKinter of Python

Trigger

I wanted to do a Grep search for Excel using Python, and I was doing a lot of trial and error. It's nice to have technically created a Windows application called "Excel Grepy", but I can't make a UI well. .. .. ..

Somehow, the layout design is troublesome.

There were several options for configuring the UI.

Apart from TKinter adopted this time, Kivy, wxPython, PySimpleGUI, etc.

There are many sites that compare each, so please take a look.

Layout image of Excel Grepy

The rough image looks like this:

image.png

Now, how to express this in Python.

TKinter layout configuration means

Well, there are various means, but refer to the following site ... https://www.delftstack.com/ja/tutorial/tkinter-tutorial/tkinter-geometry-managers/

1.pack() It feels like the Form is regarded as one screen and arranged in four directions. I don't really understand.

2.grid() It's like dividing the Form vertically and horizontally and setting the Grid, which is similar to the HTML layout structure? No, it's hard for people with VB rise. And it has a lot more habit than HTML. I don't really understand.

3.place() This was the easiest to understand. But it's annoying. Why in 2020, I have to specify all X Y Width Height.

Examination result (grid)

For the time being, Grid seemed to be the easiest, so I tried it!

image.png

Yes, rattling.

Examination result (pack)

Then try with Pack. image.png

Hmmm, it doesn't go according to the image.

So I decided to create a framework.

If you try these and combine them in a nice way, the screen composition in Python will look good, right? The concept is like this.

・ One screen is divided in advance, and the layout is composed of row and col images. ・ Divide the layout into 10x10 so that the layout can be placed intuitively. -The layout can be specified as X, Y, Col_SPAN, ROW_SPAN. ・ To support screen scaling

image.png

I used "3.place ()" to implement this. After a lot of research, I found a property that can be set as a ratio to the width of the screen and the feeling.

・ Relex: Ratio of screen width of X coordinates ・ Rely: Ratio of screen height of Y coordinates -Relwidth: Ratio of object width to screen width -Relheight: Ratio of object height to screen height

Each has a maximum of 1.0, and if it is 0.1, it is 1/10 size. -Screen size: width = 1000px height = 600px In this case, 0.1 is 100px, 0.05 is 50px.

The nice thing about this property is that it supports Scaling. If you change the width of the screen, each object will also change.

Result: It looks like this. image.png

Full screen: image.png

How nice!

The source looks like this.

I wonder if it can be commercialized if it is upgraded a little more.

First, the Framework side:

TKinterK.py



# -*- coding: utf-8 -*-

####import
import tkinter as tk
import tkinter.ttk as ttk


class FormK(tk.Tk):
    pass

    def __init__(self, p_max_row, p_max_col, p_padding):
        super(FormK, self).__init__()
        
        ##Layout properties
        self.MAX_ROW = p_max_row
        self.MAX_COL = p_max_col
        self.PAD_OUT = p_padding
        self.PAD_IN  = p_padding

        #Constant setting
        self.CONST_MSG_ICON_INFO = 1
        self.CONST_MSG_ICON_ALERT = 2
        self.CONST_MSG_ICON_ERROR = 3
         
        self.CONST_MSG_QUES_YES_NO = 1
        self.CONST_MSG_QUES_OK_CANCEL = 2
        self.CONST_MSG_QUES_RETRY_CANCEL = 4

    ##Screen size setting at definition
    def geometry(self,newGeometry=None):
        super(FormK, self).geometry(newGeometry)
        sp = newGeometry.split("x")
        self.WIDTH  = int(sp[0])
        self.HEIGHT = int(sp[1])


    ##message-box
    def MsgBox(self,p_msg,p_title,p_icon,p_ques):

        #Return value Initial value
        o_res = None

        if (p_ques == None):
            if (p_icon == self.CONST_MSG_ICON_INFO):
                messagebox.showinfo(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ALERT):
                messagebox.showwarning(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ERROR):
                messagebox.showerror(p_title,p_msg)
        if (p_ques == self.CONST_MSG_QUES_YES_NO):
            if (p_icon == self.CONST_MSG_ICON_INFO):
                o_res = messagebox.askyesno(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ALERT):
                o_res = messagebox.askyesno(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ERROR):
                o_res = messagebox.askyesno(p_title,p_msg)
        if (p_ques == self.CONST_MSG_QUES_OK_CANCEL):
            if (p_icon == self.CONST_MSG_ICON_INFO):
                o_res = messagebox.askokcancel(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ALERT):
                o_res = messagebox.askokcancel(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ERROR):
                o_res = messagebox.askokcancel(p_title,p_msg)
        if (p_ques == self.CONST_MSG_QUES_RETRY_CANCEL):
            if (p_icon == self.CONST_MSG_ICON_INFO):
                o_res = messagebox.askretrycancel(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ALERT):
                o_res = messagebox.askretrycancel(p_title,p_msg)
            if (p_icon == self.CONST_MSG_ICON_ERROR):
                o_res = messagebox.askretrycancel(p_title,p_msg)
        return o_res

    ##Place the object
    def set_layout(self):

        n_height_in = self.HEIGHT - (self.PAD_OUT * 2)
        n_height_one = (n_height_in - ((self.MAX_ROW - 1) * self.PAD_IN)) / self.MAX_ROW

        n_width_in = self.WIDTH - (self.PAD_OUT * 2)
        n_width_one = (n_width_in  - ((self.MAX_COL - 1) * self.PAD_IN)) / self.MAX_COL
        for v in self.children:
            try:
                if self.children[v].layout != None:
                    sp = self.children[v].layout.split(",")

                    self.children[v].place_configure(
                        relx     =round((float(self.PAD_OUT) + ((int(sp[0])-1) * n_width_one)  + ((int(sp[0]) - 1) * self.PAD_IN)) / self.WIDTH ,4)
                       ,rely     =round((float(self.PAD_OUT) + ((int(sp[1])-1) * n_height_one) + ((int(sp[1]) - 1) * self.PAD_IN)) / self.HEIGHT ,4)
                       ,relwidth =round(((int(sp[2]) * n_width_one)  + ((int(sp[2]) - 1) * self.PAD_IN)) / self.WIDTH ,4)
                       ,relheight=round(((int(sp[3]) * n_height_one) + ((int(sp[3]) - 1) * self.PAD_IN)) / self.HEIGHT ,4)
                    )
            except:
                print("No TkinterK Object(" + v +").")
                pass


        pass

class ButtonK(tk.Button):
    pass

    def __init__(self):
        super(ButtonK, self).__init__()
        self.layout = None


class EntryK(tk.Entry):
    pass

    def __init__(self):
        super(EntryK, self).__init__()
        self.layout = None
        self["highlightthickness"] = 1
        self.config(highlightcolor= "red")

class ProgressbarK(ttk.Progressbar):
    pass

    def __init__(self):
        super(ProgressbarK, self).__init__()
        self.layout = None

class LabelK(tk.Label):
    pass

    def __init__(self):
        super(LabelK, self).__init__()
        self.layout = None

class TreeviewK(ttk.Treeview):
    pass

    def __init__(self):
        super(TreeviewK, self).__init__()
        self.layout = None

Then Excel Grepy:

ExcelGrepy.py



# -*- coding: utf-8 -*-

####import
import os
import tkinter as tk
import tkinter.ttk as ttk
import openpyxl as px
import subprocess

import TKinterK as tkk


from tkinter import messagebox
from tkinter import filedialog
from pathlib import Path





####root frame settings
root = tkk.FormK(20,10,10)
root.title("Excel Grepy")
root.geometry("1000x800")

#Background color
root.bg = '#B7E899'

root.configure(background=root.bg)

root.result = tk.StringVar()


#Style settings
style = ttk.Style() 
style.configure('TButton', font = 
               ('calibri', 20, 'bold'),
                    borderwidth = '4') 
  
# Changes will be reflected 
# by the movement of mouse. 
style.map('Button'
         , foreground = [('active', '!disabled', 'green')]
         , background = [('active', 'black')]
         )





####Screen event function

#Folder selection dialog
def btnFolderDir_click():
    root = tk.Tk()
    root.withdraw()
    iDir = ""
    file = filedialog.askdirectory(initialdir = iDir)
    
    #Output of processing file name
    if file != "":
        txtPath.delete(0, tk.END)
        txtPath.insert(0, file)

#Clear button
def btnClear_click():

    #Message initialization
    root.result.set("")

    #Various path initialization
    txtPath.delete(0, tk.END)
    txtStr.delete(0, tk.END)

    #grid initialization
    x = tree.get_children()
    for item in x:
        tree.delete(item)

    #Progress bar update (initialization)
    progress.configure(value=0, maximum=100)
    progress.update()


#Scrutiny button
def btnCheck_click():

    #Message initialization
    root.result.set("")

    #grid initialization
    x = tree.get_children()
    for item in x:
        tree.delete(item)

    #Parameter acquisition
    p_temp = Path(txtPath.get())

    #Error checking
    if txtPath.get() == "":
        messagebox.showerror("error", "Select the folder you want to search.")
        return

    cnt = 0
    for i in p_temp.glob('**/*.xlsx'):
        row_data =[i.name, '-', i]
        tree.insert("","end",tags=cnt,values=row_data)
        cnt += 1

    if cnt == 0:
        root.result.set(str(cnt) + "The xlsx file did not exist in the folder.")
    else:
        root.result.set(str(cnt) + "Found 1 file!")


#Grep button
def btnGrep_click():

    #Message initialization
    root.result.set("")

    #grid initialization
    x = tree.get_children()
    for item in x:
        tree.delete(item)

    #Parameter acquisition
    p_temp = Path(txtPath.get())
    s_str = txtStr.get()

    #Error checking
    if txtPath.get() == "":
        messagebox.showerror("error", "Select the folder you want to search.")
        return
    if s_str == "":
        messagebox.showerror("error", "Please enter the character string to search.")
        return

    cnt = 0
    prg_cnt = 0
    max_cnt = 0

    #Count the number of search results
    for i in p_temp.glob('**/*.xlsx'):
        max_cnt += 1

    #Set progress bar
    progress.configure(value=prg_cnt, maximum=max_cnt)

    for i in p_temp.glob('**/*.xlsx'):
        #Open the argument Excel file
        wb = px.load_workbook(i, data_only=True)
        for nm in wb.get_sheet_names():
            ws = wb[nm]
            value_matrix = str(list(ws.values))
            value_matrix = value_matrix.replace('(None','')
            value_matrix = value_matrix.replace('None), ','')
            value_matrix = value_matrix.replace(', None','')
            if (s_str in str(value_matrix)):
                row_data =[i.name, nm, i]
                tree.insert("","end",tags=cnt,values=row_data)
                cnt += 1
        
        #Progress bar update
        prg_cnt += 1
        progress.configure(value=prg_cnt)
        progress.update()

    #Progress bar update (END)
    progress.configure(value=max_cnt, maximum=max_cnt)
    progress.update()

    if cnt == 0:
        root.result.set("The search string did not exist in the folder.")
    else:
        root.result.set(str(cnt) + "Found 1 file!")

#treeview double click
def tree_row_dclick(self):
    #Get row data
    selected_items = tree.selection()
    row_data = tree.item(selected_items[0])
    #Get the path
    row_value = row_data['values']
    file_path = row_value[2]
    #Open file
    #print (file_path)
    subprocess.Popen(['start', file_path], shell=True)




####Screen object creation

# 1.Menu settings
btnQuit = tkk.ButtonK()
btnQuit["text"] = "End"
btnQuit["command"] = root.destroy
btnQuit.layout = "10,1,1,1"


#Label generation
lblProg = tkk.LabelK()
lblProg["text"] = "progress"
lblProg["bg"] = root.bg
lblProg["anchor"] = "e"
lblProg.layout = "1,1,1,1"

progress = tkk.ProgressbarK()
progress.configure( value=0
                  , mode='determinate'
                  , maximum=1000
                  , length=600)
progress.layout = "2,1,8,1"


## 2 row ################################################
#Label generation
lblFilePath = tkk.LabelK()
lblFilePath["text"] = "Folder path"
lblFilePath["bg"] = root.bg
lblFilePath["anchor"] = "e"
lblFilePath.layout = "1,2,1,1"

#Input box(FilePath)
txtPath = tkk.EntryK()
txtPath.layout = "2,2,8,1"

#Browse button
btnFolderDir = tkk.ButtonK()
btnFolderDir["text"] = "reference"
btnFolderDir["command"] = btnFolderDir_click
btnFolderDir.layout = "10,2,1,1"


## 3 row ################################################
#Label generation
lblFilePath = tkk.LabelK()
lblFilePath["text"] = "Search string"
lblFilePath["bg"] = root.bg
lblFilePath["anchor"] = "e"
lblFilePath.layout = "1,3,1,1"

#Search character
txtStr = tkk.EntryK()
txtStr.layout = "2,3,8,1"

## 4 row ################################################
#Label generation
lblCond = tkk.LabelK()
lblCond["text"] = "search results"
lblCond["bg"] = root.bg
lblCond["anchor"] = "e"
lblCond.layout = "1,4,1,1"

lblCondResult = tkk.LabelK()
lblCondResult["textvariable"] = root.result
lblCondResult["anchor"] = "w"
lblCondResult.layout = "2,4,8,1"


## 5 row ################################################

#Search process
btnGrep = tkk.ButtonK()
btnGrep["text"] = "Grep"
btnGrep["command"] = btnGrep_click
btnGrep.layout = "10,5,1,1"

btnCheck = tkk.ButtonK()
btnCheck["text"] = "Scrutiny"
btnCheck["command"] = btnCheck_click
btnCheck.layout = "9,5,1,1"

btnCheck = tkk.ButtonK()
btnCheck["text"] = "clear"
btnCheck["command"] = btnClear_click
btnCheck.layout = "1,5,1,1"


## 6-20 row ################################################
#Creating a tree view
tree = tkk.TreeviewK()

tree["columns"] = (1,2,3)
tree["show"] = "headings"
tree.column(1,width=100)
tree.column(2,width=75)
tree.column(3,width=100)
tree.heading(1,text="file name")
tree.heading(2,text="Sheet name")
tree.heading(3,text="File Path")
tree.bind('<Double-1>', tree_row_dclick)
tree.layout = "1,6,10,15"

#Arrange objects according to layout
root.set_layout()

#Main loop
root.mainloop()

Source commentary

What you are doing is simple:

1. Define the form (root)

root = tkk.FormK(20,10,10) This argument is (MAX_ROW, MAX_COL, PADDING).

2. Set the screen size of the form

root.geometry("1000x800") I use this as it is, but the Framework keeps Width and Height as the screen size.

3. Create + set "layout" property for each object

'# 1. Menu settings btnQuit = tkk.ButtonK() btnQuit ["text"] = "end" btnQuit["command"] = root.destroy

The end button is arranged. At the time of the original Button definition, you can set the property to the argument at the time of definition, I can't do it well with this framework. Please set each after creating the object.

btnQuit.layout = "10,1,1,1"

This creates buttons for the 10th column, 1st row, 1/10 width, and 1/10 height.

4. After defining each object, call the root.set_layout function to set the layout

root.set_layout()

If you do not write this, the screen will be blank.

Contents of 5.4

Well, it's faster to look at the code, so I won't explain it in detail. -Calculate the size of one column and the size of one row from the screen size and the number of divisions when FormK is defined. -Split the layout property of each object by',' and calculate the width you want to display -Based on the calculation result, you can redefine with Object.place_configure (...)

Only this. I struggled with the formula for a while, but that's it.

About future development

I'm thinking about developing a framework for those who are currently making Windows applications and those who are making Python applications, with various functions.

I think there is a way to raise it to Git and develop it, but how many people have the same opinion?

There is no motivation to create something that has no needs w There are things that haven't reached the full potential of Python yet ... Well, the layout is intuitively easy to understand, so if it becomes mainstream someday, I will have a wider range of technologies. .. .. In the first place, is it okay to license GUI tools as a framework? ?? w

Well, stay tuned w

Recommended Posts

About building GUI using TKinter of Python
Create a python GUI using tkinter
GUI creation in python using tkinter 2
GUI creation in python using tkinter part 1
Basics of I / O screen using tkinter in python3
About the ease of Python
[Question] About API conversion of chat bot using Python
About various encodings of Python 3
python: Basics of using scikit-learn ①
About the features of Python
[Python GUI] DICOM contrast adjustment and BMP conversion using Tkinter
GUI programming in Python using Appjar
Python Note: About comparison using is
Image capture of firefox using python
Removal of haze using Python detailEnhanceFilter
Implementation of desktop notifications using Python
About the basics list of Python basics
Python: Basics of image recognition using CNN
[Python] Extension using inheritance of matplotlib (NavigationToolbar2TK)
Automatic collection of stock prices using python
Periodic execution processing when using tkinter [Python3]
(Bad) practice of using this in Python
About the virtual environment of python version 3.7
Memorandum of python beginners About inclusion notation
Python: Application of image recognition using CNN
External display of matplotlib diagrams using tkinter
Study on Tokyo Rent Using Python (3-1 of 3)
Memo of troubles about coexistence of Python 2/3 system
[Python] Chapter 02-04 Basics of Python Program (About Comments)
GUI display train delay information using python
Meaning of using DI framework in Python
About python slices
Try using Tkinter
Time variation analysis of black holes using python
Chord recognition using chromagram of python library librosa
About Python tqdm.
About python yield
About python, class
GUI image cropping tool made with Python + Tkinter
Start using Python
Introduction of Python Imaging Library (PIL) using HomeBrew
About python inheritance
Character encoding when using csv module of python 2.7.3
Basics of Python ①
Basics of python ①
A note about the python version of python virtualenv
About python, range ()
Copy of python
Think about building a Python 3 environment in a Mac environment
About python decorators
[Note] About the role of underscore "_" in Python
About the behavior of Model.get_or_create () of peewee in Python
Try using the collections module (ChainMap) of python3
Anonymous upload of images using Imgur API (using Python)
Find the geometric mean of n! Using Python
Upload images to S3 with GUI using tkinter
About python reference
Python introductory study-output of sales data using tuples-
About Python decorators
[Python] About multi-process
About the * (asterisk) argument of python (and itertools.starmap)