相關軟體資訊:
Win 10 | Python 3.7.2 | PySimpleGUI 4.6.0 |
說明:所有內容歡迎引用,只需註明來源及作者,本文內容如有錯誤或用詞不當,敬請指正.
前言
最近看了一個PySimpleGUI, 基礎是來自Tkinter, 我想他們建立這一個庫, 主要的目的在於, 可以方便或簡單的方式建立GUI. 事實上, 的確是很簡單而直接, 程式碼又短 (如果你沒有太多自己的想法或意見). 舉個簡單的例子, 以下的個視窗, 如果要用Tkinter來佈局, 估計得好幾百行才能搞定, 而且挺花時間的.
但是使用PySimleGUI, 只用了70行, 而且可能不懂PySimpleGUI的人也可以看懂以下大致的內容.
範例
#!/usr/bin/env python
'''
Example of (almost) all widgets, that you can use in PySimpleGUI.
'''
import PySimpleGUI as sg
sg.change_look_and_feel('GreenTan')
# ------ Menu Definition ------ #
menu_def = [['&File', ['&Open', '&Save', 'E&xit', 'Properties']],
['&Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ],
['&Help', '&About...'], ]
# ------ Column Definition ------ #
column1 = [[sg.Text('Column 1', background_color='lightblue',
justification='center', size=(10, 1))],
[sg.Spin(values=('Spin Box 1', '2', '3'),
initial_value='Spin Box 1')],
[sg.Spin(values=('Spin Box 1', '2', '3'),
initial_value='Spin Box 2')],
[sg.Spin(values=('Spin Box 1', '2', '3'),
initial_value='Spin Box 3')]]
layout = [
[sg.Menu(menu_def, tearoff=True)],
[sg.Text('(Almost) All widgets in one Window!', size=(
30, 1), justification='center', font=("Helvetica", 25),
relief=sg.RELIEF_RIDGE)],
[sg.Text('Here is some text.... and a place to enter text')],
[sg.InputText('This is my text')],
[sg.Frame(layout=[
[sg.CBox('Checkbox', size=(10, 1)),
sg.CBox('My second checkbox!', default=True)],
[sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10, 1)),
sg.Radio('My second Radio!', "RADIO1")]], title='Options',
title_color='red',
relief=sg.RELIEF_SUNKEN,
tooltip='Use these to set flags')],
[sg.MLine(default_text=
'This is the default Text should you decide not to type anything',
size=(35, 3)),
sg.MLine(default_text='A second multi-line', size=(35, 3))],
[sg.Combo(('Combobox 1', 'Combobox 2'), size=(20, 1)),
sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)],
[sg.OptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))],
[sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)),
sg.Frame('Labelled Group', [[
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25,
tick_interval=25),
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75),
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10),
sg.Col(column1, background_color='lightblue')]])
],
[sg.Text('_' * 80)],
[sg.Text('Choose A Folder', size=(35, 1))],
[sg.Text('Your Folder', size=(15, 1), justification='right'),
sg.InputText('Default Folder'), sg.FolderBrowse()],
[sg.Submit(tooltip='Click to submit this form'), sg.Cancel()]]
window = sg.Window('Everything bagel', layout,
default_element_size=(40, 1), grab_anywhere=False)
event, values = window.read()
sg.popup('Title',
'The results of the window.',
'The button clicked was "{}"'.format(event),
'The values are', values)
window.close()
簡單說明
說不如作, 自己實作一下, 果然發現有好有壞, Tkinter就像一磚一瓦, 你想怎麼蓋房子都可以自己搞, 而PySimpleGUI就像組合屋一樣, 你只能拼圖, 但很方便, 而且又簡單又快, 不過很多Tkinter的東西沒有被包進來, 如果你也要使用PySimpleGUI, 不要問我為什麼…Tkinter可以設定某些選項, 或有什麼功能, PySimpleGUI就不能改或沒有這些功能 ? “半成品”和”材料”肯定是不一樣的啊.
還有一件事, PySimpleGUI原則上不用寫Callback, 自己可以寫while迴圈重複檢查GUI的結果來動作, 因為 PySimpleGUI視窗中的event及values, 可以read()的方式讀出, 以供處理.
下面附上自己寫的一個小軟體, 用來檢查桌面上, 每個點的座標, 顏色, 以及距離, 也能檢查桌面上一些應用框的位置, 尺寸等等. 沒有在我的視窗外畫線以及應用框標示方框以利點選, 雖然看起來少點感覺, 但在使用上來說, 應該沒有太多問題. (事實上, 這裡的版面排版只用了54行, 其他都是內部處理的程式)
程式碼
from threading import Timer # Thread function
from PIL import Image, ImageGrab, ImageTk # Image processing
import PySimpleGUI as sg # Easy library as Tkinter
import win32api # API for Windows Platform
import win32gui # GUI for Windows Platform
import ctypes # C data types library
import sys # System parameters and functions
import math # Mathematical functions
# Confirm Windows System
if not sys.platform.startswith('win'):
sg.popup_error('Sorry, you gotta be on Windows')
sys.exit(0)
font_size = 16
font = 'simsun '+str(font_size)+' bold'
stop_flag = False # thread stop flag
width = 143 # half width of view window
height = 120 # half height of view window
graph_size=(width*2+1, height*2+1) # size of view window
x_old, y_old = 0, 0 # record old position of cursor
bttn_color = ('black', 'white') # Normal Button
bttn_color_select = ('white', 'black') # Selected Button
box_color=['yellow', 'blue'] # box color on view window
index = 0 # box color index
scale = 1 # scale of view window
resize = ('1x','2x','4x','8x','16x','32x') # scale text
im = None # image of view window
period = 0.05 # Thread Timer
point = 'start' # start point of line
# Set the unit of position as pixel
ctypes.windll.user32.SetProcessDPIAware()
# Set the view styple of window layout
sg.change_look_and_feel('Reds')
# sg.change_look_and_feel('GreenTan')
def button_click(string):
# Change colors of buttons to show the selection
global scale
window[str(scale)+'x'].update(button_color=bttn_color) # Old one
window[string].update(button_color=bttn_color_select) # new one
scale=int(string[:-1]) # set scale
def capture():
# Get cursor position, pixel color, and grab image on screen
global x_old, y_old
flags, hcursor, (x, y) = win32gui.GetCursorInfo() # Get cursor position
x_old, y_old = x ,y
w = int(width/scale)
h = int(height/scale)
im=ImageGrab.grab(bbox=(x-w, y-h, x+w+1, y+h+1)) # Grab image on screen
color = im.getpixel((w, h)) # Get pixel color
if scale!=1:
im=im.resize(graph_size, resample=Image.NEAREST)# resize
im = ImageTk.PhotoImage(im)
return im, x, y, color
def mouse():
# Thread for mouse movement
global image, stop_flag, im, point, start_x, start_y, index
# Stop thread or not
if stop_flag:
return
# capture mouse position, pixel color and image
im, x, y, color = capture()
r, g, b = color
# update image, cursor position and pixel color
position = '{},{}'.format(x,y)
window['Graph'].TKCanvas.itemconfig(image, image=im)
window['position'].update(position)
window['color'].update('{},{},{}'.format(r, g, b))
# update start and end position of a line
window[point].update(position)
# start point
if point=='start':
start_x = x
start_y = y
window['distance'].update('距離(w,h)')
window['angle'].update('角度')
# end point
else:
# Calculate the distance and angle of line
distance = '{},{}'.format(abs(x-start_x)+1, abs(y-start_y)+1)
degree = - math.degrees((math.atan2(y-start_y, x-start_x)))
if degree < 0:
degree += 360
# Update the distance and angle of line
degree = '{:+.1f}度'.format(degree)
window['distance'].update(distance)
window['angle'].update(degree)
# Get information of frame on mouse position
handle = win32gui.WindowFromPoint((x,y))
x1,y1,x2,y2 = win32gui.GetWindowRect(handle)
# Get position of center point and four corners, also the width and height
center = '{:.1f},{:.1f}'.format((x1+x2)/2, (y1+y2)/2)
left_right = '{},{}'.format(x1,x2)
top_bottom = '{},{}'.format(y1,y2)
width_height = '{},{}'.format(abs(x2-x1)+1,abs(y2-y1)+1)
window['center'].update(center)
window['left_right'].update(left_right)
window['top_bottom'].update(top_bottom)
window['width_height'].update(width_height)
# update a flash box on view window
index = 1 - index
line_color = box_color[index]
window['Graph'].TKCanvas.itemconfig(box, outline=line_color)
# Thread again after period time
thread = Timer(period, mouse)
thread.start()
def text(string, key=None, size=(13,1)):
# Define a Text element to avoid long statement
return sg.Text(text=string, size=size, font=font, key=key,
text_color='black', background_color='white',
border_width=0, justification='center', pad=(0, 5),
auto_size_text=False)
def button(string):
# Define a Button Element to avoid long statement
return sg.Button(button_text=string, border_width=2, size=(5,1),
button_color=('black', 'white'), font=font, pad=(5,5),
auto_size_button=False, focus=False)
def frame(frm, title):
# Define a Frame Element to avoid long statement, also for alignment of text
return sg.Frame(layout=frm, title=title, pad=(0,5), font=font,
element_justification='left', background_color='green',
border_width=2)
def graph():
# Define a Graph Element, almost same as Tk.Canvas
return sg.Graph(background_color='gray', canvas_size=graph_size,
graph_bottom_left=(0,0), key='Graph', pad=(0,0),
graph_top_right=graph_size)
# Frame 1 for Cursor Information - Position / Color
frame1 = [[text('位置', size=(4,1)), text('x,y', key='position') ],
[text('顏色', size=(4,1)), text('RR, GG, BB',key='color') ]]
# Frame 2 for start_point and end_point of a line - Position/Distance/Angle
frame2 = [[text('起點', size=(4,1)), text('x,y', key='start') ],
[text('終點', size=(4,1)), text('x,y', key='end') ],
[text('距離', size=(4,1)), text('距離(w,h)', key='distance') ],
[text('角度', size=(4,1)), text('角度', key='angle') ]]
# Frame 3 for object information at cursor position - center/coners/dimensions
frame3 = [[text('中心', size=(4,1)), text('x,y', key='center') ],
[text('左右', size=(4,1)), text('x1,x2', key='left_right') ],
[text('上下', size=(4,1)), text('y1,y2', key='top_bottom') ],
[text('寬高', size=(4,1)), text('寬度,高度', key='width_height')],]
# Final layout for windows
layout = [[frame(frame1, '滑鼠 - ←↑↓→')],
[frame(frame2, '兩點 - 空格鍵切換')],
[frame(frame3, '物件 - 不標示物件')],
[button(resize[i]) for i in range(0, 3)],
[button(resize[i]) for i in range(3, 6)],
[graph()]]
# Create window per layout
window = sg.Window('點素測量', layout=layout, background_color='white',
finalize=True, return_keyboard_events=True)
# Initial image on view window
# function of image type is not good for PySimplyGUI
# Use internal steps in PySimpltGUI.py, not by method directly
im, x, y, color = capture()
converted_point = window['Graph']._convert_xy_to_canvas_xy(width, height+1)
image = window['Graph']._TKCanvas2.create_image(converted_point, image=im)
window['Graph'].Images[image] = im
# Draw a rectangle on center of view image for cursor position
box = window['Graph'].DrawRectangle((width-16, height-16),(width+16, height+16),
line_color='black', line_width=2)
# 1x scale for initial setting of zoom
button_click('1x')
# Start thread for mouse information and updates
mouse()
# Event loop
while True:
# Read action on window
event, values = window.read(timeout=100)
# event:None for window close event - 'x' icon
if event==None:
# Stop thread
stop_flag = True
break
# No event
elif event == sg.TIMEOUT_KEY:
continue
# Button clicked and return the button texts '1x',...,'32x' as event
elif event[-1] == 'x':
# Process button clicked
button_click(event)
# Space key for switching beteen start_point and ene_point of line
elif event == ' ':
if point == 'start':
point = 'end'
else:
point = 'start'
# Use Left/Right/Up/Down arrow key to move cursor by 1 pixel
# especially for exact pointing when 32x scaling
elif event == 'Left:37':
win32api.SetCursorPos((x_old-1,y_old))
elif event == 'Up:38':
win32api.SetCursorPos((x_old,y_old-1))
elif event == 'Right:39':
win32api.SetCursorPos((x_old+1,y_old))
elif event == 'Down:40':
win32api.SetCursorPos((x_old,y_old+1))
# Close window
window.close()
本作品採用《CC 協議》,轉載必須註明作者和本文連結