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.
The rough image looks like this:
Now, how to express this in Python.
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.
For the time being, Grid seemed to be the easiest, so I tried it!
Yes, rattling.
Then try with Pack.
Hmmm, it doesn't go according to the image.
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
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.
Full screen:
How nice!
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()
What you are doing is simple:
root = tkk.FormK(20,10,10) This argument is (MAX_ROW, MAX_COL, PADDING).
root.geometry("1000x800") I use this as it is, but the Framework keeps Width and Height as the screen size.
'# 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.
root.set_layout()
If you do not write this, the screen will be blank.
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.
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